diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index bdcfe0cdc42..64b1ef72763 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -974,7 +974,7 @@ async fn cmd_nexus_blueprints_show( .blueprint_view(&args.blueprint_id) .await .with_context(|| format!("fetching blueprint {}", args.blueprint_id))?; - println!("{:?}", blueprint); + println!("{}", blueprint.display()); Ok(()) } diff --git a/dev-tools/omdb/tests/env.out b/dev-tools/omdb/tests/env.out index 512c05fc867..0e0a198f342 100644 --- a/dev-tools/omdb/tests/env.out +++ b/dev-tools/omdb/tests/env.out @@ -3,8 +3,8 @@ termination: Exited(0) --------------------------------------------- stdout: SERIAL IP ROLE ID -sim-039be560 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED -sim-b6d65341 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED +sim-039be560 [::1]:REDACTED_PORT scrimlet ..................... +sim-b6d65341 [::1]:REDACTED_PORT scrimlet ..................... --------------------------------------------- stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable @@ -293,8 +293,8 @@ termination: Exited(0) --------------------------------------------- stdout: SERIAL IP ROLE ID -sim-039be560 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED -sim-b6d65341 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED +sim-039be560 [::1]:REDACTED_PORT scrimlet ..................... +sim-b6d65341 [::1]:REDACTED_PORT scrimlet ..................... --------------------------------------------- stderr: note: database URL not specified. Will search DNS. @@ -307,8 +307,8 @@ termination: Exited(0) --------------------------------------------- stdout: SERIAL IP ROLE ID -sim-039be560 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED -sim-b6d65341 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED +sim-039be560 [::1]:REDACTED_PORT scrimlet ..................... +sim-b6d65341 [::1]:REDACTED_PORT scrimlet ..................... --------------------------------------------- stderr: note: database URL not specified. Will search DNS. diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index 6e25f7b3a3e..ff19bbb9a72 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -26,7 +26,7 @@ termination: Exited(0) stdout: DNS zone: oxide-dev.test (External) requested version: 2 (created at ) -version created by Nexus: REDACTED_UUID_REDACTED_UUID_REDACTED +version created by Nexus: ..................... version created because: create silo: "test-suite-silo" changes: names added: 1, names removed: 0 @@ -58,7 +58,7 @@ stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable note: database schema version matches expected () ============================================= -EXECUTING COMMAND: omdb ["db", "reconfigurator-save", ""] +EXECUTING COMMAND: omdb ["db", "reconfigurator-save", ""] termination: Exited(0) --------------------------------------------- stdout: @@ -67,19 +67,19 @@ stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable note: database schema version matches expected () assembling reconfigurator state ... done -wrote +wrote ============================================= EXECUTING COMMAND: omdb ["db", "services", "list-instances"] termination: Exited(0) --------------------------------------------- stdout: SERVICE INSTANCE_ID ADDR SLED_SERIAL -CruciblePantry REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT sim-b6d65341 -ExternalDns REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT sim-b6d65341 -InternalDns REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT sim-b6d65341 -Nexus REDACTED_UUID_REDACTED_UUID_REDACTED [::ffff:127.0.0.1]:REDACTED_PORT sim-b6d65341 -Mgd REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT sim-039be560 -Mgd REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT sim-b6d65341 +CruciblePantry ..................... [::1]:REDACTED_PORT sim-b6d65341 +ExternalDns ..................... [::1]:REDACTED_PORT sim-b6d65341 +InternalDns ..................... [::1]:REDACTED_PORT sim-b6d65341 +Nexus ..................... [::ffff:127.0.0.1]:REDACTED_PORT sim-b6d65341 +Mgd ..................... [::1]:REDACTED_PORT sim-039be560 +Mgd ..................... [::1]:REDACTED_PORT sim-b6d65341 --------------------------------------------- stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable @@ -89,19 +89,19 @@ EXECUTING COMMAND: omdb ["db", "services", "list-by-sled"] termination: Exited(0) --------------------------------------------- stdout: -sled: sim-039be560 (id REDACTED_UUID_REDACTED_UUID_REDACTED) +sled: sim-039be560 (id .....................) SERVICE INSTANCE_ID ADDR - Mgd REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT + Mgd ..................... [::1]:REDACTED_PORT -sled: sim-b6d65341 (id REDACTED_UUID_REDACTED_UUID_REDACTED) +sled: sim-b6d65341 (id .....................) SERVICE INSTANCE_ID ADDR - CruciblePantry REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT - ExternalDns REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT - InternalDns REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT - Nexus REDACTED_UUID_REDACTED_UUID_REDACTED [::ffff:127.0.0.1]:REDACTED_PORT - Mgd REDACTED_UUID_REDACTED_UUID_REDACTED [::1]:REDACTED_PORT + CruciblePantry ..................... [::1]:REDACTED_PORT + ExternalDns ..................... [::1]:REDACTED_PORT + InternalDns ..................... [::1]:REDACTED_PORT + Nexus ..................... [::ffff:127.0.0.1]:REDACTED_PORT + Mgd ..................... [::1]:REDACTED_PORT --------------------------------------------- stderr: @@ -113,8 +113,8 @@ termination: Exited(0) --------------------------------------------- stdout: SERIAL IP ROLE ID -sim-039be560 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED -sim-b6d65341 [::1]:REDACTED_PORT scrimlet REDACTED_UUID_REDACTED_UUID_REDACTED +sim-039be560 [::1]:REDACTED_PORT scrimlet ..................... +sim-b6d65341 [::1]:REDACTED_PORT scrimlet ..................... --------------------------------------------- stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable @@ -405,12 +405,12 @@ task: "external_endpoints" external API endpoints: 2 ('*' below marks default) SILO_ID DNS_NAME - REDACTED_UUID_REDACTED_UUID_REDACTED default-silo.sys.oxide-dev.test - * REDACTED_UUID_REDACTED_UUID_REDACTED test-suite-silo.sys.oxide-dev.test + ..................... default-silo.sys.oxide-dev.test + * ..................... test-suite-silo.sys.oxide-dev.test warnings: 2 - warning: silo REDACTED_UUID_REDACTED_UUID_REDACTED with DNS name "default-silo.sys.oxide-dev.test" has no usable certificates - warning: silo REDACTED_UUID_REDACTED_UUID_REDACTED with DNS name "test-suite-silo.sys.oxide-dev.test" has no usable certificates + warning: silo ..................... with DNS name "default-silo.sys.oxide-dev.test" has no usable certificates + warning: silo ..................... with DNS name "test-suite-silo.sys.oxide-dev.test" has no usable certificates TLS certificates: 0 @@ -419,7 +419,7 @@ task: "inventory_collection" currently executing: no last completed activation: iter 3, triggered by an explicit signal started at (s ago) and ran for ms - last collection id: REDACTED_UUID_REDACTED_UUID_REDACTED + last collection id: ..................... last collection started: last collection done: @@ -464,3 +464,46 @@ warning: unknown background task: "switch_port_config_manager" (don't know how t stderr: note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ ============================================= +EXECUTING COMMAND: omdb ["nexus", "blueprints", "list"] +termination: Exited(0) +--------------------------------------------- +stdout: +T ENA ID PARENT TIME_CREATED +* no ............. +--------------------------------------------- +stderr: +note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ +============================================= +EXECUTING COMMAND: omdb ["nexus", "blueprints", "show", "............."] +termination: Exited(0) +--------------------------------------------- +stdout: +blueprint ............. +parent: + + ----------------------------------------------------------------------------------------- + zone type zone ID disposition underlay IP + ----------------------------------------------------------------------------------------- + + sled .....................: zones at generation 2 + (no zones) + + sled .....................: zones at generation 2 + clickhouse ..................... in service ::1 + cockroach_db ..................... in service ::1 + crucible_pantry ..................... in service ::1 + external_dns ..................... in service ::1 + internal_dns ..................... in service ::1 + nexus ..................... in service ::ffff:127.0.0.1 + +METADATA: + created by: nexus-test-utils + created at: + comment: initial test blueprint + internal DNS version: 1 + external DNS version: 2 + +--------------------------------------------- +stderr: +note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ +============================================= diff --git a/dev-tools/omdb/tests/test_all_output.rs b/dev-tools/omdb/tests/test_all_output.rs index 2c16cc1482a..ca246370404 100644 --- a/dev-tools/omdb/tests/test_all_output.rs +++ b/dev-tools/omdb/tests/test_all_output.rs @@ -12,8 +12,9 @@ use nexus_test_utils_macros::nexus_test; use nexus_types::deployment::SledFilter; use nexus_types::deployment::UnstableReconfiguratorState; use omicron_test_utils::dev::test_cmds::path_to_executable; -use omicron_test_utils::dev::test_cmds::redact_variable; +use omicron_test_utils::dev::test_cmds::redact_extra; use omicron_test_utils::dev::test_cmds::run_command; +use omicron_test_utils::dev::test_cmds::ExtraRedactions; use slog_error_chain::InlineErrorChain; use std::fmt::Write; use std::path::Path; @@ -57,7 +58,7 @@ async fn test_omdb_usage_errors() { ]; for args in invocations { - do_run(&mut output, |exec| exec, &cmd_path, args, &[]).await; + do_run(&mut output, |exec| exec, &cmd_path, args).await; } assert_contents("tests/usage_errors.out", &output); @@ -78,7 +79,10 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { let tmpdir = camino_tempfile::tempdir() .expect("failed to create temporary directory"); let tmppath = tmpdir.path().join("reconfigurator-save.out"); + let initial_blueprint_id = cptestctx.initial_blueprint_id.to_string(); + let mut output = String::new(); + let invocations: &[&[&str]] = &[ &["db", "disks", "list"], &["db", "dns", "show"], @@ -92,6 +96,8 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { &["mgs", "inventory"], &["nexus", "background-tasks", "doc"], &["nexus", "background-tasks", "show"], + &["nexus", "blueprints", "list"], + &["nexus", "blueprints", "show", &initial_blueprint_id], // We can't easily test the sled agent output because that's only // provided by a real sled agent, which is not available in the // ControlPlaneTestContext. @@ -102,7 +108,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { let p = postgres_url.to_string(); let u = nexus_internal_url.clone(); let g = mgs_url.clone(); - do_run( + do_run_extra( &mut output, move |exec| { exec.env("OMDB_DB_URL", &p) @@ -111,7 +117,9 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { }, &cmd_path, args, - &[tmppath.as_str()], + ExtraRedactions::new() + .variable_length("tmp_path", tmppath.as_str()) + .fixed_length("blueprint_id", &initial_blueprint_id), ) .await; } @@ -170,7 +178,7 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { // Database URL // Case 1: specified on the command line let args = &["db", "--db-url", &postgres_url, "sleds"]; - do_run(&mut output, |exec| exec, &cmd_path, args, &[]).await; + do_run(&mut output, |exec| exec, &cmd_path, args).await; // Case 2: specified in multiple places (command-line argument wins) let args = &["db", "--db-url", "junk", "sleds"]; @@ -180,7 +188,6 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { move |exec| exec.env("OMDB_DB_URL", &p), &cmd_path, args, - &[], ) .await; @@ -193,7 +200,7 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { "background-tasks", "doc", ]; - do_run(&mut output, |exec| exec, &cmd_path.clone(), args, &[]).await; + do_run(&mut output, |exec| exec, &cmd_path.clone(), args).await; // Case 2: specified in multiple places (command-line argument wins) let args = @@ -204,7 +211,6 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { move |exec| exec.env("OMDB_NEXUS_URL", &n), &cmd_path, args, - &[], ) .await; @@ -217,7 +223,6 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { move |exec| exec.env("OMDB_DNS_SERVER", dns_sockaddr.to_string()), &cmd_path, args, - &[], ) .await; @@ -228,7 +233,7 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { "background-tasks", "doc", ]; - do_run(&mut output, move |exec| exec, &cmd_path, args, &[]).await; + do_run(&mut output, move |exec| exec, &cmd_path, args).await; let args = &["db", "sleds"]; do_run( @@ -236,12 +241,11 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { move |exec| exec.env("OMDB_DNS_SERVER", dns_sockaddr.to_string()), &cmd_path, args, - &[], ) .await; let args = &["--dns-server", &dns_sockaddr.to_string(), "db", "sleds"]; - do_run(&mut output, move |exec| exec, &cmd_path, args, &[]).await; + do_run(&mut output, move |exec| exec, &cmd_path, args).await; assert_contents("tests/env.out", &output); } @@ -251,7 +255,19 @@ async fn do_run( modexec: F, cmd_path: &Path, args: &[&str], - extra_redactions: &[&str], +) where + F: FnOnce(Exec) -> Exec + Send + 'static, +{ + do_run_extra(output, modexec, cmd_path, args, &ExtraRedactions::new()) + .await; +} + +async fn do_run_extra( + output: &mut String, + modexec: F, + cmd_path: &Path, + args: &[&str], + extra_redactions: &ExtraRedactions<'_>, ) where F: FnOnce(Exec) -> Exec + Send + 'static, { @@ -261,7 +277,7 @@ async fn do_run( "EXECUTING COMMAND: {} {:?}\n", cmd_path.file_name().expect("missing command").to_string_lossy(), args.iter() - .map(|r| redact_variable(r, extra_redactions)) + .map(|r| redact_extra(r, extra_redactions)) .collect::>(), ) .unwrap(); @@ -294,9 +310,9 @@ async fn do_run( write!(output, "termination: {:?}\n", exit_status).unwrap(); write!(output, "---------------------------------------------\n").unwrap(); write!(output, "stdout:\n").unwrap(); - output.push_str(&redact_variable(&stdout_text, extra_redactions)); + output.push_str(&redact_extra(&stdout_text, extra_redactions)); write!(output, "---------------------------------------------\n").unwrap(); write!(output, "stderr:\n").unwrap(); - output.push_str(&redact_variable(&stderr_text, extra_redactions)); + output.push_str(&redact_extra(&stderr_text, extra_redactions)); write!(output, "=============================================\n").unwrap(); } diff --git a/dev-tools/reconfigurator-cli/tests/output/cmd-stdout b/dev-tools/reconfigurator-cli/tests/output/cmd-stdout index 10b158f2187..7bedb54bf9b 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmd-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmd-stdout @@ -9,50 +9,50 @@ ID > -> sled-show REDACTED_UUID_REDACTED_UUID_REDACTED -error: no sled with id REDACTED_UUID_REDACTED_UUID_REDACTED +> sled-show ..................... +error: no sled with id ..................... -> sled-add REDACTED_UUID_REDACTED_UUID_REDACTED +> sled-add ..................... added sled > sled-list ID NZPOOLS SUBNET -REDACTED_UUID_REDACTED_UUID_REDACTED 10 fd00:1122:3344:101::/64 +..................... 10 fd00:1122:3344:101::/64 -> sled-show REDACTED_UUID_REDACTED_UUID_REDACTED -sled REDACTED_UUID_REDACTED_UUID_REDACTED +> sled-show ..................... +sled ..................... subnet fd00:1122:3344:101::/64 zpools (10): - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - ZpoolName("oxp_REDACTED_UUID_REDACTED_UUID_REDACTED") - - -> sled-add REDACTED_UUID_REDACTED_UUID_REDACTED + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + ZpoolName("oxp_.....................") + + +> sled-add ..................... added sled -> sled-add REDACTED_UUID_REDACTED_UUID_REDACTED +> sled-add ..................... added sled > sled-list ID NZPOOLS SUBNET -REDACTED_UUID_REDACTED_UUID_REDACTED 10 fd00:1122:3344:103::/64 -REDACTED_UUID_REDACTED_UUID_REDACTED 10 fd00:1122:3344:102::/64 -REDACTED_UUID_REDACTED_UUID_REDACTED 10 fd00:1122:3344:101::/64 +..................... 10 fd00:1122:3344:103::/64 +..................... 10 fd00:1122:3344:102::/64 +..................... 10 fd00:1122:3344:101::/64 > > inventory-generate -generated inventory collection REDACTED_UUID_REDACTED_UUID_REDACTED from configured sleds +generated inventory collection ..................... from configured sleds > inventory-list ID NERRORS TIME_DONE -REDACTED_UUID_REDACTED_UUID_REDACTED 0 +..................... 0 diff --git a/dev-tools/reconfigurator-cli/tests/test_basic.rs b/dev-tools/reconfigurator-cli/tests/test_basic.rs index 5502d954b4a..87450181ec4 100644 --- a/dev-tools/reconfigurator-cli/tests/test_basic.rs +++ b/dev-tools/reconfigurator-cli/tests/test_basic.rs @@ -42,7 +42,7 @@ fn test_basic() { let exec = Exec::cmd(path_to_cli()).arg("tests/input/cmds.txt"); let (exit_status, stdout_text, stderr_text) = run_command(exec); assert_exit_code(exit_status, EXIT_SUCCESS, &stderr_text); - let stdout_text = redact_variable(&stdout_text, &[]); + let stdout_text = redact_variable(&stdout_text); assert_contents("tests/output/cmd-stdout", &stdout_text); assert_contents("tests/output/cmd-stderr", &stderr_text); } diff --git a/nexus/reconfigurator/execution/src/dns.rs b/nexus/reconfigurator/execution/src/dns.rs index 3cb963ae62b..13b21f9961a 100644 --- a/nexus/reconfigurator/execution/src/dns.rs +++ b/nexus/reconfigurator/execution/src/dns.rs @@ -1139,7 +1139,7 @@ mod test { .await .expect("failed to read current target blueprint") .expect("no target blueprint set"); - eprintln!("blueprint: {:?}", blueprint); + eprintln!("blueprint: {}", blueprint.display()); // Now, execute the initial blueprint. let overrides = Overridables::for_test(cptestctx); @@ -1239,7 +1239,7 @@ mod test { .unwrap(); assert_eq!(rv, EnsureMultiple::Added(1)); let blueprint2 = builder.build(); - eprintln!("blueprint2: {:?}", blueprint2); + eprintln!("blueprint2: {}", blueprint2.display()); // Figure out the id of the new zone. let zones_before = blueprint .all_omicron_zones() diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index 81814efda36..c1acdc18480 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -114,6 +114,7 @@ pub struct ControlPlaneTestContext { pub external_dns_zone_name: String, pub external_dns: dns_server::TransientServer, pub internal_dns: dns_server::TransientServer, + pub initial_blueprint_id: Uuid, pub silo_name: Name, pub user_name: UserId, } @@ -299,6 +300,7 @@ pub struct ControlPlaneTestContextBuilder<'a, N: NexusServer> { pub external_dns: Option, pub internal_dns: Option, dns_config: Option, + initial_blueprint_id: Option, omicron_zones: Vec, omicron_zones2: Vec, @@ -343,6 +345,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { external_dns: None, internal_dns: None, dns_config: None, + initial_blueprint_id: None, omicron_zones: Vec::new(), omicron_zones2: Vec::new(), silo_name: None, @@ -836,6 +839,8 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { } }; + self.initial_blueprint_id = Some(blueprint.id); + // Handoff all known service information to Nexus let server = N::start( self.nexus_internal @@ -1124,6 +1129,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { external_dns_zone_name: self.external_dns_zone_name.unwrap(), external_dns: self.external_dns.unwrap(), internal_dns: self.internal_dns.unwrap(), + initial_blueprint_id: self.initial_blueprint_id.unwrap(), silo_name: self.silo_name.unwrap(), user_name: self.user_name.unwrap(), } diff --git a/test-utils/src/dev/test_cmds.rs b/test-utils/src/dev/test_cmds.rs index 6500eaddfda..5ef2da672b3 100644 --- a/test-utils/src/dev/test_cmds.rs +++ b/test-utils/src/dev/test_cmds.rs @@ -125,21 +125,12 @@ pub fn error_for_enoent() -> String { /// invocation to invocation (e.g., assigned TCP port numbers, timestamps) /// /// This allows use to use expectorate to verify the shape of the CLI output. -pub fn redact_variable(input: &str, extra_redactions: &[&str]) -> String { - // Perform extra redactions at the beginning, not the end. This is because - // some of the built-in redactions below might match a substring of - // something that should be handled by extra_redactions (e.g. a temporary - // path). - let mut s = input.to_owned(); - for r in extra_redactions { - s = s.replace(r, ""); - } - +pub fn redact_variable(input: &str) -> String { // Replace TCP port numbers. We include the localhost characters to avoid // catching any random sequence of numbers. let s = regex::Regex::new(r"\[::1\]:\d{4,5}") .unwrap() - .replace_all(&s, "[::1]:REDACTED_PORT") + .replace_all(&input, "[::1]:REDACTED_PORT") .to_string(); let s = regex::Regex::new(r"\[::ffff:127.0.0.1\]:\d{4,5}") .unwrap() @@ -151,12 +142,16 @@ pub fn redact_variable(input: &str, extra_redactions: &[&str]) -> String { .to_string(); // Replace uuids. + // + // The length of a UUID is 32 nibbles for the hex encoding of a u128 + 4 + // dashes = 36. + const UUID_LEN: usize = 36; let s = regex::Regex::new( "[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-\ [a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}", ) .unwrap() - .replace_all(&s, "REDACTED_UUID_REDACTED_UUID_REDACTED") + .replace_all(&s, fill_redaction_text("uuid", UUID_LEN)) .to_string(); // Replace timestamps. @@ -196,15 +191,113 @@ pub fn redact_variable(input: &str, extra_redactions: &[&str]) -> String { s } +/// Redact text from a string, allowing for extra redactions to be specified. +pub fn redact_extra( + input: &str, + extra_redactions: &ExtraRedactions<'_>, +) -> String { + // Perform extra redactions at the beginning, not the end. This is because + // some of the built-in redactions in redact_variable might match a + // substring of something that should be handled by extra_redactions (e.g. + // a temporary path). + let mut s = input.to_owned(); + for (name, replacement) in &extra_redactions.redactions { + s = s.replace(name, replacement); + } + redact_variable(&s) +} + +/// Represents a list of extra redactions for [`redact_variable`]. +/// +/// Extra redactions are applied in-order, before any builtin redactions. +#[derive(Clone, Debug, Default)] +pub struct ExtraRedactions<'a> { + // A pair of redaction and replacement strings. + redactions: Vec<(&'a str, String)>, +} + +impl<'a> ExtraRedactions<'a> { + pub fn new() -> Self { + Self { redactions: Vec::new() } + } + + pub fn fixed_length( + &mut self, + name: &str, + text_to_redact: &'a str, + ) -> &mut Self { + // Use the same number of chars as the number of bytes in + // text_to_redact. We're almost entirely in ASCII-land so they're the + // same, and getting the length right is nice but doesn't matter for + // correctness. + // + // A technically more correct impl would use unicode-width, but ehhh. + let replacement = fill_redaction_text(name, text_to_redact.len()); + self.redactions.push((text_to_redact, replacement)); + self + } + + pub fn variable_length( + &mut self, + name: &str, + text_to_redact: &'a str, + ) -> &mut Self { + let gen = format!("<{}_REDACTED>", name.to_uppercase()); + let replacement = gen.to_string(); + + self.redactions.push((text_to_redact, replacement)); + self + } +} + +fn fill_redaction_text(name: &str, text_to_redact_len: usize) -> String { + // The overall plan is to generate a string of the form + // ------, depending on the length of the text to + // redact. + // + // * Always include the < > signs for clarity, and either shorten the + // text or add dashes to compensate for the length. + + let base = format!("REDACTED_{}", name.to_uppercase()); + + let text_len_minus_2 = text_to_redact_len.saturating_sub(2); + + let replacement = if text_len_minus_2 <= base.len() { + // Shorten the base string to fit the text. + format!("<{:.width$}>", base, width = text_len_minus_2) + } else { + // Add dashes on both sides to make up the difference. + let dash_len = text_len_minus_2 - base.len(); + format!( + "{}<{base}>{}", + ".".repeat(dash_len / 2), + ".".repeat(dash_len - dash_len / 2) + ) + }; + replacement +} + #[cfg(test)] mod tests { use super::*; #[test] - fn test_redact_variable() { - // Ens - let input = "time: 123ms, path: /var/tmp/tmp.456ms123s"; - let actual = redact_variable(input, &["/var/tmp/tmp.456ms123s"]); - assert_eq!(actual, "time: ms, path: "); + fn test_redact_extra() { + let input = "time: 123ms, path: /var/tmp/tmp.456ms123s, \ + path2: /short, \ + path3: /variable-length/path"; + let actual = redact_extra( + input, + ExtraRedactions::new() + .fixed_length("tp", "/var/tmp/tmp.456ms123s") + .fixed_length("short_redact", "/short") + .variable_length("variable", "/variable-length/path"), + ); + assert_eq!( + actual, + "time: ms, path: ........., \ + path2: , \ + path3: " + ); } }