diff --git a/docs/environments/index.md b/docs/environments/index.md index bf24850436..0fe79aff57 100644 --- a/docs/environments/index.md +++ b/docs/environments/index.md @@ -40,7 +40,17 @@ tools. To do that, turn the value into a map with `tools = true`: ```toml [env] MY_VAR = { value = "tools path: {{env.PATH}}", tools = true } -_.path = { value = ["{{env.GEM_HOME}}/bin"], tools = true } # directives may also set tools = true +_.path = { path = ["{{env.GEM_HOME}}/bin"], tools = true } # directives may also set tools = true +``` + +## Redactions + +Variables can be redacted from the output by setting `redact = true`: + +```toml +[env] +SECRET = { value = "my_secret", redact = true } +_.file = { path = [".env.json"], tools = true } # directives may also set redact = true ``` ## `env._` directives diff --git a/docs/tasks/task-configuration.md b/docs/tasks/task-configuration.md index 76ba06967d..0e777b304a 100644 --- a/docs/tasks/task-configuration.md +++ b/docs/tasks/task-configuration.md @@ -321,47 +321,24 @@ run = "echo task4" If you want auto-completion/validation in included toml tasks files, you can use the following JSON schema: -## `[redactions]` options +## `redactions` + +- **Type**: `string[]` Redactions are a way to hide sensitive information from the output of tasks. This is useful for things like API keys, passwords, or other sensitive information that you don't want to accidentally leak in logs or other output. -### `redactions.env` - -- **Type**: `string[]` - A list of environment variables to redact from the output. ```toml -[redactions] -env = ["API_KEY", "PASSWORD"] -[tasks.test] -run = "echo $API_KEY" +redactions = ["API_KEY", "PASSWORD"] ``` Running the above task will output `echo [redacted]` instead. You can also specify these as a glob pattern, e.g.: `redactions.env = ["SECRETS_*"]`. -### `redactions.vars` - -- **Type**: `string[]` - -A list of [vars](#vars) to redact from the output. - -```toml -[vars] -secret = "mysecret" -[tasks.test] -run = "echo {{vars.secret}}" -``` - -:::tip -This is generally useful when using `mise.local.toml` to put secret vars in which can be shared -with any other `mise.toml` file in the hierarchy. -::: - ## `[vars]` options Vars are variables that can be shared between tasks like environment variables but they are not diff --git a/e2e/tasks/test_task_redactions b/e2e/tasks/test_task_redactions index 4212256a50..9ba168d5ae 100644 --- a/e2e/tasks/test_task_redactions +++ b/e2e/tasks/test_task_redactions @@ -1,40 +1,51 @@ #!/usr/bin/env bash cat <mise.toml +redactions = ["SECRET"] [env] SECRET = "my_secret" [tasks.a] run = 'echo secret: \$SECRET' - -[redactions] -env = ["SECRET"] EOF assert "mise run a" "secret: [redacted]" cat <mise.toml +redactions = ["secret"] [tasks.a] run = 'echo secret: {{ vars.secret }}' -[redactions] -vars = ["secret"] - [vars] secret = "my_secret" EOF - assert "mise run a" "secret: [redacted]" cat <mise.toml +redactions = ["SECRET*"] [env] SECRET_FOO = "my_secret_wild" [tasks.a] run = 'echo secret: \$SECRET_FOO' +EOF +assert "mise run a" "secret: [redacted]" -[redactions] -env = ["SECRET*"] +cat <mise.toml +[env] +SECRET= {value = "my_secret", redact = true} + +[tasks.a] +run = 'echo secret: \$SECRET' EOF +assert "mise run a" "secret: [redacted]" + +echo '{ "SECRET": "my_secret" }' >.env.json +cat <mise.toml +[env] +_.file = {path = ".env.json", redact = true} +[tasks.a] +run = 'echo secret: \$SECRET' +EOF assert "mise run a" "secret: [redacted]" diff --git a/schema/mise.json b/schema/mise.json index f8479a4e50..29d15dde02 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -7,6 +7,32 @@ "env": { "additionalProperties": { "oneOf": [ + { + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "tools": { + "type": "boolean", + "description": "load tools before resolving" + }, + "redact": { + "type": "boolean", + "description": "redact the value from logs" + } + } + }, { "type": "string" }, @@ -14,7 +40,6 @@ "type": "number" }, { - "enum": [false], "type": "boolean" } ] @@ -27,6 +52,32 @@ "properties": { "file": { "oneOf": [ + { + "type": "object", + "properties": { + "path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "tools": { + "type": "boolean", + "description": "load tools before resolving" + }, + "redact": { + "type": "boolean", + "description": "redact the value from logs" + } + } + }, { "description": "dotenv file to load", "type": "string" @@ -43,6 +94,28 @@ }, "path": { "oneOf": [ + { + "type": "object", + "properties": { + "path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "redact": { + "type": "boolean", + "description": "redact the value from logs" + } + } + }, { "description": "PATH entry to add", "type": "string" @@ -101,6 +174,32 @@ }, "source": { "oneOf": [ + { + "type": "object", + "properties": { + "path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "tools": { + "type": "boolean", + "description": "load tools before resolving" + }, + "redact": { + "type": "boolean", + "description": "redact the value from logs" + } + } + }, { "description": "bash script to load", "type": "string" @@ -1249,6 +1348,13 @@ "pattern": "^\\d+\\.\\d+\\.\\d+$", "type": "string" }, + "redactions": { + "description": "env or vars keys to redact from logs", + "type": "array", + "items": { + "type": "string" + } + }, "plugins": { "additionalProperties": { "description": "url to plugin repository", diff --git a/src/cli/run.rs b/src/cli/run.rs index facf43427a..7be5155fcc 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashSet}; use std::io::Write; use std::iter::once; +use std::ops::Deref; use std::path::{Path, PathBuf}; use std::process::Stdio; use std::sync::Mutex; @@ -380,7 +381,7 @@ impl Run { .bright() .to_string(); if !self.quiet(Some(task)) { - let msg = format!("{prefix} {}", config.redact(cmd)?); + let msg = format!("{prefix} {}", config.redact(cmd)); eprintln!("{}", trunc(&msg)); } @@ -481,7 +482,7 @@ impl Run { if !self.quiet(Some(task)) { let cmd = format!("{} {}", display_path(file), args.join(" ")); let cmd = style::ebold(format!("$ {cmd}")).bright().to_string(); - let cmd = trunc(&format!("{prefix} {}", config.redact(cmd)?)); + let cmd = trunc(&format!("{prefix} {}", config.redact(cmd))); eprintln!("{cmd}"); } @@ -510,11 +511,11 @@ impl Run { ) -> Result<()> { let config = Config::get(); let program = program.to_executable(); - let redactions = config.redactions()?; + let redactions = config.redactions(); let mut cmd = CmdLineRunner::new(program.clone()) .args(args) .envs(env) - .redact(redactions.clone()) + .redact(redactions.deref().clone()) .raw(self.raw(Some(task))); cmd.with_pass_signals(); match self.output(Some(task)) { diff --git a/src/config/config_file/mise_toml.rs b/src/config/config_file/mise_toml.rs index e3e3a18d33..eb1bd26d78 100644 --- a/src/config/config_file/mise_toml.rs +++ b/src/config/config_file/mise_toml.rs @@ -851,7 +851,10 @@ impl<'de> de::Deserialize<'de> for EnvList { python: venv.python, uv_create_args: venv.uv_create_args, python_create_args: venv.python_create_args, - options: EnvDirectiveOptions { tools: true }, + options: EnvDirectiveOptions { + tools: true, + redact: false, + }, }); } } @@ -860,7 +863,11 @@ impl<'de> de::Deserialize<'de> for EnvList { Int(i64), Str(String), Bool(bool), - Map { value: Box, tools: bool }, + Map { + value: Box, + tools: bool, + redact: bool, + }, } impl Display for Val { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { @@ -868,11 +875,18 @@ impl<'de> de::Deserialize<'de> for EnvList { Val::Int(i) => write!(f, "{}", i), Val::Str(s) => write!(f, "{}", s), Val::Bool(b) => write!(f, "{}", b), - Val::Map { value, tools } => { + Val::Map { + value, + tools, + redact, + } => { write!(f, "{}", value)?; if *tools { write!(f, " tools")?; } + if *redact { + write!(f, " redact")?; + } Ok(()) } } @@ -926,6 +940,7 @@ impl<'de> de::Deserialize<'de> for EnvList { { let mut value: Option = None; let mut tools = None; + let mut redact = None; while let Some((key, val)) = map.next_entry::()? { @@ -936,10 +951,13 @@ impl<'de> de::Deserialize<'de> for EnvList { "tools" => { tools = Some(val); } + "redact" => { + redact = Some(val); + } _ => { return Err(de::Error::unknown_field( &key, - &["value", "tools"], + &["value", "tools", "redact"], )); } } @@ -954,6 +972,15 @@ impl<'de> de::Deserialize<'de> for EnvList { Ok(Val::Map { value: Box::new(value), tools, + redact: redact + .map(|r| { + if let Val::Bool(b) = r { + b + } else { + false + } + }) + .unwrap_or_default(), }) } } @@ -982,8 +1009,12 @@ impl<'de> de::Deserialize<'de> for EnvList { Val::Bool(false) => { env.push(EnvDirective::Rm(key, Default::default())) } - Val::Map { value, tools } => { - let opts = EnvDirectiveOptions { tools }; + Val::Map { + value, + tools, + redact, + } => { + let opts = EnvDirectiveOptions { tools, redact }; env.push(EnvDirective::Val(key, value.to_string(), opts)); } } @@ -1165,12 +1196,15 @@ impl<'de> de::Deserialize<'de> for MiseTomlEnvDirective { let mut value = None; while let Some((k, v)) = map.next_entry::()? { match k.as_str() { - "value" => { + "value" | "path" => { value = Some(v.as_str().unwrap().to_string()); } "tools" => { options.tools = v.as_bool().unwrap(); } + "redact" => { + options.redact = v.as_bool().unwrap(); + } _ => { return Err(de::Error::custom("invalid key")); } diff --git a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__env-4.snap b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__env-4.snap index 8a07b4aea5..154c7a84e4 100644 --- a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__env-4.snap +++ b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__env-4.snap @@ -11,6 +11,7 @@ MiseToml(~/cwd/.test.mise.toml): ToolRequestSet: { "bar", EnvDirectiveOptions { tools: false, + redact: false, }, ), Val( @@ -18,6 +19,7 @@ MiseToml(~/cwd/.test.mise.toml): ToolRequestSet: { "qux\\nquux", EnvDirectiveOptions { tools: false, + redact: false, }, ), Val( @@ -25,6 +27,7 @@ MiseToml(~/cwd/.test.mise.toml): ToolRequestSet: { "qux\nquux", EnvDirectiveOptions { tools: false, + redact: false, }, ), ], diff --git a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-5.snap b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-5.snap index 444b8461d3..3749cc5d52 100644 --- a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-5.snap +++ b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-5.snap @@ -10,6 +10,7 @@ MiseToml(~/fixtures/.mise.toml): ToolRequestSet: terraform@1.0.0 node@18 node@pr "production", EnvDirectiveOptions { tools: false, + redact: false, }, ), ], diff --git a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture.snap b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture.snap index 7d2f45c7c7..7b4f1ccdb8 100644 --- a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture.snap +++ b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture.snap @@ -9,6 +9,7 @@ snapshot_kind: text "production", EnvDirectiveOptions { tools: false, + redact: false, }, ), ] diff --git a/src/config/env_directive/mod.rs b/src/config/env_directive/mod.rs index 729e36ee01..23cde2f957 100644 --- a/src/config/env_directive/mod.rs +++ b/src/config/env_directive/mod.rs @@ -1,16 +1,15 @@ -use crate::env; -use std::cmp::PartialEq; -use std::collections::{BTreeSet, HashMap}; -use std::fmt::{Debug, Display, Formatter}; -use std::path::{Path, PathBuf}; - use crate::config::config_file::{config_root, trust_check}; use crate::dirs; +use crate::env; use crate::env_diff::EnvMap; use crate::file::display_path; use crate::tera::{get_tera, tera_exec}; use eyre::{eyre, Context}; use indexmap::IndexMap; +use std::cmp::PartialEq; +use std::collections::{BTreeSet, HashMap}; +use std::fmt::{Debug, Display, Formatter}; +use std::path::{Path, PathBuf}; mod file; mod module; @@ -21,6 +20,7 @@ mod venv; #[derive(Debug, Clone, Default, PartialEq)] pub struct EnvDirectiveOptions { pub(crate) tools: bool, + pub(crate) redact: bool, } #[derive(Debug, Clone, PartialEq)] @@ -115,6 +115,7 @@ pub struct EnvResults { pub env_files: Vec, pub env_paths: Vec, pub env_scripts: Vec, + pub redactions: Vec, } impl EnvResults { @@ -135,6 +136,7 @@ impl EnvResults { env_files: Vec::new(), env_paths: Vec::new(), env_scripts: Vec::new(), + redactions: Vec::new(), }; let normalize_path = |config_root: &Path, p: PathBuf| { let p = p.strip_prefix("./").unwrap_or(&p); @@ -189,6 +191,7 @@ impl EnvResults { .map(|(k, (v, _))| (k.clone(), v.clone())) .collect::(); ctx.insert("env", &env_vars); + let redact = directive.options().redact; // trace!("resolve: ctx.get('env'): {:#?}", &ctx.get("env")); match directive { EnvDirective::Val(k, v, _opts) => { @@ -257,6 +260,12 @@ impl EnvResults { Self::module(&mut r, source, name, &value)?; } }; + + if redact { + for k in env.keys() { + r.redactions.push(k.clone()); + } + } } let env_vars = env .iter() @@ -281,6 +290,7 @@ impl EnvResults { .map(|s| normalize_path(&config_root, s)) .for_each(|p| r.env_paths.push(p.clone())); } + Ok(r) } diff --git a/src/config/env_directive/venv.rs b/src/config/env_directive/venv.rs index 4db32a092f..bbefd0b377 100644 --- a/src/config/env_directive/venv.rs +++ b/src/config/env_directive/venv.rs @@ -158,7 +158,10 @@ mod tests { python: None, uv_create_args: None, python_create_args: None, - options: EnvDirectiveOptions { tools: true }, + options: EnvDirectiveOptions { + tools: true, + redact: false, + }, }, Default::default(), ), @@ -169,7 +172,10 @@ mod tests { python: None, uv_create_args: None, python_create_args: None, - options: EnvDirectiveOptions { tools: true }, + options: EnvDirectiveOptions { + tools: true, + redact: false, + }, }, Default::default(), ), diff --git a/src/config/mod.rs b/src/config/mod.rs index ef71cbc50e..6cfc362ba7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,6 +7,7 @@ pub use settings::Settings; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt::{Debug, Formatter}; use std::iter::once; +use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, OnceLock, RwLock}; use std::time::Duration; @@ -34,11 +35,11 @@ pub mod settings; pub mod tracking; use crate::cli::self_update::SelfUpdate; +use crate::env_diff::EnvMap; use crate::hook_env::WatchFilePattern; use crate::hooks::Hook; use crate::plugins::PluginType; -use crate::redactions::Redactions; -use crate::tera::{get_tera, BASE_CONTEXT}; +use crate::tera::BASE_CONTEXT; use crate::watch_files::WatchFile; use crate::wildcard::Wildcard; pub use settings::SETTINGS; @@ -57,7 +58,6 @@ pub struct Config { aliases: AliasMap, env: OnceCell, env_with_sources: OnceCell, - redactions: OnceCell>, shorthands: OnceLock, hooks: OnceCell>, tasks: OnceCell>, @@ -72,6 +72,7 @@ pub struct Alias { } static _CONFIG: RwLock>> = RwLock::new(None); +static _REDACTIONS: Lazy>>> = Lazy::new(Default::default); pub fn is_loaded() -> bool { _CONFIG.read().unwrap().is_some() @@ -108,7 +109,7 @@ impl Config { let mut tera_ctx = BASE_CONTEXT.clone(); let vars_results = load_vars(tera_ctx.clone(), &config_files)?; - let vars = vars_results + let vars: IndexMap = vars_results .env .iter() .map(|(k, (v, _))| (k.clone(), v.clone())) @@ -120,11 +121,10 @@ impl Config { project_root: get_project_root(&config_files), repo_urls: load_plugins(&config_files)?, tera_ctx, - vars, + vars: vars.clone(), config_files, env: OnceCell::new(), env_with_sources: OnceCell::new(), - redactions: OnceCell::new(), shorthands: OnceLock::new(), hooks: OnceCell::new(), tasks: OnceCell::new(), @@ -140,6 +140,9 @@ impl Config { config.all_aliases = config.load_all_aliases(); time!("load all aliases"); + config.add_redactions(config.redaction_keys(), &vars.into_iter().collect()); + time!("load redactions"); + if log::log_enabled!(log::Level::Trace) { trace!("config: {config:#?}"); } else if log::log_enabled!(log::Level::Debug) { @@ -147,6 +150,7 @@ impl Config { debug!("config: {}", display_path(p)); } } + time!("load done"); for (plugin, url) in &config.repo_urls { @@ -584,7 +588,19 @@ impl Config { // trace!("load_env: entries: {:#?}", entries); let env_results = EnvResults::resolve(self.tera_ctx.clone(), &env::PRISTINE_ENV, entries, false)?; - time!("load_env done"); + let redact_keys = self + .redaction_keys() + .into_iter() + .chain(env_results.redactions.clone()) + .collect_vec(); + self.add_redactions( + redact_keys, + &env_results + .env + .iter() + .map(|(k, v)| (k.clone(), v.0.clone())) + .collect(), + ); if log::log_enabled!(log::Level::Trace) { trace!("{env_results:#?}"); } else { @@ -649,53 +665,68 @@ impl Config { .collect()) } - fn tera(&self, config_root: &Path) -> (tera::Tera, tera::Context) { - let tera = get_tera(Some(config_root)); - let mut ctx = self.tera_ctx.clone(); - ctx.insert("config_root", &config_root); - (tera, ctx) + pub fn redaction_keys(&self) -> Vec { + self.config_files + .values() + .flat_map(|cf| cf.redactions().0.iter()) + .cloned() + .collect() + } + pub fn add_redactions(&self, redactions: impl IntoIterator, env: &EnvMap) { + let mut r = _REDACTIONS.lock().unwrap(); + let redactions = redactions.into_iter().flat_map(|r| { + let matcher = Wildcard::new(vec![r]); + env.iter() + .filter(|(k, _)| matcher.match_any(k)) + .map(|(_, v)| v.clone()) + .collect::>() + }); + *r = Arc::new(r.iter().cloned().chain(redactions).collect()); } - pub fn redactions(&self) -> Result<&IndexSet> { - self.redactions.get_or_try_init(|| { - let mut redactions = Redactions::default(); - for cf in self.config_files.values() { - let r = cf.redactions(); - if !r.is_empty() { - let mut r = r.clone(); - let (tera, ctx) = self.tera(&cf.config_root()); - r.render(&mut tera.clone(), &ctx)?; - redactions.merge(r); - } - } - if redactions.is_empty() { - return Ok(Default::default()); - } - - let ts = self.get_toolset()?; - let env = ts.full_env()?; - - let env_matcher = Wildcard::new(redactions.env.clone()); - let var_matcher = Wildcard::new(redactions.vars.clone()); - - let env_vals = env - .into_iter() - .filter(|(k, _)| env_matcher.match_any(k)) - .map(|(_, v)| v); - let var_vals = self - .vars - .iter() - .filter(|(k, _)| var_matcher.match_any(k)) - .map(|(_, v)| v.to_string()); - Ok(env_vals.chain(var_vals).collect()) - }) + pub fn redactions(&self) -> Arc> { + let r = _REDACTIONS.lock().unwrap(); + r.deref().clone() + + // self.redactions.get_or_try_init(|| { + // let mut redactions = Redactions::default(); + // for cf in self.config_files.values() { + // let r = cf.redactions(); + // if !r.is_empty() { + // let mut r = r.clone(); + // let (tera, ctx) = self.tera(&cf.config_root()); + // r.render(&mut tera.clone(), &ctx)?; + // redactions.merge(r); + // } + // } + // if redactions.is_empty() { + // return Ok(Default::default()); + // } + // + // let ts = self.get_toolset()?; + // let env = ts.full_env()?; + // + // let env_matcher = Wildcard::new(redactions.env.clone()); + // let var_matcher = Wildcard::new(redactions.vars.clone()); + // + // let env_vals = env + // .into_iter() + // .filter(|(k, _)| env_matcher.match_any(k)) + // .map(|(_, v)| v); + // let var_vals = self + // .vars + // .iter() + // .filter(|(k, _)| var_matcher.match_any(k)) + // .map(|(_, v)| v.to_string()); + // Ok(env_vals.chain(var_vals).collect()) + // }) } - pub fn redact(&self, mut input: String) -> Result { - for redaction in self.redactions()? { + pub fn redact(&self, mut input: String) -> String { + for redaction in self.redactions().deref() { input = input.replace(redaction, "[redacted]"); } - Ok(input) + input } } diff --git a/src/logger.rs b/src/logger.rs index 85655bacad..4160df6021 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,4 +1,4 @@ -use crate::config::Settings; +use crate::config::{Config, Settings}; use eyre::Result; use std::fs::{create_dir_all, File, OpenOptions}; use std::io::Write; @@ -6,7 +6,7 @@ use std::path::Path; use std::sync::Mutex; use std::thread; -use crate::{env, ui}; +use crate::{config, env, ui}; use log::{Level, LevelFilter, Metadata, Record}; use once_cell::sync::Lazy; @@ -74,6 +74,11 @@ impl Logger { } fn render(&self, record: &Record, level: LevelFilter) -> String { + let mut args = record.args().to_string(); + if config::is_loaded() { + let config = Config::get(); + args = config.redact(args); + } match level { LevelFilter::Off => "".to_string(), LevelFilter::Trace => { @@ -87,17 +92,11 @@ impl Logger { thread_id = thread_id(), line = record.line().unwrap_or(0), )); - format!( - "{level} {meta} {args}", - level = self.styled_level(level), - args = record.args() - ) + format!("{level} {meta} {args}", level = self.styled_level(level),) + } + LevelFilter::Debug => { + format!("{level} {args}", level = self.styled_level(record.level()),) } - LevelFilter::Debug => format!( - "{level} {args}", - level = self.styled_level(record.level()), - args = record.args() - ), _ => { let mise = match record.level() { Level::Error => ui::style::ered("mise"), @@ -105,11 +104,10 @@ impl Logger { _ => ui::style::edim("mise"), }; match record.level() { - Level::Info => format!("{mise} {args}", args = record.args()), + Level::Info => format!("{mise} {args}"), _ => format!( "{mise} {level} {args}", level = self.styled_level(record.level()), - args = record.args() ), } } diff --git a/src/redactions.rs b/src/redactions.rs index e77b1f37fb..fc952137fa 100644 --- a/src/redactions.rs +++ b/src/redactions.rs @@ -1,34 +1,21 @@ use indexmap::IndexSet; #[derive(Default, Clone, Debug, serde::Deserialize)] -pub struct Redactions { - #[serde(default)] - pub env: IndexSet, - #[serde(default)] - pub vars: IndexSet, -} +pub struct Redactions(pub IndexSet); impl Redactions { pub fn merge(&mut self, other: Self) { - for e in other.env { - self.env.insert(e); - } - for v in other.vars { - self.vars.insert(v); - } + self.0.extend(other.0); } pub fn render(&mut self, tera: &mut tera::Tera, ctx: &tera::Context) -> eyre::Result<()> { - for r in self.env.clone().drain(..) { - self.env.insert(tera.render_str(&r, ctx)?); - } - for r in self.vars.clone().drain(..) { - self.vars.insert(tera.render_str(&r, ctx)?); + for r in self.0.clone().drain(..) { + self.0.insert(tera.render_str(&r, ctx)?); } Ok(()) } pub fn is_empty(&self) -> bool { - self.env.is_empty() && self.vars.is_empty() + self.0.is_empty() } }