generated from pure_sagacity/rust-starter
fixed tests, and added the deletion and overwriting confirmation
This commit is contained in:
+53
-28
@@ -125,9 +125,18 @@ pub enum Commands {
|
||||
vars: Vec<String>,
|
||||
},
|
||||
Delete {
|
||||
#[arg(short = 'e', long = "environment")]
|
||||
environment: Option<String>,
|
||||
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
keys: Vec<String>,
|
||||
},
|
||||
Show {
|
||||
#[arg(short = 'e', long = "environment")]
|
||||
environment: Option<String>,
|
||||
|
||||
key: String,
|
||||
},
|
||||
Setup {},
|
||||
List {},
|
||||
Project {
|
||||
@@ -212,6 +221,10 @@ pub mod interactions {
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
fn db_url() -> String {
|
||||
std::env::var("DATABASE_URL").unwrap_or_else(|_| DB_URL.to_string())
|
||||
}
|
||||
|
||||
fn construct_error(message: &str) -> Result<()> {
|
||||
Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::AlreadyExists,
|
||||
@@ -221,7 +234,7 @@ pub mod interactions {
|
||||
|
||||
pub fn project_exists(proj_name: &str) -> Result<bool> {
|
||||
use super::db::schema::projects::dsl::{name, projects};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
let existing_project = projects
|
||||
.filter(name.eq(proj_name))
|
||||
@@ -234,7 +247,7 @@ pub mod interactions {
|
||||
|
||||
pub fn create_project(proj_name: &str) -> Result<()> {
|
||||
use super::db::schema::projects::dsl::{created_at, id, name, projects};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
if project_exists(proj_name)? {
|
||||
return construct_error("Project already exists");
|
||||
@@ -260,7 +273,7 @@ pub mod interactions {
|
||||
|
||||
pub fn delete_project(proj_name: &str) -> Result<()> {
|
||||
use super::db::schema::projects::dsl::{name, projects};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
if !project_exists(proj_name)? {
|
||||
return construct_error("Project does not exist");
|
||||
@@ -273,7 +286,7 @@ pub mod interactions {
|
||||
|
||||
pub fn get_projects() -> Result<Vec<Project>> {
|
||||
use super::db::schema::projects::dsl::projects;
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
let results = projects
|
||||
.select(Project::as_select())
|
||||
@@ -288,7 +301,7 @@ pub mod interactions {
|
||||
environment: Environment,
|
||||
) -> Result<bool> {
|
||||
use super::db::schema::secrets::dsl::{config, id, name, project_id, secrets};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
let existing_secret = secrets
|
||||
.filter(project_id.eq(proj_id))
|
||||
@@ -303,7 +316,7 @@ pub mod interactions {
|
||||
|
||||
pub fn get_project_id(proj_name: &str) -> Result<String> {
|
||||
use super::db::schema::projects::dsl::{id, name, projects};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
let project_id = projects
|
||||
.filter(name.eq(proj_name))
|
||||
@@ -330,26 +343,30 @@ pub mod interactions {
|
||||
use super::db::schema::secrets::dsl::{
|
||||
config, created_at, id, name, nonce, project_id, secret, secrets,
|
||||
};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
if secret_exists(proj_id, secret_name, conf)? {
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::AlreadyExists,
|
||||
"Secret already exists",
|
||||
)));
|
||||
}
|
||||
|
||||
insert_into(secrets)
|
||||
.values((
|
||||
id.eq(Uuid::new_v4().to_string()),
|
||||
name.eq(secret_name),
|
||||
secret.eq(secret_value),
|
||||
project_id.eq(proj_id),
|
||||
config.eq(conf),
|
||||
nonce.eq(non.to_vec()),
|
||||
created_at.eq(chrono::Utc::now().naive_utc()),
|
||||
))
|
||||
update(
|
||||
secrets
|
||||
.filter(project_id.eq(proj_id))
|
||||
.filter(name.eq(secret_name))
|
||||
.filter(config.eq(conf)),
|
||||
)
|
||||
.set((secret.eq(secret_value), nonce.eq(non.to_vec())))
|
||||
.execute(&mut conn)?;
|
||||
} else {
|
||||
insert_into(secrets)
|
||||
.values((
|
||||
id.eq(Uuid::new_v4().to_string()),
|
||||
name.eq(secret_name),
|
||||
secret.eq(secret_value),
|
||||
project_id.eq(proj_id),
|
||||
config.eq(conf),
|
||||
nonce.eq(non.to_vec()),
|
||||
created_at.eq(chrono::Utc::now().naive_utc()),
|
||||
))
|
||||
.execute(&mut conn)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -359,7 +376,7 @@ pub mod interactions {
|
||||
environment: Environment,
|
||||
) -> Result<Vec<(String, Vec<u8>, Vec<u8>)>> {
|
||||
use super::db::schema::secrets::dsl::{config, name, nonce, project_id, secret, secrets};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
let results = secrets
|
||||
.filter(project_id.eq(proj_id))
|
||||
@@ -370,12 +387,14 @@ pub mod interactions {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub fn delete_secret(secret_name: &str) -> Result<()> {
|
||||
use super::db::schema::secrets::dsl::{id, name, secrets};
|
||||
let mut conn = establish_connection(DB_URL.to_string());
|
||||
pub fn delete_secret(proj_id: &str, secret_name: &str, environment: Environment) -> Result<()> {
|
||||
use super::db::schema::secrets::dsl::{config, id, name, project_id, secrets};
|
||||
let mut conn = establish_connection(db_url());
|
||||
|
||||
let existing_secret = secrets
|
||||
.filter(project_id.eq(proj_id))
|
||||
.filter(name.eq(secret_name))
|
||||
.filter(config.eq(environment))
|
||||
.select(id)
|
||||
.first::<String>(&mut conn)
|
||||
.optional()?;
|
||||
@@ -387,7 +406,13 @@ pub mod interactions {
|
||||
)));
|
||||
}
|
||||
|
||||
diesel::delete(secrets.filter(name.eq(secret_name))).execute(&mut conn)?;
|
||||
diesel::delete(
|
||||
secrets
|
||||
.filter(project_id.eq(proj_id))
|
||||
.filter(name.eq(secret_name))
|
||||
.filter(config.eq(environment)),
|
||||
)
|
||||
.execute(&mut conn)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+167
-6
@@ -12,6 +12,7 @@ use cli::interactions::delete_secret;
|
||||
use cli::interactions::get_project_id;
|
||||
use cli::interactions::get_project_secrets;
|
||||
use cli::interactions::get_projects;
|
||||
use cli::interactions::secret_exists;
|
||||
use cli::interactions::set_secret;
|
||||
use colored::*;
|
||||
use crypto::encrypt;
|
||||
@@ -55,6 +56,80 @@ fn main() {
|
||||
print_error("Missing .harbor.toml. Run `harbor config create` first.");
|
||||
process::exit(1);
|
||||
}
|
||||
Commands::Show { environment, key } => {
|
||||
let config = require_config(&root);
|
||||
let environment: Environment = match environment {
|
||||
Some(env) => match env.parse::<Environment>() {
|
||||
Ok(parsed) => parsed,
|
||||
Err(_) => {
|
||||
print_error(format!("Invalid environment '{}'.", env));
|
||||
process::exit(1);
|
||||
}
|
||||
},
|
||||
None => config.default_env.into(),
|
||||
};
|
||||
|
||||
let project = match get_project_id(&config.name) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
print_error(format!(
|
||||
"Unable to get project ID for '{}': {}",
|
||||
config.name, e
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let secrets = match get_project_secrets(&project, environment) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
print_error(format!("Error getting secrets: {}", e));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let secret = secrets.into_iter().find(|(name, _, _)| name == &key);
|
||||
|
||||
match secret {
|
||||
Some((_, ciphertext, nonce)) => {
|
||||
let key = match gen_or_get_key() {
|
||||
Ok(k) => k,
|
||||
Err(_) => {
|
||||
print_error("Error generating or getting key");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let nonce = crypto::Nonce::from_slice(&nonce);
|
||||
let decrypted = match crypto::decrypt(&key, ciphertext, nonce) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
print_error(format!("Error decrypting secret: {}", e));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let decrypted_str = match String::from_utf8(decrypted) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
print_error(format!(
|
||||
"Error converting decrypted secret to string: {}",
|
||||
e
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
println!("{}", decrypted_str);
|
||||
}
|
||||
None => {
|
||||
print_error(format!(
|
||||
"Secret with key '{}' not found in project '{}'.",
|
||||
key, config.name
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::Set { environment, vars } => {
|
||||
let config = require_config(&root);
|
||||
let environment: Environment = match environment {
|
||||
@@ -93,6 +168,47 @@ fn main() {
|
||||
};
|
||||
|
||||
for pair in pairs {
|
||||
let existed = match secret_exists(&project, &pair.0, environment) {
|
||||
Ok(exists) => exists,
|
||||
Err(e) => {
|
||||
print_error(format!(
|
||||
"Error checking existing secret for key '{}': {}",
|
||||
pair.0, e
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
if existed {
|
||||
let confirmation = match get_input(
|
||||
format!(
|
||||
"Secret '{}' already exists. Overwrite? (y/N)",
|
||||
pair.0
|
||||
)
|
||||
.yellow(),
|
||||
':',
|
||||
false,
|
||||
) {
|
||||
Ok(input) => input,
|
||||
Err(e) => {
|
||||
print_error(format!("Error getting confirmation: {}", e));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if confirmation.to_lowercase() != "y" {
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"Skipped secret '{}' in {}.",
|
||||
pair.0,
|
||||
environment.as_str()
|
||||
)
|
||||
.dimmed()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let key = match gen_or_get_key() {
|
||||
Ok(k) => k,
|
||||
Err(_) => {
|
||||
@@ -111,7 +227,20 @@ fn main() {
|
||||
}
|
||||
};
|
||||
match set_secret(&project, &pair.0, encrypted, environment, nonce) {
|
||||
Ok(()) => {}
|
||||
Ok(()) => {
|
||||
let verb = if existed { "Updated" } else { "Set" };
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"{} secret '{}' in {}.",
|
||||
verb,
|
||||
pair.0,
|
||||
environment.as_str()
|
||||
)
|
||||
.bright_green()
|
||||
.bold()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!(
|
||||
"Error setting secret for key '{}': {}",
|
||||
@@ -122,7 +251,28 @@ fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::Delete { keys } => {
|
||||
Commands::Delete { environment, keys } => {
|
||||
let config = require_config(&root);
|
||||
let environment: Environment = match environment {
|
||||
Some(env) => match env.parse::<Environment>() {
|
||||
Ok(parsed) => parsed,
|
||||
Err(_) => {
|
||||
print_error(format!("Invalid environment '{}'.", env));
|
||||
process::exit(1);
|
||||
}
|
||||
},
|
||||
None => config.default_env.into(),
|
||||
};
|
||||
let proj_id = match get_project_id(&config.name) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
print_error(format!(
|
||||
"Unable to get project ID for '{}': {}",
|
||||
config.name, e
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let mut cleaned = Vec::new();
|
||||
|
||||
for key in keys {
|
||||
@@ -141,7 +291,7 @@ fn main() {
|
||||
}
|
||||
|
||||
for key in cleaned {
|
||||
match delete_secret(&key) {
|
||||
match delete_secret(&proj_id, &key, environment) {
|
||||
Ok(()) => {
|
||||
println!("Deleted secret for key '{}'", key);
|
||||
}
|
||||
@@ -300,7 +450,9 @@ fn main() {
|
||||
};
|
||||
|
||||
match cli::interactions::create_project(&project_name) {
|
||||
Ok(()) => println!("{}", "Project created successfully.".cyan()),
|
||||
Ok(()) => {
|
||||
println!("{}", "Project created successfully.".bright_green().bold())
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!("Error creating project: {}", e));
|
||||
process::exit(1);
|
||||
@@ -342,7 +494,10 @@ fn main() {
|
||||
if confirmation.to_lowercase() == "y" {
|
||||
match cli::interactions::delete_project(&name) {
|
||||
Ok(()) => {
|
||||
println!("{}", "Project deleted successfully.".cyan())
|
||||
println!(
|
||||
"{}",
|
||||
"Project deleted successfully.".bright_green().bold()
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!("Error deleting project: {}", e));
|
||||
@@ -530,7 +685,12 @@ config = "dev""#,
|
||||
let config_path = root.join(".harbor.toml");
|
||||
match std::fs::write(&config_path, new_config) {
|
||||
Ok(_) => {
|
||||
println!("Created .harbor.toml for project '{}'.", project);
|
||||
println!(
|
||||
"{}",
|
||||
format!("Created .harbor.toml for project '{}'.", project)
|
||||
.bright_green()
|
||||
.bold()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!(
|
||||
@@ -571,6 +731,7 @@ fn requires_config(command: &cli::Commands) -> bool {
|
||||
matches!(
|
||||
command,
|
||||
cli::Commands::Inject { .. }
|
||||
| cli::Commands::Show { .. }
|
||||
| cli::Commands::Set { .. }
|
||||
| cli::Commands::Delete { .. }
|
||||
| cli::Commands::Shell { .. }
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
use cli::Environment;
|
||||
use cli::config::Config;
|
||||
use cli::interactions::{
|
||||
create_project,
|
||||
delete_project,
|
||||
delete_secret,
|
||||
get_project_id,
|
||||
get_project_secrets,
|
||||
get_projects,
|
||||
project_exists,
|
||||
secret_exists,
|
||||
set_secret,
|
||||
create_project, delete_project, delete_secret, get_project_id, get_project_secrets,
|
||||
get_projects, project_exists, secret_exists, set_secret,
|
||||
};
|
||||
use cli::{format_doppler_set_command, parse_secret_pairs};
|
||||
use diesel::connection::SimpleConnection;
|
||||
@@ -70,7 +63,10 @@ fn environment_parsing_and_display() {
|
||||
assert_eq!(format!("{}", Environment::Prod), "prod");
|
||||
|
||||
let err = "unknown".parse::<Environment>().unwrap_err();
|
||||
assert_eq!(err.to_string(), "Environment must be one of: dev, prod, staging");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Environment must be one of: dev, prod, staging"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -108,10 +104,7 @@ fn format_doppler_set_command_outputs_expected_format() {
|
||||
("B".to_string(), "2".to_string()),
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
many,
|
||||
"doppler secrets set \\\n A=\"1\" \\\n B=\"2\""
|
||||
);
|
||||
assert_eq!(many, "doppler secrets set \\\n A=\"1\" \\\n B=\"2\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -158,7 +151,7 @@ fn project_and_secret_lifecycle() {
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].name, "alpha");
|
||||
|
||||
assert!(!secret_exists().expect("secret exists"));
|
||||
assert!(!secret_exists(&project_id, "API_KEY", Environment::Dev).expect("secret exists"));
|
||||
|
||||
let nonce = crypto::helper::gen_nonce();
|
||||
set_secret(
|
||||
@@ -170,7 +163,7 @@ fn project_and_secret_lifecycle() {
|
||||
)
|
||||
.expect("set secret");
|
||||
|
||||
assert!(secret_exists().expect("secret exists"));
|
||||
assert!(secret_exists(&project_id, "API_KEY", Environment::Dev).expect("secret exists"));
|
||||
|
||||
let secrets = get_project_secrets(&project_id, Environment::Dev).expect("get secrets");
|
||||
assert_eq!(secrets.len(), 1);
|
||||
@@ -191,8 +184,8 @@ fn project_and_secret_lifecycle() {
|
||||
assert_eq!(updated.len(), 1);
|
||||
assert_eq!(updated[0].1, b"second".to_vec());
|
||||
|
||||
delete_secret("API_KEY").expect("delete secret");
|
||||
assert!(!secret_exists().expect("secret exists"));
|
||||
delete_secret(&project_id, "API_KEY", Environment::Dev).expect("delete secret");
|
||||
assert!(!secret_exists(&project_id, "API_KEY", Environment::Dev).expect("secret exists"));
|
||||
|
||||
delete_project("alpha").expect("delete project");
|
||||
assert!(!project_exists("alpha").expect("project exists"));
|
||||
@@ -211,7 +204,8 @@ fn errors_for_missing_project_or_secret() {
|
||||
let err = get_project_id("missing").unwrap_err();
|
||||
assert_eq!(err.to_string(), "Project not found");
|
||||
|
||||
let err = delete_secret("missing").unwrap_err();
|
||||
let missing_project_id = Uuid::new_v4().to_string();
|
||||
let err = delete_secret(&missing_project_id, "missing", Environment::Dev).unwrap_err();
|
||||
assert_eq!(err.to_string(), "Secret does not exist");
|
||||
|
||||
cleanup_test_db(db_path);
|
||||
|
||||
Reference in New Issue
Block a user