diff --git a/Makefile b/Makefile index 1315e39..584e0fe 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ build-nix: nix build -o build/nix .PHONY: test -test: test-docker test-doc test-cargo +test: test-docker test-doc test-clippy test-cargo .PHONY: test-docker test-docker: @@ -40,6 +40,10 @@ test-docker: test-cargo: cargo test +.PHONY: test-clippy +test-clippy: + cargo clippy -- -D warnings + # Fails if doc is not up to date with current code .PHONY: test-doc test-doc: doc diff --git a/src/core.rs b/src/core.rs index 4c1d706..6ea5371 100644 --- a/src/core.rs +++ b/src/core.rs @@ -46,6 +46,7 @@ pub struct NovopsConfigFile { /// Global Novops configuration defining behavior for modules /// #[derive(Debug, Deserialize, Clone, PartialEq, JsonSchema)] +#[derive(Default)] pub struct NovopsConfig { /// Novops default configurations pub default: Option, @@ -57,29 +58,16 @@ pub struct NovopsConfig { pub aws: Option } -impl Default for NovopsConfig { - fn default() -> NovopsConfig { - NovopsConfig { - default: None, - hashivault: None, - aws: None - } - } -} + #[derive(Debug, Deserialize, Clone, PartialEq, JsonSchema)] +#[derive(Default)] pub struct NovopsConfigDefault { /// Default environment name, selected by default if no user input is provided pub environment: Option, } -impl Default for NovopsConfigDefault { - fn default() -> NovopsConfigDefault { - NovopsConfigDefault { - environment: None, - } - } -} + /// Modules to be loaded for an environment. Each module defines one or more Input diff --git a/src/lib.rs b/src/lib.rs index 84d689f..82b4c30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,17 +9,17 @@ use log::{info, debug, error, warn}; use std::os::linux::fs::MetadataExt; use std::os::unix::prelude::OpenOptionsExt; use std::{fs, io::prelude::*, os::unix::prelude::PermissionsExt}; -use users; + use anyhow::{self, Context}; use std::os::unix; -use std::path::PathBuf; +use std::path::{PathBuf, Path}; use std::env; use is_terminal::IsTerminal; // todo use std::io::IsTerminal; rust 1.71+ use std::collections::HashMap; use schemars::schema_for; use std::process::Command; use std::os::unix::process::CommandExt; -use dialoguer; + use console::Term; #[derive(Debug)] @@ -57,9 +57,9 @@ pub struct NovopsOutputs { pub async fn load_environment_write_vars(args: &NovopsLoadArgs, symlink: &Option, format: &str, skip_tty_check: bool) -> Result<(), anyhow::Error> { // safety checks - check_stdout_tty_and_exit(skip_tty_check, &symlink); + check_stdout_tty_and_exit(skip_tty_check, symlink); - let outputs = load_context_and_resolve(&args).await?; + let outputs = load_context_and_resolve(args).await?; // Write files to filesystem export_file_outputs(&outputs)?; @@ -76,7 +76,7 @@ pub async fn load_environment_write_vars(args: &NovopsLoadArgs, symlink: &Option /// Used by `novops run` to load environment and run child process pub async fn load_environment_and_exec(args: &NovopsLoadArgs, command_args: Vec<&String>) -> Result<(), anyhow::Error> { - let outputs = load_context_and_resolve(&args).await?; + let outputs = load_context_and_resolve(args).await?; // Write files to filesystem export_file_outputs(&outputs)?; @@ -96,14 +96,14 @@ pub async fn load_context_and_resolve(args: &NovopsLoadArgs) -> Result, variables: &Vec Result, anyhow: .with_context(|| format!("Error reading config file '{:}'", &config_file))?; let envs = list_environments_from_config(&config); - return Ok(envs) + Ok(envs) } /** @@ -189,7 +189,7 @@ pub async fn list_outputs_for_environment(config_file: &str, env_name: Option Result { - let r = aws.assume_role.resolve(&ctx).await + let r = aws.assume_role.resolve(ctx).await .with_context(|| format!("Could not resolve AWS input {:?}", aws))?; for vo in r { @@ -304,7 +304,7 @@ pub async fn resolve_environment_inputs(ctx: &NovopsContext, inputs: NovopsEnvir match &inputs.hashivault { Some(hashivault) => { - let r = hashivault.aws.resolve(&ctx).await + let r = hashivault.aws.resolve(ctx).await .with_context(|| format!("Could not resolve Hashivault input {:?}", hashivault))?; for vo in r { @@ -318,7 +318,7 @@ pub async fn resolve_environment_inputs(ctx: &NovopsContext, inputs: NovopsEnvir match &inputs.sops_dotenv { Some(sops_dotenv) => { for s in sops_dotenv { - let r = s.resolve(&ctx).await + let r = s.resolve(ctx).await .with_context(|| format!("Could not resolve SopsDotenv input {:?}", s))?; for vo in r { @@ -338,7 +338,7 @@ fn read_config_file(config_path: &str) -> Result Result let xdg_prefix = format!("novops/{:}/{:}", app_name, env_name); let xdg_basedir = xdg::BaseDirectories::new()? - .create_runtime_directory(&xdg_prefix)?; + .create_runtime_directory(xdg_prefix)?; - return Ok(xdg_basedir); + Ok(xdg_basedir) } @@ -432,7 +432,7 @@ fn prepare_working_directory_tmp(app_name: &String, env_name: &String) -> Result fs::create_dir_all(&workdir) .with_context(|| format!("Couldn't create working directory {:?}", &workdir))?; - return Ok(PathBuf::from(workdir)); + Ok(PathBuf::from(workdir)) } /** @@ -440,7 +440,7 @@ fn prepare_working_directory_tmp(app_name: &String, env_name: &String) -> Result * by anyone other than current user or root */ pub fn check_working_dir_permissions(workdir: &PathBuf) -> Result<(), anyhow::Error> { - let metadata = fs::metadata(&workdir) + let metadata = fs::metadata(workdir) .with_context(|| format!("Couldn't get metadata for working directory {:?}", &workdir))?; // check mode is rwx------ (or more restrictive) @@ -456,7 +456,7 @@ pub fn check_working_dir_permissions(workdir: &PathBuf) -> Result<(), anyhow::Er return Err(anyhow::anyhow!("Working directory {:?} ownership is unsafe (owned by {:}). Only current user {:} or root can have ownership.", &workdir, &workdir_owner_uid, ¤t_uid)); } - return Ok(()); + Ok(()) } /** @@ -473,7 +473,7 @@ fn prompt_for_environment(config_file_data: &NovopsConfigFile) -> Result Result return Err(anyhow::anyhow!("No environment selected")), }; - return Ok(selected); + Ok(selected) } /** @@ -520,7 +520,7 @@ fn prompt_for_environment(config_file_data: &NovopsConfigFile) -> Result Vec { let mut sorted = config_file_data.environments.keys().cloned().collect::>(); sorted.sort(); - return sorted + sorted } /** @@ -554,7 +554,7 @@ fn export_file_outputs(outputs: &NovopsOutputs) -> Result<(), anyhow::Error>{ * ' * */ -fn export_variable_outputs(format: &str, symlink: &Option, vars: &Vec, working_dir: &PathBuf) -> Result<(), anyhow::Error>{ +fn export_variable_outputs(format: &str, symlink: &Option, vars: &Vec, working_dir: &Path) -> Result<(), anyhow::Error>{ let safe_vars = prepare_variable_outputs(vars); let vars_string = format_variable_outputs(format, &safe_vars)?; @@ -578,7 +578,7 @@ fn export_variable_outputs(format: &str, symlink: &Option, vars: &Vec, vars: &Vec) -> Vec { let mut safe_vars : Vec = Vec::new(); for v in vars{ - let safe_val = v.value.replace("'", "'\"'\"'"); + let safe_val = v.value.replace('\'', "'\"'\"'"); safe_vars.push(VariableOutput { name: v.name.clone(), value: safe_val }) } - return safe_vars; + safe_vars } /** @@ -629,23 +629,23 @@ pub fn format_variable_outputs(format: &str, safe_vars: &Vec) -> return Err(anyhow::format_err!("Unknown format {:}. Available formats: {:?}", format, FORMAT_ALL)) } - return Ok(vars_string); + Ok(vars_string) } fn create_symlink(lnk: &PathBuf, target: &PathBuf) -> Result<(), anyhow::Error> { - let attr_result = fs::symlink_metadata(&lnk); + let attr_result = fs::symlink_metadata(lnk); if attr_result.is_ok() { let attr = attr_result?; if attr.is_symlink() && attr.st_uid() == users::get_current_uid(){ debug!("Deleting existing symlink {:?} before creating new one", &lnk); - fs::remove_file(&lnk).with_context(|| format!("Couldn't remove existing symlink {:?}", &lnk))?; + fs::remove_file(lnk).with_context(|| format!("Couldn't remove existing symlink {:?}", &lnk))?; } else { return Err(anyhow::anyhow!("Symlink creation error: {:?} already exists and is not a symlink you own.", &lnk)); } } - unix::fs::symlink(&target, &lnk) + unix::fs::symlink(target, lnk) .with_context(|| format!("Couldn't create symlink {:?} -> {:?}", &lnk, &target)) } @@ -653,7 +653,7 @@ pub fn get_config_schema() -> Result{ let schema = schema_for!(NovopsConfigFile); let output = serde_json::to_string_pretty(&schema) .with_context(|| "Couldn't convert JSON schema to pretty string.")?; - return Ok(output) + Ok(output) } /// See should_error_tty @@ -685,5 +685,5 @@ pub fn should_error_tty(terminal_is_tty: bool, skip_tty_check: bool, symlink: &O return true } - return false + false } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8d6d343..cf1b3f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::io; use anyhow::{Context, anyhow}; -use tokio; + use clap::{Arg, Command, value_parser, ArgAction, crate_version, ArgMatches}; use novops::{self, init_logger, get_config_schema, NovopsLoadArgs}; use clap_complete::{generate, Shell}; @@ -65,7 +65,8 @@ fn build_cli() -> Command { .short('o') .default_value("plain"); - let app = Command::new("novops") + + Command::new("novops") .about("Cross-plaform secret loader") .version(crate_version!()) .author("Pierre Beucher") @@ -160,8 +161,6 @@ fn build_cli() -> Command { Command::new("schema") .about("Output Novops config JSON schema") ) - ; - return app } #[tokio::main] @@ -291,8 +290,8 @@ async fn cmd_load(cmd_args: &ArgMatches) -> Result<(), anyhow::Error> { let env_format = cmd_args.get_one::("format") .ok_or(anyhow!("Format is None. This is probably a bug as CLI defines default value."))?.clone(); - let skip_tty_check = cmd_args.get_one::("skip_tty_check").map(|e| *e) - .ok_or(anyhow!("skip_tty_check is None. This is probably a bug as CLI defines default value."))?.clone(); + let skip_tty_check = cmd_args.get_one::("skip_tty_check").copied() + .ok_or(anyhow!("skip_tty_check is None. This is probably a bug as CLI defines default value."))?; let novops_load_args = build_novops_args(cmd_args)?; @@ -336,8 +335,8 @@ fn build_novops_args(cmd_args: &ArgMatches) -> Result("environment").map(String::from), working_directory: cmd_args.get_one::("working_dir").map(String::from), - skip_working_directory_check: cmd_args.get_one::("skip_workdir_check").map(|b| *b), - dry_run: cmd_args.get_one::("dry_run").map(|e| *e) + skip_working_directory_check: cmd_args.get_one::("skip_workdir_check").copied(), + dry_run: cmd_args.get_one::("dry_run").copied() }; Ok(args) diff --git a/src/modules/aws/client.rs b/src/modules/aws/client.rs index babeb7e..3aa2bdf 100644 --- a/src/modules/aws/client.rs +++ b/src/modules/aws/client.rs @@ -1,4 +1,4 @@ -use crate::core::{NovopsContext}; +use crate::core::NovopsContext; use super::config::AwsClientConfig; use aws_sdk_secretsmanager::output::GetSecretValueOutput; use aws_sdk_ssm::{output::GetParameterOutput, model::Parameter}; @@ -25,9 +25,9 @@ pub trait AwsClient { pub async fn get_client(ctx: &NovopsContext) -> Box { if ctx.dry_run { - return Box::new(DryRunAwsClient{}) + Box::new(DryRunAwsClient{}) } else { - return Box::new(DefaultAwsClient{ + Box::new(DefaultAwsClient{ config: build_mutable_client_config_from_context(ctx) }) } @@ -35,7 +35,7 @@ pub async fn get_client(ctx: &NovopsContext) -> Box pub async fn get_client_with_profile(ctx: &NovopsContext, profile: &Option) -> Box { if ctx.dry_run { - return Box::new(DryRunAwsClient{}) + Box::new(DryRunAwsClient{}) } else { let mut config = build_mutable_client_config_from_context(ctx); @@ -43,8 +43,8 @@ pub async fn get_client_with_profile(ctx: &NovopsContext, profile: &Option AwsClientConfig { - let client_conf = match &ctx.config_file_data.config { + + + match &ctx.config_file_data.config { Some(config) => { match &config.aws { Some(aws) => { @@ -132,9 +134,7 @@ pub fn build_mutable_client_config_from_context(ctx: &NovopsContext) -> AwsClien } }, None => AwsClientConfig::default() - }; - - return client_conf; + } } /** @@ -164,7 +164,7 @@ pub async fn get_sdk_config(client_conf: &AwsClientConfig) -> Result {}, } - return Ok(aws_config.load().await); + Ok(aws_config.load().await) } @@ -172,26 +172,26 @@ pub async fn get_iam_client(novops_aws: &AwsClientConfig) -> Result Result{ let conf = get_sdk_config(novops_aws).await?; debug!("Creating AWS STS client with config {:?}", conf); - return Ok(aws_sdk_sts::Client::new(&conf)); + Ok(aws_sdk_sts::Client::new(&conf)) } pub async fn get_ssm_client(novops_aws: &AwsClientConfig) -> Result{ let conf = get_sdk_config(novops_aws).await?; debug!("Creating AWS SSM client with config {:?}", conf); - return Ok(aws_sdk_ssm::Client::new(&conf)); + Ok(aws_sdk_ssm::Client::new(&conf)) } pub async fn get_secretsmanager_client(novops_aws: &AwsClientConfig) -> Result{ let conf = get_sdk_config(novops_aws).await?; debug!("Creating AWS Secrets Manager client with config {:?}", conf); - return Ok(aws_sdk_secretsmanager::Client::new(&conf)); + Ok(aws_sdk_secretsmanager::Client::new(&conf)) } \ No newline at end of file diff --git a/src/modules/aws/config.rs b/src/modules/aws/config.rs index 3e72b7e..f938aae 100644 --- a/src/modules/aws/config.rs +++ b/src/modules/aws/config.rs @@ -1,4 +1,4 @@ -use crate::{modules::aws::assume_role::AwsAssumeRoleInput}; +use crate::modules::aws::assume_role::AwsAssumeRoleInput; use schemars::JsonSchema; use serde::Deserialize; @@ -13,23 +13,17 @@ pub struct AwsInput { * Generic AWS Client config xrapped around builder pattern * for easy loading from Novops config and per-module override */ +#[derive(Default)] pub struct AwsClientConfig { pub profile: Option, pub endpoint: Option } -impl Default for AwsClientConfig { - fn default() -> Self { - AwsClientConfig { - profile: None, - endpoint: None - } - } -} + impl From<&AwsConfig> for AwsClientConfig { fn from(cf: &AwsConfig) -> AwsClientConfig{ - return AwsClientConfig { + AwsClientConfig { profile: cf.profile.clone(), endpoint: cf.endpoint.clone() } @@ -51,6 +45,7 @@ impl AwsClientConfig { /// Global AWS config #[derive(Debug, Deserialize, Clone, PartialEq, JsonSchema)] +#[derive(Default)] pub struct AwsConfig { /// Override endpoint for all AWS services @@ -64,11 +59,3 @@ pub struct AwsConfig { pub profile: Option } -impl Default for AwsConfig { - fn default() -> AwsConfig { - AwsConfig{ - endpoint: None, - profile: None - } - } -} \ No newline at end of file diff --git a/src/modules/aws/secretsmanager.rs b/src/modules/aws/secretsmanager.rs index f2d3e7d..116027f 100644 --- a/src/modules/aws/secretsmanager.rs +++ b/src/modules/aws/secretsmanager.rs @@ -76,10 +76,10 @@ async fn retrieve_secret(ctx: &NovopsContext, input: &AwsSecretsManagerSecretInp let client = get_client(ctx).await; let output = client.get_secret_value( - &input.aws_secret.id.as_str(), + input.aws_secret.id.as_str(), input.aws_secret.version_id.clone(), input.aws_secret.version_stage.clone() ).await?; - return Ok(output); + Ok(output) } diff --git a/src/modules/azure/client.rs b/src/modules/azure/client.rs index 5599344..d029452 100644 --- a/src/modules/azure/client.rs +++ b/src/modules/azure/client.rs @@ -23,7 +23,7 @@ impl AzureClient for DefaultAzureClient{ let credential = DefaultAzureCredential::default(); let url = &format!("https://{}.vault.azure.net", vault); - let client = KeyvaultClient::new(&url, std::sync::Arc::new(credential)) + let client = KeyvaultClient::new(url, std::sync::Arc::new(credential)) .with_context(|| format!("Couldn't create Azure Vault client for {:}", url))? .secret_client(); @@ -57,8 +57,8 @@ impl AzureClient for DryRunAzureClient{ pub fn get_client(ctx: &NovopsContext) -> Box { if ctx.dry_run { - return Box::new(DryRunAzureClient{}) + Box::new(DryRunAzureClient{}) } else { - return Box::new(DefaultAzureClient{}) + Box::new(DefaultAzureClient{}) } } \ No newline at end of file diff --git a/src/modules/bitwarden.rs b/src/modules/bitwarden.rs index 6b670d8..4249c33 100644 --- a/src/modules/bitwarden.rs +++ b/src/modules/bitwarden.rs @@ -32,12 +32,12 @@ impl core::ResolveTo for BitwardenItemInput { let json_value = match json_result { Ok(v) => v, - Err(e) => return Err(e.into()) + Err(e) => return Err(e) }; // Novops config let user specify a string like "login.password" // we need to retrieve this field nested in our JSON (or fail if not found) - let fields = self.bitwarden.field.split(".").map(|s| String::from(s)).collect(); + let fields = self.bitwarden.field.split('.').map(String::from).collect(); return Ok( get_string_in_value(&json_value, fields) .with_context(|| format!("Error retrieving field '{:?}' in value {:?} for input {:?}", self.bitwarden.field, &json_value, &self))? @@ -91,7 +91,7 @@ pub fn get_item(item: &String) -> Result { .with_context(|| format!("Couldn't parse Bitwarden stdout as JSON. {:?}", command_context))?; - return Ok(json); + Ok(json) } @@ -107,9 +107,9 @@ pub fn get_string_in_value(value: &serde_json::Value, mut fields: Vec) - let found_value = value.get(&field) .with_context(|| format!("Couldn't find field '{:}' in value {:?}", field, value))?; - if fields.len() > 0 { - return get_string_in_value(found_value, fields); + if !fields.is_empty() { + get_string_in_value(found_value, fields) } else { - return Ok(found_value.as_str().with_context(|| format!("Couldn't convert to string: {:?}", found_value))?.to_string()); + Ok(found_value.as_str().with_context(|| format!("Couldn't convert to string: {:?}", found_value))?.to_string()) } } \ No newline at end of file diff --git a/src/modules/files.rs b/src/modules/files.rs index 5eda7f3..5846dbf 100644 --- a/src/modules/files.rs +++ b/src/modules/files.rs @@ -7,7 +7,7 @@ use sha2::{Sha256, Digest}; use schemars::JsonSchema; use crate::core::{ResolveTo, NovopsContext, BytesResolvableInput}; -use crate::modules::variables::{VariableOutput}; +use crate::modules::variables::VariableOutput; /// @@ -92,7 +92,7 @@ impl ResolveTo for FileInput { name: variable_name, value: file_path_str }, - content: content + content } ) diff --git a/src/modules/gcloud/client.rs b/src/modules/gcloud/client.rs index 2cabbe0..26cdeb2 100644 --- a/src/modules/gcloud/client.rs +++ b/src/modules/gcloud/client.rs @@ -67,7 +67,7 @@ async fn get_authenticator() -> Result return Ok(auth), + Ok(auth) => Ok(auth), Err(e) => { debug!("Couldn't generate Service Account authenticator: ${:?}", e); @@ -76,7 +76,7 @@ async fn get_authenticator() -> Result return Ok(auth), + Ok(auth) => Ok(auth), Err(e) => { debug!("Couldn't generate Authorized User Credentials authenticator: ${:?}", e); @@ -89,7 +89,7 @@ async fn get_authenticator() -> Result { debug!("Couldn't generate Instance Metadata authenticator: ${:?}", e); - return Err(anyhow::anyhow!("Couldn't generate Authenticator for Google client. Did you setup credentials compatible with Application Default Credentials? ")) + Err(anyhow::anyhow!("Couldn't generate Authenticator for Google client. Did you setup credentials compatible with Application Default Credentials? ")) }, } }, @@ -115,7 +115,7 @@ async fn try_metadata_authenticator() -> Result return Err(anyhow::anyhow!("Expected ServiceAccount authenticator.")) }; - return Ok(result); + Ok(result) } // Try to generate a Service Account Authenticator using GOOGLE_APPLICATION_CREDENTIALS env var @@ -134,7 +134,7 @@ async fn try_service_account_authenticator() -> Result return Err(anyhow::anyhow!("Expected ServiceAccount authenticator.")) }; - return Ok(result); + Ok(result) } @@ -147,13 +147,13 @@ async fn try_user_authenticator() -> Result Box { if ctx.dry_run { - return Box::new(DryRunGCloudClient{}) + Box::new(DryRunGCloudClient{}) } else { - return Box::new(DefaultGCloudClient{}) + Box::new(DefaultGCloudClient{}) } } \ No newline at end of file diff --git a/src/modules/gcloud/secretmanager.rs b/src/modules/gcloud/secretmanager.rs index 945bc1a..85ba308 100644 --- a/src/modules/gcloud/secretmanager.rs +++ b/src/modules/gcloud/secretmanager.rs @@ -79,5 +79,5 @@ async fn retrieve_secret_bytes_for(ctx: &NovopsContext, secret: &GCloudSecretMan &secret.name, expected_checksum, calculated_checksum)); } - return Ok(payload_data); + Ok(payload_data) } diff --git a/src/modules/hashivault/client.rs b/src/modules/hashivault/client.rs index 191fa02..ec8ae15 100644 --- a/src/modules/hashivault/client.rs +++ b/src/modules/hashivault/client.rs @@ -67,7 +67,7 @@ impl HashivaultClient for DefaultHashivaultClient { let secret_data: HashMap = kv1::get( &self.client, mount.clone().unwrap_or("secret".to_string()).as_str(), - &path + path ).await.with_context(|| format!("Error reading '{:}' mount at path '{:}'", &_mount, &path))?; return secret_data.get(key) @@ -152,12 +152,12 @@ impl HashivaultClient for DryRunHashivaultClient { pub fn get_client(ctx: &NovopsContext) -> Result, anyhow::Error> { if ctx.dry_run { - return Ok(Box::new(DryRunHashivaultClient{})) + Ok(Box::new(DryRunHashivaultClient{})) } else { let client = build_client(ctx) .with_context(|| "Couldn't build Hashivault client")?; - return Ok(Box::new(DefaultHashivaultClient{ - client: client + Ok(Box::new(DefaultHashivaultClient{ + client })) } @@ -240,7 +240,7 @@ pub fn load_vault_token(ctx: &NovopsContext, home_var: Option, token_va debug!("No vault token found, returning empty string..."); - return Ok(VaultClientSettingsBuilder::default().build()?.token) + Ok(VaultClientSettingsBuilder::default().build()?.token) } @@ -255,7 +255,7 @@ pub fn load_vault_address(ctx: &NovopsContext, addr_var: Option) -> Resu if addr_var.is_some() { debug!("Found VAULT_ADDR variable, using it."); - return Ok(parse_vault_addr_str(addr_var.unwrap())?); + return parse_vault_addr_str(addr_var.unwrap()); } let hvault_config = &ctx.clone().config_file_data.config.unwrap_or_default() @@ -275,14 +275,14 @@ pub fn load_vault_address(ctx: &NovopsContext, addr_var: Option) -> Resu debug!("No vault address found, returning empty string..."); - return Ok(VaultClientSettingsBuilder::default().build()?.address) + Ok(VaultClientSettingsBuilder::default().build()?.address) } fn parse_vault_addr_str(vault_addr_str: String) -> Result { let vault_addr = Url::parse(&vault_addr_str) .with_context(|| format!("Couldn't parse Vault URL '{:?}'", &vault_addr_str))?; - return Ok(vault_addr); + Ok(vault_addr) } fn read_vault_token_file(token_path: &PathBuf) -> Result{ diff --git a/src/modules/hashivault/config.rs b/src/modules/hashivault/config.rs index a0389b9..6c53e64 100644 --- a/src/modules/hashivault/config.rs +++ b/src/modules/hashivault/config.rs @@ -14,6 +14,7 @@ pub struct HashiVaultInput { #[derive(Debug, Deserialize, Clone, PartialEq, JsonSchema)] +#[derive(Default)] pub struct HashivaultConfig { /// Address in form http(s)://HOST:PORT /// @@ -35,13 +36,4 @@ pub struct HashivaultConfig { pub verify: Option } -impl Default for HashivaultConfig { - fn default() -> HashivaultConfig { - HashivaultConfig{ - address: None, - token: None, - token_path: None, - verify: None - } - } -} + diff --git a/src/modules/sops.rs b/src/modules/sops.rs index 18a1ad1..03fb3e4 100644 --- a/src/modules/sops.rs +++ b/src/modules/sops.rs @@ -71,15 +71,13 @@ impl core::ResolveTo for SopsValueInput { let mut args = vec![]; // add --extract flag if specidief in input - self.sops.extract.clone().map(|e| { + if let Some(e) = self.sops.extract.clone() { args.push(String::from("--extract")); args.push(e); - }); + } // Add additional flags if any - self.sops.additional_flags.clone().map(|af| { - args.extend(af); - }); + if let Some(af) = self.sops.additional_flags.clone() { args.extend(af); } let output = run_sops_decrypt(args, &self.sops.file).with_context(|| "Error running sops command.")?; @@ -95,7 +93,7 @@ impl core::ResolveTo> for SopsDotenvInput { if ctx.dry_run { return Ok(vec![VariableOutput { name: String::from("RESULT"), - value: format!("{}:{}", &self.file, &self.additional_flags.clone().unwrap_or(vec![]).join("-")) + value: format!("{}:{}", &self.file, &self.additional_flags.clone().unwrap_or_default().join("-")) }]); } @@ -105,16 +103,13 @@ impl core::ResolveTo> for SopsDotenvInput { ]; // add --extract flag if specidief in input - self.extract.clone().map(|e| { + if let Some(e) = self.extract.clone() { args.push(String::from("--extract")); args.push(e); - }); + } // Add additional flags if any - self.additional_flags.clone().map(|af| { - args.extend(af); - }); - + if let Some(af) = self.additional_flags.clone() { args.extend(af); } let output = run_sops_decrypt(args, &self.file).with_context(|| "Error running sops command.")?; @@ -123,9 +118,9 @@ impl core::ResolveTo> for SopsDotenvInput { let mut variables = vec![]; for line in output.lines() { - let mut parts = line.splitn(2, "="); - let name = parts.next().unwrap(); - let value = parts.next().unwrap(); + let (name, value) = line.split_once('=').unwrap(); + + variables.push(VariableOutput { name: name.to_string(), value: value.to_string() @@ -166,6 +161,6 @@ pub fn run_sops_decrypt(additional_args: Vec, file: &str) -> Result for VariableInput { return Ok( VariableOutput { name: self.name.clone(), - value: value + value } ) } diff --git a/tests/test_aws.rs b/tests/test_aws.rs index e3e46c5..98bb833 100644 --- a/tests/test_aws.rs +++ b/tests/test_aws.rs @@ -1,11 +1,9 @@ pub mod test_lib; - use novops::modules::aws::client::{get_ssm_client, get_secretsmanager_client}; use aws_sdk_ssm::model::ParameterType; use aws_smithy_types::Blob; use test_lib::{load_env_for, test_setup, aws_ensure_role_exists, aws_test_config}; - use log::info; #[tokio::test] @@ -18,9 +16,9 @@ async fn test_assume_role() -> Result<(), anyhow::Error> { info!("test_assume_role: Found variables: {:?}", outputs.variables); - assert!(outputs.variables.get("AWS_ACCESS_KEY_ID").unwrap().value.len() > 0); - assert!(outputs.variables.get("AWS_SECRET_ACCESS_KEY").unwrap().value.len() > 0); - assert!(outputs.variables.get("AWS_SESSION_TOKEN").unwrap().value.len() > 0); + assert!(!outputs.variables.get("AWS_ACCESS_KEY_ID").unwrap().value.is_empty()); + assert!(!outputs.variables.get("AWS_SECRET_ACCESS_KEY").unwrap().value.is_empty()); + assert!(!outputs.variables.get("AWS_SESSION_TOKEN").unwrap().value.is_empty()); Ok(()) } @@ -113,22 +111,19 @@ async fn ensure_test_secret_exists(sname: &str, string_value: Option, bi -> Result<(), anyhow::Error> { let client = get_secretsmanager_client(&aws_test_config()).await?; - match client.describe_secret().secret_id(sname).send().await { - Ok(r) => { - info!("Secret {} already exists, deleting...", sname); + if let Ok(r) = client.describe_secret().secret_id(sname).send().await { + info!("Secret {} already exists, deleting...", sname); - client.delete_secret() - .secret_id(r.arn().unwrap()) - .force_delete_without_recovery(true) - .send().await?; - }, - Err(_) => {}, // secret does not exists, ignore + client.delete_secret() + .secret_id(r.arn().unwrap()) + .force_delete_without_recovery(true) + .send().await?; } let r = client.create_secret() .name(sname) .set_secret_string(string_value) - .set_secret_binary(binary_value.map(|b| Blob::new(b))) + .set_secret_binary(binary_value.map(Blob::new)) .send().await?; info!("Create AWS secret {} response: {:?}", sname, r); diff --git a/tests/test_core.rs b/tests/test_core.rs index 7f23e04..f241a88 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -101,7 +101,7 @@ async fn test_simple_run() -> Result<(), anyhow::Error>{ let file_dog_mode = file_dog_metadata.permissions().mode(); let expected_file_cat_path = PathBuf::from("/tmp/novops_cat"); - let expected_file_cat_content = fs::read_to_string(&expected_file_cat_path)?; + let expected_file_cat_content = fs::read_to_string(expected_file_cat_path)?; // Expect to match content of CONFIG_STANDALONE // use r#"_"# for raw string literal @@ -218,18 +218,18 @@ async fn test_dry_run() -> Result<(), anyhow::Error> { info!("test_dry_run: Found files: {:?}", &result.files); - assert!(result.variables.get("VAR").unwrap().value.len() > 0); - assert!(result.variables.get("AWS_SECRETMANAGER").unwrap().value.len() > 0); - assert!(result.variables.get("AWS_SSM_PARAMETER").unwrap().value.len() > 0); - assert!(result.variables.get("HASHIVAULT_KV_V2").unwrap().value.len() > 0); - assert!(result.variables.get("BITWARDEN").unwrap().value.len() > 0); - assert!(result.variables.get("GCLOUD_SECRETMANAGER").unwrap().value.len() > 0); - assert!(result.files.get("/tmp/novopsfile").unwrap().content.len() > 0); + assert!(!result.variables.get("VAR").unwrap().value.is_empty()); + assert!(!result.variables.get("AWS_SECRETMANAGER").unwrap().value.is_empty()); + assert!(!result.variables.get("AWS_SSM_PARAMETER").unwrap().value.is_empty()); + assert!(!result.variables.get("HASHIVAULT_KV_V2").unwrap().value.is_empty()); + assert!(!result.variables.get("BITWARDEN").unwrap().value.is_empty()); + assert!(!result.variables.get("GCLOUD_SECRETMANAGER").unwrap().value.is_empty()); + assert!(!result.files.get("/tmp/novopsfile").unwrap().content.is_empty()); // aws.assumerole - assert!(result.variables.get("AWS_ACCESS_KEY_ID").unwrap().value.len() > 0); - assert!(result.variables.get("AWS_SESSION_TOKEN").unwrap().value.len() > 0); - assert!(result.variables.get("AWS_SECRET_ACCESS_KEY").unwrap().value.len() > 0); + assert!(!result.variables.get("AWS_ACCESS_KEY_ID").unwrap().value.is_empty()); + assert!(!result.variables.get("AWS_SESSION_TOKEN").unwrap().value.is_empty()); + assert!(!result.variables.get("AWS_SECRET_ACCESS_KEY").unwrap().value.is_empty()); Ok(()) } @@ -264,7 +264,7 @@ async fn test_run_prepare_process() -> Result<(), anyhow::Error> { assert_eq!(result.get_program(), OsStr::new("sh")); - let result_args : Vec<&OsStr> = result.get_args().map(|arg| arg).collect(); + let result_args : Vec<&OsStr> = result.get_args().collect(); assert_eq!(result_args, vec![OsStr::new(&arg1), OsStr::new(&arg2)]); Ok(()) @@ -277,16 +277,16 @@ async fn test_should_error_tty() -> Result<(), anyhow::Error> { let symlink_some = Some(String::from(".envrc")); // terminal is tty - assert_eq!(should_error_tty(true, true, &symlink_none), false, "Skipped tty check should not provoke failsafe"); - assert_eq!(should_error_tty(true, true, &symlink_some), false, "Skipped tty check should not provoke failsafe"); - assert_eq!(should_error_tty(true, false, &symlink_none), true, "tty terminal without symlink should provoke failsafe"); - assert_eq!(should_error_tty(true, false, &symlink_some), false, "tty terminal with symlink should not provoke failsafe"); + assert!(!should_error_tty(true, true, &symlink_none), "Skipped tty check should not provoke failsafe"); + assert!(!should_error_tty(true, true, &symlink_some), "Skipped tty check should not provoke failsafe"); + assert!(should_error_tty(true, false, &symlink_none), "tty terminal without symlink should provoke failsafe"); + assert!(!should_error_tty(true, false, &symlink_some), "tty terminal with symlink should not provoke failsafe"); // terminal is NOT tty - assert_eq!(should_error_tty(false, true, &symlink_none), false, "Non-tty terminal should not cause failsafe"); - assert_eq!(should_error_tty(false, true, &symlink_some), false, "Non-tty terminal should not cause failsafe"); - assert_eq!(should_error_tty(false, false, &symlink_none), false, "Non-tty terminal should not cause failsafe"); - assert_eq!(should_error_tty(false, false, &symlink_some), false, "Non-tty terminal should not cause failsafe"); + assert!(!should_error_tty(false, true, &symlink_none), "Non-tty terminal should not cause failsafe"); + assert!(!should_error_tty(false, true, &symlink_some), "Non-tty terminal should not cause failsafe"); + assert!(!should_error_tty(false, false, &symlink_none), "Non-tty terminal should not cause failsafe"); + assert!(!should_error_tty(false, false, &symlink_some), "Non-tty terminal should not cause failsafe"); Ok(()) } @@ -337,7 +337,7 @@ async fn check_working_dir_permissions_test() -> Result<(), anyhow::Error> { let dir = tempfile::tempdir().unwrap().into_path(); let perm = Permissions::from_mode(mode); fs::set_permissions(&dir, perm).unwrap(); - return dir + dir } let dir_user = make_tmp_dir(0o700); diff --git a/tests/test_hvault.rs b/tests/test_hvault.rs index 6a71442..620778a 100644 --- a/tests/test_hvault.rs +++ b/tests/test_hvault.rs @@ -19,7 +19,7 @@ use novops::modules::hashivault::{ client::{load_vault_token, load_vault_address}, config::HashivaultConfig }; -use novops::core::{NovopsConfig, NovopsContext}; +use novops::core::NovopsContext; #[tokio::test] @@ -95,9 +95,9 @@ async fn test_hashivault_aws() -> Result<(), anyhow::Error> { info!("Hashivault AWS credentials: {:?}", outputs); - assert!(outputs.variables.get("AWS_ACCESS_KEY_ID").unwrap().value.len() > 0); - assert!(outputs.variables.get("AWS_SECRET_ACCESS_KEY").unwrap().value.len() > 0); - assert!(outputs.variables.get("AWS_SESSION_TOKEN").unwrap().value.len() > 0); + assert!(!outputs.variables.get("AWS_ACCESS_KEY_ID").unwrap().value.is_empty()); + assert!(!outputs.variables.get("AWS_SECRET_ACCESS_KEY").unwrap().value.is_empty()); + assert!(!outputs.variables.get("AWS_SESSION_TOKEN").unwrap().value.is_empty()); Ok(()) } @@ -141,7 +141,7 @@ async fn test_hashivault_client_token_load() -> Result<(), anyhow::Error> { // Providing token path should read token path before plain token let tmp_token_path = "/tmp/token"; let token_file_content = "token_in_file"; - fs::write(&tmp_token_path, token_file_content) + fs::write(tmp_token_path, token_file_content) .with_context(|| format!("Couldn't write test token to {tmp_token_path}"))?; let ctx_token_path = create_dummy_context_with_hvault( @@ -210,9 +210,9 @@ async fn enable_engine(client: &VaultClient, path: &str, engine_type: &str, opts if ! mounts.contains_key(format!("{:}/", path).as_str()) { let mut options = vaultrs::api::sys::requests::EnableEngineRequest::builder(); - if opts.is_some(){ - options.options(opts.unwrap()); - }; + if let Some(opts) = opts { + options.options(opts); + } vaultrs::sys::mount::enable(client, path, engine_type, Some(&mut options)).await .with_context(|| format!("Couldn!'t enable engine {:} at path {:}", engine_type, path))?; @@ -226,16 +226,14 @@ async fn enable_engine(client: &VaultClient, path: &str, engine_type: &str, opts fn create_dummy_context_with_hvault(addr: Option, token: Option, token_path: Option) -> NovopsContext { let mut ctx = create_dummy_context(); - let mut novops_config = NovopsConfig::default(); - - novops_config.hashivault = Some(HashivaultConfig { + let novops_config = novops::core::NovopsConfig { hashivault: Some(HashivaultConfig { address: addr, - token: token, - token_path: token_path, + token, + token_path, verify: Some(false) - }); + }), ..Default::default() }; ctx.config_file_data.config = Some(novops_config); - return ctx + ctx } \ No newline at end of file diff --git a/tests/test_lib/mod.rs b/tests/test_lib/mod.rs index 9011b03..1cbc435 100644 --- a/tests/test_lib/mod.rs +++ b/tests/test_lib/mod.rs @@ -26,7 +26,7 @@ pub fn clean_and_setup_test_dir(test_name: &str) -> Result Result<(), anyhow::Error> { let aws_creds = std::env::current_dir()?.join("tests/aws/credentials"); std::env::set_var("AWS_CONFIG_FILE", aws_config.to_str().unwrap()); - std::env::set_var("AWS_SHARED_CREDENTIALS_FILE", &aws_creds.to_str().unwrap()); + std::env::set_var("AWS_SHARED_CREDENTIALS_FILE", aws_creds.to_str().unwrap()); // known age keys std::env::set_var("SOPS_AGE_KEY_FILE", "tests/sops/age1"); @@ -96,7 +96,7 @@ pub async fn test_setup() -> Result<(), anyhow::Error> { pub fn aws_test_config() -> AwsClientConfig { let mut aws_conf = AwsClientConfig::default(); aws_conf.endpoint("http://localhost:4566/"); // Localstack - return aws_conf; + aws_conf } /** @@ -107,12 +107,9 @@ pub async fn aws_ensure_role_exists(role_name: &str) -> Result<(), anyhow::Error let client = get_iam_client(&aws_test_config()).await?; let existing_role_result = client.get_role().role_name(role_name).send().await; - match existing_role_result { - Ok(_) => { - // role exists, clean before running test - client.delete_role().role_name(role_name).send().await?; - } - Err(_) => {} // role do not exists, do nothing + if existing_role_result.is_ok() { + // role exists, clean before running test + client.delete_role().role_name(role_name).send().await?; } client