Skip to content

Commit

Permalink
Merge pull request #7 from generative-ai-inc/tests
Browse files Browse the repository at this point in the history
Add tests
  • Loading branch information
jorge-menjivar authored Dec 31, 2024
2 parents e72d132 + e0a908d commit 3dfd687
Show file tree
Hide file tree
Showing 18 changed files with 478 additions and 111 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ trim_trailing_whitespace = true

[*.rs]
indent_size = 4

[*.json]
indent_size = 2
insert_final_newline = true
33 changes: 33 additions & 0 deletions .github/workflows/test-pull.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Test - Pull Request

on: [pull_request]

jobs:
test:
timeout-minutes: 20
permissions:
contents: read
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-24.04
targets: ["x86_64-unknown-linux-gnu"]
- os: macos-latest
targets: ["x86_64-apple-darwin"]
runs-on: ${{ matrix.os }}

steps:
- name: Check out code
uses: actions/[email protected]

- name: Rust setup
uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3

- name: Rust cache
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3
with:
shared-key: "test-${{ matrix.os }}"

- name: Run tests
run: cargo test
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Test

on:
push:
branches:
- main

jobs:
test:
timeout-minutes: 20
permissions:
contents: read
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-24.04
targets: ["x86_64-unknown-linux-gnu"]
- os: macos-latest
targets: ["x86_64-apple-darwin"]
runs-on: ${{ matrix.os }}

steps:
- name: Check out code
uses: actions/[email protected]

- name: Rust setup
uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3

- name: Rust cache
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3
with:
shared-key: "test-${{ matrix.os }}"

- name: Run tests
run: cargo test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ bws*
target
meili_data
supabase

tests/test_results
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Type "help", "copyright", "credits" or "license" for more information.

Secrets are read from the following sources, in this order:

1. A dotenv file (.env)
1. ~~A dotenv file (.env)~~ (TODO)
2. Environment variables
3. Secret Sources (e.g. Bitwarden)
4. Keyring
Expand Down
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn build() -> Command {
.subcommand(Command::new("run")
.about("Run a command defined in the secrets_machine.toml configuration file")
.arg(
arg!(<command_name> "Name of the command to run, as defined in the configuration file")
arg!([command_name] "Name of the command to run, as defined in the configuration file")
.required(true)
.value_parser(value_parser!(String))
)
Expand Down
67 changes: 41 additions & 26 deletions src/library/commands/execute.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::env;
use std::{env, error::Error};

use tokio::process::Command;

Expand All @@ -11,12 +11,20 @@ use super::prepare;

/// Executes a command with the secrets machine
///
/// # Panics
/// - If the command fails to execute
/// - If function fails to get the output of the command
/// - If function fails to kill the process
pub async fn execute(config: Config, secrets: serde_json::Value, command_to_run: &str) {
prepare(&config, &secrets).await;
/// # Errors
/// - When `return_output` is true:
/// - If the command fails to execute
/// - If function fails to get the output of the command
/// - If function fails to kill the process
///
/// - When `return_output` is false:
/// - Never
pub async fn execute(
config: Config,
secrets: serde_json::Value,
command_to_run: &str,
) -> Result<(), Box<dyn Error>> {
prepare(&config, &secrets).await?;

logging::nl().await;
logging::print_color(logging::BG_GREEN, " Executing command ").await;
Expand All @@ -27,49 +35,56 @@ pub async fn execute(config: Config, secrets: serde_json::Value, command_to_run:
.await;

// Get the default shell from the SHELL environment variable
let default_shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let default_shell = match env::var("SHELL") {
Ok(shell) => shell,
Err(_) => "/bin/sh".to_string(),
};

let child: tokio::process::Child = Command::new(default_shell)
let Ok(child) = Command::new(default_shell)
.arg("-c")
.arg(command_to_run)
.envs(env::vars())
.spawn()
.expect("Failed to execute command");
else {
return Err(Box::from("Failed to execute command"));
};

let pid = child.id().expect("Failed to get child pid");
let Some(pid) = child.id() else {
return Err(Box::from("Failed to get child pid"));
};
let handle = child.wait_with_output();

tokio::spawn(async move {
tokio::signal::ctrl_c().await.unwrap();
logging::nl().await;
logging::info("πŸ‘ Shutting down gracefully...").await;
let result = Command::new("kill").arg(pid.to_string()).status().await;
match tokio::signal::ctrl_c().await {
Ok(()) => {
logging::info("πŸ‘ Shutting down gracefully...").await;
}
Err(e) => {
logging::error(&format!("πŸ›‘ Failed to listen for Ctrl+C: {e}")).await;
}
}

match result {
match Command::new("kill").arg(pid.to_string()).status().await {
Ok(_) => {
logging::info("βœ… All processes have been terminated.").await;
std::process::exit(0);
}
Err(e) => {
logging::error(&format!("πŸ›‘ Failed to kill process: {e}")).await;
std::process::exit(1);
}
}
});

let output = handle.await;

match output {
match handle.await {
Ok(output) => {
if output.status.success() {
std::process::exit(0);
Ok(())
} else {
std::process::exit(1);
Err(Box::from("Command failed"))
}
}
Err(e) => {
logging::error(&format!("πŸ›‘ Failed to wait for command execution: {e}")).await;
std::process::exit(1);
}
Err(e) => Err(Box::from(format!(
"πŸ›‘ Failed to wait for command execution: {e}"
))),
}
}
30 changes: 20 additions & 10 deletions src/library/commands/prepare.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use std::{collections::HashMap, env};
use std::{collections::HashMap, env, error::Error};

use crate::{
library::{secrets_sources, utils::env_vars},
library::{
secrets_sources,
utils::{env_vars, logging},
},
models::config::Config,
};

/// Sync secrets and set environment variables
///
/// # Panics
/// # Errors
/// - If the secrets map is not found
/// - If the environment variables fail to be set
pub async fn prepare(config: &Config, secrets: &serde_json::Value) {
pub async fn prepare(config: &Config, secrets: &serde_json::Value) -> Result<(), Box<dyn Error>> {
let vars_iter = env::vars();

let mut original_env_vars: HashMap<String, String> = HashMap::new();
Expand All @@ -20,13 +24,17 @@ pub async fn prepare(config: &Config, secrets: &serde_json::Value) {

let mut env_vars: Vec<(String, String, String)> = Vec::new();

let Some(secrets_map) = secrets.as_object() else {
return Err(Box::from("Secrets map not found"));
};

// Add keyring secrets to the environment variables
for (key, value) in secrets.as_object().unwrap() {
env_vars.push((
key.to_string(),
value.as_str().unwrap().to_string(),
"keyring".to_string(),
));
for (key, value) in secrets_map {
if let Some(value) = value.as_str() {
env_vars.push((key.to_string(), value.to_string(), "keyring".to_string()));
} else {
logging::error(&format!("Failed to set secret {key} from keyring")).await;
}
}

secrets_sources::sync(config, secrets, &mut env_vars).await;
Expand All @@ -36,4 +44,6 @@ pub async fn prepare(config: &Config, secrets: &serde_json::Value) {
if config.general.print_secrets_table {
env_vars::print_variables_box(original_env_vars, &env_vars).await;
}

Ok(())
}
63 changes: 35 additions & 28 deletions src/library/commands/run.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::error::Error;

use tokio::process::Command;

use crate::{
Expand All @@ -12,7 +14,8 @@ use super::prepare;

/// Runs a command specified in the config file with the secrets machine
///
/// # Panics
/// # Errors
/// - If the command is not found in the config file
/// - If the command fails to execute
/// - If the function fails to get the output of the command
/// - If the function fails to kill the process
Expand All @@ -22,8 +25,8 @@ pub async fn run(
secrets: serde_json::Value,
command_name: String,
command_args: String,
) {
prepare(&config, &secrets).await;
) -> Result<(), Box<dyn Error>> {
prepare(&config, &secrets).await?;

if let Some(pre_command) = commands_config.pre_commands.get(&command_name) {
let result = command::run(pre_command).await;
Expand All @@ -35,59 +38,63 @@ pub async fn run(
Err(e) => {
logging::error(e.to_string().as_str().trim()).await;
logging::error("πŸ›‘ Failed to run pre command").await;
std::process::exit(1);
return Err(e);
}
}
}

let command = commands_config.commands.get(&command_name).unwrap();
let command = format!("{command} {command_args}");
let Some(command) = commands_config.commands.get(&command_name) else {
return Err(Box::from("Command not found"));
};

let full_command = format!("{command} {command_args}");
logging::nl().await;
logging::print_color(logging::BG_GREEN, " Running command ").await;
logging::info(&format!(
"Running: {}",
env_vars::replace(&command, true).await
env_vars::replace(&full_command, true).await
))
.await;
let child = Command::new("sh")
.arg("-c")
.arg(&command)
.spawn()
.expect("Failed to start main command");

let pid = child.id().expect("Failed to get child pid");
let Ok(child) = Command::new("sh").arg("-c").arg(&full_command).spawn() else {
return Err(Box::from("Failed to execute command"));
};

let Some(pid) = child.id() else {
return Err(Box::from("Failed to get child pid"));
};
let handle = child.wait_with_output();

tokio::spawn(async move {
tokio::signal::ctrl_c().await.unwrap();
logging::nl().await;
logging::info("πŸ‘ Shutting down gracefully...").await;
let result = Command::new("kill").arg(pid.to_string()).status().await;

match result {
match tokio::signal::ctrl_c().await {
Ok(()) => {
logging::info("πŸ‘ Shutting down gracefully...").await;
}
Err(e) => {
logging::error(&format!("πŸ›‘ Failed to listen for Ctrl+C: {e}")).await;
}
}
match Command::new("kill").arg(pid.to_string()).status().await {
Ok(_) => {
logging::info("βœ… All processes have been terminated.").await;
std::process::exit(0);
}
Err(e) => {
logging::error(&format!("πŸ›‘ Failed to kill process: {e}")).await;
std::process::exit(1);
}
}
});

let output = handle.await;

match output {
match handle.await {
Ok(output) => {
if output.status.success() {
std::process::exit(0);
Ok(())
} else {
std::process::exit(1);
Err(Box::from("Command failed"))
}
}
Err(e) => {
logging::error(&format!("πŸ›‘ Failed to wait for main command: {e}")).await;
}
Err(e) => Err(Box::from(format!(
"πŸ›‘ Failed to wait for command execution: {e}"
))),
}
}
Loading

0 comments on commit 3dfd687

Please sign in to comment.