From c5b4d45bb3c03f8526298af960725574469552c9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 04:30:19 -0400 Subject: [PATCH 001/320] Change log updates --- CHANGELOG.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.lock | 3 ++- Cargo.toml | 2 +- README.md | 2 +- 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cac7ea0..3ed3980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,69 @@ # rabbitmqadmin-ng Change Log -## v0.28.0 (in development) +## v0.29.0 (in development) + +In (documented) changes yet. + + +## v0.28.0 (Mar 23, 2025) ### Enhancements + * New command group: `federation`, see + + ```shell + rabbitmqadmin federation help + ``` + +* New command: `federation declare_upstream_for_queues` for declaring upstreams that will exclusively be used for queue + federation. This command does not support any options related to exchange federation. + + ```shell + rabbitmqadmin federation --vhost "local.vhost" declare_upstream \ + --name "dc.vancouver" \ + --uri "amqp://192.168.0.25/demote.vhost" \ + --ack-mode "on-confirm" + ``` + +* New command: `federation declare_upstream_for_exchanges` for declaring upstreams that will exclusively be used exchange + federation. This command does not support any options related to queue federation. + + ```shell + rabbitmqadmin federation --vhost "local.vhost" declare_upstream \ + --name "dc.vancouver" \ + --uri "amqp://192.168.0.25/demote.vhost" \ + --ack-mode "on-confirm" + ``` + + * New command: `federation declare_upstream` for declaring upstreams that can be used for either queue or exchange + federation. This command supports the whole spectrum of federation upstream options, that is, both the settings + of queue and exchange federation. + + ```shell + rabbitmqadmin federation --vhost "local.vhost" declare_upstream \ + --name "dc.canada.bc.vancouver" \ + --uri "amqp://192.168.0.25/demote.vhost" \ + --ack-mode "on-confirm" + ``` + + * New command: `federation list_all_upstreams` for listing all upstreams (that is, upstreams across all the virtual hosts in the cluster). + + ```shell + rabbitmqadmin federation list_all_upstreams + ``` + + * New command: `federation list_all_links` for listing all links (that is, links across all the virtual hosts in the cluster). + + ```shell + rabbitmqadmin federation list_all_links + ``` + + * New command: `federation delete_upstream`. As the name suggests, it deletes an upstream. + + ```shell + rabbitmqadmin federation delete_upstream --name "dc.canada.bc.vancouver" + ``` + * New definitions export `--transformations` value, `obfuscate_usernames`, changes usernames to dummy values (e.g. so that definitions could be shared safely with external teams) * New definitions export `--transformations` value, `exclude_users`, removes users from the result diff --git a/Cargo.lock b/Cargo.lock index 489c034..17a8bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1449,7 +1449,8 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" version = "0.28.0" -source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#477cb629d46d33a23bc672988d52ec701c21a381" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2194962ca732d91205f59e9476f30cb1aa9c5ec895d7bf81d93e802d3833ce0c" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 955ad7f..688fd9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { git = "https://github.com/michaelklishin/rabbitmq-http-api-rs.git", features = [ +rabbitmq_http_client = { version = "0.28.0", features = [ "core", "blocking", "tabled", diff --git a/README.md b/README.md index 6f8c97d..1edf523 100644 --- a/README.md +++ b/README.md @@ -75,9 +75,9 @@ Commands: publish publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. get fetches message(s) from a queue or stream via polling. Only suitable for development and test environments. shovels Operations on shovels + federation Operations on federation upstreams and links tanzu Tanzu RabbitMQ-specific commands help Print this message or the help of the given subcommand(s) - ``` To explore commands in a specific group, use From f1ce540a5bbe0e48fc012e9577003ace8a73eb4c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 04:51:26 -0400 Subject: [PATCH 002/320] Change log: correct typos --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ed3980..37468fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ In (documented) changes yet. federation. This command does not support any options related to exchange federation. ```shell - rabbitmqadmin federation --vhost "local.vhost" declare_upstream \ + rabbitmqadmin federation --vhost "local.vhost" declare_upstream_for_queues \ --name "dc.vancouver" \ --uri "amqp://192.168.0.25/demote.vhost" \ --ack-mode "on-confirm" @@ -29,7 +29,7 @@ In (documented) changes yet. federation. This command does not support any options related to queue federation. ```shell - rabbitmqadmin federation --vhost "local.vhost" declare_upstream \ + rabbitmqadmin federation --vhost "local.vhost" declare_upstream_for_exchanges \ --name "dc.vancouver" \ --uri "amqp://192.168.0.25/demote.vhost" \ --ack-mode "on-confirm" From 24348b8bdf711013f7c9c8f5f8634d7e3e551088 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 04:52:04 -0400 Subject: [PATCH 003/320] Bump development version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 688fd9c..225e63a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "0.28.0" +version = "0.29.0" edition = "2024" description = "rabbitmqadmin v2 is a major revision of rabbitmqadmin, one of the RabbitMQ CLI tools that target the HTTP API" From 4e1893497e240a90a8406687f76da47540e88390 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 04:58:52 -0400 Subject: [PATCH 004/320] Bump development version --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 17a8bd5..7c0cb5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1470,7 +1470,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "0.28.0" +version = "0.29.0" dependencies = [ "assert_cmd", "clap", From 2e61c7b8cb8693c562cee8ec7a040050f76bd56f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 06:26:13 -0400 Subject: [PATCH 005/320] 'definitions import': support --file="-" for reading from the standard input --- src/commands.rs | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index a8db825..d62dd7c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -28,6 +28,7 @@ use rabbitmq_http_client::requests::{ RuntimeParameterDefinition, }; use std::fs; +use std::io; use std::process; use rabbitmq_http_client::commons::BindingDestinationType; @@ -1254,18 +1255,21 @@ pub fn export_vhost_definitions( } pub fn import_definitions(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { - let file = command_args.get_one::("file").unwrap(); - let definitions = fs::read_to_string(file); + let path = command_args + .get_one::("file") + .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')) + .unwrap(); + let definitions = read_definitions(path); match definitions { Ok(defs) => { let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON file: {}", file, err); + eprintln!("`{}` is not a valid JSON file: {}", path, err); process::exit(1) }); client.import_definitions(defs_json) } Err(err) => { - eprintln!("`{}` could not be read: {}", file, err); + eprintln!("`{}` could not be read: {}", path, err); process::exit(1) } } @@ -1276,23 +1280,48 @@ pub fn import_vhost_definitions( vhost: &str, command_args: &ArgMatches, ) -> ClientResult<()> { - let file = command_args.get_one::("file").unwrap(); - let definitions = fs::read_to_string(file); + let path = command_args + .get_one::("file") + .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')) + .unwrap(); + let definitions = read_definitions(path); match definitions { Ok(defs) => { let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON file: {}", file, err); + eprintln!("`{}` is not a valid JSON file: {}", path, err); process::exit(1) }); client.import_vhost_definitions(vhost, defs_json) } Err(err) => { - eprintln!("`{}` could not be read: {}", file, err); + eprintln!("`{}` could not be read: {}", path, err); process::exit(1) } } } +fn read_definitions(path: &str) -> io::Result { + match path { + "-" => { + let mut buffer = String::new(); + let stdin = io::stdin(); + let lines = stdin.lines(); + for ln in lines { + match ln { + Ok(line) => buffer.push_str(&line), + Err(err) => { + eprintln!("Error reading from standard input: {}", err); + process::exit(1); + } + } + } + + Ok(buffer) + } + _ => fs::read_to_string(path), + } +} + pub fn publish_message( client: APIClient, vhost: &str, From b6d0f7a0a7117ddba2b5b99ec01b28473ea5a7b1 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 07:47:27 -0400 Subject: [PATCH 006/320] A nicer way to export definitions to stdout and import them from stdin instead of a brittle and shell/escaping-dependent special value of '-', which is still supported for backwards compatibility, even in single quotes, the user is now asked to pass --stdout and --stdin, respectively. ' --- CHANGELOG.md | 46 ++++++++++++++++- src/cli.rs | 64 ++++++++++++++++++++--- src/commands.rs | 131 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 201 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37468fb..b6cb6b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,50 @@ # rabbitmqadmin-ng Change Log -## v0.29.0 (in development) +## v0.30.0 (in development) -In (documented) changes yet. +No (documented) changes yet. + + +## v0.29.0 (Mar 23, 2025) + +### Breaking Changes + + * `definitions export`'s special `--file` value of `-` for "standard input" is deprecated. Use `--stdout` instead: + + ```shell + rabbitmqadmin definitions export --stdout > definitions.json + ``` + + ```shell + # exports 3.x-era definitions that might contain classic queue mirroring keys, transforms + # them to not use any CMQ policies, injects an explicit queue type into the matched queues, + # and drops all the policies that had nothing beyond the CMQ keys, + # then passes the result to the standard input of + # 'rabbitmqadmin definitions import --stdin' + rabbitmqadmin --node "source.node" definitions export --transformations strip_cmq_keys_from_policies,drop_empty_policies --stdout | rabbitmqadmin --node "destination.node" definitions import --stdin + ``` + +### Enhancements + + * 'definitions import` now supports reading definitions from the standard input instead of a file. + For that, pass `--stdin` instead of `--file "/path/to/definitions.json"`. + + ```shell + rabbitmqadmin definitions import --stdin < definitions.json + ``` + + ```shell + cat definitions.json | rabbitmqadmin definitions import --stdin + ``` + + ```shell + # exports 3.x-era definitions that might contain classic queue mirroring keys, transforms + # them to not use any CMQ policies, injects an explicit queue type into the matched queues, + # and drops all the policies that had nothing beyond the CMQ keys, + # then passes the result to the standard input of + # 'rabbitmqadmin definitions import --stdin' + rabbitmqadmin --node "source.node" definitions export --transformations strip_cmq_keys_from_policies,drop_empty_policies --stdout | rabbitmqadmin --node "destination.node" definitions import --stdin + ``` ## v0.28.0 (Mar 23, 2025) diff --git a/src/cli.rs b/src/cli.rs index f66e87e..19a5495 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1203,10 +1203,22 @@ fn definitions_subcommands() -> [Command; 4] { )) .arg( Arg::new("file") + .group("output") .long("file") - .help("output file path or '-' for standard output") + .help("output file path") .required(false) - .default_value("-"), + .default_value("-") + .conflicts_with("stdout"), + ) + .arg( + Arg::new("stdout") + .group("output") + .long("stdout") + .help("print result to the standard output stream") + .required(false) + .num_args(0) + .action(ArgAction::SetTrue) + .conflicts_with("file"), ) .arg( Arg::new("transformations") @@ -1250,10 +1262,22 @@ Examples: )) .arg( Arg::new("file") + .group("output") .long("file") - .help("output file path or '-' for standard output") + .help("output file path") .required(false) - .default_value("-"), + .default_value("-") + .conflicts_with("stdout"), + ) + .arg( + Arg::new("stdout") + .group("output") + .long("stdout") + .help("print result to the standard output stream") + .required(false) + .num_args(0) + .action(ArgAction::SetTrue) + .conflicts_with("file"), ); let import_cmd = Command::new("import") @@ -1264,9 +1288,21 @@ Examples: )) .arg( Arg::new("file") + .group("input") .long("file") - .help("JSON file with cluster-wide definitions") - .required(true), + .help("cluster-wide definitions JSON file path; mutually exclusive with --stdin") + .required(true) + .conflicts_with("stdin"), + ) + .arg( + Arg::new("stdin") + .group("input") + .long("stdin") + .help("read input JSON from the standard input stream, mutually exclusive with --file") + .required(false) + .num_args(0) + .action(ArgAction::SetTrue) + .conflicts_with("file"), ); let import_into_vhost_cmd = Command::new("import_into_vhost") @@ -1277,9 +1313,21 @@ Examples: )) .arg( Arg::new("file") + .group("input") .long("file") - .help("JSON file with virtual host-specific definitions") - .required(true), + .help("cluster-wide definitions JSON file path; mutually exclusive with --stdin") + .required(true) + .conflicts_with("stdin"), + ) + .arg( + Arg::new("stdin") + .group("input") + .long("stdin") + .help("read input JSON from the standard input stream, mutually exclusive with --file") + .required(false) + .num_args(0) + .action(ArgAction::SetTrue) + .conflicts_with("file"), ); [ diff --git a/src/commands.rs b/src/commands.rs index d62dd7c..0a26f80 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1215,16 +1215,31 @@ fn export_cluster_wide_definitions_without_transformations( ) -> ClientResult<()> { match client.export_cluster_wide_definitions() { Ok(definitions) => { - let path = command_args.get_one::("file").unwrap(); - match path.as_str() { - "-" => { + let path = command_args.get_one::("file").cloned(); + let use_stdout = command_args.get_one::("stdout").copied(); + match (path, use_stdout) { + (Some(_val), Some(true)) => { println!("{}", &definitions); Ok(()) } - file => { - _ = fs::write(file, &definitions); + (Some(val), Some(false)) => match val.as_str() { + "-" => { + println!("{}", &definitions); + Ok(()) + } + _ => { + _ = fs::write(val, &definitions); + Ok(()) + } + }, + (_, Some(true)) => { + println!("{}", &definitions); Ok(()) } + _ => { + eprintln!("either --file or --stdout must be provided"); + process::exit(1) + } } } Err(err) => Err(err), @@ -1257,19 +1272,39 @@ pub fn export_vhost_definitions( pub fn import_definitions(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { let path = command_args .get_one::("file") - .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')) - .unwrap(); - let definitions = read_definitions(path); + .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')); + let use_stdin = command_args.get_one::("stdin").copied(); + let definitions = read_definitions(path, use_stdin); match definitions { Ok(defs) => { let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON file: {}", path, err); + match path { + None => { + eprintln!("could not parse the value read from the standard input: {}", err); + } + Some(val) => { + eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err); + } + } process::exit(1) }); client.import_definitions(defs_json) } Err(err) => { - eprintln!("`{}` could not be read: {}", path, err); + match path { + None => { + eprintln!( + "could not parse the value read from the standard input: {}", + err + ); + } + Some(val) => { + eprintln!( + "`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", + val, err + ); + } + } process::exit(1) } } @@ -1282,43 +1317,79 @@ pub fn import_vhost_definitions( ) -> ClientResult<()> { let path = command_args .get_one::("file") - .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')) - .unwrap(); - let definitions = read_definitions(path); + .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')); + let use_stdin = command_args.get_one::("stdin").copied(); + let definitions = read_definitions(path, use_stdin); match definitions { Ok(defs) => { let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON file: {}", path, err); + match path { + None => { + eprintln!("could not parse the value read from the standard input: {}", err); + } + Some(val) => { + eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err); + } + } process::exit(1) }); client.import_vhost_definitions(vhost, defs_json) } Err(err) => { - eprintln!("`{}` could not be read: {}", path, err); + match path { + None => { + eprintln!( + "could not parse the value read from the standard input: {}", + err + ); + } + Some(val) => { + eprintln!( + "`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", + val, err + ); + } + } process::exit(1) } } } -fn read_definitions(path: &str) -> io::Result { - match path { - "-" => { +fn read_definitions(path: Option<&str>, use_stdin: Option) -> io::Result { + match (path, use_stdin) { + (_, Some(true)) => { let mut buffer = String::new(); - let stdin = io::stdin(); - let lines = stdin.lines(); - for ln in lines { - match ln { - Ok(line) => buffer.push_str(&line), - Err(err) => { - eprintln!("Error reading from standard input: {}", err); - process::exit(1); - } - } - } + read_stdin_lines(&mut buffer); Ok(buffer) } - _ => fs::read_to_string(path), + (Some(val), _) => match val { + "-" => { + let mut buffer = String::new(); + read_stdin_lines(&mut buffer); + + Ok(buffer) + } + _ => fs::read_to_string(val), + }, + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "either an input file path or --stdin must be specified", + )), + } +} + +fn read_stdin_lines(buffer: &mut String) { + let stdin = io::stdin(); + let lines = stdin.lines(); + for ln in lines { + match ln { + Ok(line) => buffer.push_str(&line), + Err(err) => { + eprintln!("Error reading from standard input: {}", err); + process::exit(1); + } + } } } From b147630baa867f39abe4b446fc55aae25dba94ad Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 07:52:59 -0400 Subject: [PATCH 007/320] Remove a stray debug output line --- src/output.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/output.rs b/src/output.rs index 3ebc0c0..8d0ddfb 100644 --- a/src/output.rs +++ b/src/output.rs @@ -228,7 +228,6 @@ impl<'a> ResultHandler<'a> { } pub fn memory_breakdown_in_bytes_result(&mut self, result: ClientResult) { - dbg!(&result); match result { Ok(output) => { self.exit_code = Some(ExitCode::Ok); From 978eed5c8408c7673c576d9eb676afc2384e5207 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 23 Mar 2025 08:03:44 -0400 Subject: [PATCH 008/320] Bump development version --- CHANGELOG.md | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6cb6b2..baf66c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ No (documented) changes yet. ### Enhancements - * 'definitions import` now supports reading definitions from the standard input instead of a file. + * `definitions import` now supports reading definitions from the standard input instead of a file. For that, pass `--stdin` instead of `--file "/path/to/definitions.json"`. ```shell diff --git a/Cargo.lock b/Cargo.lock index 7c0cb5c..74885e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1470,7 +1470,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "0.29.0" +version = "0.30.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 225e63a..2fc5b34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "0.29.0" +version = "0.30.0" edition = "2024" description = "rabbitmqadmin v2 is a major revision of rabbitmqadmin, one of the RabbitMQ CLI tools that target the HTTP API" From db9bc4c1e9eae1e8b0f50167d745b64ab993e161 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 25 Mar 2025 01:31:40 -0400 Subject: [PATCH 009/320] cargo update --- Cargo.lock | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74885e4..68b8a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,9 +405,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "deranged" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" dependencies = [ "powerfmt", "serde", @@ -789,14 +789,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1073,9 +1074,9 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -2089,9 +2090,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.40" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -2110,9 +2111,9 @@ checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", From 4c06a67a7d81e8931c3d8ecf24c978c2d77d5549 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 25 Mar 2025 11:08:47 -0400 Subject: [PATCH 010/320] cargo update --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68b8a10..136729e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1420,9 +1420,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" dependencies = [ "cfg_aliases", "libc", @@ -1748,9 +1748,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.0" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "aws-lc-rs", "ring", From 15c2c12b712e81b4f4be5c0774faf87615fab49e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 26 Mar 2025 23:45:08 -0400 Subject: [PATCH 011/320] cargo update --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 136729e..7b28e7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ "clap_builder", "clap_derive", @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ "anstream", "anstyle", @@ -852,9 +852,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -876,9 +876,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" From 4839d444cd7d09d5da9587f821f0f81b66e633ba Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 00:38:41 -0400 Subject: [PATCH 012/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b28e7f..7a85eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1194,9 +1194,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" From 4ebb77963f2ef218ad94e23b63adafacbc33233d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 18:02:58 -0400 Subject: [PATCH 013/320] Command line interface cosmetics --- src/cli.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 19a5495..3d17795 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -204,7 +204,6 @@ pub fn parser() -> Command { .value_parser(value_parser!(TableStyle)) ) .subcommand_required(true) - .subcommand_value_name("command") .subcommands([ Command::new("show") .about("overview") @@ -216,15 +215,12 @@ pub fn parser() -> Command { .subcommands(show_subcommands()), Command::new("list") .about("lists objects by type") - .subcommand_value_name("objects") .subcommands(list_subcommands()), Command::new("declare") - .about("creates or declares things") - .subcommand_value_name("object") + .about("creates or declares objects") .subcommands(declare_subcommands()), Command::new("delete") .about("deletes objects") - .subcommand_value_name("object") .subcommands(delete_subcommands()), Command::new("purge") .about("purges queues") From 22305e6530103918b761415696f5e24e7b93e255 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 18:14:36 -0400 Subject: [PATCH 014/320] Selectively support environment variables for key global options This is preparatory work and not really a feature in and of itself. Let me explain. Environment variables have objective downsides: 1. Mostly non-existent type checking 2. Hard to inspect 3. Hard to reason about, given that shell environment inheritance is always an afterthought Therefore using environment variables for CLI arguments is a catastrophic disservice to the user most of the time, and they should be used as little as possible. However, for "pre-flight" global arguments, such as the --config-file default, they are a reasonable or even the only alternative available. This commit enables Clap's environment variable feature for a few global flags. One reason to have this feature is that in order to enable CLI parser-level features, such as subcommand name inference or --long-option inference, both of which Clap supports and both of which can be a solid time saver for heavy users, we need to have a "pre-CLI args" and "pre-config file" configuration mechanism, and environment variables is the only option that's universally known. The end goal of this change is not so much to allow people set RABBITMQADMIN_TARGET_HOST but rather enable features such as * RABBITMQADMIN_NON_INTERACTIVE_MODE * RABBITMQADMIN_CONFIG_FILE_PATH * RABBITMQADMIN_INFER_CLI_SUBCOMMANDS * RABBITMQADMIN_INFER_CLI_LONG_ARGUMENTS For --host, --port, --username and such, use a config file and steer clear of environment variables for the aforementioned reasons. --- Cargo.toml | 2 +- src/cli.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2fc5b34..90c2a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ description = "rabbitmqadmin v2 is a major revision of rabbitmqadmin, one of the license = "MIT OR Apache-2.0" [dependencies] -clap = { version = "4.5", features = ["derive", "help", "color", "cargo"] } +clap = { version = "4.5", features = ["derive", "help", "color", "cargo", "env"] } url = "2" sysexits = "0.9" reqwest = { version = "0.12.12", features = [ diff --git a/src/cli.rs b/src/cli.rs index 3d17795..4555761 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -57,6 +57,7 @@ pub fn parser() -> Command { Arg::new("config_file_path") .short('c') .long("config") + .env("RABBITMQADMIN_CONFIG_FILE_PATH") .value_parser(value_parser!(PathBuf)) .default_value(DEFAULT_CONFIG_FILE_PATH), ) @@ -67,6 +68,7 @@ pub fn parser() -> Command { Arg::new("node_alias") .short('N') .long("node") + .env("RABBITMQADMIN_NODE_ALIAS") .required(false) .default_value(DEFAULT_NODE_ALIAS), ) @@ -75,6 +77,7 @@ pub fn parser() -> Command { Arg::new("host") .short('H') .long("host") + .env("RABBITMQADMIN_TARGET_HOST") .help("HTTP API hostname to use when connecting"), ) .visible_alias("hostname") @@ -83,6 +86,7 @@ pub fn parser() -> Command { Arg::new("port") .short('P') .long("port") + .env("RABBITMQADMIN_TARGET_PORT") .help("HTTP API port to use when connecting") .required(false) .value_parser(value_parser!(u16)) @@ -93,6 +97,7 @@ pub fn parser() -> Command { Arg::new("base_uri") .short('U') .long("base-uri") + .env("RABBITMQADMIN_BASE_URI") .help("base HTTP API endpoint URI") .required(false) .conflicts_with_all(["host", "port"]), @@ -101,6 +106,7 @@ pub fn parser() -> Command { .arg( Arg::new("path_prefix") .long("path-prefix") + .env("RABBITMQADMIN_API_PATH_PREFIX") .help("use if target node uses a path prefix. Defaults to '/api'"), ) // --vhost @@ -108,6 +114,7 @@ pub fn parser() -> Command { Arg::new("vhost") .short('V') .long("vhost") + .env("RABBITMQADMIN_TARGET_VHOST") .help("target virtual host. Defaults to '/'"), ) // --username @@ -115,6 +122,7 @@ pub fn parser() -> Command { Arg::new("username") .short('u') .long("username") + .env("RABBITMQADMIN_USERNAME") .help(format!( "this user must have the permissions for HTTP API access, see {}", HTTP_API_ACCESS_PERMISSIONS_GUIDE_URL @@ -125,6 +133,7 @@ pub fn parser() -> Command { Arg::new("password") .short('p') .long("password") + .env("RABBITMQADMIN_PASSWORD") .requires("username") .help("requires username to be specified via --username or in the config file"), ) @@ -143,6 +152,7 @@ pub fn parser() -> Command { Arg::new("tls") .long("use-tls") .help("use TLS (HTTPS) for HTTP API requests ") + .env("RABBITMQADMIN_USE_TLS") .value_parser(value_parser!(bool)) .action(ArgAction::SetTrue) .requires("tls-ca-cert-file"), @@ -179,6 +189,7 @@ pub fn parser() -> Command { Arg::new("quiet") .short('q') .long("quiet") + .env("RABBITMQADMIN_QUIET_MODE") .help("produce less output") .required(false) .value_parser(value_parser!(bool)) @@ -188,6 +199,7 @@ pub fn parser() -> Command { .arg( Arg::new("non_interactive") .long("non-interactive") + .env("RABBITMQADMIN_NON_INTERACTIVE_MODE") .help("pass when invoking from scripts") .conflicts_with("table_style") .required(false) @@ -198,6 +210,7 @@ pub fn parser() -> Command { .arg( Arg::new("table_style") .long("table-style") + .env("RABBITMQADMIN_TABLE_STYLE") .help("style preset to apply to output tables: modern, borderless, ascii, dots, psql, markdown, sharp") .conflicts_with("non_interactive") .required(false) From f870f56a416f45bd155cdd3216c8f66f3f32898a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 19:10:22 -0400 Subject: [PATCH 015/320] Documentation/README updates --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1edf523..3c703c2 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,16 @@ to learn about the breaking changes in the command line interface. The general "shape and feel" of the interface is still very similar to `rabbitmqadmin` v1. +## Supported RabbitMQ Series + +`rabbitmqadmin` v2 targets + + * Open source RabbitMQ `4.x` + * Open source RabbitMQ `3.13.x` (specifically for the command groups and commands related to upgrades) + * Tanzu RabbitMQ `4.x` + * Tanzu RabbitMQ `3.13.x` + + ## Getting Started ### Installation @@ -510,12 +520,48 @@ the original version of `rabbitmqadmin`. It can be overridden on the command lin rabbitmqadmin --config $HOME/.configuration/rabbitmqadmin.conf --node staging show churn ``` +## Intentionally Restricted Environment Variable Support + +Environment variables have a number of serious downsides compared to a `rabbitmqadmin.conf` +and the regular `--long-options` on the command line: + +1. Non-existent support for value types and validation ("everything is a string") +2. Subprocess inheritance restrictions that can be very time-consuming to debug +3. Different syntax for setting them between the classic POSIX-era shells (such as `bash`, `zsh`) and modern ones (such as [`nushell`](https://www.nushell.sh/)) + +For these reasons and others, `rabbitmqadmin` v2 intentionally uses the configuration file and the +CLI options over the environment variables. + +`rabbitmqadmin` v2 does, however, supports a number of environment variables for a few +global settings that cannot be configured any other way (besides a CLI option), +or truly represent an environment characteristic, e.g. either the non-interactive mode +should be enabled. + +These environment variables are as follows: + +| Environment variable | Type | When used | Description | +|--------------------------------------|---------------------------------------------------|---------------------------------------|--------------------------------------------------------------| +| `RABBITMQADMIN_CONFIG_FILE_PATH` | Local filesystem path | Pre-flight (before command execution) | Same meaning as the global `--confg-file` argument | +| `RABBITMQADMIN_NON_INTERACTIVE_MODE` | Boolean | Command execution | Enables the non-interactive mode.

Same meaning as the global `--non-interactive` argument | +| `RABBITMQADMIN_QUIET_MODE`
| Boolean | Command execution | Instructs the tool to produce less output.

Same meaning as the global `--quiet` argument | +| `RABBITMQADMIN_NODE_ALIAS` | String | Command execution | Same meaning as the global `--node` argument | +| `RABBITMQADMIN_TARGET_HOST` | String | Command execution | Same meaning as the global `--host` argument | +| `RABBITMQADMIN_TARGET_PORT` | Positive integer | Command execution | Same meaning as the global `--port` argument | +| `RABBITMQADMIN_API_PATH_PREFIX` | String | Command execution | Same meaning as the global `--path-prefix` argument | +| `RABBITMQADMIN_TARGET_VHOST` | String | Command execution | Same meaning as the global `--vhost` argument | +| `RABBITMQADMIN_BASE_URI` | String | Command execution | Same meaning as the global `--base-uri` argument | +| `RABBITMQADMIN_USE_TLS` | Boolean | Command execution | Same meaning as the global `--tls` argument | +| `RABBITMQADMIN_USERNAME` | String | Command execution | Same meaning as the global `--username` argument | +| `RABBITMQADMIN_PASSWORD` | String | Command execution | Same meaning as the global `--password` argument | +| `RABBITMQADMIN_TABLE_STYLE` | Enum, see `--table-style` in `rabbitmqadmin help` | Command execution | Same meaning as the global `--table-style` argument | + + ## Project Goals Compared to `rabbitmqadmin` v1 This version of `rabbitmqadmin` has a few ideas in mind: -* This is a major version bump. Therefore, reasonable breaking changes are OK. `rabbitmqadmin` hasn't seen a revision in fourteen years +* This is a major version bump. Therefore, reasonable breaking changes are OK. `rabbitmqadmin` hasn't seen a revision in fifteen years * Some features in `rabbitmqadmin` v1 arguably should never have been built-ins, external tools for data processing and [modern shells](https://www.nushell.sh/) can manipulate tabular data better than `rabbitmqadmin` ever would From 7df0bb91fa87e21e4f2b80f553967731d4ab2384 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 19:11:12 -0400 Subject: [PATCH 016/320] Change log updates for 2.0.0 --- CHANGELOG.md | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baf66c4..24f50f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,41 @@ # rabbitmqadmin-ng Change Log -## v0.30.0 (in development) - -No (documented) changes yet. +## v2.0.0 (in development) + +### Intentionally Restricted Environment Variable Support + +Environment variables have a number of serious downsides compared to a `rabbitmqadmin.conf` +and the regular `--long-options` on the command line: + +1. Non-existent support for value types and validation ("everything is a string") +2. Subprocess inheritance restrictions that can be very time-consuming to debug +3. Different syntax for setting them between the classic POSIX-era shells (such as `bash`, `zsh`) and modern ones (such as [`nushell`](https://www.nushell.sh/)) + +For these reasons and others, `rabbitmqadmin` v2 intentionally uses the configuration file and the +CLI options over the environment variables. + +`rabbitmqadmin` v2 does, however, supports a number of environment variables for a few +global settings that cannot be configured any other way (besides a CLI option), +or truly represent an environment characteristic, e.g. either the non-interactive mode +should be enabled. + +These environment variables are as follows: + +| Environment variable | Type | When used | Description | +|--------------------------------------|---------------------------------------------------|---------------------------------------|--------------------------------------------------------------| +| `RABBITMQADMIN_CONFIG_FILE_PATH` | Local filesystem path | Pre-flight (before command execution) | Same meaning as the global `--confg-file` argument | +| `RABBITMQADMIN_NON_INTERACTIVE_MODE` | Boolean | Command execution | Enables the non-interactive mode.

Same meaning as the global `--non-interactive` argument | +| `RABBITMQADMIN_QUIET_MODE`
| Boolean | Command execution | Instructs the tool to produce less output.

Same meaning as the global `--quiet` argument | +| `RABBITMQADMIN_NODE_ALIAS` | String | Command execution | Same meaning as the global `--node` argument | +| `RABBITMQADMIN_TARGET_HOST` | String | Command execution | Same meaning as the global `--host` argument | +| `RABBITMQADMIN_TARGET_PORT` | Positive integer | Command execution | Same meaning as the global `--port` argument | +| `RABBITMQADMIN_API_PATH_PREFIX` | String | Command execution | Same meaning as the global `--path-prefix` argument | +| `RABBITMQADMIN_TARGET_VHOST` | String | Command execution | Same meaning as the global `--vhost` argument | +| `RABBITMQADMIN_BASE_URI` | String | Command execution | Same meaning as the global `--base-uri` argument | +| `RABBITMQADMIN_USE_TLS` | Boolean | Command execution | Same meaning as the global `--tls` argument | +| `RABBITMQADMIN_USERNAME` | String | Command execution | Same meaning as the global `--username` argument | +| `RABBITMQADMIN_PASSWORD` | String | Command execution | Same meaning as the global `--password` argument | +| `RABBITMQADMIN_TABLE_STYLE` | Enum, see `--table-style` in `rabbitmqadmin help` | Command execution | Same meaning as the global `--table-style` argument | ## v0.29.0 (Mar 23, 2025) From 70a1a3325c61adc4e861cbfb700fabe5f7de12c7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 19:40:21 -0400 Subject: [PATCH 017/320] Tweak two built-in Cargo profiles --- Cargo.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 90c2a6a..8c0e4d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,13 @@ rustls = { version = "0.23", features = ["aws_lc_rs"] } [dev-dependencies] assert_cmd = "2.0" predicates = "3.1" + +[profiles.dev] +incremental = true +overflow-checks = true +debug-assertions = true + +[profiles.release] +incremental = false +overflow-checks = false +debug-assertions = false From 62f39209e1f3ac21ba56002a9d3995647b78c6b0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 22:41:58 -0400 Subject: [PATCH 018/320] If the `RABBITMQADMIN_NON_INTERACTIVE_MODE` is not set to `true`, this tool now can infer subcommand and --long-option names. This means that a subcommand can be referenced with its unique prefix, that is, * 'del queue' will be inferred as 'delete queue' * 'del q --nam "a.queue"' will be inferred as 'delete queue --name "a.queue"' To enable each feature, set the following environment variables to 'true': * `RABBITMQADMIN_INFER_SUBCOMMANDS` * `RABBITMQADMIN_INFER_LONG_OPTIONS` This feature is only mean to be used interactively. For non-interactive use, it can be potentially dangerous and therefore requires an explicit opt-in from the user. --- src/cli.rs | 145 +++++++++++++++++++++++++++++++-------------- src/config.rs | 29 +++++++++ src/main.rs | 13 +++- src/pre_flight.rs | 32 ++++++++++ src/static_urls.rs | 1 + 5 files changed, 173 insertions(+), 47 deletions(-) create mode 100644 src/pre_flight.rs diff --git a/src/cli.rs b/src/cli.rs index 4555761..fa65beb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -16,6 +16,7 @@ use std::path::PathBuf; use super::constants::*; use super::static_urls::*; use super::tanzu_cli::tanzu_subcommands; +use crate::config::PreFlightSettings; use crate::output::TableStyle; use clap::{Arg, ArgAction, ArgGroup, Command, value_parser}; use rabbitmq_http_client::commons::{ @@ -24,7 +25,7 @@ use rabbitmq_http_client::commons::{ }; use rabbitmq_http_client::requests::FederationResourceCleanupMode; -pub fn parser() -> Command { +pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { let after_help = color_print::cformat!( r#" Documentation and Community Resources @@ -50,6 +51,8 @@ pub fn parser() -> Command { "RabbitMQ CLI that uses the HTTP API. Version: {}", clap::crate_version!() )) + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_help(after_help) .disable_version_flag(true) // --config-file @@ -224,29 +227,43 @@ pub fn parser() -> Command { "Doc guide: {}", MONITORING_GUIDE_URL )) + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("summary") - .subcommands(show_subcommands()), + .subcommands(show_subcommands(pre_flight_settings.clone())), Command::new("list") .about("lists objects by type") - .subcommands(list_subcommands()), + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(list_subcommands(pre_flight_settings.clone())), Command::new("declare") .about("creates or declares objects") - .subcommands(declare_subcommands()), + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(declare_subcommands(pre_flight_settings.clone())), Command::new("delete") .about("deletes objects") - .subcommands(delete_subcommands()), + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(delete_subcommands(pre_flight_settings.clone())), Command::new("purge") .about("purges queues") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("queue") - .subcommands(purge_subcommands()), + .subcommands(purge_subcommands(pre_flight_settings.clone())), Command::new("policies") .about("operations on policies") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("policy") - .subcommands(policies_subcommands()), + .subcommands(policies_subcommands(pre_flight_settings.clone())), Command::new("health_check") .about("runs health checks") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("check") - .subcommands(health_check_subcommands()) + .subcommands(health_check_subcommands(pre_flight_settings.clone())) .after_long_help(color_print::cformat!( r#"Doc guides: @@ -257,73 +274,95 @@ pub fn parser() -> Command { )), Command::new("close") .about("closes connections") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("connection") - .subcommands(close_subcommands()), + .subcommands(close_subcommands(pre_flight_settings.clone())), Command::new("rebalance") .about("rebalances queue leaders") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( "Doc guide: {}", QUORUM_QUEUE_GUIDE_URL )) .subcommand_value_name("queues") - .subcommands(rebalance_subcommands()), + .subcommands(rebalance_subcommands(pre_flight_settings.clone())), Command::new("definitions") .about("operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc)") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) .subcommand_value_name("export") - .subcommands(definitions_subcommands()), + .subcommands(definitions_subcommands(pre_flight_settings.clone())), Command::new("export") .about("see 'definitions export'") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) .subcommand_value_name("definitions") - .subcommands(export_subcommands()), + .subcommands(export_subcommands(pre_flight_settings.clone())), Command::new("import") .about("see 'definitions import'") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) .subcommand_value_name("definitions") - .subcommands(import_subcommands()), + .subcommands(import_subcommands(pre_flight_settings.clone())), Command::new("feature_flags") .about("operations on feature flags") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL )) .subcommand_value_name("feature flag") - .subcommands(feature_flags_subcommands()), + .subcommands(feature_flags_subcommands(pre_flight_settings.clone())), Command::new("deprecated_features") .about("operations on deprecated features") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )) .subcommand_value_name("deprecated feature") - .subcommands(deprecated_features_subcommands()), + .subcommands(deprecated_features_subcommands(pre_flight_settings.clone())), Command::new("publish") .about(color_print::cstr!("publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments.")) + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) .subcommand_value_name("message") - .subcommands(publish_subcommands()), + .subcommands(publish_subcommands(pre_flight_settings.clone())), Command::new("get") .about(color_print::cstr!("fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) .subcommand_value_name("message") - .subcommands(get_subcommands()), + .subcommands(get_subcommands(pre_flight_settings.clone())), Command::new("shovels") .about("Operations on shovels") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!("Doc guide: {}", SHOVEL_GUIDE_URL)) .subcommand_value_name("shovels") - .subcommands(shovel_subcommands()), + .subcommands(shovel_subcommands(pre_flight_settings.clone())), Command::new("federation") .about("Operations on federation upstreams and links") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( r#"Doc guides: @@ -336,16 +375,18 @@ pub fn parser() -> Command { FEDERATED_QUEUES_GUIDE_URL, FEDERATION_REFERENCE_URL )) - .subcommands(federation_subcommands()), + .subcommands(federation_subcommands(pre_flight_settings.clone())), Command::new("tanzu") .about("Tanzu RabbitMQ-specific commands") - // TODO: documentation link + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_long_help(color_print::cformat!("Doc guide: {}", COMMERCIAL_OFFERINGS_GUIDE_URL)) .subcommand_value_name("subcommand") .subcommands(tanzu_subcommands()), ]) } -fn list_subcommands() -> [Command; 19] { +fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { [ Command::new("nodes").long_about("Lists cluster members"), Command::new("users").long_about("Lists users in the internal database"), @@ -461,9 +502,10 @@ fn list_subcommands() -> [Command; 19] { DEPRECATED_FEATURE_GUIDE_URL )), ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn declare_subcommands() -> [Command; 12] { +fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] { [ Command::new("user") .about("creates a user") @@ -814,10 +856,10 @@ fn declare_subcommands() -> [Command; 12] { .help("limit value") .required(true), ) - ] + ].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn show_subcommands() -> [Command; 5] { +fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let overview_cmd = Command::new("overview") .about("displays a essential information about target node and its cluster"); let churn_cmd = Command::new("churn").about("displays object churn metrics"); @@ -856,9 +898,10 @@ fn show_subcommands() -> [Command; 5] { memory_breakdown_in_bytes_cmd, memory_breakdown_in_percent_cmd, ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn delete_subcommands() -> [Command; 13] { +fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -1015,20 +1058,22 @@ fn delete_subcommands() -> [Command; 13] { .required(true), ), ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn purge_subcommands() -> [Command; 1] { - [Command::new("queue") +fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { + let queue_cmd = Command::new("queue") .long_about("purges (permanently removes unacknowledged messages from) a queue") .arg( Arg::new("name") .long("name") .help("name of the queue to purge") .required(true), - )] + ); + [queue_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn policies_subcommands() -> [Command; 5] { +fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let declare_cmd = Command::new("declare") .about("creates or updates a policy") .after_long_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) @@ -1112,9 +1157,10 @@ fn policies_subcommands() -> [Command; 5] { list_in_cmd, list_matching_cmd, ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn health_check_subcommands() -> [Command; 6] { +fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { let node_is_quorum_critical_after_help = color_print::cformat!( r#" Doc guides: @@ -1176,13 +1222,15 @@ fn health_check_subcommands() -> [Command; 6] { port_listener, protocol_listener, ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn rebalance_subcommands() -> [Command; 1] { - [Command::new("queues").about("rebalances queue leaders")] +fn rebalance_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { + let queues_cmd = Command::new("queues").about("rebalances queue leaders"); + [queues_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn close_subcommands() -> [Command; 2] { +fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { let close_connection = Command::new("connection") .about("closes a client connection") .arg( @@ -1201,9 +1249,10 @@ fn close_subcommands() -> [Command; 2] { .required(true), ); [close_connection, close_user_connections] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn definitions_subcommands() -> [Command; 4] { +fn definitions_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let export_cmd = Command::new("export") .about("export cluster-wide definitions") .after_long_help(color_print::cformat!( @@ -1345,9 +1394,10 @@ Examples: import_cmd, import_into_vhost_cmd, ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn export_subcommands() -> [Command; 1] { +fn export_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { let definitions = Command::new("definitions") .about("prefer 'definitions export'") .after_long_help(color_print::cformat!( @@ -1382,10 +1432,10 @@ Example use: --transformations strip_cmq_keys_from_policies,drop_empty_policies .action(ArgAction::Append) .required(false), ); - [definitions] + [definitions].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn import_subcommands() -> [Command; 1] { +fn import_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("definitions") .about("prefer 'definitions import'") .after_long_help(color_print::cformat!( @@ -1398,9 +1448,10 @@ fn import_subcommands() -> [Command; 1] { .help("JSON file with definitions") .required(true), )] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn feature_flags_subcommands() -> [Command; 3] { +pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list") .long_about("lists feature flags and their cluster state") .after_long_help(color_print::cformat!( @@ -1429,9 +1480,10 @@ pub fn feature_flags_subcommands() -> [Command; 3] { )); [list_cmd, enable_cmd, enable_all_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn deprecated_features_subcommands() -> [Command; 2] { +pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { let list_cmd = Command::new("list") .long_about("lists deprecated features") .after_long_help(color_print::cformat!( @@ -1447,9 +1499,10 @@ pub fn deprecated_features_subcommands() -> [Command; 2] { )); [list_cmd, list_in_use_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn publish_subcommands() -> [Command; 1] { +pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("message") .about("publishes a message to an exchange") .about(color_print::cstr!("publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) @@ -1485,10 +1538,10 @@ pub fn publish_subcommands() -> [Command; 1] { .required(false) .default_value("{}") .help("Message properties"), - )] + )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn get_subcommands() -> [Command; 1] { +pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("messages") .about(color_print::cstr!("Fetches (via polling, very inefficiently) message(s) from a queue. Only suitable for development and test environments")) .after_long_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) @@ -1514,10 +1567,10 @@ pub fn get_subcommands() -> [Command; 1] { .required(false) .default_value("ack_requeue_false") .help("Accepted values are: ack_requeue_false, reject_requeue_false, ack_requeue_true, reject_requeue_true"), - )] + )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn shovel_subcommands() -> [Command; 4] { +pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let list_all_cmd = Command::new("list_all") .long_about("Lists shovels in all virtual hosts") .after_long_help(color_print::cformat!( @@ -1660,9 +1713,10 @@ pub fn shovel_subcommands() -> [Command; 4] { ); [list_all_cmd, declare_091_cmd, declare_10_cmd, delete_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn federation_subcommands() -> [Command; 6] { +fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { let list_all_upstreams = Command::new("list_all_upstreams") .long_about("Lists federation upstreams in all virtual hosts") .after_long_help(color_print::cformat!( @@ -1982,4 +2036,5 @@ fn federation_subcommands() -> [Command; 6] { delete_upstream, list_all_links, ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/config.rs b/src/config.rs index f057b3e..bb73daa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,6 +24,35 @@ use std::path::{Path, PathBuf}; use thiserror::Error; use url::Url; +/// A set of settings that must be set very early on. +/// More specifically, before the command line argument parser is +/// configured. +#[derive(Debug, Clone)] +pub struct PreFlightSettings { + pub infer_subcommands: bool, + pub infer_long_options: bool, +} + +impl Default for PreFlightSettings { + fn default() -> Self { + Self { + infer_long_options: true, + infer_subcommands: false, + } + } +} + +impl PreFlightSettings { + /// Returns a set of [`PreFlightSettings`] that disable inference. + /// Primarily meant to be used by/for the non-interactive mode. + pub fn non_interactive() -> Self { + Self { + infer_long_options: false, + infer_subcommands: false, + } + } +} + #[derive(Error, Debug)] pub enum ConfigFileError { #[error("provided config file at '{0}' does not exist")] diff --git a/src/main.rs b/src/main.rs index eb6e602..1c3a02e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,12 +28,13 @@ mod config; mod constants; mod errors; mod output; +mod pre_flight; mod static_urls; mod tables; mod tanzu_cli; mod tanzu_commands; -use crate::config::SharedSettings; +use crate::config::{PreFlightSettings, SharedSettings}; use crate::constants::{ DEFAULT_CONFIG_FILE_PATH, DEFAULT_HTTPS_PORT, DEFAULT_NODE_ALIAS, DEFAULT_VHOST, TANZU_COMMAND_PREFIX, @@ -48,7 +49,15 @@ use rustls::pki_types::{CertificateDer, PrivateKeyDer}; type APIClient<'a> = GenericAPIClient<&'a str, &'a str, &'a str>; fn main() { - let parser = cli::parser(); + let pre_flight_settings = match pre_flight::is_non_interactive() { + true => PreFlightSettings::non_interactive(), + false => PreFlightSettings { + infer_subcommands: pre_flight::should_infer_subcommands(), + infer_long_options: pre_flight::should_infer_long_options(), + }, + }; + + let parser = cli::parser(pre_flight_settings); let cli = parser.get_matches(); let default_config_file_path = PathBuf::from(DEFAULT_CONFIG_FILE_PATH); let config_file_path = cli diff --git a/src/pre_flight.rs b/src/pre_flight.rs new file mode 100644 index 0000000..8b79336 --- /dev/null +++ b/src/pre_flight.rs @@ -0,0 +1,32 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub fn is_non_interactive() -> bool { + is_enabled_in_env("RABBITMQADMIN_NON_INTERACTIVE_MODE") +} + +pub fn should_infer_subcommands() -> bool { + is_enabled_in_env("RABBITMQADMIN_INFER_SUBCOMMANDS") +} + +pub fn should_infer_long_options() -> bool { + is_enabled_in_env("RABBITMQADMIN_INFER_LONG_OPTIONS") +} + +fn is_enabled_in_env(key: &str) -> bool { + match std::env::var(key) { + Ok(val) => val.to_lowercase().trim() == "true", + Err(_) => false, + } +} diff --git a/src/static_urls.rs b/src/static_urls.rs index cd3c778..3d304c6 100644 --- a/src/static_urls.rs +++ b/src/static_urls.rs @@ -62,3 +62,4 @@ pub(crate) const FEDERATED_QUEUES_GUIDE_URL: &str = "https://rabbitmq.com/docs/f%20pub(crate)%20const%20FEDERATED_EXCHANGES_GUIDE_URL:%20&str%20="https://rabbitmq.com/docs/federated-exchanges"; pub(crate) const FEDERATION_REFERENCE_URL: &str = "https://rabbitmq.com/docs/federation-reference"; +pub(crate) const COMMERCIAL_OFFERINGS_GUIDE_URL: &str = "https://www.rabbitmq.com/contact"; From 37ac28f1c2d19d24c4156cf524a4df46b6e89330 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 23:02:06 -0400 Subject: [PATCH 019/320] Change log and README updates --- CHANGELOG.md | 25 ++++++++++++++++++++++++- README.md | 32 ++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f50f7..dfa474a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,30 @@ ## v2.0.0 (in development) -### Intentionally Restricted Environment Variable Support +### Enhancements + +#### Subcommand and Long Option Inference + + If the `RABBITMQADMIN_NON_INTERACTIVE_MODE` is not set to `true`, this tool + now can infer subcommand and --long-option names. + + This means that a subcommand can be referenced with its unique prefix, + that is, + + * 'del queue' will be inferred as 'delete queue' + * 'del q --nam "a.queue"' will be inferred as 'delete queue --name "a.queue"' + + To enable each feature, set the following environment variables to + 'true': + + * `RABBITMQADMIN_INFER_SUBCOMMANDS` + * `RABBITMQADMIN_INFER_LONG_OPTIONS` + + This feature is only mean to be used interactively. For non-interactive + use, it can be potentially dangerous and therefore requires + an explicit opt-in from the user. + +#### Intentionally Restricted Environment Variable Support Environment variables have a number of serious downsides compared to a `rabbitmqadmin.conf` and the regular `--long-options` on the command line: diff --git a/README.md b/README.md index 3c703c2..cf486a8 100644 --- a/README.md +++ b/README.md @@ -209,10 +209,10 @@ as a result: ``` key Product name RabbitMQ - Product version 4.0.5 - RabbitMQ version 4.0.5 - Erlang version 26.2.5.6 - Erlang details Erlang/OTP 26 [erts-14.2.5.5] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] + Product version 4.0.7 + RabbitMQ version 4.0.7 + Erlang version 26.2.5.10 + Erlang details Erlang/OTP 26 [erts-14.2.5.9] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] ``` ### Retrieving Basic Node Information @@ -475,6 +475,30 @@ rabbitmqadmin deprecated_features list rabbitmqadmin list deprecated_features ``` +## Subcommand and Long Option Inference + +This feature is available only in the `main` branch +at the moment. + +If the `RABBITMQADMIN_NON_INTERACTIVE_MODE` is not set to `true`, this tool +now can infer subcommand and --long-option names. + +This means that a subcommand can be referenced with its unique prefix, +that is, + +* 'del queue' will be inferred as 'delete queue' +* 'del q --nam "a.queue"' will be inferred as 'delete queue --name "a.queue"' + +To enable each feature, set the following environment variables to +'true': + +* `RABBITMQADMIN_INFER_SUBCOMMANDS` +* `RABBITMQADMIN_INFER_LONG_OPTIONS` + +This feature is only mean to be used interactively. For non-interactive +use, it can be potentially dangerous and therefore requires +an explicit opt-in from the user. + ## Configuration Files From 1a2a352d2f6d047fd4e2e34fb4baa9e2892b2160 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 29 Mar 2025 23:16:12 -0400 Subject: [PATCH 020/320] Doc corrections --- CHANGELOG.md | 25 ++++++++++++------------- README.md | 3 +-- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfa474a..b3917d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,24 +6,23 @@ #### Subcommand and Long Option Inference - If the `RABBITMQADMIN_NON_INTERACTIVE_MODE` is not set to `true`, this tool - now can infer subcommand and --long-option names. +If the `RABBITMQADMIN_NON_INTERACTIVE_MODE` is not set to `true`, this tool +now can infer subcommand and --long-option names. - This means that a subcommand can be referenced with its unique prefix, - that is, +This means that a subcommand can be referenced with its unique prefix, +that is, - * 'del queue' will be inferred as 'delete queue' - * 'del q --nam "a.queue"' will be inferred as 'delete queue --name "a.queue"' +* 'del queue' will be inferred as 'delete queue' +* 'del q --nam "a.queue"' will be inferred as 'delete queue --name "a.queue"' - To enable each feature, set the following environment variables to - 'true': +To enable each feature, set the following environment variables to +'true': - * `RABBITMQADMIN_INFER_SUBCOMMANDS` - * `RABBITMQADMIN_INFER_LONG_OPTIONS` +* `RABBITMQADMIN_INFER_SUBCOMMANDS` +* `RABBITMQADMIN_INFER_LONG_OPTIONS` - This feature is only mean to be used interactively. For non-interactive - use, it can be potentially dangerous and therefore requires - an explicit opt-in from the user. +This feature is only mean to be used interactively. For non-interactive +use, it can be potentially too dangerous to allow. #### Intentionally Restricted Environment Variable Support diff --git a/README.md b/README.md index cf486a8..83582ec 100644 --- a/README.md +++ b/README.md @@ -496,8 +496,7 @@ To enable each feature, set the following environment variables to * `RABBITMQADMIN_INFER_LONG_OPTIONS` This feature is only mean to be used interactively. For non-interactive -use, it can be potentially dangerous and therefore requires -an explicit opt-in from the user. +use, it can be potentially too dangerous to allow. ## Configuration Files From c69382673c6a0e5ad0b5fcf7385c8db47dec7679 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 00:32:52 -0400 Subject: [PATCH 021/320] Change log update --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3917d7..12af6f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ These environment variables are as follows: | `RABBITMQADMIN_CONFIG_FILE_PATH` | Local filesystem path | Pre-flight (before command execution) | Same meaning as the global `--confg-file` argument | | `RABBITMQADMIN_NON_INTERACTIVE_MODE` | Boolean | Command execution | Enables the non-interactive mode.

Same meaning as the global `--non-interactive` argument | | `RABBITMQADMIN_QUIET_MODE`
| Boolean | Command execution | Instructs the tool to produce less output.

Same meaning as the global `--quiet` argument | +| `RABBITMQADMIN_INFER_SUBCOMMANDS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of subcommands .

Does not apply to the non-interactive mode. | +| `RABBITMQADMIN_INFER_LONG_OPTIONS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of `--long-options` .

Does not apply to the non-interactive mode. | | `RABBITMQADMIN_NODE_ALIAS` | String | Command execution | Same meaning as the global `--node` argument | | `RABBITMQADMIN_TARGET_HOST` | String | Command execution | Same meaning as the global `--host` argument | | `RABBITMQADMIN_TARGET_PORT` | Positive integer | Command execution | Same meaning as the global `--port` argument | @@ -59,7 +61,6 @@ These environment variables are as follows: | `RABBITMQADMIN_PASSWORD` | String | Command execution | Same meaning as the global `--password` argument | | `RABBITMQADMIN_TABLE_STYLE` | Enum, see `--table-style` in `rabbitmqadmin help` | Command execution | Same meaning as the global `--table-style` argument | - ## v0.29.0 (Mar 23, 2025) ### Breaking Changes From 16f8c1e0a0fb1027b4ce68410edfca314a2e9066 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 05:20:37 -0400 Subject: [PATCH 022/320] Update change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12af6f7..3f4639a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ These environment variables are as follows: | `RABBITMQADMIN_CONFIG_FILE_PATH` | Local filesystem path | Pre-flight (before command execution) | Same meaning as the global `--confg-file` argument | | `RABBITMQADMIN_NON_INTERACTIVE_MODE` | Boolean | Command execution | Enables the non-interactive mode.

Same meaning as the global `--non-interactive` argument | | `RABBITMQADMIN_QUIET_MODE`
| Boolean | Command execution | Instructs the tool to produce less output.

Same meaning as the global `--quiet` argument | -| `RABBITMQADMIN_INFER_SUBCOMMANDS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of subcommands .

Does not apply to the non-interactive mode. | +| `RABBITMQADMIN_INFER_SUBCOMMANDS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of subcommands.

Does not apply to the non-interactive mode. | | `RABBITMQADMIN_INFER_LONG_OPTIONS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of `--long-options` .

Does not apply to the non-interactive mode. | | `RABBITMQADMIN_NODE_ALIAS` | String | Command execution | Same meaning as the global `--node` argument | | `RABBITMQADMIN_TARGET_HOST` | String | Command execution | Same meaning as the global `--host` argument | From e04911f66b60e5a22d03c68a90c3c39fdf4788f9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 05:21:07 -0400 Subject: [PATCH 023/320] Alias (global) --host as --hostname --- src/cli.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli.rs b/src/cli.rs index fa65beb..2d8109c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -80,6 +80,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { Arg::new("host") .short('H') .long("host") + .alias("hostname") .env("RABBITMQADMIN_TARGET_HOST") .help("HTTP API hostname to use when connecting"), ) From 926b3e91bd7b02a91e3b0f30b052b4e3288abb84 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 06:12:01 -0400 Subject: [PATCH 024/320] The profiles belong to Cargo config.toml --- Cargo.toml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c0e4d4..90c2a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,13 +39,3 @@ rustls = { version = "0.23", features = ["aws_lc_rs"] } [dev-dependencies] assert_cmd = "2.0" predicates = "3.1" - -[profiles.dev] -incremental = true -overflow-checks = true -debug-assertions = true - -[profiles.release] -incremental = false -overflow-checks = false -debug-assertions = false From db4fef723dd20c941c72a3e73d5fde1f3c9d167e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 18:07:39 -0400 Subject: [PATCH 025/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a85eb4..74ebe05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1922,9 +1922,9 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", From 901340e12c737f62e86740e009de3036d5ec55a2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 18:07:51 -0400 Subject: [PATCH 026/320] Make it possible to specify a few global options --at-the-end This generation of 'rabbitmqadmin' strictly separates global options from the command-specific ones. This has both pros and cons. Specifying a few very common options, namely --vhost, at the end of the argument list, was common with 'rabbitmqadmin' v1. Clap allows a global option to be marked as such, which means it will still be correctly parsed even when given amongst the command-specific ones. This has a serious downside, too: if a subcommand declares an option with the same ID, long or short name, the parser will panic. Therefore this commit cherry picks a few options that are truly global and unlikely will run into conflicts: * --vhost * TLS-related options * --quiet/-q The docs will continue saying that all global options must be passed before the subcommands because that's still true for the majority of them. --- src/cli.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index 2d8109c..684e5b4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -118,6 +118,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { Arg::new("vhost") .short('V') .long("vhost") + .global(true) .env("RABBITMQADMIN_TARGET_VHOST") .help("target virtual host. Defaults to '/'"), ) @@ -155,6 +156,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls") .long("use-tls") + .global(true) .help("use TLS (HTTPS) for HTTP API requests ") .env("RABBITMQADMIN_USE_TLS") .value_parser(value_parser!(bool)) @@ -165,6 +167,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls-ca-cert-file") .long("tls-ca-cert-file") + .global(true) .required(false) .requires("tls") .help("Local path to a CA certificate file in the PEM format") @@ -174,6 +177,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls-cert-file") .long("tls-cert-file") + .global(true) .required(false) .requires("tls-key-file") .help("Local path to a client certificate file in the PEM format") @@ -183,6 +187,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls-key-file") .long("tls-key-file") + .global(true) .required(false) .requires("tls-cert-file") .help("Local path to a client private key file in the PEM format") @@ -203,6 +208,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("non_interactive") .long("non-interactive") + .global(true) .env("RABBITMQADMIN_NON_INTERACTIVE_MODE") .help("pass when invoking from scripts") .conflicts_with("table_style") @@ -214,6 +220,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("table_style") .long("table-style") + .global(true) .env("RABBITMQADMIN_TABLE_STYLE") .help("style preset to apply to output tables: modern, borderless, ascii, dots, psql, markdown, sharp") .conflicts_with("non_interactive") From 3d49ff45220da92c8db649f4df131ced06c18133 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 18:14:05 -0400 Subject: [PATCH 027/320] Explain --- src/cli.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index 684e5b4..800105c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -118,6 +118,8 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { Arg::new("vhost") .short('V') .long("vhost") + // IMPORTANT: this means that subcommands won't be able to override --vhost or -V, + // otherwise the parser will panic. MK. .global(true) .env("RABBITMQADMIN_TARGET_VHOST") .help("target virtual host. Defaults to '/'"), From e3dcbb8a7f33cfd5ffd5a7520c3e1b4b3dfacc82 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 18:17:33 -0400 Subject: [PATCH 028/320] Shorten the list of global --options Because they can cause conflicts, we cannot mark all of them as global, and it makes the CLI a bit weird. There are three exceptions, still: * --vhost (a very common option) * --non-interactive (will never clash) * --table-style (will never clash) --- src/cli.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 800105c..f278028 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -158,7 +158,6 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls") .long("use-tls") - .global(true) .help("use TLS (HTTPS) for HTTP API requests ") .env("RABBITMQADMIN_USE_TLS") .value_parser(value_parser!(bool)) @@ -169,7 +168,6 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls-ca-cert-file") .long("tls-ca-cert-file") - .global(true) .required(false) .requires("tls") .help("Local path to a CA certificate file in the PEM format") @@ -179,7 +177,6 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls-cert-file") .long("tls-cert-file") - .global(true) .required(false) .requires("tls-key-file") .help("Local path to a client certificate file in the PEM format") @@ -189,7 +186,6 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .arg( Arg::new("tls-key-file") .long("tls-key-file") - .global(true) .required(false) .requires("tls-cert-file") .help("Local path to a client private key file in the PEM format") From 4a69ba66910fa366afeb881973e3325029178ef9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 18:26:07 -0400 Subject: [PATCH 029/320] Cosmetics [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4639a..b4a8a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# rabbitmqadmin-ng Change Log +# rabbitmqadmin gen 2 Change Log ## v2.0.0 (in development) From d836e65289c47697bc39dee4eb865fe14c20c5c4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 21:34:47 -0400 Subject: [PATCH 030/320] Make sure command group descriptions are capitalized --- src/cli.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index f278028..c33fa68 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -228,7 +228,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_required(true) .subcommands([ Command::new("show") - .about("overview") + .about("Overview") .after_long_help(color_print::cformat!( "Doc guide: {}", MONITORING_GUIDE_URL @@ -238,34 +238,34 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("summary") .subcommands(show_subcommands(pre_flight_settings.clone())), Command::new("list") - .about("lists objects by type") + .about("Lists objects") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(list_subcommands(pre_flight_settings.clone())), Command::new("declare") - .about("creates or declares objects") + .about("Creates or declares objects") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(declare_subcommands(pre_flight_settings.clone())), Command::new("delete") - .about("deletes objects") + .about("Deletes objects") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(delete_subcommands(pre_flight_settings.clone())), Command::new("purge") - .about("purges queues") + .about("Purges queues") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("queue") .subcommands(purge_subcommands(pre_flight_settings.clone())), Command::new("policies") - .about("operations on policies") + .about("Operations on policies") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("policy") .subcommands(policies_subcommands(pre_flight_settings.clone())), Command::new("health_check") - .about("runs health checks") + .about("Runs health checks") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("check") @@ -279,13 +279,13 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { DEPRECATED_FEATURE_GUIDE_URL )), Command::new("close") - .about("closes connections") + .about("Closes connections") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("connection") .subcommands(close_subcommands(pre_flight_settings.clone())), Command::new("rebalance") - .about("rebalances queue leaders") + .about("Rebalancing of leader replicas") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( @@ -295,7 +295,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("queues") .subcommands(rebalance_subcommands(pre_flight_settings.clone())), Command::new("definitions") - .about("operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc)") + .about("Operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc)") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( @@ -305,7 +305,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("export") .subcommands(definitions_subcommands(pre_flight_settings.clone())), Command::new("export") - .about("see 'definitions export'") + .about("See 'definitions export'") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( @@ -315,7 +315,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("definitions") .subcommands(export_subcommands(pre_flight_settings.clone())), Command::new("import") - .about("see 'definitions import'") + .about("See 'definitions import'") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( @@ -325,7 +325,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("definitions") .subcommands(import_subcommands(pre_flight_settings.clone())), Command::new("feature_flags") - .about("operations on feature flags") + .about("Operations on feature flags") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( @@ -335,7 +335,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("feature flag") .subcommands(feature_flags_subcommands(pre_flight_settings.clone())), Command::new("deprecated_features") - .about("operations on deprecated features") + .about("Operations on deprecated features") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!( @@ -345,14 +345,14 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("deprecated feature") .subcommands(deprecated_features_subcommands(pre_flight_settings.clone())), Command::new("publish") - .about(color_print::cstr!("publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments.")) + .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments.")) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) .subcommand_value_name("message") .subcommands(publish_subcommands(pre_flight_settings.clone())), Command::new("get") - .about(color_print::cstr!("fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) + .about(color_print::cstr!("Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_long_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) From 78a014137299fb7b7d2b91a049ae268a562ce365 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 21:51:29 -0400 Subject: [PATCH 031/320] Capitalize command descriptions --- src/cli.rs | 114 ++++++++++++++++++++++++++--------------------------- 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index c33fa68..aba9acd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -228,14 +228,13 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_required(true) .subcommands([ Command::new("show") - .about("Overview") + .about("Overview, memory footprint breakdown, and more") .after_long_help(color_print::cformat!( "Doc guide: {}", MONITORING_GUIDE_URL )) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .subcommand_value_name("summary") .subcommands(show_subcommands(pre_flight_settings.clone())), Command::new("list") .about("Lists objects") @@ -514,7 +513,7 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] { [ Command::new("user") - .about("creates a user") + .about("Creates a user") .arg( Arg::new("name") .long("name") @@ -542,7 +541,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .default_value(""), ), Command::new("vhost") - .about("creates a virtual host") + .about("Creates a virtual host") .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) .arg( Arg::new("name") @@ -598,7 +597,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .required(true), ), Command::new("queue") - .about("declares a queue or a stream") + .about("Declares a queue or a stream") .after_long_help(color_print::cformat!("Doc guide:: {}", QUEUE_GUIDE_URL)) .arg(Arg::new("name").long("name").required(true).help("name")) .arg( @@ -632,7 +631,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .value_parser(value_parser!(String)), ), Command::new("stream") - .about("declares a stream") + .about("Declares a stream") .after_long_help(color_print::cformat!("Doc guide:: {}", STREAM_GUIDE_URL)) .arg(Arg::new("name").long("name").required(true).help("name")) .arg( @@ -665,7 +664,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .value_parser(value_parser!(String)), ), Command::new("exchange") - .about("declares an exchange") + .about("Declares an exchange") .arg( Arg::new("name") .long("name") @@ -702,7 +701,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .value_parser(value_parser!(String)), ), Command::new("binding") - .about("creates a binding between a source exchange and a destination (a queue or an exchange)") + .about("Creates a binding between a source exchange and a destination (a queue or an exchange)") .arg( Arg::new("source") .long("source") @@ -737,7 +736,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .value_parser(value_parser!(String)), ), Command::new("parameter"). - about("sets a runtime parameter") + about("Sets a runtime parameter") .after_long_help(color_print::cformat!("Doc guide:: {}", RUNTIME_PARAMETER_GUIDE_URL)) .arg( Arg::new("name") @@ -755,7 +754,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .help("parameter's value") .required(true)), Command::new("policy") - .about("creates or updates a policy") + .about("Creates or updates a policy") .after_long_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) .arg( Arg::new("name") @@ -791,7 +790,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .required(true), ), Command::new("operator_policy") - .about("creates or updates an operator policy") + .about("Creates or updates an operator policy") .after_long_help(color_print::cformat!("Doc guide:: {}", OPERATOR_POLICY_GUIDE_URL)) .arg( Arg::new("name") @@ -827,7 +826,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .required(true), ), Command::new("vhost_limit") - .about("set a vhost limit") + .about("Set a vhost limit") .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_LIMIT_GUIDE_URL)) .arg( Arg::new("name") @@ -842,7 +841,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .required(true), ), Command::new("user_limit") - .about("set a user limit") + .about("Set a user limit") .after_long_help(color_print::cformat!("Doc guide:: {}", USER_LIMIT_GUIDE_URL)) .arg( Arg::new("user") @@ -867,12 +866,12 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let overview_cmd = Command::new("overview") - .about("displays a essential information about target node and its cluster"); - let churn_cmd = Command::new("churn").about("displays object churn metrics"); + .about("Displays a essential information about target node and its cluster"); + let churn_cmd = Command::new("churn").about("Displays object churn metrics"); let endpoint_cmd = Command::new("endpoint") - .about("for troubleshooting: displays the computed HTTP API endpoint URI"); + .about("For troubleshooting: displays the computed HTTP API endpoint URI"); let memory_breakdown_in_bytes_cmd = Command::new("memory_breakdown_in_bytes") - .about("provides a memory footprint breakdown (in bytes) for the target node") + .about("Provides a memory footprint breakdown (in bytes) for the target node") .arg( Arg::new("node") .long("node") @@ -885,7 +884,7 @@ fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { )); let memory_breakdown_in_percent_cmd = Command::new("memory_breakdown_in_percent") - .about("provides a memory footprint breakdown (in percent) for the target node") + .about("Provides a memory footprint breakdown (in percent) for the target node") .arg( Arg::new("node") .long("node") @@ -917,7 +916,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { [ Command::new("user") - .about("deletes a user") + .about("Deletes a user") .arg( Arg::new("name") .long("name") @@ -926,7 +925,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { ) .arg(idempotently_arg.clone()), Command::new("vhost") - .about("deletes a virtual host") + .about("Deletes a virtual host") .arg( Arg::new("name") .long("name") @@ -935,7 +934,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { ) .arg(idempotently_arg.clone()), Command::new("permissions") - .about("revokes user permissions to a given vhost") + .about("Revokes user permissions to a given vhost") .arg( Arg::new("user") .long("user") @@ -944,7 +943,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { ) .arg(idempotently_arg.clone()), Command::new("queue") - .about("deletes a queue") + .about("Deletes a queue") .arg( Arg::new("name") .long("name") @@ -953,7 +952,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { ) .arg(idempotently_arg.clone()), Command::new("stream") - .about("deletes a stream") + .about("Deletes a stream") .arg( Arg::new("name") .long("name") @@ -962,7 +961,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { ) .arg(idempotently_arg.clone()), Command::new("exchange") - .about("deletes an exchange") + .about("Deletes an exchange") .arg( Arg::new("name") .long("name") @@ -971,7 +970,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { ) .arg(idempotently_arg.clone()), Command::new("binding") - .about("deletes a binding") + .about("Deletes a binding") .arg( Arg::new("source") .long("source") @@ -1005,7 +1004,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .value_parser(value_parser!(String)), ), Command::new("parameter") - .about("clears a runtime parameter") + .about("Clears a runtime parameter") .arg( Arg::new("name") .long("name") @@ -1018,14 +1017,14 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .help("component (eg. federation-upstream)") .required(true), ), - Command::new("policy").about("deletes a policy").arg( + Command::new("policy").about("Deletes a policy").arg( Arg::new("name") .long("name") .help("policy name") .required(true), ), Command::new("operator_policy") - .about("deletes an operator policy") + .about("Deletes an operator policy") .arg( Arg::new("name") .long("name") @@ -1041,7 +1040,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .required(true), ), Command::new("user_limit") - .about("clears a user limit") + .about("Clears a user limit") .arg( Arg::new("user") .long("user") @@ -1055,7 +1054,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .required(true), ), Command::new("shovel") - .about("delete a shovel") + .about("Delete a shovel") .arg(idempotently_arg.clone()) .arg( Arg::new("name") @@ -1069,7 +1068,7 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { let queue_cmd = Command::new("queue") - .long_about("purges (permanently removes unacknowledged messages from) a queue") + .long_about("Purges (permanently removes unacknowledged messages from) a queue") .arg( Arg::new("name") .long("name") @@ -1081,7 +1080,7 @@ fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let declare_cmd = Command::new("declare") - .about("creates or updates a policy") + .about("Creates or updates a policy") .after_long_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) .arg( Arg::new("name") @@ -1118,13 +1117,13 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] ); let list_cmd = Command::new("list") - .long_about("lists policies") + .long_about("Lists policies") .after_long_help(color_print::cformat!( "Doc guide: {}", POLICY_GUIDE_URL )); - let delete_cmd = Command::new("delete").about("deletes a policy").arg( + let delete_cmd = Command::new("delete").about("Deletes a policy").arg( Arg::new("name") .long("name") .help("policy name") @@ -1132,7 +1131,7 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] ); let list_in_cmd = Command::new("list_in") - .about("lists policies in a specific virtual host") + .about("Lists policies in a specific virtual host") .arg( Arg::new("apply_to") .long("apply-to") @@ -1141,7 +1140,7 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] ); let list_matching_cmd = Command::new("list_matching_object") - .about("lists policies that match an object (queue, stream, exchange) name") + .about("Lists policies that match an object (queue, stream, exchange) name") .arg( Arg::new("name") .long("name") @@ -1178,14 +1177,14 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; ); let local_alarms = Command::new("local_alarms") - .about("checks if there are any resource alarms in effect on the target node"); + .about("Checks if there are any resource alarms in effect on the target node"); let cluster_wide_alarms = Command::new("cluster_wide_alarms") - .about("checks if there are any resource alarms in effect across the entire cluster"); + .about("Checks if there are any resource alarms in effect across the entire cluster"); let node_is_quorum_critical = Command::new("node_is_quorum_critical") - .about("fails if there are queues/streams with minimum online quorum (queues/streams that will lose their quorum if the target node shuts down)") + .about("Fails if there are queues/streams with minimum online quorum (queues/streams that will lose their quorum if the target node shuts down)") .after_long_help(node_is_quorum_critical_after_help); let deprecated_features_in_use = Command::new("deprecated_features_in_use") - .about("fails if there are any deprecated features in use in the cluster") + .about("Fails if there are any deprecated features in use in the cluster") .after_long_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL @@ -1193,7 +1192,7 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; let port_listener = Command::new("port_listener") .about( - "verifies that there's a reachable TCP listener on the given port on the target node", + "Verifies that there's a reachable TCP listener on the given port on the target node", ) .arg( Arg::new("port") @@ -1207,7 +1206,7 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; let protocol_listener = Command::new("protocol_listener") .about( - "verifies that there's a reachable TCP listener on the given protocol alias on the target node", + "Verifies that there's a reachable TCP listener on the given protocol alias on the target node", ) .arg( Arg::new("protocol") @@ -1232,13 +1231,13 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; } fn rebalance_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { - let queues_cmd = Command::new("queues").about("rebalances queue leaders"); + let queues_cmd = Command::new("queues").about("Rebalances queue leaders"); [queues_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { let close_connection = Command::new("connection") - .about("closes a client connection") + .about("Closes a client connection") .arg( Arg::new("name") .long("name") @@ -1246,7 +1245,7 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { .required(true), ); let close_user_connections = Command::new("user_connections") - .about("closes all connections that authenticated with a specific username") + .about("Closes all connections that authenticated with a specific username") .arg( Arg::new("username") .short('u') @@ -1260,7 +1259,7 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { fn definitions_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let export_cmd = Command::new("export") - .about("export cluster-wide definitions") + .about("Export cluster-wide definitions") .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL @@ -1319,7 +1318,7 @@ Examples: ); let export_from_vhost_cmd = Command::new("export_from_vhost") - .about("export definitions of a specific virtual host") + .about("Export definitions of a specific virtual host") .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL @@ -1345,7 +1344,7 @@ Examples: ); let import_cmd = Command::new("import") - .about("import cluster-wide definitions (of multiple virtual hosts)") + .about("Import cluster-wide definitions (of multiple virtual hosts)") .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL @@ -1370,7 +1369,7 @@ Examples: ); let import_into_vhost_cmd = Command::new("import_into_vhost") - .about("import a virtual host-specific definitions file into a virtual host") + .about("Import a virtual host-specific definitions file into a virtual host") .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL @@ -1405,7 +1404,7 @@ Examples: fn export_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { let definitions = Command::new("definitions") - .about("prefer 'definitions export'") + .about("Prefer 'definitions export'") .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL @@ -1443,7 +1442,7 @@ Example use: --transformations strip_cmq_keys_from_policies,drop_empty_policies fn import_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("definitions") - .about("prefer 'definitions import'") + .about("Prefer 'definitions import'") .after_long_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL @@ -1459,14 +1458,14 @@ fn import_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list") - .long_about("lists feature flags and their cluster state") + .long_about("Lists feature flags and their cluster state") .after_long_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL )); let enable_cmd = Command::new("enable") - .long_about("enables a feature flag") + .long_about("Enables a feature flag") .after_long_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL @@ -1479,7 +1478,7 @@ pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Com ); let enable_all_cmd = Command::new("enable_all") - .long_about("enables all stable feature flags") + .long_about("Enables all stable feature flags") .after_long_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL @@ -1491,14 +1490,14 @@ pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Com pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { let list_cmd = Command::new("list") - .long_about("lists deprecated features") + .long_about("Lists deprecated features") .after_long_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )); let list_in_use_cmd = Command::new("list_used") - .long_about("lists the deprecated features that are found to be in use in the cluster") + .long_about("Lists the deprecated features that are found to be in use in the cluster") .after_long_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL @@ -1510,8 +1509,7 @@ pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) - pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("message") - .about("publishes a message to an exchange") - .about(color_print::cstr!("publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) + .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) .after_long_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) .arg( Arg::new("routing_key") From e062df60cd721fee226d311fb837696e2a94bda3 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 30 Mar 2025 21:56:46 -0400 Subject: [PATCH 032/320] Help: command description edits --- src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index aba9acd..851f3f3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -866,10 +866,10 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let overview_cmd = Command::new("overview") - .about("Displays a essential information about target node and its cluster"); + .about("Displays essential information about target node and its cluster"); let churn_cmd = Command::new("churn").about("Displays object churn metrics"); let endpoint_cmd = Command::new("endpoint") - .about("For troubleshooting: displays the computed HTTP API endpoint URI"); + .about("Displays the computed HTTP API endpoint URI. Use for troubleshooting only."); let memory_breakdown_in_bytes_cmd = Command::new("memory_breakdown_in_bytes") .about("Provides a memory footprint breakdown (in bytes) for the target node") .arg( From b071950d0a0b6633e5ccd3a5da4d070fc3fe7104 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 31 Mar 2025 11:15:15 -0400 Subject: [PATCH 033/320] Add one more doc link to --help --- src/cli.rs | 2 ++ src/static_urls.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index 851f3f3..dfa70bc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -30,6 +30,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { r#" Documentation and Community Resources + rabbitmqadmin docs: {} RabbitMQ docs: {} GitHub Discussions: {} Discord server: {} @@ -37,6 +38,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { Contribute On GitHub: {}"#, + RABBITMQADMIN_DOC_GUIDE_URL, RABBITMQ_DOC_GUIDES_URL, GITHUB_DISCUSSIONS_URL, DISCORD_SERVER_INVITATION_URL, diff --git a/src/static_urls.rs b/src/static_urls.rs index 3d304c6..4fa6954 100644 --- a/src/static_urls.rs +++ b/src/static_urls.rs @@ -15,6 +15,7 @@ #![allow(dead_code)] #![allow(unused_variables)] +pub(crate) const RABBITMQADMIN_DOC_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/management-cli"; pub(crate) const RABBITMQ_DOC_GUIDES_URL: &str = "https://rabbitmq.com/docs/"; pub(crate) const GITHUB_DISCUSSIONS_URL: &str = "https://github.com/rabbitmq/rabbitmq-server/discussions"; From 5bb553c52eee7e88a065c247c88535cba8569840 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 31 Mar 2025 15:52:57 -0400 Subject: [PATCH 034/320] Sync README with the latest `help` output [skip ci] --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 83582ec..83614c1 100644 --- a/README.md +++ b/README.md @@ -65,25 +65,25 @@ rabbitmqadmin help which will output a list of command groups: ``` -Usage: rabbitmqadmin [OPTIONS] +Usage: rabbitmqadmin [OPTIONS] Commands: - show overview - list lists objects by type - declare creates or declares things - delete deletes objects - purge purges queues - policies operations on policies - health_check runs health checks - close closes connections - rebalance rebalances queue leaders - definitions operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc) - export see 'definitions export' - import see 'definitions import' - feature_flags operations on feature flags - deprecated_features operations on deprecated features - publish publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. - get fetches message(s) from a queue or stream via polling. Only suitable for development and test environments. + show Overview, memory footprint breakdown, and more + list Lists objects + declare Creates or declares objects + delete Deletes objects + purge Purges queues + policies Operations on policies + health_check Runs health checks + close Closes connections + rebalance Rebalancing of leader replicas + definitions Operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc) + export See 'definitions export' + import See 'definitions import' + feature_flags Operations on feature flags + deprecated_features Operations on deprecated features + publish Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. + get Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments. shovels Operations on shovels federation Operations on federation upstreams and links tanzu Tanzu RabbitMQ-specific commands From afbbdf7c9061a50610a8c004958a8b9c3b93be1c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 31 Mar 2025 16:01:33 -0400 Subject: [PATCH 035/320] Change log updates --- CHANGELOG.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a8a76..57d3c4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ -# rabbitmqadmin gen 2 Change Log +# rabbitmqadmin-ng Change Log -## v2.0.0 (in development) +## v2.1.0 (in development) + +No changes yet. + + +## v2.0.0 (Mar 31, 2024) ### Enhancements @@ -48,8 +53,8 @@ These environment variables are as follows: | `RABBITMQADMIN_CONFIG_FILE_PATH` | Local filesystem path | Pre-flight (before command execution) | Same meaning as the global `--confg-file` argument | | `RABBITMQADMIN_NON_INTERACTIVE_MODE` | Boolean | Command execution | Enables the non-interactive mode.

Same meaning as the global `--non-interactive` argument | | `RABBITMQADMIN_QUIET_MODE`
| Boolean | Command execution | Instructs the tool to produce less output.

Same meaning as the global `--quiet` argument | -| `RABBITMQADMIN_INFER_SUBCOMMANDS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of subcommands.

Does not apply to the non-interactive mode. | -| `RABBITMQADMIN_INFER_LONG_OPTIONS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of `--long-options` .

Does not apply to the non-interactive mode. | +| `RABBITMQADMIN_INFER_SUBCOMMANDS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of subcommands. Does not apply to the non-interactive mode. | +| `RABBITMQADMIN_INFER_LONG_OPTIONS` | Boolean | Pre-flight (before command execution) | Enables inference (completion of partial names) of `--long-options`. Does not apply to the non-interactive mode. | | `RABBITMQADMIN_NODE_ALIAS` | String | Command execution | Same meaning as the global `--node` argument | | `RABBITMQADMIN_TARGET_HOST` | String | Command execution | Same meaning as the global `--host` argument | | `RABBITMQADMIN_TARGET_PORT` | Positive integer | Command execution | Same meaning as the global `--port` argument | From 1f150d11b6cc9d7ea81b17a7be551e64c9dfa532 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 31 Mar 2025 16:21:19 -0400 Subject: [PATCH 036/320] cargo update --- Cargo.lock | 52 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74ebe05..608974f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -780,6 +780,7 @@ dependencies = [ "http", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -789,9 +790,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.62" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2475,11 +2476,37 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2495,7 +2522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", - "windows-strings", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] @@ -2517,6 +2544,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" From 9cd7fcfc744cae52999c2d4a870e1c54fadd0ac6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 31 Mar 2025 16:26:13 -0400 Subject: [PATCH 037/320] 2.0.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 608974f..6966fef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1472,7 +1472,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "0.30.0" +version = "2.0.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 90c2a6a..13cc155 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "0.30.0" +version = "2.0.0" edition = "2024" description = "rabbitmqadmin v2 is a major revision of rabbitmqadmin, one of the RabbitMQ CLI tools that target the HTTP API" From 98c80d0e193aa45c1ecc78355d768f845d0e8bfb Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 31 Mar 2025 17:12:42 -0400 Subject: [PATCH 038/320] Bump dev version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6966fef..105e300 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1472,7 +1472,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.0.0" +version = "2.1.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 13cc155..10785f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.0.0" +version = "2.1.0" edition = "2024" description = "rabbitmqadmin v2 is a major revision of rabbitmqadmin, one of the RabbitMQ CLI tools that target the HTTP API" From 7f33ee1d1d808301f5bc85b7cdbff80054285620 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 1 Apr 2025 16:23:47 -0400 Subject: [PATCH 039/320] Update package description --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 10785f3..d789628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "rabbitmqadmin" version = "2.1.0" edition = "2024" -description = "rabbitmqadmin v2 is a major revision of rabbitmqadmin, one of the RabbitMQ CLI tools that target the HTTP API" +description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" license = "MIT OR Apache-2.0" [dependencies] From 7dee8a9482da67ed64700d3b9ace1f9f040dc159 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 1 Apr 2025 16:42:37 -0400 Subject: [PATCH 040/320] cargo update --- Cargo.lock | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 105e300..79e62da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.12.6" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01" +checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" dependencies = [ "aws-lc-sys", "zeroize", @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f" +checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" dependencies = [ "bindgen", "cc", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.34" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", "clap_derive", @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.34" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstream", "anstyle", @@ -990,10 +990,11 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -1690,9 +1691,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags", "errno", @@ -2039,7 +2040,7 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix 1.0.3", + "rustix 1.0.5", "windows-sys 0.59.0", ] From 644837aee7d0e8ea51b2fa2be6fc5e0f7705b859 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 3 Apr 2025 19:53:55 -0400 Subject: [PATCH 041/320] cargo update --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79e62da..aab8912 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1038,9 +1038,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d0e07885d6a754b9c7993f2625187ad694ee985d60f23355ff0e7077261502" +checksum = "6b20daca3a4ac14dbdc753c5e90fc7b490a48a9131daed3c9a9ced7b2defd37b" dependencies = [ "cc", "libc", @@ -1088,9 +1088,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mimalloc" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99585191385958383e13f6b822e6b6d8d9cf928e7d286ceb092da92b43c87bc1" +checksum = "03cb1f88093fe50061ca1195d336ffec131347c7b833db31f9ab62a2d1b7925f" dependencies = [ "libmimalloc-sys", ] @@ -1119,9 +1119,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" dependencies = [ "adler2", ] From fb5e9d607f3d3a3f5e5cf659c337e42727b6b802 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 3 Apr 2025 22:09:37 -0400 Subject: [PATCH 042/320] New command group: vhosts Right now it combines the existing commands but new commands directly related to virtual hosts can be added over time. --- CHANGELOG.md | 5 +- src/cli.rs | 1349 +++++++++++++++++++++++------------------ src/main.rs | 12 + tests/test_helpers.rs | 4 +- tests/vhosts_tests.rs | 16 + 5 files changed, 776 insertions(+), 610 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d3c4d..f5e62b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## v2.1.0 (in development) -No changes yet. +### Enhancements + + * `vhosts` is a new command group that aggregates all the existing + commands directly related to virtual hosts: `list vhosts`, `declare vhost`, `delete vhost` ## v2.0.0 (Mar 31, 2024) diff --git a/src/cli.rs b/src/cli.rs index dfa70bc..08608dc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -279,11 +279,15 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { HEALTH_CHECK_GUIDE_URL, DEPRECATED_FEATURE_GUIDE_URL )), + Command::new("vhosts") + .about("Virtual host operations") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(vhosts_subcommands(pre_flight_settings.clone())), Command::new("close") .about("Closes connections") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .subcommand_value_name("connection") .subcommands(close_subcommands(pre_flight_settings.clone())), Command::new("rebalance") .about("Rebalancing of leader replicas") @@ -394,476 +398,536 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { } fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { + let nodes_cmd = Command::new("nodes").long_about("Lists cluster members"); + let users_cmd = Command::new("users").long_about("Lists users in the internal database"); + let vhosts_cmd = Command::new("vhosts") + .long_about("Lists virtual hosts") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + VIRTUAL_HOST_GUIDE_URL + )); + + let permissions_cmd = Command::new("permissions") + .long_about("Lists user permissions") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + ACCESS_CONTROL_GUIDE_URL + )); + let connections_cmd = Command::new("connections") + .long_about("Lists client connections") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + CONNECTION_GUIDE_URL + )); + let user_connections_cmd = Command::new("user_connections") + .arg( + Arg::new("username") + .short('u') + .long("username") + .required(true) + .help("Name of the user whose connections to list"), + ) + .long_about("Lists client connections that authenticated with a specific username") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + CONNECTION_GUIDE_URL + )); + let channels_cmd = Command::new("channels") + .long_about("Lists AMQP 0-9-1 channels") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + CHANNEL_GUIDE_URL + )); + let queues_cmd = Command::new("queues") + .long_about("Lists queues and streams") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + QUEUE_GUIDE_URL + )); + let exchanges_cmd = Command::new("exchanges").long_about("Lists exchanges"); + let bindings_cmd = Command::new("bindings").long_about("Lists bindings"); + let consumers_cmd = Command::new("consumers") + .long_about("Lists consumers") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + CONSUMER_GUIDE_URL + )); + let parameters_cmd = Command::new("parameters") + .arg( + Arg::new("component") + .long("component") + .help("component (for example: federation-upstream, vhost-limits)") + .required(false), + ) + .long_about("Lists runtime parameters") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + RUNTIME_PARAMETER_GUIDE_URL + )); + let policies_cmd = Command::new("policies") + .long_about("Lists policies") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + POLICY_GUIDE_URL + )); + let operator_policies_cmd = Command::new("operator_policies") + .long_about("Lists operator policies") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + OPERATOR_POLICY_GUIDE_URL + )); + let vhost_limits_cmd = Command::new("vhost_limits") + .long_about("Lists virtual host (resource) limits") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + VIRTUAL_HOST_GUIDE_URL + )); + let user_limits_cmd = Command::new("user_limits") + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(false), + ) + .long_about("Lists per-user (resource) limits") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + USER_LIMIT_GUIDE_URL + )); + let feature_flags_cmd = Command::new("feature_flags") + .long_about("Lists feature flags and their cluster state") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + FEATURE_FLAG_GUIDE_URL + )); + let deprecated_features_cmd = Command::new("deprecated_features") + .long_about("Lists all deprecated features") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + DEPRECATED_FEATURE_GUIDE_URL + )); + let deprecated_features_in_use_cmd = Command::new("deprecated_features_in_use") + .long_about("Lists the deprecated features that are in used in the cluster") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + DEPRECATED_FEATURE_GUIDE_URL + )); [ - Command::new("nodes").long_about("Lists cluster members"), - Command::new("users").long_about("Lists users in the internal database"), - Command::new("vhosts") - .long_about("Lists virtual hosts") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - VIRTUAL_HOST_GUIDE_URL - )), - Command::new("permissions") - .long_about("Lists user permissions") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - ACCESS_CONTROL_GUIDE_URL - )), - Command::new("connections") - .long_about("Lists client connections") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - CONNECTION_GUIDE_URL - )), - Command::new("user_connections") - .arg( - Arg::new("username") - .short('u') - .long("username") - .required(true) - .help("Name of the user whose connections to list"), - ) - .long_about("Lists client connections that authenticated with a specific username") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - CONNECTION_GUIDE_URL - )), - Command::new("channels") - .long_about("Lists AMQP 0-9-1 channels") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - CHANNEL_GUIDE_URL - )), - Command::new("queues") - .long_about("Lists queues and streams") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - QUEUE_GUIDE_URL - )), - Command::new("exchanges").long_about("Lists exchanges"), - Command::new("bindings").long_about("Lists bindings"), - Command::new("consumers") - .long_about("Lists consumers") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - CONSUMER_GUIDE_URL - )), - Command::new("parameters") - .arg( - Arg::new("component") - .long("component") - .help("component (for example: federation-upstream, vhost-limits)") - .required(false), - ) - .long_about("Lists runtime parameters") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - RUNTIME_PARAMETER_GUIDE_URL - )), - Command::new("policies") - .long_about("Lists policies") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - POLICY_GUIDE_URL - )), - Command::new("operator_policies") - .long_about("Lists operator policies") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - OPERATOR_POLICY_GUIDE_URL - )), - Command::new("vhost_limits") - .long_about("Lists virtual host (resource) limits") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - VIRTUAL_HOST_GUIDE_URL - )), - Command::new("user_limits") - .arg( - Arg::new("user") - .long("user") - .help("username") - .required(false), - ) - .long_about("Lists per-user (resource) limits") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - USER_LIMIT_GUIDE_URL - )), - Command::new("feature_flags") - .long_about("Lists feature flags and their cluster state") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - FEATURE_FLAG_GUIDE_URL - )), - Command::new("deprecated_features") - .long_about("Lists all deprecated features") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - DEPRECATED_FEATURE_GUIDE_URL - )), - Command::new("deprecated_features_in_use") - .long_about("Lists the deprecated features that are in used in the cluster") - .after_long_help(color_print::cformat!( - "Doc guide: {}", - DEPRECATED_FEATURE_GUIDE_URL - )), + nodes_cmd, + users_cmd, + vhosts_cmd, + permissions_cmd, + connections_cmd, + user_connections_cmd, + channels_cmd, + queues_cmd, + exchanges_cmd, + bindings_cmd, + consumers_cmd, + parameters_cmd, + policies_cmd, + operator_policies_cmd, + vhost_limits_cmd, + user_limits_cmd, + feature_flags_cmd, + deprecated_features_cmd, + deprecated_features_in_use_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] { - [ - Command::new("user") - .about("Creates a user") - .arg( - Arg::new("name") - .long("name") - .help("username") - .required(true), - ) - .arg( - Arg::new("password_hash") - .help(color_print::cformat!("salted password hash, see {}", PASSWORD_GUIDE_URL)) - .long("password-hash") - .required(false) - .default_value(""), - ) - .arg( - Arg::new("password") - .long("password") - .help(color_print::cformat!("prefer providing a hash, see {}", PASSWORD_GUIDE_URL)) - .required(false) - .default_value(""), - ) - .arg( - Arg::new("tags") - .long("tags") - .help("a list of comma-separated tags") - .default_value(""), - ), - Command::new("vhost") - .about("Creates a virtual host") - .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) - .arg( - Arg::new("name") - .long("name") - .help("virtual host name") - .required(true), - ) - .arg( - Arg::new("default_queue_type") - .long("default-queue-type") - .required(false) - .default_value(DEFAULT_QUEUE_TYPE) - .help(color_print::cformat!("default queue type, one of: classic, quorum, stream")) - ) - .arg( - Arg::new("description") - .long("description") - .required(false) - .help("what's the purpose of this virtual host?"), - ) - .arg( - Arg::new("tracing") - .long("tracing") - .required(false) - .action(ArgAction::SetTrue) - .help("should tracing be enabled for this virtual host?"), - ), - Command::new("permissions") - .about("grants permissions to a user") - .after_long_help(color_print::cformat!("Doc guide:: {}", ACCESS_CONTROL_GUIDE_URL)) - .arg( - Arg::new("user") - .long("user") - .help("username") - .required(true), - ) - .arg( - Arg::new("configure") - .long("configure") - .help("name pattern for configuration access") - .required(true), - ) - .arg( - Arg::new("read") - .long("read") - .help("name pattern for read access") - .required(true), - ) - .arg( - Arg::new("write") - .long("write") - .help("name pattern for write access") - .required(true), - ), - Command::new("queue") - .about("Declares a queue or a stream") - .after_long_help(color_print::cformat!("Doc guide:: {}", QUEUE_GUIDE_URL)) - .arg(Arg::new("name").long("name").required(true).help("name")) - .arg( - Arg::new("type") - .long("type") - .help("queue type") - .value_parser(value_parser!(QueueType)) - .required(false) - .default_value("classic"), - ) - .arg( - Arg::new("durable") - .long("durable") - .help("should it persist after a restart") - .required(false) - .value_parser(value_parser!(bool)), - ) - .arg( - Arg::new("auto_delete") - .long("auto-delete") - .help("should it be deleted when the last consumer disconnects") - .required(false) - .value_parser(value_parser!(bool)), - ) - .arg( - Arg::new("arguments") - .long("arguments") - .help("additional exchange arguments") - .required(false) - .default_value("{}") - .value_parser(value_parser!(String)), - ), - Command::new("stream") - .about("Declares a stream") - .after_long_help(color_print::cformat!("Doc guide:: {}", STREAM_GUIDE_URL)) - .arg(Arg::new("name").long("name").required(true).help("name")) - .arg( - Arg::new("expiration") - .long("expiration") - .help("stream expiration, e.g. 12h for 12 hours, 7D for 7 days, or 1M for 1 month") - .required(true) - .value_parser(value_parser!(String)), - ) - .arg( - Arg::new("max_length_bytes") - .long("max-length-bytes") - .help("maximum stream length in bytes") - .required(false) - .value_parser(value_parser!(u64)), - ) - .arg( - Arg::new("max_segment_length_bytes") - .long("stream-max-segment-size-bytes") - .help("maximum stream segment file length in bytes") - .required(false) - .value_parser(value_parser!(u64)), - ) - .arg( - Arg::new("arguments") - .long("arguments") - .help("additional exchange arguments") - .required(false) - .default_value("{}") - .value_parser(value_parser!(String)), - ), - Command::new("exchange") - .about("Declares an exchange") - .arg( - Arg::new("name") - .long("name") - .help("exchange name") - .required(true), - ) - .arg( - Arg::new("type") - .long("type") - .help("exchange type") - .value_parser(value_parser!(ExchangeType)) - .required(false), - ) - .arg( - Arg::new("durable") - .long("durable") - .help("should it persist after a restart") - .required(false) - .value_parser(value_parser!(bool)), - ) - .arg( - Arg::new("auto_delete") - .long("auto-delete") - .help("should it be deleted when the last queue is unbound") - .required(false) - .value_parser(value_parser!(bool)), - ) - .arg( - Arg::new("arguments") - .long("arguments") - .help("additional exchange arguments") - .required(false) - .default_value("{}") - .value_parser(value_parser!(String)), - ), - Command::new("binding") - .about("Creates a binding between a source exchange and a destination (a queue or an exchange)") - .arg( - Arg::new("source") - .long("source") - .help("source exchange") - .required(true), - ) - .arg( - Arg::new("destination_type") - .long("destination-type") - .help("destination type: exchange or queue") - .required(true) - .value_parser(value_parser!(BindingDestinationType)), - ) - .arg( - Arg::new("destination") - .long("destination") - .help("destination exchange/queue name") - .required(true), - ) - .arg( - Arg::new("routing_key") - .long("routing-key") - .help("routing key") - .required(true), - ) - .arg( - Arg::new("arguments") - .long("arguments") - .help("additional arguments") - .required(false) - .default_value("{}") - .value_parser(value_parser!(String)), - ), - Command::new("parameter"). - about("Sets a runtime parameter") - .after_long_help(color_print::cformat!("Doc guide:: {}", RUNTIME_PARAMETER_GUIDE_URL)) - .arg( - Arg::new("name") - .long("name") - .help("parameter's name") - .required(true) - ).arg( + let user_cmd = Command::new("user") + .about("Creates a user") + .arg( + Arg::new("name") + .long("name") + .help("username") + .required(true), + ) + .arg( + Arg::new("password_hash") + .help(color_print::cformat!( + "salted password hash, see {}", + PASSWORD_GUIDE_URL + )) + .long("password-hash") + .required(false) + .default_value(""), + ) + .arg( + Arg::new("password") + .long("password") + .help(color_print::cformat!( + "prefer providing a hash, see {}", + PASSWORD_GUIDE_URL + )) + .required(false) + .default_value(""), + ) + .arg( + Arg::new("tags") + .long("tags") + .help("a list of comma-separated tags") + .default_value(""), + ); + let vhost_cmd = Command::new("vhost") + .about("Creates a virtual host") + .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) + .arg( + Arg::new("name") + .long("name") + .help("virtual host name") + .required(true), + ) + .arg( + Arg::new("default_queue_type") + .long("default-queue-type") + .required(false) + .default_value(DEFAULT_QUEUE_TYPE) + .help(color_print::cformat!("default queue type, one of: classic, quorum, stream")) + ) + .arg( + Arg::new("description") + .long("description") + .required(false) + .help("what's the purpose of this virtual host?"), + ) + .arg( + Arg::new("tracing") + .long("tracing") + .required(false) + .action(ArgAction::SetTrue) + .help("should tracing be enabled for this virtual host?"), + ); + let permissions_cmd = Command::new("permissions") + .about("grants permissions to a user") + .after_long_help(color_print::cformat!( + "Doc guide:: {}", + ACCESS_CONTROL_GUIDE_URL + )) + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg( + Arg::new("configure") + .long("configure") + .help("name pattern for configuration access") + .required(true), + ) + .arg( + Arg::new("read") + .long("read") + .help("name pattern for read access") + .required(true), + ) + .arg( + Arg::new("write") + .long("write") + .help("name pattern for write access") + .required(true), + ); + let queue_cmd = Command::new("queue") + .about("Declares a queue or a stream") + .after_long_help(color_print::cformat!( + "Doc guide:: {}", + QUEUE_GUIDE_URL + )) + .arg(Arg::new("name").long("name").required(true).help("name")) + .arg( + Arg::new("type") + .long("type") + .help("queue type") + .value_parser(value_parser!(QueueType)) + .required(false) + .default_value("classic"), + ) + .arg( + Arg::new("durable") + .long("durable") + .help("should it persist after a restart") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("auto_delete") + .long("auto-delete") + .help("should it be deleted when the last consumer disconnects") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional exchange arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let stream_cmd = Command::new("stream") + .about("Declares a stream") + .after_long_help(color_print::cformat!( + "Doc guide:: {}", + STREAM_GUIDE_URL + )) + .arg(Arg::new("name").long("name").required(true).help("name")) + .arg( + Arg::new("expiration") + .long("expiration") + .help("stream expiration, e.g. 12h for 12 hours, 7D for 7 days, or 1M for 1 month") + .required(true) + .value_parser(value_parser!(String)), + ) + .arg( + Arg::new("max_length_bytes") + .long("max-length-bytes") + .help("maximum stream length in bytes") + .required(false) + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("max_segment_length_bytes") + .long("stream-max-segment-size-bytes") + .help("maximum stream segment file length in bytes") + .required(false) + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional exchange arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let exchange_cmd = Command::new("exchange") + .about("Declares an exchange") + .arg( + Arg::new("name") + .long("name") + .help("exchange name") + .required(true), + ) + .arg( + Arg::new("type") + .long("type") + .help("exchange type") + .value_parser(value_parser!(ExchangeType)) + .required(false), + ) + .arg( + Arg::new("durable") + .long("durable") + .help("should it persist after a restart") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("auto_delete") + .long("auto-delete") + .help("should it be deleted when the last queue is unbound") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional exchange arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let binding_cmd = Command::new("binding") + .about("Creates a binding between a source exchange and a destination (a queue or an exchange)") + .arg( + Arg::new("source") + .long("source") + .help("source exchange") + .required(true), + ) + .arg( + Arg::new("destination_type") + .long("destination-type") + .help("destination type: exchange or queue") + .required(true) + .value_parser(value_parser!(BindingDestinationType)), + ) + .arg( + Arg::new("destination") + .long("destination") + .help("destination exchange/queue name") + .required(true), + ) + .arg( + Arg::new("routing_key") + .long("routing-key") + .help("routing key") + .required(true), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let parameter_cmd = Command::new("parameter") + .about("Sets a runtime parameter") + .after_long_help(color_print::cformat!( + "Doc guide:: {}", + RUNTIME_PARAMETER_GUIDE_URL + )) + .arg( + Arg::new("name") + .long("name") + .help("parameter's name") + .required(true), + ) + .arg( Arg::new("component") .long("component") .help("component (eg. federation)") - .required(true)) - .arg( - Arg::new("value") - .long("value") - .help("parameter's value") - .required(true)), - Command::new("policy") - .about("Creates or updates a policy") - .after_long_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) - .arg( - Arg::new("name") - .long("name") - .help("policy name") - .required(true), - ) - .arg( - Arg::new("pattern") - .long("pattern") - .help("the pattern that is used to match entity (queue, stream, exchange) names") - .required(true), - ) - .arg( - Arg::new("apply_to") - .long("apply-to") - .alias("applies-to") - .help("entities to apply to (queues, classic_queues, quorum_queues, streams, exchanges, all)") - .value_parser(value_parser!(PolicyTarget)) - .required(true), - ) - .arg( - Arg::new("priority") - .long("priority") - .help("policy priority (only the policy with the highest priority is effective)") - .required(false) - .default_value("0"), - ) - .arg( - Arg::new("definition") - .long("definition") - .help("policy definition") - .required(true), - ), - Command::new("operator_policy") - .about("Creates or updates an operator policy") - .after_long_help(color_print::cformat!("Doc guide:: {}", OPERATOR_POLICY_GUIDE_URL)) - .arg( - Arg::new("name") - .long("name") - .help("operator policy name") - .required(true), - ) - .arg( - Arg::new("pattern") - .long("pattern") - .help("queue/exchange name pattern") - .required(true), - ) - .arg( - Arg::new("apply_to") - .long("apply-to") - .alias("applies-to") - .help("entities to apply to (queues, classic_queues, quorum_queues, streams, exchanges, all)") - .value_parser(value_parser!(PolicyTarget)) - .required(true), - ) - .arg( - Arg::new("priority") - .long("priority") - .help("policy priority (only the policy with the highest priority is effective)") - .required(false) - .default_value("0"), - ) - .arg( - Arg::new("definition") - .long("definition") - .help("policy definition") - .required(true), - ), - Command::new("vhost_limit") - .about("Set a vhost limit") - .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_LIMIT_GUIDE_URL)) - .arg( - Arg::new("name") - .long("name") - .help("limit name (eg. max-connections, max-queues)") - .required(true), - ) - .arg( - Arg::new("value") - .long("value") - .help("limit value") - .required(true), - ), - Command::new("user_limit") - .about("Set a user limit") - .after_long_help(color_print::cformat!("Doc guide:: {}", USER_LIMIT_GUIDE_URL)) - .arg( - Arg::new("user") - .long("user") - .help("username") - .required(true), - ) - .arg( - Arg::new("name") - .long("name") - .help("limit name (eg. max-connections, max-queues)") - .required(true), - ) - .arg( - Arg::new("value") - .long("value") - .help("limit value") - .required(true), - ) - ].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .required(true), + ) + .arg( + Arg::new("value") + .long("value") + .help("parameter's value") + .required(true), + ); + let policy_cmd = Command::new("policy") + .about("Creates or updates a policy") + .after_long_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) + .arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ) + .arg( + Arg::new("pattern") + .long("pattern") + .help("the pattern that is used to match entity (queue, stream, exchange) names") + .required(true), + ) + .arg( + Arg::new("apply_to") + .long("apply-to") + .alias("applies-to") + .help("entities to apply to (queues, classic_queues, quorum_queues, streams, exchanges, all)") + .value_parser(value_parser!(PolicyTarget)) + .required(true), + ) + .arg( + Arg::new("priority") + .long("priority") + .help("policy priority (only the policy with the highest priority is effective)") + .required(false) + .default_value("0"), + ) + .arg( + Arg::new("definition") + .long("definition") + .help("policy definition") + .required(true), + ); + let operator_policy_cmd = Command::new("operator_policy") + .about("Creates or updates an operator policy") + .after_long_help(color_print::cformat!("Doc guide:: {}", OPERATOR_POLICY_GUIDE_URL)) + .arg( + Arg::new("name") + .long("name") + .help("operator policy name") + .required(true), + ) + .arg( + Arg::new("pattern") + .long("pattern") + .help("queue/exchange name pattern") + .required(true), + ) + .arg( + Arg::new("apply_to") + .long("apply-to") + .alias("applies-to") + .help("entities to apply to (queues, classic_queues, quorum_queues, streams, exchanges, all)") + .value_parser(value_parser!(PolicyTarget)) + .required(true), + ) + .arg( + Arg::new("priority") + .long("priority") + .help("policy priority (only the policy with the highest priority is effective)") + .required(false) + .default_value("0"), + ) + .arg( + Arg::new("definition") + .long("definition") + .help("policy definition") + .required(true), + ); + let vhost_limit_cmd = Command::new("vhost_limit") + .about("Set a vhost limit") + .after_long_help(color_print::cformat!( + "Doc guide:: {}", + VIRTUAL_HOST_LIMIT_GUIDE_URL + )) + .arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ) + .arg( + Arg::new("value") + .long("value") + .help("limit value") + .required(true), + ); + let user_limit_cmd = Command::new("user_limit") + .about("Set a user limit") + .after_long_help(color_print::cformat!( + "Doc guide:: {}", + USER_LIMIT_GUIDE_URL + )) + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ) + .arg( + Arg::new("value") + .long("value") + .help("limit value") + .required(true), + ); + [ + user_cmd, + vhost_cmd, + permissions_cmd, + queue_cmd, + stream_cmd, + exchange_cmd, + binding_cmd, + parameter_cmd, + policy_cmd, + operator_policy_cmd, + vhost_limit_cmd, + user_limit_cmd, + ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { @@ -916,154 +980,167 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .help("do not consider 404 Not Found API responses to be errors") .required(false); - [ - Command::new("user") - .about("Deletes a user") - .arg( - Arg::new("name") - .long("name") - .help("username") - .required(true), - ) - .arg(idempotently_arg.clone()), - Command::new("vhost") - .about("Deletes a virtual host") - .arg( - Arg::new("name") - .long("name") - .help("virtual host") - .required(true), - ) - .arg(idempotently_arg.clone()), - Command::new("permissions") - .about("Revokes user permissions to a given vhost") - .arg( - Arg::new("user") - .long("user") - .help("username") - .required(true), - ) - .arg(idempotently_arg.clone()), - Command::new("queue") - .about("Deletes a queue") - .arg( - Arg::new("name") - .long("name") - .help("queue name") - .required(true), - ) - .arg(idempotently_arg.clone()), - Command::new("stream") - .about("Deletes a stream") - .arg( - Arg::new("name") - .long("name") - .help("stream name") - .required(true), - ) - .arg(idempotently_arg.clone()), - Command::new("exchange") - .about("Deletes an exchange") - .arg( - Arg::new("name") - .long("name") - .help("exchange name") - .required(true), - ) - .arg(idempotently_arg.clone()), - Command::new("binding") - .about("Deletes a binding") - .arg( - Arg::new("source") - .long("source") - .help("source exchange") - .required(true), - ) - .arg( - Arg::new("destination_type") - .long("destination-type") - .help("destination type: exchange or queue") - .required(true), - ) - .arg( - Arg::new("destination") - .long("destination") - .help("destination exchange/queue name") - .required(true), - ) - .arg( - Arg::new("routing_key") - .long("routing-key") - .help("routing key") - .required(true), - ) - .arg( - Arg::new("arguments") - .long("arguments") - .help("additional arguments") - .required(false) - .default_value("{}") - .value_parser(value_parser!(String)), - ), - Command::new("parameter") - .about("Clears a runtime parameter") - .arg( - Arg::new("name") - .long("name") - .help("parameter's name") - .required(true), - ) - .arg( - Arg::new("component") - .long("component") - .help("component (eg. federation-upstream)") - .required(true), - ), - Command::new("policy").about("Deletes a policy").arg( + let user_cmd = Command::new("user") + .about("Deletes a user") + .arg( Arg::new("name") .long("name") - .help("policy name") + .help("username") .required(true), - ), - Command::new("operator_policy") - .about("Deletes an operator policy") - .arg( - Arg::new("name") - .long("name") - .help("operator policy name") - .required(true), - ), - Command::new("vhost_limit") - .about("delete a vhost limit") - .arg( - Arg::new("name") - .long("name") - .help("limit name (eg. max-connections, max-queues)") - .required(true), - ), - Command::new("user_limit") - .about("Clears a user limit") - .arg( - Arg::new("user") - .long("user") - .help("username") - .required(true), - ) - .arg( - Arg::new("name") - .long("name") - .help("limit name (eg. max-connections, max-queues)") - .required(true), - ), - Command::new("shovel") - .about("Delete a shovel") - .arg(idempotently_arg.clone()) - .arg( - Arg::new("name") - .long("name") - .help("shovel name") - .required(true), - ), + ) + .arg(idempotently_arg.clone()); + let vhost_cmd = Command::new("vhost") + .about("Deletes a virtual host") + .arg( + Arg::new("name") + .long("name") + .help("virtual host") + .required(true), + ) + .arg(idempotently_arg.clone()); + let permissions_cmd = Command::new("permissions") + .about("Revokes user permissions to a given vhost") + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg(idempotently_arg.clone()); + let queue_cmd = Command::new("queue") + .about("Deletes a queue") + .arg( + Arg::new("name") + .long("name") + .help("queue name") + .required(true), + ) + .arg(idempotently_arg.clone()); + let stream_cmd = Command::new("stream") + .about("Deletes a stream") + .arg( + Arg::new("name") + .long("name") + .help("stream name") + .required(true), + ) + .arg(idempotently_arg.clone()); + let exchange_cmd = Command::new("exchange") + .about("Deletes an exchange") + .arg( + Arg::new("name") + .long("name") + .help("exchange name") + .required(true), + ) + .arg(idempotently_arg.clone()); + let binding_cmd = Command::new("binding") + .about("Deletes a binding") + .arg( + Arg::new("source") + .long("source") + .help("source exchange") + .required(true), + ) + .arg( + Arg::new("destination_type") + .long("destination-type") + .help("destination type: exchange or queue") + .required(true), + ) + .arg( + Arg::new("destination") + .long("destination") + .help("destination exchange/queue name") + .required(true), + ) + .arg( + Arg::new("routing_key") + .long("routing-key") + .help("routing key") + .required(true), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let parameter_cmd = Command::new("parameter") + .about("Clears a runtime parameter") + .arg( + Arg::new("name") + .long("name") + .help("parameter's name") + .required(true), + ) + .arg( + Arg::new("component") + .long("component") + .help("component (eg. federation-upstream)") + .required(true), + ); + let policy_cmd = Command::new("policy").about("Deletes a policy").arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ); + let operator_policy_cmd = Command::new("operator_policy") + .about("Deletes an operator policy") + .arg( + Arg::new("name") + .long("name") + .help("operator policy name") + .required(true), + ); + let vhost_limit_cmd = Command::new("vhost_limit") + .about("delete a vhost limit") + .arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ); + let user_limit_cmd = Command::new("user_limit") + .about("Clears a user limit") + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ); + let shovel_cmd = Command::new("shovel") + .about("Delete a shovel") + .arg(idempotently_arg.clone()) + .arg( + Arg::new("name") + .long("name") + .help("shovel name") + .required(true), + ); + [ + user_cmd, + vhost_cmd, + permissions_cmd, + queue_cmd, + stream_cmd, + exchange_cmd, + binding_cmd, + parameter_cmd, + policy_cmd, + operator_policy_cmd, + vhost_limit_cmd, + user_limit_cmd, + shovel_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } @@ -1509,6 +1586,64 @@ pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) - .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let list_cmd = Command::new("list") + .long_about("Lists virtual hosts") + .after_long_help(color_print::cformat!( + "Doc guide: {}", + VIRTUAL_HOST_GUIDE_URL + )); + + let declare_cmd = Command::new("declare") + .about("Creates a virtual host") + .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) + .arg( + Arg::new("name") + .long("name") + .help("virtual host name") + .required(true), + ) + .arg( + Arg::new("default_queue_type") + .long("default-queue-type") + .required(false) + .default_value(DEFAULT_QUEUE_TYPE) + .help(color_print::cformat!("default queue type, one of: classic, quorum, stream")) + ) + .arg( + Arg::new("description") + .long("description") + .required(false) + .help("what's the purpose of this virtual host?"), + ) + .arg( + Arg::new("tracing") + .long("tracing") + .required(false) + .action(ArgAction::SetTrue) + .help("should tracing be enabled for this virtual host?"), + ); + + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let delete_cmd = Command::new("delete") + .about("Deletes a virtual host") + .arg( + Arg::new("name") + .long("name") + .help("virtual host") + .required(true), + ) + .arg(idempotently_arg.clone()); + + [list_cmd, declare_cmd, delete_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("message") .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) diff --git a/src/main.rs b/src/main.rs index 1c3a02e..b691c3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -476,6 +476,18 @@ fn dispatch_common_subcommand( let result = commands::delete_parameter(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("vhosts", "declare") => { + let result = commands::declare_vhost(client, second_level_args); + res_handler.no_output_on_success(result); + } + ("vhosts", "list") => { + let result = commands::list_vhosts(client); + res_handler.tabular_result(result) + } + ("vhosts", "delete") => { + let result = commands::delete_vhost(client, second_level_args); + res_handler.delete_operation_result(result); + } ("purge", "queue") => { let result = commands::purge_queue(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/test_helpers.rs b/tests/test_helpers.rs index bd192ae..a94a199 100644 --- a/tests/test_helpers.rs +++ b/tests/test_helpers.rs @@ -82,14 +82,14 @@ where pub fn create_vhost(vhost: &str) -> CommandRunResult { let mut cmd = Command::cargo_bin("rabbitmqadmin")?; - cmd.args(["declare", "vhost", "--name", vhost]); + cmd.args(["vhosts", "declare", "--name", vhost]); cmd.assert().success(); Ok(()) } pub fn delete_vhost(vhost: &str) -> CommandRunResult { let mut cmd = Command::cargo_bin("rabbitmqadmin")?; - cmd.args(["delete", "vhost", "--name", vhost, "--idempotently"]); + cmd.args(["vhosts", "delete", "--name", vhost, "--idempotently"]); cmd.assert().success(); Ok(()) } diff --git a/tests/vhosts_tests.rs b/tests/vhosts_tests.rs index dc5777a..0b189e1 100644 --- a/tests/vhosts_tests.rs +++ b/tests/vhosts_tests.rs @@ -31,3 +31,19 @@ fn list_vhosts() -> Result<(), Box> { Ok(()) } + +#[test] +fn vhosts_list() -> Result<(), Box> { + let vh = "list_vhosts.2"; + delete_vhost(vh).expect("failed to delete a virtual host"); + + run_succeeds(["vhost", "declare", "--name", vh]); + run_succeeds(["vhosts", "list"]) + .stdout(predicate::str::contains("/").and(predicate::str::contains(vh))); + + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["vhosts", "list"]) + .stdout(predicate::str::contains("/").and(predicate::str::contains(vh).not())); + + Ok(()) +} From 94fce5b5e63aaf12eb202c2cdb5aa5ff056906a0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 4 Apr 2025 00:07:44 -0400 Subject: [PATCH 043/320] Document definition transformations --- CHANGELOG.md | 2 +- README.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5e62b1..8657179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ To enable each feature, set the following environment variables to * `RABBITMQADMIN_INFER_SUBCOMMANDS` * `RABBITMQADMIN_INFER_LONG_OPTIONS` -This feature is only mean to be used interactively. For non-interactive +This feature is only meant to be used interactively. For non-interactive use, it can be potentially too dangerous to allow. #### Intentionally Restricted Environment Variable Support diff --git a/README.md b/README.md index 83614c1..c3c404d 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ of tagging on `--help` at the end of command name: ```shell rabbitmqadmin declare help queue # => declares a queue or a stream -# => +# => # => Usage: rabbitmqadmin declare queue [OPTIONS] --name ``` @@ -209,8 +209,8 @@ as a result: ``` key Product name RabbitMQ - Product version 4.0.7 - RabbitMQ version 4.0.7 + Product version 4.0.8 + RabbitMQ version 4.0.8 Erlang version 26.2.5.10 Erlang details Erlang/OTP 26 [erts-14.2.5.9] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] ``` @@ -475,6 +475,88 @@ rabbitmqadmin deprecated_features list rabbitmqadmin list deprecated_features ``` +### Export Definitions + +To export [definitions](https://www.rabbitmq.com/docs/definitions) to standard output, use `definitions export --stdout`: + +```shell +rabbitmqadmin definitions export --stdout +``` + +To export definitions to a file, use `definitions export --file /path/to/definitions.file.json`: + +```shell +rabbitmqadmin definitions export --file /path/to/definitions.file.json +``` + +### Export and Transform Definitions + +`definitions export` can transform the exported JSON definitions file it gets from the +target node. This is done by applying one or more transformations to the exported +JSON file. + +This can be useful to remove classic queue mirroring-related keys (such as `ha-mode`) from a definitions +set originating from a 3.13.x node, or to obfuscate usernames and passwords, or exclude certain definitions file +sections entirely. + +To specify what transformations should be applied, use the `--transformations` options, +which takes a comma-separated list of supported operation names. + +The following table explains what transformations are available and what they do: + +| Transformation name | Description | +|--------------------------------|--------------------------------------------------------------| +| `strip_cmq_keys_from_policies` | Deletes all classic queue mirroring-related keys (such as `ha-mode`) from all exported policies.

Must be followed by `drop_empty_policies` to strip off the policies whose definition has become empty (and thus invalid at import time) after the removal of all classic queue mirroring-related keys | +| `drop_empty_policies` | Should be used after `strip_cmq_keys_from_policies` to strip off the policies whose definition has become empty (and thus invalid at import time) after the removal of all classic queue mirroring-related keys | +| `obfuscate_usernames` | Replaces usernames and passwords with dummy values.

For usernames the values used are: `obfuscated-username-1`, `obfuscated-username-2`, and so on.

For passwords the values generated are: `password-1`, `password-2`, and so forth.

This transformations updates both the users and the permissions sections, consistently | +| `exclude_users` | Removes all users from the result. Commonly used together with `exclude_permissions` | +| `exclude_permissions` | Removes all permissions from the result. Commonly used together with `exclude_users` | +| `exclude_runtime_parameters` | Removes all runtime parameters (including federation upstreams, shovels, WSR and SDS settings in Tanzu RabbitMQ) from the result | +| `exclude_policies` | Removes all policies from the result | +| `no_op` | Does nothing. Can be used as the default in dynamically computed transformation lists, e.g. in scripts | + +#### Examples + +The following command applies two transformations named `strip_cmq_keys_from_policies` and `drop_empty_policies` +that will strip all classic queue mirroring-related policy keys that RabbitMQ 3.13 nodes supported, +then removes the policies that did not have any keys left (ended up having an empty definition): + +```shell +# strips classic mirrored queue-related policy keys from the exported definitions, then prints them +# to the standard output stream +rabbitmqadmin definitions export --stdout --transformations strip_cmq_keys_from_policies,drop_empty_policies +``` + +The following example exports definitions without users and permissions: + +```shell +# removes users and user permissions from the exported definitions, then prints them +# to the standard output stream +rabbitmqadmin definitions export --stdout --transformations exclude_users,exclude_permissions +``` + +To export definitions with usernames replaced by dummy values (usernames: `obfuscated-username-1`, `obfuscated-username-2`, and so on; +passwords: `password-1`, `password-2`, and so forth), use the `obfuscate_usernames` transformation: + +```shell +rabbitmqadmin definitions export --file /path/to/definitions.file.json --transformations obfuscate_usernames +``` + +### Import Definition + +To import definitions from the standard input, use `definitions import --stdin`: + +```shell +cat /path/to/definitions.file.json | rabbitmqadmin definitions import --stdin +``` + +To import definitions from a file, use `definitions import --file /path/to/definitions.file.json`: + +```shell +rabbitmqadmin definitions import --file /path/to/definitions.file.json +``` + + ## Subcommand and Long Option Inference This feature is available only in the `main` branch @@ -495,14 +577,14 @@ To enable each feature, set the following environment variables to * `RABBITMQADMIN_INFER_SUBCOMMANDS` * `RABBITMQADMIN_INFER_LONG_OPTIONS` -This feature is only mean to be used interactively. For non-interactive +This feature is only meant to be used interactively. For non-interactive use, it can be potentially too dangerous to allow. ## Configuration Files `rabbitmqadmin` v2 supports [TOML](https://toml.io/en/)-based configuration files -stores groups of HTTP API connection settings under aliases ("node names" in original `rabbitmqadmin` speak). +stores groups of HTTP API connection settings under aliases ("node names" in original `rabbitmqadmin` speak). Here is an example `rabbitmqadmin` v2 configuration file: @@ -625,7 +707,7 @@ rabbitmqadmin-v1 --vhost "vh-2" declare queue name="qq.1" type="quorum" durable= ```shell # Note: --auto-delete -rabbitmqadmin --vhost "vh-2" declare queue --name "qq.1" --type "quorum" --durable true --auto-delete false +rabbitmqadmin --vhost "vh-2" declare queue --name "qq.1" --type "quorum" --durable true --auto-delete false ``` ### Global Arguments Come First From f0dc9129f5379b5cacf8666d9e4f04ad03f19610 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 4 Apr 2025 01:11:49 -0400 Subject: [PATCH 044/320] Document shovel operations --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index c3c404d..0f191c1 100644 --- a/README.md +++ b/README.md @@ -556,6 +556,57 @@ To import definitions from a file, use `definitions import --file /path/to/defin rabbitmqadmin definitions import --file /path/to/definitions.file.json ``` +### Declare an AMQP 0-9-1 Shovel + +To declare a [dynamic shovel](https://www.rabbitmq.com/docs/shovel-dynamic) that uses AMQP 0-9-1 for both source and desitnation, use +`shovel declare_amqp091`: + +```shell +rabbitmqadmin shovel declare_amqp091 --name my-amqp091-shovel \ + --source-uri amqp://username:s3KrE7@source.hostname:5672 \ + --destination-uri amqp://username:s3KrE7@source.hostname:5672 \ + --ack-mode "on-confirm" \ + --source-queue "src.queue" \ + --destination-queue "dest.queue" \ + --predeclared-source false \ + --predeclared-destination false +``` + +### Declare an AMQP 1.0 Shovel + +To declare a [dynamic shovel](https://www.rabbitmq.com/docs/shovel-dynamic) that uses AMQP 1.0 for both source and desitnation, use +`shovel declare_amqp10`. + +Note that + +1. With AMQP 1.0 shovels, credentials in the URI are mandatory (there are no defaults) +2. With AMQP 1.0 shovels, the topology must be pre-declared (an equivalent of `--predeclared-source true` and `--predeclared-destination true` for AMQP 0-9-1 shovels) +2. AMQP 1.0 shovels should use [AMQP 1.0 addresses v2](https://www.rabbitmq.com/docs/amqp#addresses) + +```shell +rabbitmqadmin shovel declare_amqp10 --name my-amqp1.0-shovel \ + --source-uri "amqp://username:s3KrE7@source.hostname:5672?hostname=vhost:src-vhost" \ + --destination-uri "amqp://username:s3KrE7@source.hostname:5672?hostname=vhost:dest-vhost" \ + --ack-mode "on-confirm" \ + --source-address "/queues/src.queue" \ + --destination-address "/queues/dest.queue" +``` + +### List Shovels + +To list shovels across all virtual hosts, use `shovel list_all`: + +```shell +rabbitmqadmin shovel list_all +``` + +### Delete a Shovel + +To delete a shovel, use `shovel delete --name`: + +```shell +rabbitmqadmin shovel delete --name my-amqp091-shovel +``` ## Subcommand and Long Option Inference From f3b3a49e6a3eb6b05e873f7e5c75f1bb283a4893 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 4 Apr 2025 01:15:04 -0400 Subject: [PATCH 045/320] Fix a test --- tests/vhosts_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/vhosts_tests.rs b/tests/vhosts_tests.rs index 0b189e1..b4a947e 100644 --- a/tests/vhosts_tests.rs +++ b/tests/vhosts_tests.rs @@ -37,7 +37,7 @@ fn vhosts_list() -> Result<(), Box> { let vh = "list_vhosts.2"; delete_vhost(vh).expect("failed to delete a virtual host"); - run_succeeds(["vhost", "declare", "--name", vh]); + run_succeeds(["vhosts", "declare", "--name", vh]); run_succeeds(["vhosts", "list"]) .stdout(predicate::str::contains("/").and(predicate::str::contains(vh))); From 45ebb65037a35d660c4e063b57f9e893213e0c0b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 5 Apr 2025 18:46:55 -0400 Subject: [PATCH 046/320] More tests for the vhosts command group --- tests/vhosts_tests.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/tests/vhosts_tests.rs b/tests/vhosts_tests.rs index b4a947e..1d7912b 100644 --- a/tests/vhosts_tests.rs +++ b/tests/vhosts_tests.rs @@ -17,7 +17,7 @@ mod test_helpers; use crate::test_helpers::*; #[test] -fn list_vhosts() -> Result<(), Box> { +fn test_list_vhosts() -> Result<(), Box> { let vh = "list_vhosts.1"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -33,7 +33,7 @@ fn list_vhosts() -> Result<(), Box> { } #[test] -fn vhosts_list() -> Result<(), Box> { +fn test_vhosts_list() -> Result<(), Box> { let vh = "list_vhosts.2"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -47,3 +47,39 @@ fn vhosts_list() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_vhosts_create() -> Result<(), Box> { + let vh = "vhosts.create.1"; + delete_vhost(vh).expect("failed to delete a virtual host"); + + run_succeeds([ + "vhosts", + "declare", + "--name", + vh, + "--default-queue-type", + "quorum", + "--description", + "just a test vhost", + "--tracing", + ]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_vhosts_delete() -> Result<(), Box> { + let vh = "vhosts.delete.1"; + run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); + + run_succeeds(["vhosts", "declare", "--name", vh]); + + run_succeeds(["vhosts", "delete", "--name", vh]); + + run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); + + Ok(()) +} From 30eca5626cd983b5c970420e3c322b1bb395958b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 6 Apr 2025 01:50:10 -0400 Subject: [PATCH 047/320] Drop a feature we ended up not using --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d789628..589d896 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" license = "MIT OR Apache-2.0" [dependencies] -clap = { version = "4.5", features = ["derive", "help", "color", "cargo", "env"] } +clap = { version = "4", features = ["help", "color", "cargo", "env"] } url = "2" sysexits = "0.9" reqwest = { version = "0.12.12", features = [ From 3d200cb75c42515f0a9967c418b1b86d978c9f1d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 6 Apr 2025 01:50:34 -0400 Subject: [PATCH 048/320] cargo update --- Cargo.lock | 49 ++++++++++++++++++------------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aab8912..5ce5081 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.17" +version = "1.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" dependencies = [ "jobserver", "libc", @@ -283,7 +283,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", - "clap_derive", ] [[package]] @@ -298,18 +297,6 @@ dependencies = [ "strsim", ] -[[package]] -name = "clap_derive" -version = "4.5.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "clap_lex" version = "0.7.4" @@ -405,9 +392,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "deranged" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", @@ -486,9 +473,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -953,9 +940,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -1202,9 +1189,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags", "cfg-if", @@ -1234,9 +1221,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", @@ -1341,9 +1328,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", "syn", @@ -1918,9 +1905,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" @@ -2148,9 +2135,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.1" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", From 214283a7fc2714836ae1e50ac4b9d070fc26bbbc Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Apr 2025 01:19:42 -0400 Subject: [PATCH 049/320] Document federation command group --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/README.md b/README.md index 0f191c1..0d7871d 100644 --- a/README.md +++ b/README.md @@ -608,6 +608,80 @@ To delete a shovel, use `shovel delete --name`: rabbitmqadmin shovel delete --name my-amqp091-shovel ``` +### List Federation Upstreams + +To list [federation upstreams](https://www.rabbitmq.com/docs/federation) across all virtual hosts, use `federation list_all_upstreams`: + +```shell +rabbitmqadmin federation list_all_upstreams +``` + +### Create a Federation Upstream for Exchange Federation + +To create a [federation upstream](https://www.rabbitmq.com/docs/federated-exchanges), use `federation declare_upstream_for_exchanges` + +```shell +rabbitmqadmin --vhost "local-vhost" federation declare_upstream_for_exchanges --name "pollux" \ + --uri "amqp://pollux.eng.megacorp.local:5672/remote-vhost" \ + --ack-mode 'on-publish' \ + --prefetch-count 2000 \ + --exchange-name "overridden.name" \ + --queue-type quorum \ + --bind-using-nowait true +``` + +### Create a Federation Upstream for Queue Federation + +To create a [federation upstream](https://www.rabbitmq.com/docs/federated-queues), use `declare_upstream_for_queues`. +This command provides a reduced set of options, only those that are relevant +specifically to queue federation. + +```shell +rabbitmqadmin --vhost "local-vhost" federation declare_upstream_for_queues --name "clusters.sirius" \ + --uri "amqp://sirius.eng.megacorp.local:5672/remote-vhost" \ + --ack-mode 'on-publish' \ + --prefetch-count 2000 \ + --queue-name "overridden.name" \ + --consumer-tag "overriden.ctag" +``` + +### Create a Universal Federation Upstream + +To create a [federation upstream](https://www.rabbitmq.com/docs/federation) that will be (or can be) +used for federating both queues and exchanges, use `declare_upstream`. It combines +[all the federation options](https://www.rabbitmq.com/docs/federation-reference), that is, +the options of both `declare_upstream_for_queues` and `declare_upstream_for_exchanges`. + +```shell +rabbitmqadmin --vhost "local-vhost" federation declare_upstream --name "pollux" \ + --uri "amqp://pollux.eng.megacorp.local:5672/remove-vhost" \ + --ack-mode 'on-publish' \ + --prefetch-count 2000 \ + --queue-name "overridden.name" \ + --consumer-tag "overriden.ctag" \ + --exchange-name "overridden.name" \ + --queue-type quorum \ + --bind-using-nowait true +``` + +### Delete a Federation Upstream + +To delete a [federation upstream](https://www.rabbitmq.com/docs/federation), use 'federation delete_upstream', +which takes a virtual host and an upstream name: + +```shell +rabbitmqadmin --vhost "local-vhost" federation delete_upstream --name "upstream.to.delete" +``` + +### List Federation Links + +To list all [federation links](https://www.rabbitmq.com/docs/federation) across all virtual hosts, use `federation list_all_links`: + +```shell +rabbitmqadmin federation list_all_links +``` + + ## Subcommand and Long Option Inference This feature is available only in the `main` branch From cb1fc06b14831020de0d74050f5e00689eb89f3f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 12 Apr 2025 21:05:34 -0400 Subject: [PATCH 050/320] cargo update --- Cargo.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ce5081..5628e0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,9 +194,9 @@ checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.18" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -278,18 +278,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" dependencies = [ "anstream", "anstyle", @@ -1025,9 +1025,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b20daca3a4ac14dbdc753c5e90fc7b490a48a9131daed3c9a9ced7b2defd37b" +checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" dependencies = [ "cc", "libc", @@ -1051,9 +1051,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -1075,9 +1075,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mimalloc" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cb1f88093fe50061ca1195d336ffec131347c7b833db31f9ab62a2d1b7925f" +checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" dependencies = [ "libmimalloc-sys", ] @@ -1106,9 +1106,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1685,15 +1685,15 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.3", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "aws-lc-rs", "log", @@ -2755,9 +2755,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] From 63b78ab9eb3aaa067a447bdd85a7bef77129cea2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 12 Apr 2025 21:50:35 -0400 Subject: [PATCH 051/320] Group mutually exclusive tests, run them sequantially --- .config/nextest.toml | 53 ++++++++++++++++++++++++++++++++++++++++++++ tests/users_tests.rs | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .config/nextest.toml diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000..1f4eaee --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,53 @@ +[profile.default] +default-filter = "all()" + +retries = 2 +test-threads = 8 + +filter = 'package(rabbitmqadmin)' +# an overridable default +priority = 0 +test-group = 'parallel_safe' + +status-level = "retry" +final-status-level = "pass" +failure-output = "immediate" + +[test-groups] +parallel_safe = { max-threads = "num-cpus" } +sequential = { max-threads = 1 } + +[[profile.default.overrides]] +filter = 'test(list)' +priority = 60 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'test(test_policies)' +priority = 55 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'test(test_memory_breakdown)' +priority = 50 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'test(definitions_export)' +priority = 40 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'test(definitions_export)' +priority = 30 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'test(test_shovel)' +priority = 20 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'test(deprecated_features)' +priority = 10 +test-group = 'sequential' \ No newline at end of file diff --git a/tests/users_tests.rs b/tests/users_tests.rs index 5941081..4b4f101 100644 --- a/tests/users_tests.rs +++ b/tests/users_tests.rs @@ -53,7 +53,7 @@ fn test_list_users_with_table_styles() -> Result<(), Box> run_succeeds(["--table-style", "markdown", "list", "users"]) .stdout(predicate::str::contains(username)); - run_succeeds(["delete", "user", "--name", username]); + run_succeeds(["delete", "user", "--name", username, "--idempotently"]); run_succeeds(["--table-style", "borderless", "list", "users"]) .stdout(predicate::str::contains(username).not()); From 3ccd1775021ee307445931b17840d270b295dd26 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 12 Apr 2025 22:07:46 -0400 Subject: [PATCH 052/320] Correct a few sequential test filters --- .config/nextest.toml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 1f4eaee..50479e4 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -2,7 +2,7 @@ default-filter = "all()" retries = 2 -test-threads = 8 +test-threads = "num-cpus" filter = 'package(rabbitmqadmin)' # an overridable default @@ -14,7 +14,7 @@ final-status-level = "pass" failure-output = "immediate" [test-groups] -parallel_safe = { max-threads = "num-cpus" } +parallel_safe = { max-threads = 8 } sequential = { max-threads = 1 } [[profile.default.overrides]] @@ -22,6 +22,11 @@ filter = 'test(list)' priority = 60 test-group = 'sequential' +[[profile.default.overrides]] +filter = 'test(test_federation)' +priority = 56 +test-group = 'sequential' + [[profile.default.overrides]] filter = 'test(test_policies)' priority = 55 @@ -33,12 +38,12 @@ priority = 50 test-group = 'sequential' [[profile.default.overrides]] -filter = 'test(definitions_export)' +filter = 'test(test_export)' priority = 40 test-group = 'sequential' [[profile.default.overrides]] -filter = 'test(definitions_export)' +filter = 'test(test_import)' priority = 30 test-group = 'sequential' From d6e626149ab1e84ca2a68a17f4b6c55a901a31a2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Apr 2025 00:53:17 -0400 Subject: [PATCH 053/320] CI: use the default profile --- .config/nextest.toml | 8 +++++++- .github/workflows/ci.yaml | 3 ++- Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 50479e4..28f8931 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -55,4 +55,10 @@ test-group = 'sequential' [[profile.default.overrides]] filter = 'test(deprecated_features)' priority = 10 -test-group = 'sequential' \ No newline at end of file +test-group = 'sequential' + + +[profile.ci] +default-filter = "all()" +retries = 4 +test-threads = 1 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6bf76c8..123f01d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,7 +3,8 @@ name: CI on: push: paths: - - ".github/workflows/ci.yaml" + - ".github/workflows/ci.yaml"] + - ".config/nextest.toml" - "src/**" - "tests/**" - "Cargo.toml" diff --git a/Cargo.toml b/Cargo.toml index 589d896..ce18a68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.28.0", features = [ +rabbitmq_http_client = { git = "", features = [ "core", "blocking", "tabled", From 9f817c29398a13f875ca27d92243bdfc15fd4f2d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Apr 2025 00:56:48 -0400 Subject: [PATCH 054/320] Consume HTTP API client from git --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5628e0b..922a8be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1438,9 +1438,8 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2194962ca732d91205f59e9476f30cb1aa9c5ec895d7bf81d93e802d3833ce0c" +version = "0.29.0" +source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#2d4dce78b9a8c63128f2a6ad1cf2606b00024403" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index ce18a68..a507e41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { git = "", features = [ +rabbitmq_http_client = { git = "https://github.com/michaelklishin/rabbitmq-http-api-rs.git", features = [ "core", "blocking", "tabled", From 67a7f59cdf047c566aecd7a222f6c747b44785aa Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Apr 2025 00:58:16 -0400 Subject: [PATCH 055/320] Squash nextest run warnings --- .config/nextest.toml | 5 --- tests/exchange_federation_tests.rs | 44 +++++++++++----------- tests/queue_federation_tests.rs | 60 +++++++++++++++--------------- tests/shovel_tests.rs | 22 +++++------ 4 files changed, 63 insertions(+), 68 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 28f8931..631ef69 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -4,11 +4,6 @@ default-filter = "all()" retries = 2 test-threads = "num-cpus" -filter = 'package(rabbitmqadmin)' -# an overridable default -priority = 0 -test-group = 'parallel_safe' - status-level = "retry" final-status-level = "pass" failure-output = "immediate" diff --git a/tests/exchange_federation_tests.rs b/tests/exchange_federation_tests.rs index dbc8b9f..b567a97 100644 --- a/tests/exchange_federation_tests.rs +++ b/tests/exchange_federation_tests.rs @@ -40,9 +40,9 @@ fn test_federation_upstream_declaration_for_exchange_federation_case0() "federation", "declare_upstream_for_exchanges", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, ]); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -73,11 +73,11 @@ fn test_federation_upstream_declaration_for_exchange_federation_case1a() "federation", "declare_upstream_for_exchanges", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--exchange-name", - &x, + x, "--queue-type", &xfp.queue_type.to_string(), ]); @@ -110,11 +110,11 @@ fn test_federation_upstream_declaration_for_exchange_federation_case1b() "federation", "declare_upstream", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--exchange-name", - &x, + x, "--queue-type", &xfp.queue_type.to_string(), // queue federation @@ -150,11 +150,11 @@ fn test_federation_upstream_declaration_for_exchange_federation_case2() "federation", "declare_upstream_for_exchanges", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--exchange-name", - &x, + x, "--queue-type", &xfp.queue_type.to_string(), "--max-hops", @@ -194,9 +194,9 @@ fn test_federation_upstream_declaration_for_exchange_federation_case3() "federation", "declare_upstream_for_exchanges", "--uri", - &upstream.uri, + upstream.uri, "--exchange-name", - &x, + x, "--queue-type", &xfp.queue_type.to_string(), ]) @@ -233,9 +233,9 @@ fn test_federation_upstream_declaration_for_exchange_federation_case4() "federation", "declare_upstream_for_exchanges", "--name", - &upstream.name, + upstream.name, "--exchange-name", - &x, + x, "--queue-type", &xfp.queue_type.to_string(), "--max-hops", @@ -274,11 +274,11 @@ fn test_federation_list_all_upstreams_with_exchange_federation() "federation", "declare_upstream_for_exchanges", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--exchange-name", - &x, + x, "--queue-type", &xfp.queue_type.to_string(), "--max-hops", @@ -323,11 +323,11 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() "federation", "declare_upstream_for_exchanges", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--exchange-name", - &x, + x, "--queue-type", &xfp.queue_type.to_string(), "--max-hops", @@ -350,7 +350,7 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() "federation", "delete_upstream", "--name", - &upstream.name, + upstream.name, ]); run_succeeds(["-V", vh, "federation", "list_all_upstreams"]) diff --git a/tests/queue_federation_tests.rs b/tests/queue_federation_tests.rs index 9525cbd..a0eb3f7 100644 --- a/tests/queue_federation_tests.rs +++ b/tests/queue_federation_tests.rs @@ -41,13 +41,13 @@ fn test_federation_upstream_declaration_for_queue_federation_case0() "federation", "declare_upstream_for_queues", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--queue-name", - &q, + q, "--consumer-tag", - &qfp.consumer_tag.unwrap(), + qfp.consumer_tag.unwrap(), ]); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -78,15 +78,15 @@ fn test_federation_upstream_declaration_for_queue_federation_case1a() "federation", "declare_upstream_for_queues", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--ack-mode", "on-confirm", "--queue-name", - &q, + q, "--consumer-tag", - &qfp.consumer_tag.unwrap(), + qfp.consumer_tag.unwrap(), ]); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -117,15 +117,15 @@ fn test_federation_upstream_declaration_for_queue_federation_case1b() "federation", "declare_upstream", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--ack-mode", "on-confirm", "--queue-name", - &q, + q, "--consumer-tag", - &qfp.consumer_tag.unwrap(), + qfp.consumer_tag.unwrap(), // exchange federation "--queue-type", "quorum", @@ -158,9 +158,9 @@ fn test_federation_upstream_declaration_for_queue_federation_case2() "federation", "declare_upstream_for_queues", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--ack-mode", "on-publish", ]); @@ -193,7 +193,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case3() "federation", "declare_upstream_for_queues", "--name", - &upstream.name, + upstream.name, ]) .stderr(predicate::str::contains( "required arguments were not provided", @@ -227,7 +227,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case4() "federation", "declare_upstream_for_queues", "--uri", - &upstream.uri, + upstream.uri, "--ack-mode", "on-publish", ]) @@ -263,13 +263,13 @@ fn test_federation_list_all_upstreams_with_queue_federation() "federation", "declare_upstream_for_queues", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--queue-name", - &q, + q, "--consumer-tag", - &qfp.consumer_tag.unwrap(), + qfp.consumer_tag.unwrap(), ]); run_succeeds(["-V", vh, "federation", "list_all_upstreams"]) @@ -306,13 +306,13 @@ fn test_federation_delete_an_upstream_with_queue_federation_settings() "federation", "declare_upstream_for_queues", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--queue-name", - &q, + q, "--consumer-tag", - &qfp.consumer_tag.unwrap(), + qfp.consumer_tag.unwrap(), ]); run_succeeds(["federation", "list_all_upstreams"]) @@ -325,7 +325,7 @@ fn test_federation_delete_an_upstream_with_queue_federation_settings() "federation", "delete_upstream", "--name", - &upstream.name, + upstream.name, ]); run_succeeds(["federation", "list_all_upstreams"]) @@ -362,19 +362,19 @@ fn test_federation_list_all_links_with_queue_federation_settings() "federation", "declare_upstream", "--name", - &upstream.name, + upstream.name, "--uri", - &upstream.uri, + upstream.uri, "--ack-mode", "on-confirm", "--queue-name", - &q, + q, "--consumer-tag", - &qfp.consumer_tag.unwrap(), + qfp.consumer_tag.unwrap(), ]); run_succeeds([ - "-V", vh1, "declare", "queue", "--name", &q, "--type", "classic", + "-V", vh1, "declare", "queue", "--name", q, "--type", "classic", ]); run_succeeds([ diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index cdee5e4..8d50a54 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -33,7 +33,7 @@ fn test_shovel_declaration_without_source_uri() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Date: Sun, 13 Apr 2025 01:07:24 -0400 Subject: [PATCH 056/320] Use HTTP API client 0.29.0 --- Cargo.lock | 3 ++- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 922a8be..65107e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1439,7 +1439,8 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" version = "0.29.0" -source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#2d4dce78b9a8c63128f2a6ad1cf2606b00024403" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b5ff327754cff39b1964cfb8bc4c14968415eba477599c5c1affb01c71862e9" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index a507e41..44c6c68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { git = "https://github.com/michaelklishin/rabbitmq-http-api-rs.git", features = [ +rabbitmq_http_client = { version = "0.29.0", features = [ "core", "blocking", "tabled", From 2af5caf10737b56365e93ac43bb73d2ad9074faf Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Apr 2025 01:08:52 -0400 Subject: [PATCH 057/320] Try default Nextest profile on CI --- .config/nextest.toml | 6 ------ .github/workflows/ci.yaml | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 631ef69..0884983 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -51,9 +51,3 @@ test-group = 'sequential' filter = 'test(deprecated_features)' priority = 10 test-group = 'sequential' - - -[profile.ci] -default-filter = "all()" -retries = 4 -test-threads = 1 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 123f01d..8e9ad3b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -73,4 +73,4 @@ jobs: run: RUST_HTTP_API_CLIENT_RABBITMQCTL=DOCKER:${{job.services.rabbitmq.id}} bin/ci/before_build.sh - name: Run tests - run: RUST_BACKTRACE=1 NEXTEST_RETRIES=2 cargo nextest run -j 1 --workspace --no-fail-fast --all-features \ No newline at end of file + run: RUST_BACKTRACE=1 NEXTEST_RETRIES=2 cargo nextest run --workspace --no-fail-fast --all-features \ No newline at end of file From ca9054c4c8066a4d6261df864c00b22543046208 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Apr 2025 01:14:54 -0400 Subject: [PATCH 058/320] Fix a ci.yaml syntax error --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8e9ad3b..f7df5d9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,7 +3,7 @@ name: CI on: push: paths: - - ".github/workflows/ci.yaml"] + - ".github/workflows/ci.yaml" - ".config/nextest.toml" - "src/**" - "tests/**" From 1e2d0218bc3022517212f9ef7e54cb338acfb7ac Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 15 Apr 2025 01:17:46 -0400 Subject: [PATCH 059/320] cargo update --- Cargo.lock | 124 ++++++++--------------------------------------------- 1 file changed, 19 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65107e9..d4a0a48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -408,23 +408,23 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -623,9 +623,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", @@ -1009,9 +1009,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -1381,7 +1381,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.12", + "thiserror", "tokio", "tracing", "web-time", @@ -1401,7 +1401,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror", "tinyvec", "tracing", "web-time", @@ -1453,7 +1453,7 @@ dependencies = [ "serde-aux", "serde_json", "tabled", - "thiserror 2.0.12", + "thiserror", "time", "tokio", ] @@ -1475,7 +1475,7 @@ dependencies = [ "shellexpand", "sysexits", "tabled", - "thiserror 2.0.12", + "thiserror", "toml", "url", ] @@ -1542,13 +1542,13 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -1881,9 +1881,9 @@ dependencies = [ [[package]] name = "shellexpand" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" dependencies = [ "dirs", ] @@ -2037,33 +2037,13 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -2541,15 +2521,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -2568,21 +2539,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -2615,12 +2571,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2633,12 +2583,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -2651,12 +2595,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2681,12 +2619,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -2699,12 +2631,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -2717,12 +2643,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -2735,12 +2655,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" From ffb6cb6adc1b21d41815ab0ca3aca73edcac41ff Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 16 Apr 2025 00:09:22 -0400 Subject: [PATCH 060/320] README cosmetics --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d7871d..3793b1d 100644 --- a/README.md +++ b/README.md @@ -209,8 +209,8 @@ as a result: ``` key Product name RabbitMQ - Product version 4.0.8 - RabbitMQ version 4.0.8 + Product version 4.1.0 + RabbitMQ version 4.1.0 Erlang version 26.2.5.10 Erlang details Erlang/OTP 26 [erts-14.2.5.9] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] ``` From d48b436f2d37ab3a31dc9b756b511e37a88eb809 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 16 Apr 2025 00:34:08 -0400 Subject: [PATCH 061/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4a0a48..1cc3089 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1360,9 +1360,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] From 3b75f6e398bf0746a665125159cde0754c10f9bb Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 16 Apr 2025 21:10:43 -0400 Subject: [PATCH 062/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1cc3089..286e00b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" dependencies = [ "anstyle", "bstr", From 30958402e8b44142889bf271378945d01c0918ab Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 17 Apr 2025 01:44:12 -0400 Subject: [PATCH 063/320] Minor README updates --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3793b1d..108893d 100644 --- a/README.md +++ b/README.md @@ -146,13 +146,13 @@ will output a table that looks like this: ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Product name │ RabbitMQ │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ Product version │ 4.0.5 │ +│ Product version │ 4.1.0 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ RabbitMQ version │ 4.0.5 │ +│ RabbitMQ version │ 4.1.0 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ Erlang version │ 27.2.1 │ +│ Erlang version │ 27.3.3 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ Erlang details │ Erlang/OTP 27 [erts-15.2.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] │ +│ Erlang details │ Erlang/OTP 27 [erts-15.2.5] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Connections (total) │ 4 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ @@ -618,7 +618,9 @@ rabbitmqadmin federation list_all_upstreams ### Create a Federation Upstream for Exchange Federation -To create a [federation upstream](https://www.rabbitmq.com/docs/federated-exchanges), use `federation declare_upstream_for_exchanges` +To create a [federation upstream](https://www.rabbitmq.com/docs/federated-exchanges), use `federation declare_upstream_for_exchanges`. +This command provides a reduced set of options, only those that are relevant +specifically to exchange federation. ```shell rabbitmqadmin --vhost "local-vhost" federation declare_upstream_for_exchanges --name "pollux" \ From a3160e3ff3a83abd9b9b2b7aad951b7bb42dd817 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 17 Apr 2025 14:59:19 -0400 Subject: [PATCH 064/320] cargo update --- Cargo.lock | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 286e00b..ceb29c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1482,13 +1482,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core", - "zerocopy", ] [[package]] From 08b2a2d3ae05b0f62c32671735c524094113ee53 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 18 Apr 2025 01:38:13 -0400 Subject: [PATCH 065/320] Docs cosmetics --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 108893d..0e82425 100644 --- a/README.md +++ b/README.md @@ -211,8 +211,8 @@ as a result: Product name RabbitMQ Product version 4.1.0 RabbitMQ version 4.1.0 - Erlang version 26.2.5.10 - Erlang details Erlang/OTP 26 [erts-14.2.5.9] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] + Erlang version 27.3.3 + Erlang details Erlang/OTP 27 [erts-15.2.5 [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] ``` ### Retrieving Basic Node Information From 9d6b8fc425203b87f2d1eea8995e0c8f2dd2841f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 19 Apr 2025 00:02:08 -0400 Subject: [PATCH 066/320] cargo update --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ceb29c8..250ab5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" +checksum = "0ddeb19ee86cb16ecfc871e5b0660aff6285760957aaedda6284cf0e790d3769" dependencies = [ "bindgen", "cc", @@ -278,18 +278,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.36" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.36" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", From cf1153f3d097c0b3bd89b7b5842bf602e558b019 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 21 Apr 2025 20:33:59 -0400 Subject: [PATCH 067/320] New command group: nodes As a drive-by change, this makes sure that both --help and -h list the relevant documentation guide URLs, previously it was just --help (unintentionally). Conflicts: Cargo.lock --- CHANGELOG.md | 2 + Cargo.lock | 4 +- src/cli.rs | 156 ++++++++++++++++++++++++------------------- src/main.rs | 4 ++ src/static_urls.rs | 2 + tests/nodes_tests.rs | 26 ++++++++ 6 files changed, 122 insertions(+), 72 deletions(-) create mode 100644 tests/nodes_tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8657179..79ca735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * `vhosts` is a new command group that aggregates all the existing commands directly related to virtual hosts: `list vhosts`, `declare vhost`, `delete vhost` + * `nodes` is a new command group for operations on nodes: `nodes list` + ## v2.0.0 (Mar 31, 2024) diff --git a/Cargo.lock b/Cargo.lock index 250ab5e..493cbb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ddeb19ee86cb16ecfc871e5b0660aff6285760957aaedda6284cf0e790d3769" +checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" dependencies = [ "bindgen", "cc", diff --git a/src/cli.rs b/src/cli.rs index 08608dc..ee20899 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -231,7 +231,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommands([ Command::new("show") .about("Overview, memory footprint breakdown, and more") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", MONITORING_GUIDE_URL )) @@ -271,7 +271,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("check") .subcommands(health_check_subcommands(pre_flight_settings.clone())) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( r#"Doc guides: * {} @@ -284,6 +284,11 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(vhosts_subcommands(pre_flight_settings.clone())), + Command::new("nodes") + .about("Node operations") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(nodes_subcommands(pre_flight_settings.clone())), Command::new("close") .about("Closes connections") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -293,7 +298,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("Rebalancing of leader replicas") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", QUORUM_QUEUE_GUIDE_URL )) @@ -303,7 +308,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("Operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc)") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -313,7 +318,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("See 'definitions export'") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -323,7 +328,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("See 'definitions import'") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -333,7 +338,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("Operations on feature flags") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL )) @@ -343,7 +348,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("Operations on deprecated features") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )) @@ -353,28 +358,28 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments.")) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) .subcommand_value_name("message") .subcommands(publish_subcommands(pre_flight_settings.clone())), Command::new("get") .about(color_print::cstr!("Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) .subcommand_value_name("message") .subcommands(get_subcommands(pre_flight_settings.clone())), Command::new("shovels") .about("Operations on shovels") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!("Doc guide: {}", SHOVEL_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide: {}", SHOVEL_GUIDE_URL)) .subcommand_value_name("shovels") .subcommands(shovel_subcommands(pre_flight_settings.clone())), Command::new("federation") .about("Operations on federation upstreams and links") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( r#"Doc guides: * {} @@ -391,7 +396,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("Tanzu RabbitMQ-specific commands") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_long_help(color_print::cformat!("Doc guide: {}", COMMERCIAL_OFFERINGS_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide: {}", COMMERCIAL_OFFERINGS_GUIDE_URL)) .subcommand_value_name("subcommand") .subcommands(tanzu_subcommands()), ]) @@ -402,20 +407,20 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { let users_cmd = Command::new("users").long_about("Lists users in the internal database"); let vhosts_cmd = Command::new("vhosts") .long_about("Lists virtual hosts") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", VIRTUAL_HOST_GUIDE_URL )); let permissions_cmd = Command::new("permissions") .long_about("Lists user permissions") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", ACCESS_CONTROL_GUIDE_URL )); let connections_cmd = Command::new("connections") .long_about("Lists client connections") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", CONNECTION_GUIDE_URL )); @@ -428,19 +433,19 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { .help("Name of the user whose connections to list"), ) .long_about("Lists client connections that authenticated with a specific username") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", CONNECTION_GUIDE_URL )); let channels_cmd = Command::new("channels") .long_about("Lists AMQP 0-9-1 channels") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", CHANNEL_GUIDE_URL )); let queues_cmd = Command::new("queues") .long_about("Lists queues and streams") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", QUEUE_GUIDE_URL )); @@ -448,7 +453,7 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { let bindings_cmd = Command::new("bindings").long_about("Lists bindings"); let consumers_cmd = Command::new("consumers") .long_about("Lists consumers") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", CONSUMER_GUIDE_URL )); @@ -460,25 +465,25 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { .required(false), ) .long_about("Lists runtime parameters") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", RUNTIME_PARAMETER_GUIDE_URL )); let policies_cmd = Command::new("policies") .long_about("Lists policies") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", POLICY_GUIDE_URL )); let operator_policies_cmd = Command::new("operator_policies") .long_about("Lists operator policies") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", OPERATOR_POLICY_GUIDE_URL )); let vhost_limits_cmd = Command::new("vhost_limits") .long_about("Lists virtual host (resource) limits") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", VIRTUAL_HOST_GUIDE_URL )); @@ -490,25 +495,25 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { .required(false), ) .long_about("Lists per-user (resource) limits") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", USER_LIMIT_GUIDE_URL )); let feature_flags_cmd = Command::new("feature_flags") .long_about("Lists feature flags and their cluster state") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL )); let deprecated_features_cmd = Command::new("deprecated_features") .long_about("Lists all deprecated features") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )); let deprecated_features_in_use_cmd = Command::new("deprecated_features_in_use") .long_about("Lists the deprecated features that are in used in the cluster") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )); @@ -573,7 +578,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let vhost_cmd = Command::new("vhost") .about("Creates a virtual host") - .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) .arg( Arg::new("name") .long("name") @@ -602,7 +607,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let permissions_cmd = Command::new("permissions") .about("grants permissions to a user") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", ACCESS_CONTROL_GUIDE_URL )) @@ -632,7 +637,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let queue_cmd = Command::new("queue") .about("Declares a queue or a stream") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", QUEUE_GUIDE_URL )) @@ -669,7 +674,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let stream_cmd = Command::new("stream") .about("Declares a stream") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", STREAM_GUIDE_URL )) @@ -777,7 +782,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let parameter_cmd = Command::new("parameter") .about("Sets a runtime parameter") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", RUNTIME_PARAMETER_GUIDE_URL )) @@ -801,7 +806,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let policy_cmd = Command::new("policy") .about("Creates or updates a policy") - .after_long_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) .arg( Arg::new("name") .long("name") @@ -837,7 +842,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let operator_policy_cmd = Command::new("operator_policy") .about("Creates or updates an operator policy") - .after_long_help(color_print::cformat!("Doc guide:: {}", OPERATOR_POLICY_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide:: {}", OPERATOR_POLICY_GUIDE_URL)) .arg( Arg::new("name") .long("name") @@ -873,7 +878,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let vhost_limit_cmd = Command::new("vhost_limit") .about("Set a vhost limit") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", VIRTUAL_HOST_LIMIT_GUIDE_URL )) @@ -891,7 +896,7 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] ); let user_limit_cmd = Command::new("user_limit") .about("Set a user limit") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", USER_LIMIT_GUIDE_URL )) @@ -944,7 +949,7 @@ fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { .help("target node, must be a cluster member") .required(true), ) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", MEMORY_FOOTPRINT_GUIDE_URL )); @@ -957,7 +962,7 @@ fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { .help("target node, must be a cluster member") .required(true), ) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide:: {}", MEMORY_FOOTPRINT_GUIDE_URL )); @@ -1160,7 +1165,7 @@ fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let declare_cmd = Command::new("declare") .about("Creates or updates a policy") - .after_long_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) .arg( Arg::new("name") .long("name") @@ -1197,7 +1202,7 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] let list_cmd = Command::new("list") .long_about("Lists policies") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", POLICY_GUIDE_URL )); @@ -1261,10 +1266,10 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; .about("Checks if there are any resource alarms in effect across the entire cluster"); let node_is_quorum_critical = Command::new("node_is_quorum_critical") .about("Fails if there are queues/streams with minimum online quorum (queues/streams that will lose their quorum if the target node shuts down)") - .after_long_help(node_is_quorum_critical_after_help); + .after_help(node_is_quorum_critical_after_help); let deprecated_features_in_use = Command::new("deprecated_features_in_use") .about("Fails if there are any deprecated features in use in the cluster") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )); @@ -1278,7 +1283,7 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; .long("port") .value_parser(value_parser!(u16)), ) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", HEALTH_CHECK_GUIDE_URL )); @@ -1293,7 +1298,7 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; .value_parser(value_parser!(SupportedProtocol)) .long_help("An alias for one of the protocols that RabbitMQ supports, with or without TLS: 'amqp', 'amqp/ssl', 'stream', 'stream/ssl', 'mqtt', 'mqtt/ssl', 'stomp', 'stomp/ssl', 'http/web-mqtt', 'https/web-mqtt', 'http/web-stomp', 'https/web-stomp', 'http/prometheus', 'https/prometheus', 'http', 'https'"), ) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", HEALTH_CHECK_GUIDE_URL )); @@ -1339,7 +1344,7 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { fn definitions_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let export_cmd = Command::new("export") .about("Export cluster-wide definitions") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -1398,7 +1403,7 @@ Examples: let export_from_vhost_cmd = Command::new("export_from_vhost") .about("Export definitions of a specific virtual host") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -1424,7 +1429,7 @@ Examples: let import_cmd = Command::new("import") .about("Import cluster-wide definitions (of multiple virtual hosts)") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -1449,7 +1454,7 @@ Examples: let import_into_vhost_cmd = Command::new("import_into_vhost") .about("Import a virtual host-specific definitions file into a virtual host") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -1484,7 +1489,7 @@ Examples: fn export_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { let definitions = Command::new("definitions") .about("Prefer 'definitions export'") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -1522,7 +1527,7 @@ Example use: --transformations strip_cmq_keys_from_policies,drop_empty_policies fn import_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("definitions") .about("Prefer 'definitions import'") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) @@ -1538,14 +1543,14 @@ fn import_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list") .long_about("Lists feature flags and their cluster state") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL )); let enable_cmd = Command::new("enable") .long_about("Enables a feature flag") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL )) @@ -1558,7 +1563,7 @@ pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Com let enable_all_cmd = Command::new("enable_all") .long_about("Enables all stable feature flags") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", FEATURE_FLAG_GUIDE_URL )); @@ -1570,14 +1575,14 @@ pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Com pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { let list_cmd = Command::new("list") .long_about("Lists deprecated features") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )); let list_in_use_cmd = Command::new("list_used") .long_about("Lists the deprecated features that are found to be in use in the cluster") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", DEPRECATED_FEATURE_GUIDE_URL )); @@ -1586,17 +1591,28 @@ pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) - .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { + let list_cmd = Command::new("list") + .long_about("Lists cluster nodes") + .after_help(color_print::cformat!( + "Doc guide: {}", + CLUSTERING_GUIDE_URL + )); + + [list_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list") .long_about("Lists virtual hosts") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", VIRTUAL_HOST_GUIDE_URL )); let declare_cmd = Command::new("declare") .about("Creates a virtual host") - .after_long_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) .arg( Arg::new("name") .long("name") @@ -1647,7 +1663,7 @@ pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3 pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("message") .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) - .after_long_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) .arg( Arg::new("routing_key") .short('k') @@ -1685,7 +1701,7 @@ pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("messages") .about(color_print::cstr!("Fetches (via polling, very inefficiently) message(s) from a queue. Only suitable for development and test environments")) - .after_long_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) + .after_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) .arg( Arg::new("queue") .short('q') @@ -1714,7 +1730,7 @@ pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let list_all_cmd = Command::new("list_all") .long_about("Lists shovels in all virtual hosts") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", SHOVEL_GUIDE_URL )); @@ -1723,7 +1739,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4 .long_about( "Declares a dynamic shovel that uses AMQP 0-9-1 for both source and destination", ) - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", SHOVEL_GUIDE_URL )) @@ -1813,7 +1829,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4 let declare_10_cmd = Command::new("declare_amqp10") .long_about("Declares a dynamic shovel that uses AMQP 1.0 for both source and destination") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", SHOVEL_GUIDE_URL )) @@ -1842,7 +1858,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4 let delete_cmd = Command::new("delete") .long_about("Deletes a dynamic shovel") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", SHOVEL_GUIDE_URL )) @@ -1860,7 +1876,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4 fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { let list_all_upstreams = Command::new("list_all_upstreams") .long_about("Lists federation upstreams in all virtual hosts") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( r#"Doc guides: * {} @@ -1873,7 +1889,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 let declare_upstream = Command::new("declare_upstream") .long_about("Declares a federation upstream to be used with both exchange and queue federation") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( r#"Doc guides: * {} @@ -1982,7 +1998,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 let declare_upstream_for_queue_federation = Command::new("declare_upstream_for_queues") .long_about("Declares an upstream that will be used only for queue federation") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( r#"Doc guides: * {} @@ -2047,7 +2063,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 let declare_upstream_for_exchange_federation = Command::new("declare_upstream_for_exchanges") .long_about("Declares an upstream that will be used only for exchange federation") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( r#"Doc guides: * {} @@ -2143,7 +2159,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 let delete_upstream = Command::new("delete_upstream") .long_about("Declares a federation upstream") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( "Doc guide: {}", FEDERATION_GUIDE_URL )) @@ -2156,7 +2172,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 let list_all_links = Command::new("list_all_links") .long_about("List federation links in all virtual hosts") - .after_long_help(color_print::cformat!( + .after_help(color_print::cformat!( r#"Doc guides: * {} diff --git a/src/main.rs b/src/main.rs index b691c3a..2cd0b06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -488,6 +488,10 @@ fn dispatch_common_subcommand( let result = commands::delete_vhost(client, second_level_args); res_handler.delete_operation_result(result); } + ("nodes", "list") => { + let result = commands::list_nodes(client); + res_handler.tabular_result(result) + } ("purge", "queue") => { let result = commands::purge_queue(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/src/static_urls.rs b/src/static_urls.rs index 4fa6954..5e755ac 100644 --- a/src/static_urls.rs +++ b/src/static_urls.rs @@ -47,6 +47,8 @@ pub(crate) const DEFINITION_GUIDE_URL: &str = "https://rabbitmq.com/docs/definit%20pub(crate)%20const%20CONSUMER_GUIDE_URL:%20&str%20="https://rabbitmq.com/docs/consumers"; pub(crate) const POLLING_CONSUMER_GUIDE_URL: &str = "https://rabbitmq.com/docs/consumers#polling"; pub(crate) const PUBLISHER_GUIDE_URL: &str = "https://rabbitmq.com/docs/publishers"; +pub(crate) const CLUSTERING_GUIDE_URL: &str = "https://rabbitmq.com/docs/clustering"; +pub(crate) const PEER_DISCOVERY_GUIDE_URL: &str = "https://rabbitmq.com/docs/cluster-formation"; pub(crate) const VIRTUAL_HOST_GUIDE_URL: &str = "https://rabbitmq.com/docs/vhosts"; pub(crate) const VIRTUAL_HOST_LIMIT_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/vhosts#limits"; pub(crate) const VIRTUAL_HOST_DEFAULT_QUEUE_TYPE_GUIDE_URL: &str = diff --git a/tests/nodes_tests.rs b/tests/nodes_tests.rs new file mode 100644 index 0000000..d139f26 --- /dev/null +++ b/tests/nodes_tests.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use predicates::prelude::*; + +mod test_helpers; +use crate::test_helpers::*; + +#[test] +fn test_list_nodes() -> Result<(), Box> { + run_succeeds(["list", "nodes"]).stdout(predicate::str::contains("rabbit@")); + + run_succeeds(["nodes", "list"]).stdout(predicate::str::contains("rabbit@")); + + Ok(()) +} From 56f7bb8de29a8a9aa9f1b6c19addf808e336d4b2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 21 Apr 2025 20:47:18 -0400 Subject: [PATCH 068/320] Expose two more node-specific commands under 'nodes' --- CHANGELOG.md | 9 ++++--- src/cli.rs | 35 ++++++++++++++++++++++++-- src/main.rs | 16 +++++++++--- tests/memory_breakdown_tests.rs | 4 +-- tests/nodes_tests.rs | 44 +++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ca735..9bc12a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,13 @@ ### Enhancements - * `vhosts` is a new command group that aggregates all the existing - commands directly related to virtual hosts: `list vhosts`, `declare vhost`, `delete vhost` + * `vhosts` is a new command group for operations on virtual hosts + * `nodes` is a new command group for operations on nodes - * `nodes` is a new command group for operations on nodes: `nodes list` +### Bug Fixes + + * Both `-h` and `--help` now display relevant doc guide URLs. + Previously it was only the case for `--help`. ## v2.0.0 (Mar 31, 2024) diff --git a/src/cli.rs b/src/cli.rs index ee20899..d53ff34 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1591,7 +1591,7 @@ pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) - .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list") .long_about("Lists cluster nodes") .after_help(color_print::cformat!( @@ -1599,7 +1599,38 @@ pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] CLUSTERING_GUIDE_URL )); - [list_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + let memory_breakdown_in_bytes_cmd = Command::new("memory_breakdown_in_bytes") + .about("Provides a memory footprint breakdown (in bytes) for the target node") + .arg( + Arg::new("node") + .long("node") + .help("target node, must be a cluster member") + .required(true), + ) + .after_help(color_print::cformat!( + "Doc guide:: {}", + MEMORY_FOOTPRINT_GUIDE_URL + )); + + let memory_breakdown_in_percent_cmd = Command::new("memory_breakdown_in_percent") + .about("Provides a memory footprint breakdown (in percent) for the target node") + .arg( + Arg::new("node") + .long("node") + .help("target node, must be a cluster member") + .required(true), + ) + .after_help(color_print::cformat!( + "Doc guide:: {}", + MEMORY_FOOTPRINT_GUIDE_URL + )); + + [ + list_cmd, + memory_breakdown_in_percent_cmd, + memory_breakdown_in_bytes_cmd, + ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { diff --git a/src/main.rs b/src/main.rs index 2cd0b06..fdea7db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -291,14 +291,14 @@ fn dispatch_common_subcommand( println!("Using endpoint: {}", endpoint); res_handler.no_output_on_success(Ok(())) } - ("show", "memory_breakdown_in_bytes") => { - let result = commands::show_memory_breakdown(client, second_level_args); - res_handler.memory_breakdown_in_bytes_result(result) - } ("show", "memory_breakdown_in_percent") => { let result = commands::show_memory_breakdown(client, second_level_args); res_handler.memory_breakdown_in_percent_result(result) } + ("show", "memory_breakdown_in_bytes") => { + let result = commands::show_memory_breakdown(client, second_level_args); + res_handler.memory_breakdown_in_bytes_result(result) + } ("list", "nodes") => { let result = commands::list_nodes(client); @@ -492,6 +492,14 @@ fn dispatch_common_subcommand( let result = commands::list_nodes(client); res_handler.tabular_result(result) } + ("nodes", "memory_breakdown_in_percent") => { + let result = commands::show_memory_breakdown(client, second_level_args); + res_handler.memory_breakdown_in_percent_result(result) + } + ("nodes", "memory_breakdown_in_bytes") => { + let result = commands::show_memory_breakdown(client, second_level_args); + res_handler.memory_breakdown_in_bytes_result(result) + } ("purge", "queue") => { let result = commands::purge_queue(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/memory_breakdown_tests.rs b/tests/memory_breakdown_tests.rs index 7ec2cc5..a28cb34 100644 --- a/tests/memory_breakdown_tests.rs +++ b/tests/memory_breakdown_tests.rs @@ -17,7 +17,7 @@ mod test_helpers; use crate::test_helpers::*; #[test] -fn test_memory_breakdown_in_bytes_succeeds() -> Result<(), Box> { +fn test_show_memory_breakdown_in_bytes_succeeds() -> Result<(), Box> { let rc = api_client(); let nodes = rc.list_nodes()?; let first = nodes.first().unwrap(); @@ -39,7 +39,7 @@ fn test_memory_breakdown_in_bytes_succeeds() -> Result<(), Box Result<(), Box> { +fn test_show_memory_breakdown_in_percent_succeeds() -> Result<(), Box> { let rc = api_client(); let nodes = rc.list_nodes()?; let first = nodes.first().unwrap(); diff --git a/tests/nodes_tests.rs b/tests/nodes_tests.rs index d139f26..c95fd51 100644 --- a/tests/nodes_tests.rs +++ b/tests/nodes_tests.rs @@ -24,3 +24,47 @@ fn test_list_nodes() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_nodes_memory_breakdown_in_bytes_succeeds() -> Result<(), Box> { + let rc = api_client(); + let nodes = rc.list_nodes()?; + let first = nodes.first().unwrap(); + + run_succeeds([ + "nodes", + "memory_breakdown_in_bytes", + "--node", + first.name.as_str(), + ]) + .stdout( + predicates::str::contains("Allocated but unused") + .and(predicates::str::contains("Quorum queue ETS tables")) + .and(predicates::str::contains("Client connections")) + .and(predicates::str::contains("Metadata store")), + ); + + Ok(()) +} + +#[test] +fn test_nodes_memory_breakdown_in_percent_succeeds() -> Result<(), Box> { + let rc = api_client(); + let nodes = rc.list_nodes()?; + let first = nodes.first().unwrap(); + + run_succeeds([ + "nodes", + "memory_breakdown_in_bytes", + "--node", + first.name.as_str(), + ]) + .stdout( + predicates::str::contains("Allocated but unused") + .and(predicates::str::contains("Quorum queue ETS tables")) + .and(predicates::str::contains("Client connections")) + .and(predicates::str::contains("Metadata store")), + ); + + Ok(()) +} From 35b6ae29dbf9617105710173fa993a9c8559d886 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 30 Apr 2025 02:06:20 -0400 Subject: [PATCH 069/320] cargo update --- Cargo.lock | 63 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 493cbb4..1eb9fae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.19" +version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "jobserver", "libc", @@ -255,9 +255,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -1389,9 +1389,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" dependencies = [ "bytes", "getrandom 0.3.2", @@ -1545,7 +1545,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror", ] @@ -1638,7 +1638,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -1814,9 +1814,9 @@ dependencies = [ [[package]] name = "serde-aux" -version = "4.6.0" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5290c39c5f6992b9dddbda28541d965dba46468294e6018a408fa297e6c602de" +checksum = "207f67b28fe90fb596503a9bf0bf1ea5e831e21307658e177c5dfcdfc3ab8a0a" dependencies = [ "chrono", "serde", @@ -1938,9 +1938,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -2149,9 +2149,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -2162,9 +2162,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -2174,26 +2174,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tower" version = "0.5.2" @@ -2668,9 +2675,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" dependencies = [ "memchr", ] @@ -2722,18 +2729,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", From 67ebdfee92a489ace30626738762a1c9b3ea25ec Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 1 May 2025 18:50:00 -0400 Subject: [PATCH 070/320] cargo update --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1eb9fae..e76a7d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,9 +642,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "heck" @@ -1221,9 +1221,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", @@ -1409,9 +1409,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ "cfg_aliases", "libc", @@ -1677,9 +1677,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", @@ -1958,9 +1958,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -2026,7 +2026,7 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -2675,9 +2675,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b" dependencies = [ "memchr", ] From 3098dc2eb10d8d5765e979d630315796086dd9d8 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 1 May 2025 21:49:40 -0400 Subject: [PATCH 071/320] Don't set a DQT default when creating a virtual host --- src/cli.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index d53ff34..f2307ad 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -589,7 +589,6 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] Arg::new("default_queue_type") .long("default-queue-type") .required(false) - .default_value(DEFAULT_QUEUE_TYPE) .help(color_print::cformat!("default queue type, one of: classic, quorum, stream")) ) .arg( @@ -1654,7 +1653,6 @@ pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3 Arg::new("default_queue_type") .long("default-queue-type") .required(false) - .default_value(DEFAULT_QUEUE_TYPE) .help(color_print::cformat!("default queue type, one of: classic, quorum, stream")) ) .arg( From eb7592d57934406fa3515ca2c20a6e24f701ee80 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 1 May 2025 22:38:55 -0400 Subject: [PATCH 072/320] Change log update --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc12a7..b3189c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,12 @@ ### Bug Fixes * Both `-h` and `--help` now display relevant doc guide URLs. - Previously it was only the case for `--help`. + Previously it was only the case for `--help` + +### Other Changes + + * `vhosts declare` no longer has a default value for `--default-queue-type`. + Instead, the default will be controlled exclusively by RabbitMQ ## v2.0.0 (Mar 31, 2024) From ec05aa4e47b145cb4ee8194475f6ec94a1f15011 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 1 May 2025 23:27:25 -0400 Subject: [PATCH 073/320] Sort command groups alphabetically --- src/cli.rs | 369 ++++++++++++++++++++++++++++------------------------- 1 file changed, 197 insertions(+), 172 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index f2307ad..aeb03c0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -45,6 +45,202 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { GITHUB_REPOSITORY_URL ); + let close_group = Command::new("close") + .about("Closes connections") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(close_subcommands(pre_flight_settings.clone())); + let declare_group = Command::new("declare") + .about("Creates or declares objects") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(declare_subcommands(pre_flight_settings.clone())); + let definitions_group = Command::new("definitions") + .about("Operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc)") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + DEFINITION_GUIDE_URL + )) + .subcommand_value_name("export") + .subcommands(definitions_subcommands(pre_flight_settings.clone())); + let delete_group = Command::new("delete") + .about("Deletes objects") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(delete_subcommands(pre_flight_settings.clone())); + let deprecated_features_group = Command::new("deprecated_features") + .about("Operations on deprecated features") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + DEPRECATED_FEATURE_GUIDE_URL + )) + .subcommand_value_name("deprecated feature") + .subcommands(deprecated_features_subcommands(pre_flight_settings.clone())); + let export_group = Command::new("export") + .about("See 'definitions export'") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + DEFINITION_GUIDE_URL + )) + .subcommand_value_name("definitions") + .subcommands(export_subcommands(pre_flight_settings.clone())); + let feature_flags_group = Command::new("feature_flags") + .about("Operations on feature flags") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + FEATURE_FLAG_GUIDE_URL + )) + .subcommand_value_name("feature flag") + .subcommands(feature_flags_subcommands(pre_flight_settings.clone())); + let federation_group = Command::new("federation") + .about("Operations on federation upstreams and links") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + r#"Doc guides: + + * {} + * {} + * {} + * {}"#, + FEDERATION_GUIDE_URL, + FEDERATED_EXCHANGES_GUIDE_URL, + FEDERATED_QUEUES_GUIDE_URL, + FEDERATION_REFERENCE_URL + )) + .subcommands(federation_subcommands(pre_flight_settings.clone())); + let get_group = Command::new("get") + .about(color_print::cstr!("Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) + .subcommand_value_name("message") + .subcommands(get_subcommands(pre_flight_settings.clone())); + let health_check_group = Command::new("health_check") + .about("Runs health checks") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommand_value_name("check") + .subcommands(health_check_subcommands(pre_flight_settings.clone())) + .after_help(color_print::cformat!( + r#"Doc guides: + + * {} + * {}"#, + HEALTH_CHECK_GUIDE_URL, + DEPRECATED_FEATURE_GUIDE_URL + )); + let import_group = Command::new("import") + .about("See 'definitions import'") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + DEFINITION_GUIDE_URL + )) + .subcommand_value_name("definitions") + .subcommands(import_subcommands(pre_flight_settings.clone())); + let list_group = Command::new("list") + .about("Lists objects") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(list_subcommands(pre_flight_settings.clone())); + let nodes_group = Command::new("nodes") + .about("Node operations") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(nodes_subcommands(pre_flight_settings.clone())); + let policies_group = Command::new("policies") + .about("Operations on policies") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommand_value_name("policy") + .subcommands(policies_subcommands(pre_flight_settings.clone())); + let publish_group = Command::new("publish") + .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments.")) + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) + .subcommand_value_name("message") + .subcommands(publish_subcommands(pre_flight_settings.clone())); + let purge_group = Command::new("purge") + .about("Purges queues") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommand_value_name("queue") + .subcommands(purge_subcommands(pre_flight_settings.clone())); + let rebalance_group = Command::new("rebalance") + .about("Rebalancing of leader replicas") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + QUORUM_QUEUE_GUIDE_URL + )) + .subcommand_value_name("queues") + .subcommands(rebalance_subcommands(pre_flight_settings.clone())); + let show_group = Command::new("show") + .about("Overview, memory footprint breakdown, and more") + .after_help(color_print::cformat!( + "Doc guide: {}", + MONITORING_GUIDE_URL + )) + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(show_subcommands(pre_flight_settings.clone())); + let shovels_group = Command::new("shovels") + .about("Operations on shovels") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!("Doc guide: {}", SHOVEL_GUIDE_URL)) + .subcommand_value_name("shovels") + .subcommands(shovel_subcommands(pre_flight_settings.clone())); + let tanzu_group = Command::new("tanzu") + .about("Tanzu RabbitMQ-specific commands") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!("Doc guide: {}", COMMERCIAL_OFFERINGS_GUIDE_URL)) + .subcommand_value_name("subcommand") + .subcommands(tanzu_subcommands()); + let vhosts_group = Command::new("vhosts") + .about("Virtual host operations") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(vhosts_subcommands(pre_flight_settings.clone())); + + let command_groups = [ + close_group, + declare_group, + definitions_group, + delete_group, + deprecated_features_group, + export_group, + feature_flags_group, + federation_group, + get_group, + health_check_group, + import_group, + list_group, + nodes_group, + policies_group, + publish_group, + purge_group, + rebalance_group, + show_group, + shovels_group, + tanzu_group, + vhosts_group, + ]; + + Command::new("rabbitmqadmin") .version(clap::crate_version!()) .author("RabbitMQ Core Team") @@ -228,178 +424,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .value_parser(value_parser!(TableStyle)) ) .subcommand_required(true) - .subcommands([ - Command::new("show") - .about("Overview, memory footprint breakdown, and more") - .after_help(color_print::cformat!( - "Doc guide: {}", - MONITORING_GUIDE_URL - )) - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommands(show_subcommands(pre_flight_settings.clone())), - Command::new("list") - .about("Lists objects") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommands(list_subcommands(pre_flight_settings.clone())), - Command::new("declare") - .about("Creates or declares objects") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommands(declare_subcommands(pre_flight_settings.clone())), - Command::new("delete") - .about("Deletes objects") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommands(delete_subcommands(pre_flight_settings.clone())), - Command::new("purge") - .about("Purges queues") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommand_value_name("queue") - .subcommands(purge_subcommands(pre_flight_settings.clone())), - Command::new("policies") - .about("Operations on policies") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommand_value_name("policy") - .subcommands(policies_subcommands(pre_flight_settings.clone())), - Command::new("health_check") - .about("Runs health checks") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommand_value_name("check") - .subcommands(health_check_subcommands(pre_flight_settings.clone())) - .after_help(color_print::cformat!( - r#"Doc guides: - - * {} - * {}"#, - HEALTH_CHECK_GUIDE_URL, - DEPRECATED_FEATURE_GUIDE_URL - )), - Command::new("vhosts") - .about("Virtual host operations") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommands(vhosts_subcommands(pre_flight_settings.clone())), - Command::new("nodes") - .about("Node operations") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommands(nodes_subcommands(pre_flight_settings.clone())), - Command::new("close") - .about("Closes connections") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .subcommands(close_subcommands(pre_flight_settings.clone())), - Command::new("rebalance") - .about("Rebalancing of leader replicas") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!( - "Doc guide: {}", - QUORUM_QUEUE_GUIDE_URL - )) - .subcommand_value_name("queues") - .subcommands(rebalance_subcommands(pre_flight_settings.clone())), - Command::new("definitions") - .about("Operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc)") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!( - "Doc guide: {}", - DEFINITION_GUIDE_URL - )) - .subcommand_value_name("export") - .subcommands(definitions_subcommands(pre_flight_settings.clone())), - Command::new("export") - .about("See 'definitions export'") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!( - "Doc guide: {}", - DEFINITION_GUIDE_URL - )) - .subcommand_value_name("definitions") - .subcommands(export_subcommands(pre_flight_settings.clone())), - Command::new("import") - .about("See 'definitions import'") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!( - "Doc guide: {}", - DEFINITION_GUIDE_URL - )) - .subcommand_value_name("definitions") - .subcommands(import_subcommands(pre_flight_settings.clone())), - Command::new("feature_flags") - .about("Operations on feature flags") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!( - "Doc guide: {}", - FEATURE_FLAG_GUIDE_URL - )) - .subcommand_value_name("feature flag") - .subcommands(feature_flags_subcommands(pre_flight_settings.clone())), - Command::new("deprecated_features") - .about("Operations on deprecated features") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!( - "Doc guide: {}", - DEPRECATED_FEATURE_GUIDE_URL - )) - .subcommand_value_name("deprecated feature") - .subcommands(deprecated_features_subcommands(pre_flight_settings.clone())), - Command::new("publish") - .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments.")) - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) - .subcommand_value_name("message") - .subcommands(publish_subcommands(pre_flight_settings.clone())), - Command::new("get") - .about(color_print::cstr!("Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) - .subcommand_value_name("message") - .subcommands(get_subcommands(pre_flight_settings.clone())), - Command::new("shovels") - .about("Operations on shovels") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!("Doc guide: {}", SHOVEL_GUIDE_URL)) - .subcommand_value_name("shovels") - .subcommands(shovel_subcommands(pre_flight_settings.clone())), - Command::new("federation") - .about("Operations on federation upstreams and links") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!( - r#"Doc guides: - - * {} - * {} - * {} - * {}"#, - FEDERATION_GUIDE_URL, - FEDERATED_EXCHANGES_GUIDE_URL, - FEDERATED_QUEUES_GUIDE_URL, - FEDERATION_REFERENCE_URL - )) - .subcommands(federation_subcommands(pre_flight_settings.clone())), - Command::new("tanzu") - .about("Tanzu RabbitMQ-specific commands") - .infer_subcommands(pre_flight_settings.infer_subcommands) - .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!("Doc guide: {}", COMMERCIAL_OFFERINGS_GUIDE_URL)) - .subcommand_value_name("subcommand") - .subcommands(tanzu_subcommands()), - ]) + .subcommands(command_groups) } fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { From 7193fcd3455fde38b807049e993bfa96d6f77e9c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 1 May 2025 23:29:18 -0400 Subject: [PATCH 074/320] cargo fmt, clippy cosmetics --- Cargo.toml | 3 +++ src/cli.rs | 67 +++++++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44c6c68..e012c34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,6 @@ rustls = { version = "0.23", features = ["aws_lc_rs"] } [dev-dependencies] assert_cmd = "2.0" predicates = "3.1" + +[lints.clippy] +uninlined_format_args = "allow" diff --git a/src/cli.rs b/src/cli.rs index aeb03c0..7539fa0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -75,9 +75,9 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!( - "Doc guide: {}", - DEPRECATED_FEATURE_GUIDE_URL - )) + "Doc guide: {}", + DEPRECATED_FEATURE_GUIDE_URL + )) .subcommand_value_name("deprecated feature") .subcommands(deprecated_features_subcommands(pre_flight_settings.clone())); let export_group = Command::new("export") @@ -85,9 +85,9 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!( - "Doc guide: {}", - DEFINITION_GUIDE_URL - )) + "Doc guide: {}", + DEFINITION_GUIDE_URL + )) .subcommand_value_name("definitions") .subcommands(export_subcommands(pre_flight_settings.clone())); let feature_flags_group = Command::new("feature_flags") @@ -95,9 +95,9 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!( - "Doc guide: {}", - FEATURE_FLAG_GUIDE_URL - )) + "Doc guide: {}", + FEATURE_FLAG_GUIDE_URL + )) .subcommand_value_name("feature flag") .subcommands(feature_flags_subcommands(pre_flight_settings.clone())); let federation_group = Command::new("federation") @@ -105,17 +105,17 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!( - r#"Doc guides: + r#"Doc guides: * {} * {} * {} * {}"#, - FEDERATION_GUIDE_URL, - FEDERATED_EXCHANGES_GUIDE_URL, - FEDERATED_QUEUES_GUIDE_URL, - FEDERATION_REFERENCE_URL - )) + FEDERATION_GUIDE_URL, + FEDERATED_EXCHANGES_GUIDE_URL, + FEDERATED_QUEUES_GUIDE_URL, + FEDERATION_REFERENCE_URL + )) .subcommands(federation_subcommands(pre_flight_settings.clone())); let get_group = Command::new("get") .about(color_print::cstr!("Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) @@ -131,21 +131,21 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("check") .subcommands(health_check_subcommands(pre_flight_settings.clone())) .after_help(color_print::cformat!( - r#"Doc guides: + r#"Doc guides: * {} * {}"#, - HEALTH_CHECK_GUIDE_URL, - DEPRECATED_FEATURE_GUIDE_URL - )); + HEALTH_CHECK_GUIDE_URL, + DEPRECATED_FEATURE_GUIDE_URL + )); let import_group = Command::new("import") .about("See 'definitions import'") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!( - "Doc guide: {}", - DEFINITION_GUIDE_URL - )) + "Doc guide: {}", + DEFINITION_GUIDE_URL + )) .subcommand_value_name("definitions") .subcommands(import_subcommands(pre_flight_settings.clone())); let list_group = Command::new("list") @@ -182,17 +182,17 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!( - "Doc guide: {}", - QUORUM_QUEUE_GUIDE_URL - )) + "Doc guide: {}", + QUORUM_QUEUE_GUIDE_URL + )) .subcommand_value_name("queues") .subcommands(rebalance_subcommands(pre_flight_settings.clone())); let show_group = Command::new("show") .about("Overview, memory footprint breakdown, and more") .after_help(color_print::cformat!( - "Doc guide: {}", - MONITORING_GUIDE_URL - )) + "Doc guide: {}", + MONITORING_GUIDE_URL + )) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(show_subcommands(pre_flight_settings.clone())); @@ -200,14 +200,20 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .about("Operations on shovels") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!("Doc guide: {}", SHOVEL_GUIDE_URL)) + .after_help(color_print::cformat!( + "Doc guide: {}", + SHOVEL_GUIDE_URL + )) .subcommand_value_name("shovels") .subcommands(shovel_subcommands(pre_flight_settings.clone())); let tanzu_group = Command::new("tanzu") .about("Tanzu RabbitMQ-specific commands") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) - .after_help(color_print::cformat!("Doc guide: {}", COMMERCIAL_OFFERINGS_GUIDE_URL)) + .after_help(color_print::cformat!( + "Doc guide: {}", + COMMERCIAL_OFFERINGS_GUIDE_URL + )) .subcommand_value_name("subcommand") .subcommands(tanzu_subcommands()); let vhosts_group = Command::new("vhosts") @@ -240,7 +246,6 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { vhosts_group, ]; - Command::new("rabbitmqadmin") .version(clap::crate_version!()) .author("RabbitMQ Core Team") From 9b63a8c0f665c426eabb3fbdbd998c165ac841ce Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 1 May 2025 23:33:33 -0400 Subject: [PATCH 075/320] Update change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3189c3..c325aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * `vhosts` is a new command group for operations on virtual hosts * `nodes` is a new command group for operations on nodes + * Command groups are now ordered alphabetically ### Bug Fixes From 62d3d9814d47823d7f95c3a9b406d6da5fd9106d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 2 May 2025 01:09:13 -0400 Subject: [PATCH 076/320] New command group: users --- Cargo.lock | 4 +- src/cli.rs | 147 ++++++++++++++++++++++++++++++++++++------- src/main.rs | 25 +++++++- tests/users_tests.rs | 28 ++++++++- 4 files changed, 178 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e76a7d3..7ab158e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.20" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "jobserver", "libc", diff --git a/src/cli.rs b/src/cli.rs index 7539fa0..6f772c4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -216,6 +216,16 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { )) .subcommand_value_name("subcommand") .subcommands(tanzu_subcommands()); + let users_group = Command::new("users") + .about("Operations on users") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + ACCESS_CONTROL_GUIDE_URL + )) + .subcommand_value_name("subcommand") + .subcommands(users_subcommands(pre_flight_settings.clone())); let vhosts_group = Command::new("vhosts") .about("Virtual host operations") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -243,6 +253,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { show_group, shovels_group, tanzu_group, + users_group, vhosts_group, ]; @@ -434,19 +445,17 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { let nodes_cmd = Command::new("nodes").long_about("Lists cluster members"); - let users_cmd = Command::new("users").long_about("Lists users in the internal database"); let vhosts_cmd = Command::new("vhosts") .long_about("Lists virtual hosts") .after_help(color_print::cformat!( "Doc guide: {}", VIRTUAL_HOST_GUIDE_URL )); - - let permissions_cmd = Command::new("permissions") - .long_about("Lists user permissions") + let vhost_limits_cmd = Command::new("vhost_limits") + .long_about("Lists virtual host (resource) limits") .after_help(color_print::cformat!( "Doc guide: {}", - ACCESS_CONTROL_GUIDE_URL + VIRTUAL_HOST_GUIDE_URL )); let connections_cmd = Command::new("connections") .long_about("Lists client connections") @@ -454,19 +463,6 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { "Doc guide: {}", CONNECTION_GUIDE_URL )); - let user_connections_cmd = Command::new("user_connections") - .arg( - Arg::new("username") - .short('u') - .long("username") - .required(true) - .help("Name of the user whose connections to list"), - ) - .long_about("Lists client connections that authenticated with a specific username") - .after_help(color_print::cformat!( - "Doc guide: {}", - CONNECTION_GUIDE_URL - )); let channels_cmd = Command::new("channels") .long_about("Lists AMQP 0-9-1 channels") .after_help(color_print::cformat!( @@ -511,11 +507,25 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { "Doc guide: {}", OPERATOR_POLICY_GUIDE_URL )); - let vhost_limits_cmd = Command::new("vhost_limits") - .long_about("Lists virtual host (resource) limits") + let users_cmd = Command::new("users").long_about("Lists users in the internal database"); + let permissions_cmd = Command::new("permissions") + .long_about("Lists user permissions") .after_help(color_print::cformat!( "Doc guide: {}", - VIRTUAL_HOST_GUIDE_URL + ACCESS_CONTROL_GUIDE_URL + )); + let user_connections_cmd = Command::new("user_connections") + .arg( + Arg::new("username") + .short('u') + .long("username") + .required(true) + .help("Name of the user whose connections to list"), + ) + .long_about("Lists client connections that authenticated with a specific username") + .after_help(color_print::cformat!( + "Doc guide: {}", + CONNECTION_GUIDE_URL )); let user_limits_cmd = Command::new("user_limits") .arg( @@ -1719,6 +1729,101 @@ pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3 .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { + let declare_cmd = Command::new("declare") + .about("Creates a user") + .arg( + Arg::new("name") + .long("name") + .help("username") + .required(true), + ) + .arg( + Arg::new("password_hash") + .help(color_print::cformat!( + "salted password hash, see {}", + PASSWORD_GUIDE_URL + )) + .long("password-hash") + .required(false) + .default_value(""), + ) + .arg( + Arg::new("password") + .long("password") + .help(color_print::cformat!( + "prefer providing a hash, see {}", + PASSWORD_GUIDE_URL + )) + .required(false) + .default_value(""), + ) + .arg( + Arg::new("tags") + .long("tags") + .help("a list of comma-separated tags") + .default_value(""), + ); + let list_cmd = Command::new("list").long_about("Lists users in the internal database"); + let permissions_cmd = Command::new("permissions") + .long_about("Lists user permissions") + .after_help(color_print::cformat!( + "Doc guide: {}", + ACCESS_CONTROL_GUIDE_URL + )); + let connections_cmd = Command::new("connections") + .arg( + Arg::new("username") + .short('u') + .long("username") + .required(true) + .help("Name of the user whose connections to list"), + ) + .long_about("Lists client connections that authenticated with a specific username") + .after_help(color_print::cformat!( + "Doc guide: {}", + CONNECTION_GUIDE_URL + )); + let limits_cmd = Command::new("limits") + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(false), + ) + .long_about("Lists per-user (resource) limits") + .after_help(color_print::cformat!( + "Doc guide: {}", + USER_LIMIT_GUIDE_URL + )); + + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let delete_cmd = Command::new("delete") + .about("Deletes a user") + .arg( + Arg::new("name") + .long("name") + .help("username") + .required(true), + ) + .arg(idempotently_arg.clone()); + + [ + connections_cmd, + declare_cmd, + delete_cmd, + limits_cmd, + list_cmd, + permissions_cmd, + ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("message") .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) diff --git a/src/main.rs b/src/main.rs index fdea7db..3e0833b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -299,7 +299,6 @@ fn dispatch_common_subcommand( let result = commands::show_memory_breakdown(client, second_level_args); res_handler.memory_breakdown_in_bytes_result(result) } - ("list", "nodes") => { let result = commands::list_nodes(client); res_handler.tabular_result(result) @@ -488,6 +487,30 @@ fn dispatch_common_subcommand( let result = commands::delete_vhost(client, second_level_args); res_handler.delete_operation_result(result); } + ("users", "declare") => { + let result = commands::declare_user(client, second_level_args); + res_handler.no_output_on_success(result); + } + ("users", "list") => { + let result = commands::list_users(client); + res_handler.tabular_result(result) + } + ("users", "delete") => { + let result = commands::delete_user(client, second_level_args); + res_handler.delete_operation_result(result); + } + ("users", "permissions") => { + let result = commands::list_permissions(client); + res_handler.tabular_result(result) + } + ("users", "connections") => { + let result = commands::list_user_connections(client, second_level_args); + res_handler.tabular_result(result) + } + ("users", "limits") => { + let result = commands::list_user_limits(client, second_level_args); + res_handler.tabular_result(result) + } ("nodes", "list") => { let result = commands::list_nodes(client); res_handler.tabular_result(result) diff --git a/tests/users_tests.rs b/tests/users_tests.rs index 4b4f101..5050eb6 100644 --- a/tests/users_tests.rs +++ b/tests/users_tests.rs @@ -19,7 +19,7 @@ use crate::test_helpers::*; #[test] fn test_list_users() -> Result<(), Box> { - let username = "new_user"; + let username = "test_list_users"; let password = "pa$$w0rd"; run_succeeds([ "declare", @@ -32,15 +32,38 @@ fn test_list_users() -> Result<(), Box> { run_succeeds(["list", "users"]).stdout(predicate::str::contains(username)); run_succeeds(["delete", "user", "--name", username]); + run_succeeds(["delete", "user", "--name", username, "--idempotently"]); run_succeeds(["list", "users"]).stdout(predicate::str::contains(username).not()); Ok(()) } +#[test] +fn test_users_list() -> Result<(), Box> { + let username = "test_users_list.2"; + let password = "pa$$w0rd"; + run_succeeds([ + "users", + "declare", + "--name", + username, + "--password", + password, + ]); + + run_succeeds(["users", "list"]).stdout(predicate::str::contains(username)); + run_succeeds(["users", "delete", "--name", username]); + run_succeeds(["users", "delete", "--name", username, "--idempotently"]); + + run_succeeds(["users", "list"]).stdout(predicate::str::contains(username).not()); + + Ok(()) +} + #[test] fn test_list_users_with_table_styles() -> Result<(), Box> { - let username = "new_user"; + let username = "test_list_users_with_table_styles"; let password = "pa$$w0rd"; run_succeeds([ "declare", @@ -53,6 +76,7 @@ fn test_list_users_with_table_styles() -> Result<(), Box> run_succeeds(["--table-style", "markdown", "list", "users"]) .stdout(predicate::str::contains(username)); + run_succeeds(["delete", "user", "--name", username]); run_succeeds(["delete", "user", "--name", username, "--idempotently"]); run_succeeds(["--table-style", "borderless", "list", "users"]) From 1b7b2b9a4653905b537c4eda6b849c8603243f6b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 2 May 2025 01:10:55 -0400 Subject: [PATCH 077/320] Change log update --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c325aca..851ffdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ ### Enhancements + * `nodes` is a new command group for operations on nodes + * `users` is a new command group for operations on users * `vhosts` is a new command group for operations on virtual hosts - * `nodes` is a new command group for operations on nodes * Command groups are now ordered alphabetically ### Bug Fixes From d123907f10045bcdddb113e533e01bf6df01bb05 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 3 May 2025 01:00:15 -0400 Subject: [PATCH 078/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ab158e..1a705a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2675,9 +2675,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b" +checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" dependencies = [ "memchr", ] From bffdc27de35c8fce761c5edad855b7dbda40d4ab Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 3 May 2025 01:58:06 -0400 Subject: [PATCH 079/320] Policies are now documented in a separate guide --- src/static_urls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static_urls.rs b/src/static_urls.rs index 5e755ac..f521a05 100644 --- a/src/static_urls.rs +++ b/src/static_urls.rs @@ -54,7 +54,7 @@ pub(crate) const VIRTUAL_HOST_LIMIT_GUIDE_URL: &str = "https://www.rabbitmq.com/%20pub(crate)%20const%20VIRTUAL_HOST_DEFAULT_QUEUE_TYPE_GUIDE_URL:%20&str%20="https://www.rabbitmq.com/docs/vhosts#default-queue-type"; pub(crate) const RUNTIME_PARAMETER_GUIDE_URL: &str = "https://rabbitmq.com/docs/parameters"; -pub(crate) const POLICY_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/parameters#policies"; +pub(crate) const POLICY_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/policies"; pub(crate) const OPERATOR_POLICY_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/parameters#operator-policies"; pub(crate) const USER_LIMIT_GUIDE_URL: &str = "https://rabbitmq.com/docs/user-limits"; From 15ab2aeb4e3c7890dd5c8051537cbeda70ec5df8 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 3 May 2025 02:01:02 -0400 Subject: [PATCH 080/320] New command group: parameters Closes #59 --- CHANGELOG.md | 1 + src/cli.rs | 77 ++++- src/main.rs | 530 +++++++++++++++--------------- tests/runtime_parameters_tests.rs | 57 +++- 4 files changed, 403 insertions(+), 262 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 851ffdb..a8061ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements * `nodes` is a new command group for operations on nodes + * `parameters` is a new command group for operations on [runtime parameters](https://www.rabbitmq.com/docs/parameters) * `users` is a new command group for operations on users * `vhosts` is a new command group for operations on virtual hosts * Command groups are now ordered alphabetically diff --git a/src/cli.rs b/src/cli.rs index 6f772c4..5843a4f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -158,10 +158,24 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(nodes_subcommands(pre_flight_settings.clone())); + let parameters_group = Command::new("parameters") + .about("Operations on runtime parameters") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + RUNTIME_PARAMETER_GUIDE_URL + )) + .subcommand_value_name("runtime_parameter") + .subcommands(parameters_subcommands(pre_flight_settings.clone())); let policies_group = Command::new("policies") .about("Operations on policies") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + POLICY_GUIDE_URL + )) .subcommand_value_name("policy") .subcommands(policies_subcommands(pre_flight_settings.clone())); let publish_group = Command::new("publish") @@ -246,6 +260,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { import_group, list_group, nodes_group, + parameters_group, policies_group, publish_group, purge_group, @@ -1201,6 +1216,66 @@ fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [queue_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let list_cmd = Command::new("list") + .arg( + Arg::new("component") + .long("component") + .help("component (for example: federation-upstream, vhost-limits)") + .required(false), + ) + .long_about("Lists runtime parameters") + .after_help(color_print::cformat!( + "Doc guide: {}", + RUNTIME_PARAMETER_GUIDE_URL + )); + + let set_cmd = Command::new("set") + .alias("declare") + .about("Sets a runtime parameter") + .after_help(color_print::cformat!( + "Doc guide:: {}", + RUNTIME_PARAMETER_GUIDE_URL + )) + .arg( + Arg::new("name") + .long("name") + .help("parameter's name") + .required(true), + ) + .arg( + Arg::new("component") + .long("component") + .help("component (eg. federation)") + .required(true), + ) + .arg( + Arg::new("value") + .long("value") + .help("parameter's value") + .required(true), + ); + + let clear_cmd = Command::new("clear") + .alias("delete") + .about("Clears (deletes) a runtime parameter") + .arg( + Arg::new("name") + .long("name") + .help("parameter's name") + .required(true), + ) + .arg( + Arg::new("component") + .long("component") + .help("component (eg. federation-upstream)") + .required(true), + ); + + [clear_cmd, list_cmd, set_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let declare_cmd = Command::new("declare") .about("Creates or updates a policy") @@ -1280,8 +1355,8 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] [ declare_cmd, - list_cmd, delete_cmd, + list_cmd, list_in_cmd, list_matching_cmd, ] diff --git a/src/main.rs b/src/main.rs index 3e0833b..2b5fa05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -279,120 +279,36 @@ fn dispatch_common_subcommand( res_handler: &mut ResultHandler, ) -> ExitCode { match &pair { - ("show", "overview") => { - let result = commands::show_overview(client); - res_handler.show_overview(result) - } - ("show", "churn") => { - let result = commands::show_overview(client); - res_handler.show_churn(result) - } - ("show", "endpoint") => { - println!("Using endpoint: {}", endpoint); - res_handler.no_output_on_success(Ok(())) - } - ("show", "memory_breakdown_in_percent") => { - let result = commands::show_memory_breakdown(client, second_level_args); - res_handler.memory_breakdown_in_percent_result(result) - } - ("show", "memory_breakdown_in_bytes") => { - let result = commands::show_memory_breakdown(client, second_level_args); - res_handler.memory_breakdown_in_bytes_result(result) - } - ("list", "nodes") => { - let result = commands::list_nodes(client); - res_handler.tabular_result(result) - } - ("list", "vhosts") => { - let result = commands::list_vhosts(client); - res_handler.tabular_result(result) - } - ("list", "vhost_limits") => { - let result = commands::list_vhost_limits(client, &vhost); - res_handler.tabular_result(result) - } - ("list", "user_limits") => { - let result = commands::list_user_limits(client, second_level_args); - res_handler.tabular_result(result) - } - ("list", "users") => { - let result = commands::list_users(client); - res_handler.tabular_result(result) - } - ("list", "connections") => { - let result = commands::list_connections(client); - res_handler.tabular_result(result) - } - ("list", "user_connections") => { - let result = commands::list_user_connections(client, second_level_args); - res_handler.tabular_result(result) - } - ("list", "channels") => { - let result = commands::list_channels(client); - res_handler.tabular_result(result) - } - ("list", "consumers") => { - let result = commands::list_consumers(client); - res_handler.tabular_result(result) - } - ("list", "policies") => { - let result = commands::list_policies(client); - res_handler.tabular_result(result) - } - ("list", "operator_policies") => { - let result = commands::list_operator_policies(client); - res_handler.tabular_result(result) - } - ("list", "queues") => { - let result = commands::list_queues(client, &vhost); - res_handler.tabular_result(result) - } - ("list", "bindings") => { - let result = commands::list_bindings(client); - res_handler.tabular_result(result) - } - ("list", "permissions") => { - let result = commands::list_permissions(client); - res_handler.tabular_result(result) - } - ("list", "parameters") => { - let result = commands::list_parameters(client, &vhost, second_level_args); - res_handler.tabular_result(result) - } - ("list", "exchanges") => { - let result = commands::list_exchanges(client, &vhost); - res_handler.tabular_result(result) - } - ("list", "feature_flags") => { - let result = commands::list_feature_flags(client); - res_handler.tabular_result(result.map(|val| val.0)) - } - ("list", "deprecated_features") => { - let result = commands::list_deprecated_features(client); - res_handler.tabular_result(result.map(|val| val.0)) + ("close", "connection") => { + let result = commands::close_connection(client, second_level_args); + res_handler.no_output_on_success(result); } - ("list", "deprecated_features_in_use") => { - let result = commands::list_deprecated_features_in_use(client); - res_handler.tabular_result(result.map(|val| val.0)) + ("close", "user_connections") => { + let result = commands::close_user_connections(client, second_level_args); + res_handler.no_output_on_success(result); } - ("declare", "vhost") => { - let result = commands::declare_vhost(client, second_level_args); + ("declare", "binding") => { + let result = commands::declare_binding(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } ("declare", "exchange") => { let result = commands::declare_exchange(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("declare", "user") => { - let result = commands::declare_user(client, second_level_args); + ("declare", "operator_policy") => { + let result = commands::declare_operator_policy(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("declare", "parameter") => { + let result = commands::declare_parameter(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } ("declare", "permissions") => { let result = commands::declare_permissions(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("delete", "permissions") => { - let result = commands::delete_permissions(client, &vhost, second_level_args); + ("declare", "policy") => { + let result = commands::declare_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } ("declare", "queue") => { @@ -403,142 +319,286 @@ fn dispatch_common_subcommand( let result = commands::declare_stream(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("declare", "binding") => { - let result = commands::declare_binding(client, &vhost, second_level_args); + ("declare", "user") => { + let result = commands::declare_user(client, second_level_args); res_handler.no_output_on_success(result); } - ("declare", "policy") => { - let result = commands::declare_policy(client, &vhost, second_level_args); + ("declare", "user_limit") => { + let result = commands::declare_user_limit(client, second_level_args); res_handler.no_output_on_success(result); } - ("declare", "operator_policy") => { - let result = commands::declare_operator_policy(client, &vhost, second_level_args); + ("declare", "vhost") => { + let result = commands::declare_vhost(client, second_level_args); res_handler.no_output_on_success(result); } ("declare", "vhost_limit") => { let result = commands::declare_vhost_limit(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("declare", "user_limit") => { - let result = commands::declare_user_limit(client, second_level_args); + ("definitions", "export") => { + let result = commands::export_cluster_wide_definitions(client, second_level_args); res_handler.no_output_on_success(result); } - ("declare", "parameter") => { - let result = commands::declare_parameter(client, &vhost, second_level_args); + ("definitions", "export_from_vhost") => { + let result = commands::export_vhost_definitions(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("delete", "vhost") => { - let result = commands::delete_vhost(client, second_level_args); - res_handler.delete_operation_result(result); + ("definitions", "import") => { + let result = commands::import_definitions(client, second_level_args); + res_handler.no_output_on_success(result); } - ("delete", "user") => { - let result = commands::delete_user(client, second_level_args); - res_handler.delete_operation_result(result); + ("definitions", "import_into_vhost") => { + let result = commands::import_vhost_definitions(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("delete", "binding") => { + let result = commands::delete_binding(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); } ("delete", "exchange") => { let result = commands::delete_exchange(client, &vhost, second_level_args); res_handler.delete_operation_result(result); } + ("delete", "operator_policy") => { + let result = commands::delete_operator_policy(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("delete", "parameter") => { + let result = commands::delete_parameter(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("delete", "permissions") => { + let result = commands::delete_permissions(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("delete", "policy") => { + let result = commands::delete_policy(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("delete", "queue") => { let result = commands::delete_queue(client, &vhost, second_level_args); res_handler.delete_operation_result(result); } + ("delete", "shovel") => { + let result = commands::delete_shovel(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("delete", "stream") => { let result = commands::delete_stream(client, &vhost, second_level_args); res_handler.delete_operation_result(result); } - ("delete", "binding") => { - let result = commands::delete_binding(client, &vhost, second_level_args); + ("delete", "user") => { + let result = commands::delete_user(client, second_level_args); + res_handler.delete_operation_result(result); + } + ("delete", "user_limit") => { + let result = commands::delete_user_limit(client, second_level_args); res_handler.no_output_on_success(result); } - ("delete", "policy") => { - let result = commands::delete_policy(client, &vhost, second_level_args); + ("delete", "vhost") => { + let result = commands::delete_vhost(client, second_level_args); + res_handler.delete_operation_result(result); + } + ("delete", "vhost_limit") => { + let result = commands::delete_vhost_limit(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("deprecated_features", "list") => { + let result = commands::list_deprecated_features(client); + res_handler.tabular_result(result.map(|val| val.0)) + } + ("deprecated_features", "list_used") => { + let result = commands::list_deprecated_features_in_use(client); + res_handler.tabular_result(result.map(|val| val.0)) + } + ("export", "definitions") => { + let result = commands::export_cluster_wide_definitions(client, second_level_args); + res_handler.no_output_on_success(result); + } + ("feature_flags", "enable") => { + let result = commands::enable_feature_flag(client, second_level_args); + res_handler.no_output_on_success(result); + } + ("feature_flags", "enable_all") => { + let result = commands::enable_all_stable_feature_flags(client); + res_handler.no_output_on_success(result); + } + ("feature_flags", "list") => { + let result = commands::list_feature_flags(client); + res_handler.tabular_result(result.map(|val| val.0)) + } + ("federation", "declare_upstream") => { + let result = commands::declare_federation_upstream(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("federation", "declare_upstream_for_exchanges") => { + let result = commands::declare_federation_upstream_for_exchange_federation( + client, + &vhost, + second_level_args, + ); + res_handler.no_output_on_success(result); + } + ("federation", "declare_upstream_for_queues") => { + let result = commands::declare_federation_upstream_for_queue_federation( + client, + &vhost, + second_level_args, + ); + res_handler.no_output_on_success(result); + } + ("federation", "delete_upstream") => { + let result = commands::delete_federation_upstream(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("federation", "list_all_links") => { + let result = commands::list_federation_links(client); + res_handler.tabular_result(result) + } + ("federation", "list_all_upstreams") => { + let result = commands::list_federation_upstreams(client); + res_handler.tabular_result(result) + } + ("get", "messages") => { + let result = commands::get_messages(client, &vhost, second_level_args); + res_handler.tabular_result(result) + } + ("health_check", "cluster_wide_alarms") => { + let result = commands::health_check_cluster_wide_alarms(client); + res_handler.health_check_result(result); + } + ("health_check", "local_alarms") => { + let result = commands::health_check_local_alarms(client); + res_handler.health_check_result(result); + } + ("health_check", "node_is_quorum_critical") => { + let result = commands::health_check_node_is_quorum_critical(client); + res_handler.health_check_result(result); + } + ("health_check", "port_listener") => { + let result = commands::health_check_port_listener(client, second_level_args); + res_handler.health_check_result(result); + } + ("health_check", "protocol_listener") => { + let result = commands::health_check_protocol_listener(client, second_level_args); + res_handler.health_check_result(result); + } + ("import", "definitions") => { + let result = commands::import_definitions(client, second_level_args); res_handler.no_output_on_success(result); } - ("delete", "operator_policy") => { - let result = commands::delete_operator_policy(client, &vhost, second_level_args); - res_handler.no_output_on_success(result); + ("list", "bindings") => { + let result = commands::list_bindings(client); + res_handler.tabular_result(result) } - ("delete", "shovel") => { - let result = commands::delete_shovel(client, &vhost, second_level_args); - res_handler.no_output_on_success(result); + ("list", "channels") => { + let result = commands::list_channels(client); + res_handler.tabular_result(result) } - ("delete", "vhost_limit") => { - let result = commands::delete_vhost_limit(client, &vhost, second_level_args); - res_handler.no_output_on_success(result); + ("list", "connections") => { + let result = commands::list_connections(client); + res_handler.tabular_result(result) } - ("delete", "user_limit") => { - let result = commands::delete_user_limit(client, second_level_args); - res_handler.no_output_on_success(result); + ("list", "consumers") => { + let result = commands::list_consumers(client); + res_handler.tabular_result(result) } - ("delete", "parameter") => { - let result = commands::delete_parameter(client, &vhost, second_level_args); - res_handler.no_output_on_success(result); + ("list", "deprecated_features") => { + let result = commands::list_deprecated_features(client); + res_handler.tabular_result(result.map(|val| val.0)) } - ("vhosts", "declare") => { - let result = commands::declare_vhost(client, second_level_args); - res_handler.no_output_on_success(result); + ("list", "deprecated_features_in_use") => { + let result = commands::list_deprecated_features_in_use(client); + res_handler.tabular_result(result.map(|val| val.0)) } - ("vhosts", "list") => { - let result = commands::list_vhosts(client); + ("list", "exchanges") => { + let result = commands::list_exchanges(client, &vhost); res_handler.tabular_result(result) } - ("vhosts", "delete") => { - let result = commands::delete_vhost(client, second_level_args); - res_handler.delete_operation_result(result); + ("list", "feature_flags") => { + let result = commands::list_feature_flags(client); + res_handler.tabular_result(result.map(|val| val.0)) } - ("users", "declare") => { - let result = commands::declare_user(client, second_level_args); - res_handler.no_output_on_success(result); + ("list", "nodes") => { + let result = commands::list_nodes(client); + res_handler.tabular_result(result) } - ("users", "list") => { - let result = commands::list_users(client); + ("list", "operator_policies") => { + let result = commands::list_operator_policies(client); res_handler.tabular_result(result) } - ("users", "delete") => { - let result = commands::delete_user(client, second_level_args); - res_handler.delete_operation_result(result); + ("list", "parameters") => { + let result = commands::list_parameters(client, &vhost, second_level_args); + res_handler.tabular_result(result) } - ("users", "permissions") => { + ("list", "permissions") => { let result = commands::list_permissions(client); res_handler.tabular_result(result) } - ("users", "connections") => { + ("list", "policies") => { + let result = commands::list_policies(client); + res_handler.tabular_result(result) + } + ("list", "queues") => { + let result = commands::list_queues(client, &vhost); + res_handler.tabular_result(result) + } + ("list", "user_connections") => { let result = commands::list_user_connections(client, second_level_args); res_handler.tabular_result(result) } - ("users", "limits") => { + ("list", "user_limits") => { let result = commands::list_user_limits(client, second_level_args); res_handler.tabular_result(result) } + ("list", "users") => { + let result = commands::list_users(client); + res_handler.tabular_result(result) + } + ("list", "vhost_limits") => { + let result = commands::list_vhost_limits(client, &vhost); + res_handler.tabular_result(result) + } + ("list", "vhosts") => { + let result = commands::list_vhosts(client); + res_handler.tabular_result(result) + } ("nodes", "list") => { let result = commands::list_nodes(client); res_handler.tabular_result(result) } + ("nodes", "memory_breakdown_in_bytes") => { + let result = commands::show_memory_breakdown(client, second_level_args); + res_handler.memory_breakdown_in_bytes_result(result) + } ("nodes", "memory_breakdown_in_percent") => { let result = commands::show_memory_breakdown(client, second_level_args); res_handler.memory_breakdown_in_percent_result(result) } - ("nodes", "memory_breakdown_in_bytes") => { - let result = commands::show_memory_breakdown(client, second_level_args); - res_handler.memory_breakdown_in_bytes_result(result) + ("parameters", "clear") => { + let result = commands::delete_parameter(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); } - ("purge", "queue") => { - let result = commands::purge_queue(client, &vhost, second_level_args); + ("parameters", "list") => { + let result = commands::list_parameters(client, &vhost, second_level_args); + res_handler.tabular_result(result) + } + ("parameters", "set") => { + let result = commands::declare_parameter(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } ("policies", "declare") => { let result = commands::declare_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("policies", "list") => { - let result = commands::list_policies(client); - res_handler.tabular_result(result) - } ("policies", "delete") => { let result = commands::delete_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("policies", "list") => { + let result = commands::list_policies(client); + res_handler.tabular_result(result) + } ("policies", "list_in") => { let typ_opt = second_level_args .get_one::("apply_to") @@ -561,85 +621,37 @@ fn dispatch_common_subcommand( let result = commands::list_matching_policies_in(client, &vhost, &name, typ); res_handler.tabular_result(result) } - ("health_check", "local_alarms") => { - let result = commands::health_check_local_alarms(client); - res_handler.health_check_result(result); - } - ("health_check", "cluster_wide_alarms") => { - let result = commands::health_check_cluster_wide_alarms(client); - res_handler.health_check_result(result); - } - ("health_check", "node_is_quorum_critical") => { - let result = commands::health_check_node_is_quorum_critical(client); - res_handler.health_check_result(result); - } - ("health_check", "port_listener") => { - let result = commands::health_check_port_listener(client, second_level_args); - res_handler.health_check_result(result); + ("publish", "message") => { + let result = commands::publish_message(client, &vhost, second_level_args); + res_handler.single_value_result(result) } - ("health_check", "protocol_listener") => { - let result = commands::health_check_protocol_listener(client, second_level_args); - res_handler.health_check_result(result); + ("purge", "queue") => { + let result = commands::purge_queue(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); } ("rebalance", "queues") => { let result = commands::rebalance_queues(client); res_handler.no_output_on_success(result); } - ("close", "connection") => { - let result = commands::close_connection(client, second_level_args); - res_handler.no_output_on_success(result); - } - ("close", "user_connections") => { - let result = commands::close_user_connections(client, second_level_args); - res_handler.no_output_on_success(result); - } - ("definitions", "export") => { - let result = commands::export_cluster_wide_definitions(client, second_level_args); - res_handler.no_output_on_success(result); - } - ("definitions", "export_from_vhost") => { - let result = commands::export_vhost_definitions(client, &vhost, second_level_args); - res_handler.no_output_on_success(result); - } - ("definitions", "import") => { - let result = commands::import_definitions(client, second_level_args); - res_handler.no_output_on_success(result); - } - ("definitions", "import_into_vhost") => { - let result = commands::import_vhost_definitions(client, &vhost, second_level_args); - res_handler.no_output_on_success(result); - } - ("export", "definitions") => { - let result = commands::export_cluster_wide_definitions(client, second_level_args); - res_handler.no_output_on_success(result); - } - ("import", "definitions") => { - let result = commands::import_definitions(client, second_level_args); - res_handler.no_output_on_success(result); - } - ("feature_flags", "list") => { - let result = commands::list_feature_flags(client); - res_handler.tabular_result(result.map(|val| val.0)) - } - ("feature_flags", "enable") => { - let result = commands::enable_feature_flag(client, second_level_args); - res_handler.no_output_on_success(result); + ("show", "churn") => { + let result = commands::show_overview(client); + res_handler.show_churn(result) } - ("feature_flags", "enable_all") => { - let result = commands::enable_all_stable_feature_flags(client); - res_handler.no_output_on_success(result); + ("show", "endpoint") => { + println!("Using endpoint: {}", endpoint); + res_handler.no_output_on_success(Ok(())) } - ("deprecated_features", "list") => { - let result = commands::list_deprecated_features(client); - res_handler.tabular_result(result.map(|val| val.0)) + ("show", "memory_breakdown_in_bytes") => { + let result = commands::show_memory_breakdown(client, second_level_args); + res_handler.memory_breakdown_in_bytes_result(result) } - ("deprecated_features", "list_used") => { - let result = commands::list_deprecated_features_in_use(client); - res_handler.tabular_result(result.map(|val| val.0)) + ("show", "memory_breakdown_in_percent") => { + let result = commands::show_memory_breakdown(client, second_level_args); + res_handler.memory_breakdown_in_percent_result(result) } - ("shovels", "list_all") => { - let result = commands::list_shovels(client); - res_handler.tabular_result(result) + ("show", "overview") => { + let result = commands::show_overview(client); + res_handler.show_overview(result) } ("shovels", "declare_amqp091") => { let source_queue = second_level_args.get_one::("source_queue").cloned(); @@ -682,44 +694,44 @@ fn dispatch_common_subcommand( let result = commands::delete_shovel(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("federation", "list_all_upstreams") => { - let result = commands::list_federation_upstreams(client); + ("shovels", "list_all") => { + let result = commands::list_shovels(client); res_handler.tabular_result(result) } - ("federation", "list_all_links") => { - let result = commands::list_federation_links(client); + ("users", "connections") => { + let result = commands::list_user_connections(client, second_level_args); res_handler.tabular_result(result) } - ("federation", "declare_upstream") => { - let result = commands::declare_federation_upstream(client, &vhost, second_level_args); + ("users", "declare") => { + let result = commands::declare_user(client, second_level_args); res_handler.no_output_on_success(result); } - ("federation", "declare_upstream_for_queues") => { - let result = commands::declare_federation_upstream_for_queue_federation( - client, - &vhost, - second_level_args, - ); - res_handler.no_output_on_success(result); + ("users", "delete") => { + let result = commands::delete_user(client, second_level_args); + res_handler.delete_operation_result(result); } - ("federation", "declare_upstream_for_exchanges") => { - let result = commands::declare_federation_upstream_for_exchange_federation( - client, - &vhost, - second_level_args, - ); - res_handler.no_output_on_success(result); + ("users", "limits") => { + let result = commands::list_user_limits(client, second_level_args); + res_handler.tabular_result(result) } - ("federation", "delete_upstream") => { - let result = commands::delete_federation_upstream(client, &vhost, second_level_args); + ("users", "list") => { + let result = commands::list_users(client); + res_handler.tabular_result(result) + } + ("users", "permissions") => { + let result = commands::list_permissions(client); + res_handler.tabular_result(result) + } + ("vhosts", "declare") => { + let result = commands::declare_vhost(client, second_level_args); res_handler.no_output_on_success(result); } - ("publish", "message") => { - let result = commands::publish_message(client, &vhost, second_level_args); - res_handler.single_value_result(result) + ("vhosts", "delete") => { + let result = commands::delete_vhost(client, second_level_args); + res_handler.delete_operation_result(result); } - ("get", "messages") => { - let result = commands::get_messages(client, &vhost, second_level_args); + ("vhosts", "list") => { + let result = commands::list_vhosts(client); res_handler.tabular_result(result) } _ => { diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index d1d1e51..6a19d44 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -18,8 +18,8 @@ mod test_helpers; use crate::test_helpers::*; #[test] -fn test_runtime_parameters() -> Result<(), Box> { - let vh = "parameters_vhost_1"; +fn test_runtime_parameters_across_groups() -> Result<(), Box> { + let vh = "test_runtime_parameters_across_groups"; run_succeeds(["declare", "vhost", "--name", vh]); run_succeeds([ "-V", @@ -69,3 +69,56 @@ fn test_runtime_parameters() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_runtime_parameters_cmd_group() -> Result<(), Box> { + let vh = "test_runtime_parameters_cmd_group"; + run_succeeds(["vhosts", "declare", "--name", vh]); + run_succeeds([ + "-V", + vh, + "parameters", + "set", + "--component", + "federation-upstream", + "--name", + "my-upstream", + "--value", + "{\"uri\":\"amqp://target.hostname\",\"expires\":3600000}", + ]); + + run_succeeds([ + "-V", + vh, + "parameters", + "list", + "--component", + "federation-upstream", + ]) + .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + + run_succeeds([ + "-V", + vh, + "parameters", + "delete", + "--component", + "federation-upstream", + "--name", + "my-upstream", + ]); + + run_succeeds([ + "-V", + vh, + "parameters", + "list", + "--component", + "federation-upstream", + ]) + .stdout(predicate::str::contains("my-upstream").not()); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} From bd839cef9fbfc125044cdbb506ba41ba0c9ae4fd Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 4 May 2025 03:02:38 -0400 Subject: [PATCH 081/320] Use HTTP API client from git --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a705a9..a981ff7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1438,9 +1438,8 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5ff327754cff39b1964cfb8bc4c14968415eba477599c5c1affb01c71862e9" +version = "0.30.0" +source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#d85b6fc77c84f00a493d1e4d0c3d435f0fdcd61a" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index e012c34..b35f307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.29.0", features = [ +rabbitmq_http_client = { git = "https://github.com/michaelklishin/rabbitmq-http-api-rs.git", features = [ "core", "blocking", "tabled", From b84740bc2809aa94153109de49dfd5aec69ef95a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 7 May 2025 01:11:13 -0400 Subject: [PATCH 082/320] Operations on global runtime parameters --- CHANGELOG.md | 1 + Cargo.lock | 27 ++++++++-------- Cargo.toml | 2 +- src/cli.rs | 53 +++++++++++++++++++++++++++++++ src/commands.rs | 31 ++++++++++++++++++ src/main.rs | 12 +++++++ tests/runtime_parameters_tests.rs | 22 +++++++++++++ 7 files changed, 134 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8061ca..506f591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements + * `global_parameters` is a new command group for operations on [global runtime parameters](https://www.rabbitmq.com/docs/parameters) * `nodes` is a new command group for operations on nodes * `parameters` is a new command group for operations on [runtime parameters](https://www.rabbitmq.com/docs/parameters) * `users` is a new command group for operations on users diff --git a/Cargo.lock b/Cargo.lock index a981ff7..12369b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -623,9 +623,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -1439,7 +1439,8 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" version = "0.30.0" -source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#d85b6fc77c84f00a493d1e4d0c3d435f0fdcd61a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a51edc350622338b1c39fcc3429c2c6e711c735decca2b9ceedc5166db8df7" dependencies = [ "backtrace", "percent-encoding", @@ -1689,9 +1690,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "log", @@ -1735,9 +1736,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" dependencies = [ "aws-lc-rs", "ring", @@ -2113,9 +2114,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -2674,9 +2675,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b35f307..46cc467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { git = "https://github.com/michaelklishin/rabbitmq-http-api-rs.git", features = [ +rabbitmq_http_client = { version = "0.30.0", features = [ "core", "blocking", "tabled", diff --git a/src/cli.rs b/src/cli.rs index 5843a4f..896b222 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -124,6 +124,16 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .after_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) .subcommand_value_name("message") .subcommands(get_subcommands(pre_flight_settings.clone())); + let global_parameters_group = Command::new("global_parameters") + .about("Operations on global runtime parameters") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + RUNTIME_PARAMETER_GUIDE_URL + )) + .subcommand_value_name("runtime_parameter") + .subcommands(global_parameters_subcommands(pre_flight_settings.clone())); let health_check_group = Command::new("health_check") .about("Runs health checks") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -256,6 +266,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { feature_flags_group, federation_group, get_group, + global_parameters_group, health_check_group, import_group, list_group, @@ -1276,6 +1287,48 @@ fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3 .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let list_cmd = Command::new("list") + .long_about("Lists global runtime parameters") + .after_help(color_print::cformat!( + "Doc guide: {}", + RUNTIME_PARAMETER_GUIDE_URL + )); + + let set_cmd = Command::new("set") + .alias("declare") + .about("Sets a global runtime parameter") + .after_help(color_print::cformat!( + "Doc guide:: {}", + RUNTIME_PARAMETER_GUIDE_URL + )) + .arg( + Arg::new("name") + .long("name") + .help("parameter's name") + .required(true), + ) + .arg( + Arg::new("value") + .long("value") + .help("parameter's value") + .required(true), + ); + + let clear_cmd = Command::new("clear") + .alias("delete") + .about("Clears (deletes) a global runtime parameter") + .arg( + Arg::new("name") + .long("name") + .help("parameter's name") + .required(true), + ); + + [clear_cmd, list_cmd, set_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let declare_cmd = Command::new("declare") .about("Creates or updates a policy") diff --git a/src/commands.rs b/src/commands.rs index 0a26f80..7c5b046 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -180,6 +180,12 @@ pub fn list_parameters( } } +pub fn list_global_parameters( + client: APIClient, +) -> ClientResult> { + client.list_global_runtime_parameters() +} + pub fn list_feature_flags(client: APIClient) -> ClientResult { client.list_feature_flags() } @@ -796,6 +802,12 @@ pub fn delete_parameter( client.clear_runtime_parameter(component, vhost, name) } +pub fn delete_global_parameter(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { + let name = command_args.get_one::("name").unwrap(); + + client.clear_global_runtime_parameter(name) +} + pub fn delete_vhost(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { // the flag is required let name = command_args.get_one::("name").unwrap(); @@ -1027,6 +1039,25 @@ pub fn declare_parameter( client.upsert_runtime_parameter(¶ms) } +pub fn declare_global_parameter(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { + let name = command_args.get_one::("name").unwrap(); + let value = command_args.get_one::("value").unwrap(); + // TODO: global runtime parameter values can be regular strings (not JSON documents) + // but we don't support that yet in the HTTP API client. + let parsed_value = serde_json::from_str::(value) + .unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON: {}", value, err); + process::exit(1); + }); + + let params = requests::GlobalRuntimeParameterDefinition { + name, + value: parsed_value, + }; + + client.upsert_global_runtime_parameter(¶ms) +} + pub fn delete_queue(client: APIClient, vhost: &str, command_args: &ArgMatches) -> ClientResult<()> { // the flag is required let name = command_args.get_one::("name").unwrap(); diff --git a/src/main.rs b/src/main.rs index 2b5fa05..183b6b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -463,6 +463,18 @@ fn dispatch_common_subcommand( let result = commands::get_messages(client, &vhost, second_level_args); res_handler.tabular_result(result) } + ("global_parameters", "clear") => { + let result = commands::delete_global_parameter(client, second_level_args); + res_handler.no_output_on_success(result); + } + ("global_parameters", "list") => { + let result = commands::list_global_parameters(client); + res_handler.tabular_result(result) + } + ("global_parameters", "set") => { + let result = commands::declare_global_parameter(client, second_level_args); + res_handler.no_output_on_success(result); + } ("health_check", "cluster_wide_alarms") => { let result = commands::health_check_cluster_wide_alarms(client); res_handler.health_check_result(result); diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index 6a19d44..99cf96d 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -122,3 +122,25 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> Ok(()) } + +#[test] +fn test_global_runtime_parameters_cmd_group() -> Result<(), Box> { + run_succeeds([ + "global_parameters", + "set", + "--name", + "cluster_tags", + "--value", + "{\"region\": \"ca-central-1\"}", + ]); + + run_succeeds(["global_parameters", "list"]) + .stdout(predicate::str::contains("region").and(predicate::str::contains("ca-central-1"))); + + run_succeeds(["global_parameters", "delete", "--name", "cluster_tags"]); + + run_succeeds(["global_parameters", "list"]) + .stdout(predicate::str::contains("cluster_tags").not()); + + Ok(()) +} From 45ff4273f82cd94c7928eb39153d41052455652b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 7 May 2025 14:52:05 -0400 Subject: [PATCH 083/320] cargo update --- Cargo.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12369b4..0a54c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1727,11 +1727,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] From cbb95fb2f8e136204fdda6dfced94eba28357ff4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 7 May 2025 18:32:05 -0400 Subject: [PATCH 084/320] New command group: queues --- CHANGELOG.md | 1 + src/cli.rs | 79 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 20 +++++++++++ tests/queues_tests.rs | 52 +++++++++++++++++++++++++++- 4 files changed, 151 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 506f591..3583f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * `global_parameters` is a new command group for operations on [global runtime parameters](https://www.rabbitmq.com/docs/parameters) * `nodes` is a new command group for operations on nodes * `parameters` is a new command group for operations on [runtime parameters](https://www.rabbitmq.com/docs/parameters) + * `queues` is a new command group for operations on queues * `users` is a new command group for operations on users * `vhosts` is a new command group for operations on virtual hosts * Command groups are now ordered alphabetically diff --git a/src/cli.rs b/src/cli.rs index 896b222..7d2b343 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -201,6 +201,12 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("queue") .subcommands(purge_subcommands(pre_flight_settings.clone())); + let queues_group = Command::new("queues") + .about("Operations on queues") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommand_value_name("queue") + .subcommands(queues_subcommands(pre_flight_settings.clone())); let rebalance_group = Command::new("rebalance") .about("Rebalancing of leader replicas") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -275,6 +281,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { policies_group, publish_group, purge_group, + queues_group, rebalance_group, show_group, shovels_group, @@ -1227,6 +1234,78 @@ fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [queue_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn queues_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { + let declare_cmd = Command::new("declare") + .about("Declares a queue or a stream") + .after_help(color_print::cformat!( + "Doc guide:: {}", + QUEUE_GUIDE_URL + )) + .arg(Arg::new("name").long("name").required(true).help("name")) + .arg( + Arg::new("type") + .long("type") + .help("queue type") + .value_parser(value_parser!(QueueType)) + .required(false) + .default_value("classic"), + ) + .arg( + Arg::new("durable") + .long("durable") + .help("should it persist after a restart") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("auto_delete") + .long("auto-delete") + .help("should it be deleted when the last consumer disconnects") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional exchange arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let delete_cmd = Command::new("delete") + .about("Deletes a queue") + .arg( + Arg::new("name") + .long("name") + .help("queue name") + .required(true), + ) + .arg(idempotently_arg.clone()); + let list_cmd = Command::new("list") + .long_about("Lists queues and streams") + .after_help(color_print::cformat!( + "Doc guide: {}", + QUEUE_GUIDE_URL + )); + let purge_cmd = Command::new("purge") + .long_about("Purges (permanently removes unacknowledged messages from) a queue") + .arg( + Arg::new("name") + .long("name") + .help("name of the queue to purge") + .required(true), + ); + let rebalance_cmd = Command::new("rebalance").about("Rebalances queue leaders"); + [declare_cmd, delete_cmd, list_cmd, purge_cmd, rebalance_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list") .arg( diff --git a/src/main.rs b/src/main.rs index 183b6b0..49b2720 100644 --- a/src/main.rs +++ b/src/main.rs @@ -641,6 +641,26 @@ fn dispatch_common_subcommand( let result = commands::purge_queue(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("queues", "declare") => { + let result = commands::declare_queue(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("queues", "delete") => { + let result = commands::delete_queue(client, &vhost, second_level_args); + res_handler.delete_operation_result(result); + } + ("queues", "list") => { + let result = commands::list_queues(client, &vhost); + res_handler.tabular_result(result) + } + ("queues", "purge") => { + let result = commands::purge_queue(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("queues", "rebalance") => { + let result = commands::rebalance_queues(client); + res_handler.no_output_on_success(result); + } ("rebalance", "queues") => { let result = commands::rebalance_queues(client); res_handler.no_output_on_success(result); diff --git a/tests/queues_tests.rs b/tests/queues_tests.rs index 00e9353..7aab892 100644 --- a/tests/queues_tests.rs +++ b/tests/queues_tests.rs @@ -49,7 +49,10 @@ fn list_queues() -> Result<(), Box> { run_succeeds(["-V", vh1, "list", "queues"]) .stdout(predicate::str::contains(q1).and(predicate::str::contains("new_queue2").not())); - // delete the queue in vhost 1 + // purge a queue in vhost 1 + run_succeeds(["-V", vh1, "purge", "queue", "--name", q1]); + + // delete a queue in vhost 1 run_succeeds(["-V", vh1, "delete", "queue", "--name", q1]); // list queues in vhost 1 @@ -60,3 +63,50 @@ fn list_queues() -> Result<(), Box> { Ok(()) } + +#[test] +fn queues_lists() -> Result<(), Box> { + let vh1 = "queue_vhost_3"; + let vh2 = "queue_vhost_4"; + let q1 = "new_queue1"; + let q2 = "new_queue2"; + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + // declare vhost 1 + run_succeeds(["vhosts", "declare", "--name", vh1]); + + // declare vhost 2 + run_succeeds(["vhosts", "declare", "--name", vh2]); + + // declare a new queue in vhost 1 + run_succeeds([ + "-V", vh1, "queues", "declare", "--name", q1, "--type", "classic", + ]); + + // declare new queue in vhost 2 + run_succeeds([ + "-V", vh2, "queues", "declare", "--name", q2, "--type", "quorum", + ]); + + await_queue_metric_emission(); + + // list queues in vhost 1 + run_succeeds(["-V", vh1, "queues", "list"]) + .stdout(predicate::str::contains(q1).and(predicate::str::contains("new_queue2").not())); + + // purge a queue in vhost 1 + run_succeeds(["-V", vh1, "queues", "purge", "--name", q1]); + + // delete a queue in vhost 1 + run_succeeds(["-V", vh1, "queues", "delete", "--name", q1]); + + // list queues in vhost 1 + run_succeeds(["-V", vh1, "queues", "list"]).stdout(predicate::str::contains(q1).not()); + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + Ok(()) +} From 549b113a9409a2112c39824be1df94d7fc60a7d3 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 7 May 2025 19:00:18 -0400 Subject: [PATCH 085/320] New command group: streams --- CHANGELOG.md | 1 + src/cli.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 12 ++++++++ tests/streams_tests.rs | 58 +++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3583f7e..05bdfbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * `nodes` is a new command group for operations on nodes * `parameters` is a new command group for operations on [runtime parameters](https://www.rabbitmq.com/docs/parameters) * `queues` is a new command group for operations on queues + * `streams` is a new command group for operations on streams * `users` is a new command group for operations on users * `vhosts` is a new command group for operations on virtual hosts * Command groups are now ordered alphabetically diff --git a/src/cli.rs b/src/cli.rs index 7d2b343..074e2fa 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -236,6 +236,12 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { )) .subcommand_value_name("shovels") .subcommands(shovel_subcommands(pre_flight_settings.clone())); + let streams_group = Command::new("streams") + .about("Operations on streams") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommand_value_name("stream") + .subcommands(streams_subcommands(pre_flight_settings.clone())); let tanzu_group = Command::new("tanzu") .about("Tanzu RabbitMQ-specific commands") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -285,6 +291,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { rebalance_group, show_group, shovels_group, + streams_group, tanzu_group, users_group, vhosts_group, @@ -1306,6 +1313,68 @@ fn queues_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn streams_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let declare_cmd = Command::new("declare") + .about("Declares a stream") + .after_help(color_print::cformat!( + "Doc guide:: {}", + STREAM_GUIDE_URL + )) + .arg(Arg::new("name").long("name").required(true).help("name")) + .arg( + Arg::new("expiration") + .long("expiration") + .help("stream expiration, e.g. 12h for 12 hours, 7D for 7 days, or 1M for 1 month") + .required(true) + .value_parser(value_parser!(String)), + ) + .arg( + Arg::new("max_length_bytes") + .long("max-length-bytes") + .help("maximum stream length in bytes") + .required(false) + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("max_segment_length_bytes") + .long("stream-max-segment-size-bytes") + .help("maximum stream segment file length in bytes") + .required(false) + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional exchange arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let delete_cmd = Command::new("delete") + .about("Deletes a queue") + .arg( + Arg::new("name") + .long("name") + .help("queue name") + .required(true), + ) + .arg(idempotently_arg.clone()); + let list_cmd = Command::new("list") + .long_about("Lists streams and queues and") + .after_help(color_print::cformat!( + "Doc guide: {}", + STREAM_GUIDE_URL + )); + [declare_cmd, delete_cmd, list_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list") .arg( diff --git a/src/main.rs b/src/main.rs index 49b2720..a2d6a10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -730,6 +730,18 @@ fn dispatch_common_subcommand( let result = commands::list_shovels(client); res_handler.tabular_result(result) } + ("streams", "declare") => { + let result = commands::declare_stream(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("streams", "delete") => { + let result = commands::delete_queue(client, &vhost, second_level_args); + res_handler.delete_operation_result(result); + } + ("streams", "list") => { + let result = commands::list_queues(client, &vhost); + res_handler.tabular_result(result) + } ("users", "connections") => { let result = commands::list_user_connections(client, second_level_args); res_handler.tabular_result(result) diff --git a/tests/streams_tests.rs b/tests/streams_tests.rs index 9016df0..b6d0dd4 100644 --- a/tests/streams_tests.rs +++ b/tests/streams_tests.rs @@ -74,3 +74,61 @@ fn list_streams() -> Result<(), Box> { Ok(()) } + +#[test] +fn streams_list() -> Result<(), Box> { + let vh1 = "stream_vhost_3"; + let vh2 = "stream_vhost_4"; + let s1 = "new_stream1"; + let s2 = "new_stream2"; + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + // declare vhost 1 + run_succeeds(["vhosts", "declare", "--name", vh1]); + + // declare vhost 2 + run_succeeds(["vhosts", "declare", "--name", vh2]); + + // declare a new stream in vhost 1 + run_succeeds([ + "-V", + vh1, + "streams", + "declare", + "--name", + s1, + "--expiration", + "2D", + ]); + + // declare new stream in vhost 2 + run_succeeds([ + "-V", + vh2, + "streams", + "declare", + "--name", + s2, + "--expiration", + "12h", + ]); + + await_queue_metric_emission(); + + // list streams in vhost 1 + run_succeeds(["-V", vh1, "streams", "list"]) + .stdout(predicate::str::contains(s1).and(predicate::str::contains("random_stream").not())); + + // delete the stream in vhost 1 + run_succeeds(["-V", vh1, "streams", "delete", "--name", s1]); + + // list streams in vhost 1 + run_succeeds(["-V", vh1, "streams", "list"]).stdout(predicate::str::contains(s1).not()); + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + Ok(()) +} From 12fbcbba7de3f7ec00a3ade419d75460fd38c0d0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 7 May 2025 19:48:08 -0400 Subject: [PATCH 086/320] New command group: exchanges, closes #63 --- CHANGELOG.md | 1 + src/cli.rs | 133 +++++++++++++++++++++++++++ src/main.rs | 20 +++++ tests/bindings_tests.rs | 2 +- tests/exchanges_tests.rs | 188 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 339 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05bdfbb..5364c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements + * `exchanges` is a new command group for operations on exchanges * `global_parameters` is a new command group for operations on [global runtime parameters](https://www.rabbitmq.com/docs/parameters) * `nodes` is a new command group for operations on nodes * `parameters` is a new command group for operations on [runtime parameters](https://www.rabbitmq.com/docs/parameters) diff --git a/src/cli.rs b/src/cli.rs index 074e2fa..7792be5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -80,6 +80,12 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { )) .subcommand_value_name("deprecated feature") .subcommands(deprecated_features_subcommands(pre_flight_settings.clone())); + let exchanges_group = Command::new("exchanges") + .about("Operations on exchanges") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommand_value_name("exchange") + .subcommands(exchanges_subcommands(pre_flight_settings.clone())); let export_group = Command::new("export") .about("See 'definitions export'") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -274,6 +280,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { definitions_group, delete_group, deprecated_features_group, + exchanges_group, export_group, feature_flags_group, federation_group, @@ -1801,6 +1808,132 @@ Examples: .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn exchanges_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { + let bind_cmd = Command::new("bind") + .about("Creates a binding between a source exchange and a destination (a queue or an exchange)") + .arg( + Arg::new("source") + .long("source") + .help("source exchange") + .required(true), + ) + .arg( + Arg::new("destination_type") + .long("destination-type") + .help("destination type: exchange or queue") + .required(true) + .value_parser(value_parser!(BindingDestinationType)), + ) + .arg( + Arg::new("destination") + .long("destination") + .help("destination exchange/queue name") + .required(true), + ) + .arg( + Arg::new("routing_key") + .long("routing-key") + .help("routing key") + .required(true), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let declare_cmd = Command::new("declare") + .about("Declares an exchange") + .arg( + Arg::new("name") + .long("name") + .help("exchange name") + .required(true), + ) + .arg( + Arg::new("type") + .long("type") + .help("exchange type") + .value_parser(value_parser!(ExchangeType)) + .required(false), + ) + .arg( + Arg::new("durable") + .long("durable") + .help("should it persist after a restart") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("auto_delete") + .long("auto-delete") + .help("should it be deleted when the last queue is unbound") + .required(false) + .value_parser(value_parser!(bool)), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional exchange arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let delete_cmd = Command::new("delete") + .about("Deletes an exchange") + .arg( + Arg::new("name") + .long("name") + .help("exchange name") + .required(true), + ) + .arg(idempotently_arg.clone()); + let list_cmd = Command::new("list").long_about("Lists exchanges"); + let unbind_cmd = Command::new("unbind") + .about("Deletes a binding") + .arg( + Arg::new("source") + .long("source") + .help("source exchange") + .required(true), + ) + .arg( + Arg::new("destination_type") + .long("destination-type") + .help("destination type: exchange or queue") + .required(true), + ) + .arg( + Arg::new("destination") + .long("destination") + .help("destination exchange/queue name") + .required(true), + ) + .arg( + Arg::new("routing_key") + .long("routing-key") + .help("routing key") + .required(true), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + [bind_cmd, declare_cmd, delete_cmd, list_cmd, unbind_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} fn export_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { let definitions = Command::new("definitions") .about("Prefer 'definitions export'") diff --git a/src/main.rs b/src/main.rs index a2d6a10..bcd418f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -415,6 +415,26 @@ fn dispatch_common_subcommand( let result = commands::export_cluster_wide_definitions(client, second_level_args); res_handler.no_output_on_success(result); } + ("exchanges", "bind") => { + let result = commands::declare_binding(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("exchanges", "declare") => { + let result = commands::declare_exchange(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("exchanges", "delete") => { + let result = commands::delete_exchange(client, &vhost, second_level_args); + res_handler.delete_operation_result(result); + } + ("exchanges", "list") => { + let result = commands::list_exchanges(client, &vhost); + res_handler.tabular_result(result) + } + ("exchanges", "unbind") => { + let result = commands::delete_binding(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("feature_flags", "enable") => { let result = commands::enable_feature_flag(client, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index 713bc86..fe5c21b 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -84,7 +84,7 @@ fn test_list_bindings() -> Result<(), Box> { ); // delete the queue from vhost 1 - run_succeeds(["-V", vh1, "delete", "queue", "--name", q1]); + run_succeeds(["-V", vh1, "queues", "delete", "--name", q1]); // these bindings were deleted with the queue run_succeeds(["-V", "bindings_vhost_1", "list", "bindings"]).stdout( diff --git a/tests/exchanges_tests.rs b/tests/exchanges_tests.rs index 667fd7a..dd66684 100644 --- a/tests/exchanges_tests.rs +++ b/tests/exchanges_tests.rs @@ -72,7 +72,63 @@ fn list_exchanges() -> Result<(), Box> { } #[test] -fn delete_an_existing_exchange() -> Result<(), Box> { +fn exchanges_list() -> Result<(), Box> { + let vh1 = "exchange_vhost_3"; + let vh2 = "exchange_vhost_4"; + + let x1 = "new_exchange_1"; + let x2 = "new_exchange_2"; + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + // declare vhost 1 + run_succeeds(["vhosts", "declare", "--name", vh1]); + + // declare vhost 2 + run_succeeds(["vhosts", "declare", "--name", vh2]); + + // declare a new exchange in vhost 1 + run_succeeds(["-V", vh1, "exchanges", "declare", "--name", x1]); + + // declare a new exchange in vhost 2 + run_succeeds(["-V", vh2, "exchanges", "declare", "--name", x2]); + + // list exchanges in vhost 1 + run_succeeds(["-V", vh1, "exchanges", "list"]).stdout( + predicate::str::contains("amq.direct") + .and(predicate::str::contains("amq.fanout")) + .and(predicate::str::contains(x1)) + .and(predicate::str::contains(x2).not()), + ); + + // delete the exchanges from vhost 1 + run_succeeds(["-V", vh1, "exchanges", "delete", "--name", x1]); + + // list exchange in vhost 1 + run_succeeds(["-V", vh1, "exchanges", "list"]).stdout( + predicate::str::contains("amq.direct") + .and(predicate::str::contains("amq.topic")) + .and(predicate::str::contains(x1).not()), + ); + + // list exchange in vhost 2 + run_succeeds(["-V", vh2, "exchanges", "list"]).stdout( + predicate::str::contains("amq.direct") + .and(predicate::str::contains("amq.headers")) + .and(predicate::str::contains(x2)) + .and(predicate::str::contains(x1).not()), + ); + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn delete_an_existing_exchange_using_original_command_group() +-> Result<(), Box> { let vh = "delete_exchange_vhost_1"; let x = "exchange_1_to_delete"; @@ -97,9 +153,36 @@ fn delete_an_existing_exchange() -> Result<(), Box> { Ok(()) } +#[test] +fn delete_an_existing_exchange_using_exchanges_command_group() +-> Result<(), Box> { + let vh = "delete_an_existing_exchange_using_exchanges_command_group_1"; + let x = "exchange_1_to_delete"; + + // create a vhost + create_vhost(vh)?; + + // declare an exchange + run_succeeds(["-V", vh, "exchanges", "declare", "--name", x]); + + // list exchanges in vhost 1 + run_succeeds(["-V", vh, "exchanges", "list"]).stdout(predicate::str::contains(x)); + + // delete the exchange + run_succeeds(["-V", vh, "exchanges", "delete", "--name", x]); + + // list exchange in vhost 1 + run_succeeds(["-V", vh, "exchanges", "list"]).stdout(predicate::str::contains(x).not()); + + // delete the vhost + delete_vhost(vh)?; + + Ok(()) +} + #[test] fn delete_a_non_existing_exchange() -> Result<(), Box> { - let vh = "delete_exchange_vhost_2"; + let vh = "delete_a_non_existing_exchange_1"; // declare a vhost create_vhost(vh)?; @@ -108,8 +191,8 @@ fn delete_a_non_existing_exchange() -> Result<(), Box> { run_succeeds([ "--vhost", vh, + "exchanges", "delete", - "exchange", "--name", "7s98df7s79df-non-existent", "--idempotently", @@ -119,8 +202,8 @@ fn delete_a_non_existing_exchange() -> Result<(), Box> { run_fails([ "--vhost", vh, + "exchanges", "delete", - "exchange", "--name", "7s98df7s79df-non-existent", ]) @@ -131,3 +214,100 @@ fn delete_a_non_existing_exchange() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_exchanges_bind_and_unbind() -> Result<(), Box> { + let vh1 = "exchanges_bind_vhost_3"; + let vh2 = "exchanges_bind_vhost_4"; + let q1 = "new_queue_1"; + let q2 = "new_queue_2"; + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + // declare vhost 1 + run_succeeds(["vhosts", "declare", "--name", vh1]); + + // declare vhost 2 + run_succeeds(["vhosts", "declare", "--name", vh2]); + + // declare a new queue in vhost 1 + run_succeeds([ + "-V", vh1, "queues", "declare", "--name", q1, "--type", "classic", + ]); + + // declare a new queue in vhost 2 + run_succeeds([ + "-V", vh2, "queues", "declare", "--name", q2, "--type", "quorum", + ]); + + // bind the queue -> a pre-existing exchange + run_succeeds([ + "-V", + vh1, + "exchanges", + "bind", + "--source", + "amq.direct", + "--destination-type", + "queue", + "--destination", + q1, + "--routing-key", + "routing_key_queue", + ]); + + // declare an exchange -> exchange binding + run_succeeds([ + "-V", + vh1, + "exchanges", + "bind", + "--source", + "amq.direct", + "--destination-type", + "exchange", + "--destination", + "amq.topic", + "--routing-key", + "routing_key_exchange", + ]); + + await_queue_metric_emission(); + + // list bindings in vhost 1 + run_succeeds(["-V", "bindings_vhost_1", "list", "bindings"]).stdout( + predicate::str::contains("new_queue_1") + .and(predicate::str::contains("routing_key_queue")) + .and(predicate::str::contains("routing_key_exchange")), + ); + + // unbind + run_succeeds([ + "-V", + vh1, + "exchanges", + "unbind", + "--source", + "amq.direct", + "--destination-type", + "queue", + "--destination", + q1, + "--routing-key", + "routing_key_queue", + ]); + + run_succeeds(["-V", "bindings_vhost_1", "list", "bindings"]).stdout( + predicate::str::contains("new_queue_1") + .not() + .and(predicate::str::contains("routing_key_queue")) + .not() + .and(predicate::str::contains("routing_key_exchange")), + ); + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + Ok(()) +} From 5bdc2752e873f12685df559bbdcf568d90097091 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 8 May 2025 00:08:54 -0400 Subject: [PATCH 087/320] New command group: bindings, closes #64 --- CHANGELOG.md | 1 + src/cli.rs | 83 +++++++++++++++++++++++++++++++ src/main.rs | 12 +++++ tests/bindings_tests.rs | 103 ++++++++++++++++++++++++++++++++++++++- tests/exchanges_tests.rs | 4 +- 5 files changed, 199 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5364c0f..4e7bba3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements + * `bindings` is a new command group for operations on bindings * `exchanges` is a new command group for operations on exchanges * `global_parameters` is a new command group for operations on [global runtime parameters](https://www.rabbitmq.com/docs/parameters) * `nodes` is a new command group for operations on nodes diff --git a/src/cli.rs b/src/cli.rs index 7792be5..f8d201b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -45,6 +45,12 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { GITHUB_REPOSITORY_URL ); + let bindings_group = Command::new("bindings") + .about("Operations on bindings") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommand_value_name("binding") + .subcommands(binding_subcommands(pre_flight_settings.clone())); let close_group = Command::new("close") .about("Closes connections") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -275,6 +281,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommands(vhosts_subcommands(pre_flight_settings.clone())); let command_groups = [ + bindings_group, close_group, declare_group, definitions_group, @@ -1248,6 +1255,82 @@ fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [queue_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn binding_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let declare_cmd = Command::new("declare") + .about("Creates a binding between a source exchange and a destination (a queue or an exchange)") + .arg( + Arg::new("source") + .long("source") + .help("source exchange") + .required(true), + ) + .arg( + Arg::new("destination_type") + .long("destination-type") + .help("destination type: exchange or queue") + .required(true) + .value_parser(value_parser!(BindingDestinationType)), + ) + .arg( + Arg::new("destination") + .long("destination") + .help("destination exchange/queue name") + .required(true), + ) + .arg( + Arg::new("routing_key") + .long("routing-key") + .help("routing key") + .required(true), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let delete_cmd = Command::new("delete") + .about("Deletes a binding") + .arg( + Arg::new("source") + .long("source") + .help("source exchange") + .required(true), + ) + .arg( + Arg::new("destination_type") + .long("destination-type") + .help("destination type: exchange or queue") + .required(true), + ) + .arg( + Arg::new("destination") + .long("destination") + .help("destination exchange/queue name") + .required(true), + ) + .arg( + Arg::new("routing_key") + .long("routing-key") + .help("routing key") + .required(true), + ) + .arg( + Arg::new("arguments") + .long("arguments") + .help("additional arguments") + .required(false) + .default_value("{}") + .value_parser(value_parser!(String)), + ); + let list_cmd = Command::new("list").long_about("Lists bindings"); + + [declare_cmd, delete_cmd, list_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn queues_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let declare_cmd = Command::new("declare") .about("Declares a queue or a stream") diff --git a/src/main.rs b/src/main.rs index bcd418f..62b8aa3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -279,6 +279,18 @@ fn dispatch_common_subcommand( res_handler: &mut ResultHandler, ) -> ExitCode { match &pair { + ("bindings", "declare") => { + let result = commands::declare_binding(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("bindings", "delete") => { + let result = commands::delete_binding(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("bindings", "list") => { + let result = commands::list_bindings(client); + res_handler.tabular_result(result) + } ("close", "connection") => { let result = commands::close_connection(client, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index fe5c21b..2284174 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -18,8 +18,8 @@ use crate::test_helpers::*; #[test] fn test_list_bindings() -> Result<(), Box> { - let vh1 = "bindings_vhost_1"; - let vh2 = "bindings_vhost_2"; + let vh1 = "test_list_bindings_1"; + let vh2 = "test_list_bindings_2"; let q1 = "new_queue_1"; let q2 = "new_queue_2"; @@ -100,3 +100,102 @@ fn test_list_bindings() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_bindings_list() -> Result<(), Box> { + let vh1 = "test_bindings_list_1"; + let vh2 = "test_bindings_list_2"; + let q1 = "new_queue_1"; + let q2 = "new_queue_2"; + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + // declare vhost 1 + run_succeeds(["vhosts", "declare", "--name", vh1]); + + // declare vhost 2 + run_succeeds(["vhosts", "declare", "--name", vh2]); + + // declare a new queue in vhost 1 + run_succeeds([ + "-V", vh1, "queues", "declare", "--name", q1, "--type", "classic", + ]); + + // declare a new queue in vhost 2 + run_succeeds([ + "-V", vh2, "queues", "declare", "--name", q2, "--type", "quorum", + ]); + + // bind the queue -> a pre-existing exchange + run_succeeds([ + "-V", + vh1, + "bindings", + "declare", + "--source", + "amq.direct", + "--destination-type", + "queue", + "--destination", + q1, + "--routing-key", + "routing_key_queue", + ]); + + // declare an exchange -> exchange binding + run_succeeds([ + "-V", + vh1, + "bindings", + "declare", + "--source", + "amq.direct", + "--destination-type", + "exchange", + "--destination", + "amq.topic", + "--routing-key", + "routing_key_exchange", + ]); + + await_queue_metric_emission(); + + // list bindings in vhost 1 + run_succeeds(["-V", vh1, "list", "bindings"]).stdout( + predicate::str::contains("new_queue_1") + .and(predicate::str::contains("routing_key_queue")) + .and(predicate::str::contains("routing_key_exchange")), + ); + + // delete a binding + // declare an exchange -> exchange binding + run_succeeds([ + "-V", + vh1, + "bindings", + "declare", + "--source", + "amq.direct", + "--destination-type", + "queue", + "--destination", + q1, + "--routing-key", + "routing_key_queue", + ]); + + // ensure that the deleted binding is no longer listed + run_succeeds(["-V", vh1, "list", "bindings"]).stdout( + predicate::str::contains("new_queue_1") + .not() + .and(predicate::str::contains("routing_key_queue")) + .not() + .and(predicate::str::contains("routing_key_exchange")), + ); + + delete_vhost(vh1).expect("failed to delete a virtual host"); + delete_vhost(vh2).expect("failed to delete a virtual host"); + + Ok(()) +} diff --git a/tests/exchanges_tests.rs b/tests/exchanges_tests.rs index dd66684..6dce59e 100644 --- a/tests/exchanges_tests.rs +++ b/tests/exchanges_tests.rs @@ -276,7 +276,7 @@ fn test_exchanges_bind_and_unbind() -> Result<(), Box> { await_queue_metric_emission(); // list bindings in vhost 1 - run_succeeds(["-V", "bindings_vhost_1", "list", "bindings"]).stdout( + run_succeeds(["-V", vh2, "list", "bindings"]).stdout( predicate::str::contains("new_queue_1") .and(predicate::str::contains("routing_key_queue")) .and(predicate::str::contains("routing_key_exchange")), @@ -298,7 +298,7 @@ fn test_exchanges_bind_and_unbind() -> Result<(), Box> { "routing_key_queue", ]); - run_succeeds(["-V", "bindings_vhost_1", "list", "bindings"]).stdout( + run_succeeds(["-V", vh1, "list", "bindings"]).stdout( predicate::str::contains("new_queue_1") .not() .and(predicate::str::contains("routing_key_queue")) From 46b2fd6e56b0594fc8f9eafc8fc3048c99a2cdc4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 8 May 2025 00:11:10 -0400 Subject: [PATCH 088/320] Change log updates --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7bba3..e5899d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.1.0 (in development) +## v2.2.0 (in development) + +No changes yet. + + +## v2.1.0 (May 8, 2025) ### Enhancements @@ -26,7 +31,7 @@ Instead, the default will be controlled exclusively by RabbitMQ -## v2.0.0 (Mar 31, 2024) +## v2.0.0 (Mar 31, 2025) ### Enhancements From 252a5e41b8e1805b7308708b0cb4d5fda2655efc Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 8 May 2025 00:20:16 -0400 Subject: [PATCH 089/320] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 46cc467..10e7706 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.1.0" +version = "2.2.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 1df0d933bdf8b7c3839cc1a65109233b52fde6a9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 8 May 2025 00:22:23 -0400 Subject: [PATCH 090/320] Bump dev version --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0a54c0a..0c2362d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1460,7 +1460,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.1.0" +version = "2.2.0" dependencies = [ "assert_cmd", "clap", From 58e895bfe58829b657ca4c7ce73bb0ce2411dc3e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 8 May 2025 01:03:44 -0400 Subject: [PATCH 091/320] Bump tabled to 0.19.0 --- Cargo.lock | 72 +++++++++++++++++++++++++++++++++++++++++++++--------- Cargo.toml | 4 +-- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c2362d..120754a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,19 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.16", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -1248,12 +1261,12 @@ dependencies = [ [[package]] name = "papergrid" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b915f831b85d984193fdc3d3611505871dc139b2534530fa01c1a6a6707b6723" +checksum = "30268a8d20c2c0d126b2b6610ab405f16517f6ba9f244d8c59ac2c512a8a1ce7" dependencies = [ + "ahash", "bytecount", - "fnv", "unicode-width", ] @@ -1293,7 +1306,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.25", ] [[package]] @@ -1438,9 +1451,8 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a51edc350622338b1c39fcc3429c2c6e711c735decca2b9ceedc5166db8df7" +version = "0.31.0" +source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#6da3475a5bb74d59c320aca7356d6d91d8a99b77" dependencies = [ "backtrace", "percent-encoding", @@ -1997,19 +2009,20 @@ dependencies = [ [[package]] name = "tabled" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121d8171ee5687a4978d1b244f7d99c43e7385a272185a2f1e1fa4dc0979d444" +checksum = "228d124371171cd39f0f454b58f73ddebeeef3cef3207a82ffea1c29465aea43" dependencies = [ "papergrid", "tabled_derive", + "testing_table", ] [[package]] name = "tabled_derive" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d9946811baad81710ec921809e2af67ad77719418673b2a3794932d57b7538" +checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846" dependencies = [ "heck", "proc-macro-error2", @@ -2037,6 +2050,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "2.0.12" @@ -2313,6 +2335,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -2728,13 +2756,33 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.25", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 10e7706..1a05521 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,14 +17,14 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.30.0", features = [ +rabbitmq_http_client = { git = "https://github.com/michaelklishin/rabbitmq-http-api-rs.git", features = [ "core", "blocking", "tabled", ] } serde = { version = "1.0", features = ["derive", "std"] } serde_json = "1" -tabled = "0.18" +tabled = "0.19" toml = "0.8" color-print = "0.3" thiserror = "2" From 7b07e30220181b5198a1af23fb8ca382bfacbf6b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 11 May 2025 15:03:36 -0400 Subject: [PATCH 092/320] New 'parameters' commands, closes #61 --- CHANGELOG.md | 6 +- Cargo.lock | 229 +++++++++++++----------------- src/cli.rs | 22 ++- src/commands.rs | 13 ++ src/main.rs | 10 ++ tests/runtime_parameters_tests.rs | 19 ++- 6 files changed, 161 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5899d4..2b1211d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## v2.2.0 (in development) -No changes yet. +### Enhancements + + * `parameters list_all` is a new command that lists all runtime parameters across all virtual hosts + * `parameters list_in` is a new command that lists runtime parameters of a given component (type) + in a specific virtual host ## v2.1.0 (May 8, 2025) diff --git a/Cargo.lock b/Cargo.lock index 120754a..b5ed361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,15 +19,15 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.16", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -134,9 +134,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" dependencies = [ "aws-lc-sys", "zeroize", @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen", "cc", @@ -236,9 +236,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.21" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "jobserver", "libc", @@ -291,18 +291,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstream", "anstyle", @@ -610,9 +610,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", @@ -814,21 +814,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -837,31 +838,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -869,67 +850,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" version = "1.0.3" @@ -943,9 +911,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -994,7 +962,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -1028,12 +996,12 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] @@ -1070,9 +1038,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "log" @@ -1080,6 +1048,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.7.4" @@ -1294,6 +1268,15 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1306,7 +1289,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.25", + "zerocopy", ] [[package]] @@ -1382,9 +1365,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", @@ -1402,12 +1385,13 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.2", + "getrandom 0.3.3", + "lru-slab", "rand", "ring", "rustc-hash 2.1.1", @@ -1452,7 +1436,7 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" version = "0.31.0" -source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#6da3475a5bb74d59c320aca7356d6d91d8a99b77" +source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#3bda77ca275af21b86b42d6dbc38576536faea7f" dependencies = [ "backtrace", "percent-encoding", @@ -1518,7 +1502,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -1749,9 +1733,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.2" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring", @@ -2038,7 +2022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix 1.0.7", "windows-sys 0.59.0", @@ -2112,9 +2096,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -2311,12 +2295,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -2720,23 +2698,17 @@ dependencies = [ "bitflags", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -2746,9 +2718,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -2756,33 +2728,13 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "zerocopy-derive 0.8.25", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] @@ -2823,11 +2775,22 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -2836,9 +2799,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/src/cli.rs b/src/cli.rs index f8d201b..1110bbb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1465,7 +1465,13 @@ fn streams_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { + let list_all_cmd = Command::new("list_all") + .long_about("Lists all runtime parameters across all virtual hosts") + .after_help(color_print::cformat!( + "Doc guide: {}", + RUNTIME_PARAMETER_GUIDE_URL + )); let list_cmd = Command::new("list") .arg( Arg::new("component") @@ -1478,6 +1484,18 @@ fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3 "Doc guide: {}", RUNTIME_PARAMETER_GUIDE_URL )); + let list_in_cmd = Command::new("list_in") + .arg( + Arg::new("component") + .long("component") + .help("component (for example: federation-upstream, vhost-limits)") + .required(true), + ) + .long_about("Lists runtime parameters") + .after_help(color_print::cformat!( + "Doc guide: {}", + RUNTIME_PARAMETER_GUIDE_URL + )); let set_cmd = Command::new("set") .alias("declare") @@ -1521,7 +1539,7 @@ fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3 .required(true), ); - [clear_cmd, list_cmd, set_cmd] + [clear_cmd, list_all_cmd, list_cmd, list_in_cmd, set_cmd] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/commands.rs b/src/commands.rs index 7c5b046..fea9cdf 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -164,6 +164,10 @@ pub fn list_permissions(client: APIClient) -> ClientResult ClientResult> { + client.list_runtime_parameters() +} + pub fn list_parameters( client: APIClient, vhost: &str, @@ -180,6 +184,15 @@ pub fn list_parameters( } } +pub fn list_parameters_of_component_in( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult> { + let component = command_args.get_one::("component").unwrap(); + client.list_runtime_parameters_of_component_in(component, vhost) +} + pub fn list_global_parameters( client: APIClient, ) -> ClientResult> { diff --git a/src/main.rs b/src/main.rs index 62b8aa3..3273fbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -623,10 +623,20 @@ fn dispatch_common_subcommand( let result = commands::delete_parameter(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("parameters", "list_all") => { + let result = + commands::list_all_parameters(client); + res_handler.tabular_result(result) + } ("parameters", "list") => { let result = commands::list_parameters(client, &vhost, second_level_args); res_handler.tabular_result(result) } + ("parameters", "list_in") => { + let result = + commands::list_parameters_of_component_in(client, &vhost, second_level_args); + res_handler.tabular_result(result) + } ("parameters", "set") => { let result = commands::declare_parameter(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index 99cf96d..f8c18df 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -87,6 +87,13 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "{\"uri\":\"amqp://target.hostname\",\"expires\":3600000}", ]); + run_succeeds([ + "-V", + vh, + "parameters", + "list_all" + ]).stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + run_succeeds([ "-V", vh, @@ -94,8 +101,16 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "list", "--component", "federation-upstream", - ]) - .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + ]).stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + + run_succeeds([ + "-V", + vh, + "parameters", + "list_in", + "--component", + "federation-upstream", + ]).stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); run_succeeds([ "-V", From fe1a8d911cb48109ca5b5b5b83396f216fd02da9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 11 May 2025 15:06:01 -0400 Subject: [PATCH 093/320] --vhost does not make sense in this test --- src/commands.rs | 2 +- src/main.rs | 3 +-- tests/runtime_parameters_tests.rs | 14 ++++++-------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index fea9cdf..ee744cc 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -166,7 +166,7 @@ pub fn list_permissions(client: APIClient) -> ClientResult ClientResult> { client.list_runtime_parameters() -} +} pub fn list_parameters( client: APIClient, diff --git a/src/main.rs b/src/main.rs index 3273fbd..0f3d96c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -624,8 +624,7 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("parameters", "list_all") => { - let result = - commands::list_all_parameters(client); + let result = commands::list_all_parameters(client); res_handler.tabular_result(result) } ("parameters", "list") => { diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index f8c18df..ef297df 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -87,12 +87,8 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "{\"uri\":\"amqp://target.hostname\",\"expires\":3600000}", ]); - run_succeeds([ - "-V", - vh, - "parameters", - "list_all" - ]).stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); run_succeeds([ "-V", @@ -101,7 +97,8 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "list", "--component", "federation-upstream", - ]).stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + ]) + .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); run_succeeds([ "-V", @@ -110,7 +107,8 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "list_in", "--component", "federation-upstream", - ]).stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + ]) + .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); run_succeeds([ "-V", From 146ad8b1790e90a4a42f07f63801f8c7a2573a2d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 17 May 2025 19:33:18 -0400 Subject: [PATCH 094/320] Update dependencies --- Cargo.lock | 33 +++++++++++++++++---------------- Cargo.toml | 3 +-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5ed361..1899629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bstr" @@ -236,9 +236,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.22" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "jobserver", "libc", @@ -486,9 +486,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1436,7 +1436,8 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" version = "0.31.0" -source = "git+https://github.com/michaelklishin/rabbitmq-http-api-rs.git#3bda77ca275af21b86b42d6dbc38576536faea7f" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43d945d7f9bfa53662536a301e91938f0ee7beb349245c956954d7dd114e400" dependencies = [ "backtrace", "percent-encoding", @@ -2017,9 +2018,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom 0.3.3", @@ -2457,15 +2458,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.0", + "windows-strings 0.4.1", ] [[package]] @@ -2509,9 +2510,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" dependencies = [ "windows-link", ] @@ -2527,9 +2528,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" dependencies = [ "windows-link", ] diff --git a/Cargo.toml b/Cargo.toml index 1a05521..45c6ee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { git = "https://github.com/michaelklishin/rabbitmq-http-api-rs.git", features = [ - "core", +rabbitmq_http_client = { version = "0.31.0", features = [ "blocking", "tabled", ] } From eb43d6f0b7bb89925f20dcc32ef63d5d496c98b5 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 18 May 2025 01:40:26 -0400 Subject: [PATCH 095/320] New command: 'policies update_definition' For updating a specific definition value in a given policy. --- src/cli.rs | 24 ++++++++++++++++++++++- src/commands.rs | 28 ++++++++++++++++++++++++++- src/main.rs | 4 ++++ tests/policies_tests.rs | 42 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 1110bbb..c437716 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1585,7 +1585,7 @@ fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { +fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { let declare_cmd = Command::new("declare") .about("Creates or updates a policy") .after_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) @@ -1662,12 +1662,34 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] .help("target type, one of 'queues', 'streams', 'exchanges'"), ); + let update_cmd = Command::new("update_definition") + .about("Updates a policy definition key") + .arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ) + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("policy definition key to update") + .required(true), + ) + .arg( + Arg::new("definition_value") + .long("new-value") + .help("new definition value to set") + .required(true), + ); + [ declare_cmd, delete_cmd, list_cmd, list_in_cmd, list_matching_cmd, + update_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/commands.rs b/src/commands.rs index ee744cc..6587071 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -24,7 +24,7 @@ use rabbitmq_http_client::requests::{ Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, EnforcedLimitParams, ExchangeFederationParams, FEDERATION_UPSTREAM_COMPONENT, - FederationResourceCleanupMode, FederationUpstreamParams, QueueFederationParams, + FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, }; use std::fs; @@ -1028,6 +1028,32 @@ pub fn declare_operator_policy( client.declare_operator_policy(¶ms) } +pub fn update_policy_definition( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").cloned().unwrap(); + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + let value = command_args + .get_one::("definition_value") + .cloned() + .unwrap(); + let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON value: {}", value, err); + process::exit(1); + }); + + let mut policy = client.get_policy(vhost, &name)?; + policy.insert_definition_key(key, parsed_value); + + let params = PolicyParams::from(&policy); + client.declare_policy(¶ms) +} + pub fn declare_parameter( client: APIClient, vhost: &str, diff --git a/src/main.rs b/src/main.rs index 0f3d96c..28cf56b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -674,6 +674,10 @@ fn dispatch_common_subcommand( let result = commands::list_matching_policies_in(client, &vhost, &name, typ); res_handler.tabular_result(result) } + ("policies", "update_definition") => { + let result = commands::update_policy_definition(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("publish", "message") => { let result = commands::publish_message(client, &vhost, second_level_args); res_handler.single_value_result(result) diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index 69b2b84..a1e5fb4 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -340,3 +340,45 @@ fn test_policies_matching_objects() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_policies_declare_list_update_definition_and_delete() +-> Result<(), Box> { + let policy_name = "test_policies_declare_list_update_definition_and_delete"; + + run_succeeds([ + "policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20}", + ]); + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + + run_succeeds([ + "policies", + "update_definition", + "--name", + policy_name, + "--definition-key", + "max-length", + "--new-value", + "131", + ]); + + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("131"))); + + run_succeeds(["policies", "delete", "--name", policy_name]); + run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} From 050087446ddc72cd598dcd75d31aeaaa0ad60256 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 18 May 2025 04:03:01 -0400 Subject: [PATCH 096/320] More policies commands * Operations on definition keys of an individual policy * Operations on definition keys of all policies in a virtual host --- src/cli.rs | 42 ++++++++++++++++++++++++- src/commands.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 14 +++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index c437716..a6d2e8f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1585,7 +1585,7 @@ fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { +fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 9] { let declare_cmd = Command::new("declare") .about("Creates or updates a policy") .after_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) @@ -1637,6 +1637,28 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] .required(true), ); + let delete_definition_key_cmd = Command::new("delete_definition_key") + .about("Deletes a definition key from a policy, unless it is the only key") + .arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ) + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("definition key"), + ); + + let delete_definition_key_from_all_in_cmd = Command::new("delete_definition_key_from_all_in") + .about("Deletes a definition key from all policies in a virtual host, unless it is the only key") + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("definition key") + ); + let list_in_cmd = Command::new("list_in") .about("Lists policies in a specific virtual host") .arg( @@ -1683,13 +1705,31 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] .required(true), ); + let update_all_in_cmd = Command::new("update_definitions_of_all_in") + .about("Updates a definition key in all policies in a virtual host") + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("policy definition key to update") + .required(true), + ) + .arg( + Arg::new("definition_value") + .long("new-value") + .help("new definition value to set") + .required(true), + ); + [ declare_cmd, delete_cmd, + delete_definition_key_cmd, + delete_definition_key_from_all_in_cmd, list_cmd, list_in_cmd, list_matching_cmd, update_cmd, + update_all_in_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/commands.rs b/src/commands.rs index 6587071..3182c60 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -33,8 +33,10 @@ use std::process; use rabbitmq_http_client::commons::BindingDestinationType; use rabbitmq_http_client::commons::QueueType; +use rabbitmq_http_client::responses::PolicyDefinitionOps; use rabbitmq_http_client::transformers::TransformationChain; use rabbitmq_http_client::{password_hashing, requests, responses}; +use serde_json::Value; type APIClient<'a> = Client<&'a str, &'a str, &'a str>; @@ -1047,8 +1049,83 @@ pub fn update_policy_definition( process::exit(1); }); - let mut policy = client.get_policy(vhost, &name)?; - policy.insert_definition_key(key, parsed_value); + update_policy_definition_with(&client, vhost, &name, &key, &parsed_value) +} + +pub fn update_all_policy_definitions_in( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let pols = client.list_policies_in(vhost)?; + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + let value = command_args + .get_one::("definition_value") + .cloned() + .unwrap(); + let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON value: {}", value, err); + process::exit(1); + }); + + for pol in pols { + update_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? + } + + Ok(()) +} + +pub fn delete_policy_definition( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").cloned().unwrap(); + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + + let pol = client.get_policy(vhost, &name)?; + let updated_pol = pol.without_keys(vec![&key]); + + let params = PolicyParams::from(&updated_pol); + client.declare_policy(¶ms) +} + +pub fn delete_policy_definition_key_in( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let pols = client.list_policies_in(vhost)?; + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + + for pol in pols { + let updated_pol = pol.without_keys(vec![&key]); + + let params = PolicyParams::from(&updated_pol); + client.declare_policy(¶ms)? + } + + Ok(()) +} + +fn update_policy_definition_with( + client: &APIClient, + vhost: &str, + name: &str, + key: &str, + parsed_value: &Value, +) -> ClientResult<()> { + let mut policy = client.get_policy(vhost, name)?; + policy.insert_definition_key(key.to_owned(), parsed_value.clone()); let params = PolicyParams::from(&policy); client.declare_policy(¶ms) diff --git a/src/main.rs b/src/main.rs index 28cf56b..e9f0b68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -648,6 +648,15 @@ fn dispatch_common_subcommand( let result = commands::delete_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("policies", "delete_definition_key") => { + let result = commands::delete_policy_definition(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("policies", "delete_definition_key_from_all_in") => { + let result = + commands::delete_policy_definition_key_in(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("policies", "list") => { let result = commands::list_policies(client); res_handler.tabular_result(result) @@ -678,6 +687,11 @@ fn dispatch_common_subcommand( let result = commands::update_policy_definition(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("policies", "update_definitions_of_all_in") => { + let result = + commands::update_all_policy_definitions_in(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("publish", "message") => { let result = commands::publish_message(client, &vhost, second_level_args); res_handler.single_value_result(result) From 5893801c95574033c7b63b8da1e161d5672adb83 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 18 May 2025 04:35:36 -0400 Subject: [PATCH 097/320] Tests for policy key manipulation --- tests/policies_tests.rs | 131 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index a1e5fb4..b095519 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -382,3 +382,134 @@ fn test_policies_declare_list_update_definition_and_delete() Ok(()) } + +#[test] +fn test_policies_individual_policy_key_manipulation() -> Result<(), Box> { + let policy_name = "test_policies_individual_policy_key_manipulation"; + + run_succeeds([ + "policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20, \"max-length-bytes\": 99999999}", + ]); + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + + run_succeeds([ + "policies", + "update_definition", + "--name", + policy_name, + "--definition-key", + "max-length", + "--new-value", + "131", + ]); + + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("131"))); + + run_succeeds([ + "policies", + "delete_definition_key", + "--name", + policy_name, + "--definition-key", + "max-length", + ]); + + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("99999999"))); + + run_succeeds(["policies", "list"]).stdout(predicate::str::contains("131").not()); + + run_succeeds(["policies", "delete", "--name", policy_name]); + run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} + +#[test] +fn test_policies_bulk_policy_key_manipulation() -> Result<(), Box> { + let policy1_name = "test_policies_bulk_policy_key_manipulation-1"; + let policy2_name = "test_policies_bulk_policy_key_manipulation-2"; + + run_succeeds([ + "policies", + "declare", + "--name", + policy1_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20, \"max-length-bytes\": 99999999}", + ]); + run_succeeds([ + "policies", + "declare", + "--name", + policy2_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 120, \"max-length-bytes\": 333333333}", + ]); + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy1_name).and(predicate::str::contains("20"))); + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy1_name).and(predicate::str::contains("333333333"))); + + run_succeeds([ + "policies", + "update_definitions_of_all_in", + "--definition-key", + "max-length", + "--new-value", + "272", + ]); + + run_succeeds(["policies", "list"]).stdout( + predicate::str::contains(policy1_name) + .and(predicate::str::contains("272")) + .and(predicate::str::contains("120").not()), + ); + + run_succeeds([ + "policies", + "delete_definition_key_from_all_in", + "--definition-key", + "max-length", + ]); + + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy1_name).and(predicate::str::contains("333333333"))); + + run_succeeds(["policies", "list"]).stdout(predicate::str::contains("272").not()); + + run_succeeds(["policies", "delete", "--name", policy1_name]); + run_succeeds(["policies", "delete", "--name", policy2_name]); + run_succeeds(["policies", "list"]).stdout( + predicate::str::contains(policy1_name) + .not() + .and(predicate::str::contains(policy2_name).not()), + ); + + Ok(()) +} From a214cd67ada5651933f804fcc990ea78f0fb9e2e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 23 May 2025 22:30:10 -0400 Subject: [PATCH 098/320] cargo update --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1899629..babd522 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", "futures-channel", @@ -861,9 +861,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" @@ -2458,15 +2458,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.1", + "windows-strings 0.4.2", ] [[package]] @@ -2510,9 +2510,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] @@ -2528,9 +2528,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] From f7e54fdb7c686601a751a5ea768431e14cae9081 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 23 May 2025 22:33:21 -0400 Subject: [PATCH 099/320] 'policies patch', a new command for partial policy definition updates --- CHANGELOG.md | 4 ++++ src/cli.rs | 18 ++++++++++++++++- src/commands.rs | 25 +++++++++++++++++++++++ src/main.rs | 4 ++++ tests/policies_tests.rs | 44 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b1211d..b37916e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### Enhancements + * `policies set` and `policies update` are two new aliases for `policies declare`. The former follows the naming + used by `rabbitmqctl` and the latter reflects the fact that the command can be used to update an existing policy, + in particular, to override its definition + * `policies patch` is a new command that updates a policy definition by merging the provided definition with the existing one * `parameters list_all` is a new command that lists all runtime parameters across all virtual hosts * `parameters list_in` is a new command that lists runtime parameters of a given component (type) in a specific virtual host diff --git a/src/cli.rs b/src/cli.rs index a6d2e8f..1aa6bf1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1585,8 +1585,9 @@ fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 9] { +fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] { let declare_cmd = Command::new("declare") + .visible_aliases(vec!["update", "set"]) .about("Creates or updates a policy") .after_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) .arg( @@ -1684,6 +1685,20 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 9] .help("target type, one of 'queues', 'streams', 'exchanges'"), ); + let patch_cmd = Command::new("patch") + .about("Merges a set of keys into existing policy definitions") + .arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ) + .arg( + Arg::new("definition") + .long("definition") + .help("policy definition changes to merge into the existing ones"), + ); + let update_cmd = Command::new("update_definition") .about("Updates a policy definition key") .arg( @@ -1728,6 +1743,7 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 9] list_cmd, list_in_cmd, list_matching_cmd, + patch_cmd, update_cmd, update_all_in_cmd, ] diff --git a/src/commands.rs b/src/commands.rs index 3182c60..6f8f030 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1052,6 +1052,31 @@ pub fn update_policy_definition( update_policy_definition_with(&client, vhost, &name, &key, &parsed_value) } +pub fn patch_policy_definition( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").cloned().unwrap(); + let value = command_args + .get_one::("definition") + .cloned() + .unwrap(); + let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON value: {}", value, err); + process::exit(1); + }); + + let mut pol = client.get_policy(vhost, &name)?; + let patch = parsed_value.as_object().unwrap(); + for (k, v) in patch.iter() { + pol.insert_definition_key(k.clone(), v.clone()); + } + + let params = PolicyParams::from(&pol); + client.declare_policy(¶ms) +} + pub fn update_all_policy_definitions_in( client: APIClient, vhost: &str, diff --git a/src/main.rs b/src/main.rs index e9f0b68..a285cc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -683,6 +683,10 @@ fn dispatch_common_subcommand( let result = commands::list_matching_policies_in(client, &vhost, &name, typ); res_handler.tabular_result(result) } + ("policies", "patch") => { + let result = commands::patch_policy_definition(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("policies", "update_definition") => { let result = commands::update_policy_definition(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index b095519..476c6c4 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -513,3 +513,47 @@ fn test_policies_bulk_policy_key_manipulation() -> Result<(), Box Result<(), Box> { + let policy_name = "test_policies_patch_definition"; + + run_succeeds([ + "policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20, \"max-length-bytes\": 99999999}", + ]); + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + + run_succeeds([ + "policies", + "patch", + "--name", + policy_name, + "--definition", + "{\"max-length\": 29, \"max-length-bytes\": 8888888888}", + ]); + + run_succeeds(["policies", "list"]).stdout( + predicate::str::contains(policy_name) + .and(predicate::str::contains("8888888888")) + .and(predicate::str::contains("29")), + ); + + run_succeeds(["policies", "list"]).stdout(predicate::str::contains("99999999").not()); + + run_succeeds(["policies", "delete", "--name", policy_name]); + run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} From 26182c9ca4818040414c8909447ce56a7516079f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 26 May 2025 05:20:58 +0400 Subject: [PATCH 100/320] carog update --- Cargo.lock | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index babd522..f2568b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,12 +95,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -236,9 +236,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.23" +version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ "jobserver", "libc", @@ -736,11 +736,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", @@ -1102,13 +1101,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1174,6 +1173,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "openssl" version = "0.10.72" @@ -1746,9 +1751,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -2122,9 +2127,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", From c0114b8e562fa5bd69a6e49fb646757a5ceab70a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 26 May 2025 05:27:11 +0400 Subject: [PATCH 101/320] New command group: connections It reuses the commands we already have under 'list' and 'close', and groups them together. --- CHANGELOG.md | 1 + src/cli.rs | 57 ++++++++++++++++++++++++++++++++++++-- src/main.rs | 16 +++++++++++ tests/connections_tests.rs | 25 +++++++++++++++-- 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37916e..e6a6a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements + * `connections` is a new command group for operations on connections * `policies set` and `policies update` are two new aliases for `policies declare`. The former follows the naming used by `rabbitmqctl` and the latter reflects the fact that the command can be used to update an existing policy, in particular, to override its definition diff --git a/src/cli.rs b/src/cli.rs index 1aa6bf1..18a4c38 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -56,6 +56,11 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(close_subcommands(pre_flight_settings.clone())); + let connections_group = Command::new("connections") + .about("Operations on connections") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(connections_subcommands(pre_flight_settings.clone())); let declare_group = Command::new("declare") .about("Creates or declares objects") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -283,6 +288,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { let command_groups = [ bindings_group, close_group, + connections_group, declare_group, definitions_group, delete_group, @@ -574,7 +580,7 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { .short('u') .long("username") .required(true) - .help("Name of the user whose connections to list"), + .help("Name of the user whose connections should be listed"), ) .long_about("Lists client connections that authenticated with a specific username") .after_help(color_print::cformat!( @@ -1842,6 +1848,53 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { + let close_connection = Command::new("close") + .about("Closes a client connection") + .arg( + Arg::new("name") + .long("name") + .help("connection name (identifying string)") + .required(true), + ); + let close_user_connections = Command::new("close_of_user") + .about("Closes all connections that are authenticated with a specific username") + .arg( + Arg::new("username") + .short('u') + .long("username") + .help("Name of the user whose connections should be closed") + .required(true), + ); + let list_cmd = Command::new("list") + .long_about("Lists client connections") + .after_help(color_print::cformat!( + "Doc guide: {}", + CONNECTION_GUIDE_URL + )); + let list_user_connections_cmd = Command::new("list_of_user") + .arg( + Arg::new("username") + .short('u') + .long("username") + .required(true) + .help("Name of the user whose connections should be listed"), + ) + .long_about("Lists client connections that are authenticated with a specific username") + .after_help(color_print::cformat!( + "Doc guide: {}", + CONNECTION_GUIDE_URL + )); + + [ + close_connection, + close_user_connections, + list_cmd, + list_user_connections_cmd, + ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn definitions_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let export_cmd = Command::new("export") .about("Export cluster-wide definitions") @@ -2365,7 +2418,7 @@ pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] .short('u') .long("username") .required(true) - .help("Name of the user whose connections to list"), + .help("Name of the user whose connections should be listed"), ) .long_about("Lists client connections that authenticated with a specific username") .after_help(color_print::cformat!( diff --git a/src/main.rs b/src/main.rs index a285cc5..f3e5cee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -299,6 +299,22 @@ fn dispatch_common_subcommand( let result = commands::close_user_connections(client, second_level_args); res_handler.no_output_on_success(result); } + ("connections", "close") => { + let result = commands::close_connection(client, second_level_args); + res_handler.no_output_on_success(result); + } + ("connections", "close_of_user") => { + let result = commands::close_user_connections(client, second_level_args); + res_handler.no_output_on_success(result); + } + ("connections", "list") => { + let result = commands::list_connections(client); + res_handler.tabular_result(result) + } + ("connections", "list_of_user") => { + let result = commands::list_user_connections(client, second_level_args); + res_handler.tabular_result(result) + } ("declare", "binding") => { let result = commands::declare_binding(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/connections_tests.rs b/tests/connections_tests.rs index 665f52d..2a0f26c 100644 --- a/tests/connections_tests.rs +++ b/tests/connections_tests.rs @@ -16,7 +16,14 @@ mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_connections() -> Result<(), Box> { +fn test_list_connections1() -> Result<(), Box> { + run_succeeds(["connections", "list"]); + + Ok(()) +} + +#[test] +fn test_list_connections2() -> Result<(), Box> { run_succeeds(["list", "connections"]); Ok(()) @@ -30,7 +37,21 @@ fn test_list_connections_table_styles() -> Result<(), Box } #[test] -fn test_list_user_connections() -> Result<(), Box> { +fn test_list_user_connections1() -> Result<(), Box> { + run_succeeds([ + "--table-style", + "markdown", + "connections", + "list_of_user", + "--username", + "monitoring", + ]); + + Ok(()) +} + +#[test] +fn test_list_user_connections2() -> Result<(), Box> { run_succeeds([ "--table-style", "markdown", From 941fb9a45a0e17b809da7bd0c0e5fa21a7328f2e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 26 May 2025 09:20:03 +0400 Subject: [PATCH 102/320] Test interference fixes --- tests/policies_tests.rs | 66 +++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index 476c6c4..b89105e 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -101,14 +101,18 @@ fn test_policies_declare_list_and_delete() -> Result<(), Box Result<(), Box> { - let vh = "rabbitmqadmin.vh.policies.1"; - run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); - run_succeeds(["declare", "vhost", "--name", vh]); + let vh1 = "rabbitmqadmin.test_policies_in.1"; + run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh1]); + + let vh2 = "rabbitmqadmin.test_policies_in.2"; + run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh2]); let policy_name = "test_policies_in"; run_succeeds([ "--vhost", - vh, + vh1, "policies", "declare", "--name", @@ -123,15 +127,16 @@ fn test_policies_in() -> Result<(), Box> { "{\"max-length\": 20}", ]); - run_succeeds(["--vhost", vh, "policies", "list_in"]) + run_succeeds(["--vhost", vh1, "policies", "list_in"]) .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("98"))); - run_succeeds(["--vhost", "/", "policies", "list_in"]) + run_succeeds(["--vhost", vh2, "policies", "list_in"]) .stdout(predicate::str::contains(policy_name).not()); - run_succeeds(["--vhost", vh, "policies", "delete", "--name", policy_name]); - run_succeeds(["--vhost", vh, "policies", "list_in"]) + run_succeeds(["--vhost", vh1, "policies", "delete", "--name", policy_name]); + run_succeeds(["--vhost", vh1, "policies", "list_in"]) .stdout(predicate::str::contains(policy_name).not()); - run_succeeds(["delete", "vhost", "--name", vh]); + run_succeeds(["delete", "vhost", "--name", vh1]); + run_succeeds(["delete", "vhost", "--name", vh2]); Ok(()) } @@ -440,10 +445,20 @@ fn test_policies_individual_policy_key_manipulation() -> Result<(), Box Result<(), Box> { + let vh1 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.1"; + let vh2 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.2"; + + run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh1]); + run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh2]); + let policy1_name = "test_policies_bulk_policy_key_manipulation-1"; let policy2_name = "test_policies_bulk_policy_key_manipulation-2"; run_succeeds([ + "--vhost", + vh1, "policies", "declare", "--name", @@ -458,6 +473,8 @@ fn test_policies_bulk_policy_key_manipulation() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { + let vh1 = "rabbitmqadmin.test_policies_patch_definition.1"; + run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh1]); + let policy_name = "test_policies_patch_definition"; run_succeeds([ + "--vhost", + vh1, "policies", "declare", "--name", @@ -532,10 +568,12 @@ fn test_policies_patch_definition() -> Result<(), Box> { "--definition", "{\"max-length\": 20, \"max-length-bytes\": 99999999}", ]); - run_succeeds(["policies", "list"]) + run_succeeds(["--vhost", vh1, "policies", "list"]) .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); run_succeeds([ + "--vhost", + vh1, "policies", "patch", "--name", @@ -552,8 +590,10 @@ fn test_policies_patch_definition() -> Result<(), Box> { run_succeeds(["policies", "list"]).stdout(predicate::str::contains("99999999").not()); - run_succeeds(["policies", "delete", "--name", policy_name]); + run_succeeds(["--vhost", vh1, "policies", "delete", "--name", policy_name]); run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); + Ok(()) } From 8d35a1f9ad8b6f4edeae8a9048079abaed8ad08d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 26 May 2025 09:23:34 +0400 Subject: [PATCH 103/320] New command group: channels --- CHANGELOG.md | 1 + src/cli.rs | 17 +++++++++++++++++ src/main.rs | 6 ++++++ tests/channels_tests.rs | 30 ++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 tests/channels_tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a6a89..b5ebe00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements * `connections` is a new command group for operations on connections + * `channels` is a new command group for operations on channels * `policies set` and `policies update` are two new aliases for `policies declare`. The former follows the naming used by `rabbitmqctl` and the latter reflects the fact that the command can be used to update an existing policy, in particular, to override its definition diff --git a/src/cli.rs b/src/cli.rs index 18a4c38..053a75c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -51,6 +51,11 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("binding") .subcommands(binding_subcommands(pre_flight_settings.clone())); + let channels_group = Command::new("channels") + .about("Operations on channels") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .subcommands(channels_subcommands(pre_flight_settings.clone())); let close_group = Command::new("close") .about("Closes connections") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -287,6 +292,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { let command_groups = [ bindings_group, + channels_group, close_group, connections_group, declare_group, @@ -1848,6 +1854,17 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn channels_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { + let list_cmd = Command::new("list") + .long_about("Lists all channels across all virtual hosts") + .after_help(color_print::cformat!( + "Doc guide: {}", + "https://www.rabbitmq.com/docs/channels" + )); + + [list_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let close_connection = Command::new("close") .about("Closes a client connection") diff --git a/src/main.rs b/src/main.rs index f3e5cee..148ae1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. #![allow(clippy::result_large_err)] +#![allow(clippy::unnecessary_unwrap)] +#![allow(clippy::collapsible_if)] use clap::ArgMatches; use errors::CommandRunError; @@ -291,6 +293,10 @@ fn dispatch_common_subcommand( let result = commands::list_bindings(client); res_handler.tabular_result(result) } + ("channels", "list") => { + let result = commands::list_channels(client); + res_handler.tabular_result(result) + } ("close", "connection") => { let result = commands::close_connection(client, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/channels_tests.rs b/tests/channels_tests.rs new file mode 100644 index 0000000..88c41cc --- /dev/null +++ b/tests/channels_tests.rs @@ -0,0 +1,30 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_helpers; +use crate::test_helpers::*; + +#[test] +fn test_list_channels1() -> Result<(), Box> { + run_succeeds(["channels", "list"]); + + Ok(()) +} + +#[test] +fn test_list_channels2() -> Result<(), Box> { + run_succeeds(["list", "channels"]); + + Ok(()) +} From 5cd6f436fa7304fd20d725c1dd21c5d8d6b56349 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 26 May 2025 11:25:35 +0400 Subject: [PATCH 104/320] New command group: operator_policies --- CHANGELOG.md | 2 + src/cli.rs | 178 ++++++++++ src/commands.rs | 164 +++++++++ src/main.rs | 67 ++++ tests/operator_policies_tests.rs | 551 +++++++++++++++++++++++++++++++ 5 files changed, 962 insertions(+) create mode 100644 tests/operator_policies_tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ebe00..72ca079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * `connections` is a new command group for operations on connections * `channels` is a new command group for operations on channels + * `operator_policies` is a new command group for working with operator policies. + It matches the `policies` group but acts on [operator policies](https://www.rabbitmq.com/docs/policies#operator-policies) * `policies set` and `policies update` are two new aliases for `policies declare`. The former follows the naming used by `rabbitmqctl` and the latter reflects the fact that the command can be used to update an existing policy, in particular, to override its definition diff --git a/src/cli.rs b/src/cli.rs index 053a75c..3556dac 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -190,6 +190,16 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(nodes_subcommands(pre_flight_settings.clone())); + let operator_policies_group = Command::new("operator_policies") + .about("Operations on operator policies") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + POLICY_GUIDE_URL + )) + .subcommand_value_name("operator policy") + .subcommands(operator_policies_subcommands(pre_flight_settings.clone())); let parameters_group = Command::new("parameters") .about("Operations on runtime parameters") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -309,6 +319,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { import_group, list_group, nodes_group, + operator_policies_group, parameters_group, policies_group, publish_group, @@ -1597,6 +1608,173 @@ fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] { + let declare_cmd = Command::new("declare") + .visible_aliases(vec!["update", "set"]) + .about("Creates or updates an operator policy") + .after_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) + .arg( + Arg::new("name") + .long("name") + .help("operator policy name") + .required(true), + ) + .arg( + Arg::new("pattern") + .long("pattern") + .help("the pattern that is used to match entity (queue, stream, exchange) names") + .required(true), + ) + .arg( + Arg::new("apply_to") + .long("apply-to") + .alias("applies-to") + .help("entities to apply to (queues, classic_queues, quorum_queues, streams, exchanges, all)") + .value_parser(value_parser!(PolicyTarget)) + .required(true), + ) + .arg( + Arg::new("priority") + .long("priority") + .help("operator policy priority (only the policy with the highest priority is effective)") + .required(false) + .default_value("0"), + ) + .arg( + Arg::new("definition") + .long("definition") + .help("operator policy definition") + .required(true), + ); + + let list_cmd = Command::new("list") + .long_about("Lists operator policies") + .after_help(color_print::cformat!( + "Doc guide: {}", + POLICY_GUIDE_URL + )); + + let delete_cmd = Command::new("delete") + .about("Deletes an operator policy") + .arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ); + + let delete_definition_key_cmd = Command::new("delete_definition_key") + .about("Deletes a definition key from an operator policy, unless it is the only key") + .arg( + Arg::new("name") + .long("name") + .help("operator policy name") + .required(true), + ) + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("definition key"), + ); + + let delete_definition_key_from_all_in_cmd = Command::new("delete_definition_key_from_all_in") + .about("Deletes a definition key from all operator policies in a virtual host, unless it is the only key") + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("definition key") + ); + + let list_in_cmd = Command::new("list_in") + .about("Lists operator policies in a specific virtual host") + .arg( + Arg::new("apply_to") + .long("apply-to") + .alias("applies-to") + .value_parser(value_parser!(PolicyTarget)), + ); + + let list_matching_cmd = Command::new("list_matching_object") + .about("Lists operator policies that match an object (queue, stream, exchange) name") + .arg( + Arg::new("name") + .long("name") + .help("name to verify") + .required(true), + ) + .arg( + Arg::new("type") + .long("type") + .value_parser(value_parser!(PolicyTarget)) + .required(true) + .help("target type, one of 'queues', 'streams', 'exchanges'"), + ); + + let patch_cmd = Command::new("patch") + .about("Merges a set of keys into existing operator policy definitions") + .arg( + Arg::new("name") + .long("name") + .help("operator policy name") + .required(true), + ) + .arg( + Arg::new("definition") + .long("definition") + .help("operator policy definition changes to merge into the existing ones"), + ); + + let update_cmd = Command::new("update_definition") + .about("Updates an operator policy definition key") + .arg( + Arg::new("name") + .long("name") + .help("operator policy name") + .required(true), + ) + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("operator policy definition key to update") + .required(true), + ) + .arg( + Arg::new("definition_value") + .long("new-value") + .help("new definition value to set") + .required(true), + ); + + let update_all_in_cmd = Command::new("update_definitions_of_all_in") + .about("Updates a definition key in all operator policies in a virtual host") + .arg( + Arg::new("definition_key") + .long("definition-key") + .help("operator policy definition key to update") + .required(true), + ) + .arg( + Arg::new("definition_value") + .long("new-value") + .help("new operator definition value to set") + .required(true), + ); + + [ + declare_cmd, + delete_cmd, + delete_definition_key_cmd, + delete_definition_key_from_all_in_cmd, + list_cmd, + list_in_cmd, + list_matching_cmd, + patch_cmd, + update_cmd, + update_all_in_cmd, + ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] { let declare_cmd = Command::new("declare") .visible_aliases(vec!["update", "set"]) diff --git a/src/commands.rs b/src/commands.rs index 6f8f030..d946ab4 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -147,6 +147,44 @@ pub fn list_operator_policies(client: APIClient) -> ClientResult ClientResult> { + client.list_operator_policies_in(vhost) +} + +pub fn list_operator_policies_in_and_applying_to( + client: APIClient, + vhost: &str, + apply_to: PolicyTarget, +) -> ClientResult> { + let policies = client.list_operator_policies_in(vhost)?; + let filtered = policies + .iter() + .filter(|&pol| apply_to.does_apply_to(pol.apply_to.clone())) + .cloned() + .collect(); + + Ok(filtered) +} + +pub fn list_matching_operator_policies_in( + client: APIClient, + vhost: &str, + name: &str, + typ: PolicyTarget, +) -> ClientResult> { + let candidates = list_operator_policies_in_and_applying_to(client, vhost, typ.clone())?; + let matching = candidates + .iter() + .filter(|&pol| pol.does_match_name(vhost, name, typ.clone())) + .cloned() + .collect(); + + Ok(matching) +} + pub fn list_queues(client: APIClient, vhost: &str) -> ClientResult> { client.list_queues_in(vhost) } @@ -1052,6 +1090,28 @@ pub fn update_policy_definition( update_policy_definition_with(&client, vhost, &name, &key, &parsed_value) } +pub fn update_operator_policy_definition( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").cloned().unwrap(); + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + let value = command_args + .get_one::("definition_value") + .cloned() + .unwrap(); + let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON value: {}", value, err); + process::exit(1); + }); + + update_operator_policy_definition_with(&client, vhost, &name, &key, &parsed_value) +} + pub fn patch_policy_definition( client: APIClient, vhost: &str, @@ -1103,6 +1163,57 @@ pub fn update_all_policy_definitions_in( Ok(()) } +pub fn patch_operator_policy_definition( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").cloned().unwrap(); + let value = command_args + .get_one::("definition") + .cloned() + .unwrap(); + let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON value: {}", value, err); + process::exit(1); + }); + + let mut pol = client.get_operator_policy(vhost, &name)?; + let patch = parsed_value.as_object().unwrap(); + for (k, v) in patch.iter() { + pol.insert_definition_key(k.clone(), v.clone()); + } + + let params = PolicyParams::from(&pol); + client.declare_operator_policy(¶ms) +} + +pub fn update_all_operator_policy_definitions_in( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let pols = client.list_operator_policies_in(vhost)?; + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + let value = command_args + .get_one::("definition_value") + .cloned() + .unwrap(); + let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON value: {}", value, err); + process::exit(1); + }); + + for pol in pols { + update_operator_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? + } + + Ok(()) +} + pub fn delete_policy_definition( client: APIClient, vhost: &str, @@ -1142,6 +1253,45 @@ pub fn delete_policy_definition_key_in( Ok(()) } +pub fn delete_operator_policy_definition( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").cloned().unwrap(); + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + + let pol = client.get_operator_policy(vhost, &name)?; + let updated_pol = pol.without_keys(vec![&key]); + + let params = PolicyParams::from(&updated_pol); + client.declare_operator_policy(¶ms) +} + +pub fn delete_operator_policy_definition_key_in( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let pols = client.list_operator_policies_in(vhost)?; + let key = command_args + .get_one::("definition_key") + .cloned() + .unwrap(); + + for pol in pols { + let updated_pol = pol.without_keys(vec![&key]); + + let params = PolicyParams::from(&updated_pol); + client.declare_operator_policy(¶ms)? + } + + Ok(()) +} + fn update_policy_definition_with( client: &APIClient, vhost: &str, @@ -1156,6 +1306,20 @@ fn update_policy_definition_with( client.declare_policy(¶ms) } +fn update_operator_policy_definition_with( + client: &APIClient, + vhost: &str, + name: &str, + key: &str, + parsed_value: &Value, +) -> ClientResult<()> { + let mut policy = client.get_operator_policy(vhost, name)?; + policy.insert_definition_key(key.to_owned(), parsed_value.clone()); + + let params = PolicyParams::from(&policy); + client.declare_operator_policy(¶ms) +} + pub fn declare_parameter( client: APIClient, vhost: &str, diff --git a/src/main.rs b/src/main.rs index 148ae1b..b486eb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -641,6 +641,73 @@ fn dispatch_common_subcommand( let result = commands::show_memory_breakdown(client, second_level_args); res_handler.memory_breakdown_in_percent_result(result) } + ("operator_policies", "declare") => { + let result = commands::declare_operator_policy(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("operator_policies", "delete") => { + let result = commands::delete_operator_policy(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("operator_policies", "delete_definition_key") => { + let result = + commands::delete_operator_policy_definition(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("operator_policies", "delete_definition_key_from_all_in") => { + let result = commands::delete_operator_policy_definition_key_in( + client, + &vhost, + second_level_args, + ); + res_handler.no_output_on_success(result); + } + ("operator_policies", "list") => { + let result = commands::list_operator_policies(client); + res_handler.tabular_result(result) + } + ("operator_policies", "list_in") => { + let typ_opt = second_level_args + .get_one::("apply_to") + .cloned(); + let result = match typ_opt { + None => commands::list_operator_policies_in(client, &vhost), + Some(typ) => { + commands::list_operator_policies_in_and_applying_to(client, &vhost, typ) + } + }; + res_handler.tabular_result(result) + } + ("operator_policies", "list_matching_object") => { + let name = second_level_args + .get_one::("name") + .cloned() + .unwrap(); + let typ = second_level_args + .get_one::("type") + .cloned() + .unwrap(); + let result = commands::list_matching_operator_policies_in(client, &vhost, &name, typ); + res_handler.tabular_result(result) + } + ("operator_policies", "patch") => { + let result = + commands::patch_operator_policy_definition(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("operator_policies", "update_definition") => { + let result = + commands::update_operator_policy_definition(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } + ("operator_policies", "update_definitions_of_all_in") => { + let result = commands::update_all_operator_policy_definitions_in( + client, + &vhost, + second_level_args, + ); + res_handler.no_output_on_success(result); + } ("parameters", "clear") => { let result = commands::delete_parameter(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/operator_policies_tests.rs b/tests/operator_policies_tests.rs new file mode 100644 index 0000000..78bcb6e --- /dev/null +++ b/tests/operator_policies_tests.rs @@ -0,0 +1,551 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use predicates::prelude::*; + +mod test_helpers; +use crate::test_helpers::*; + +#[test] +fn test_list_operator_policies() -> Result<(), Box> { + let policy_name = "test_policy"; + + run_succeeds([ + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 12345}", + ]); + + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("12345"))); + run_succeeds(["delete", "operator_policy", "--name", policy_name]); + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} + +#[test] +fn test_operator_policies() -> Result<(), Box> { + let operator_policy_name = "test_operator_policy"; + + run_succeeds([ + "declare", + "operator_policy", + "--name", + operator_policy_name, + "--pattern", + "op-foo.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 12345}", + ]); + + run_succeeds(["list", "operator_policies"]).stdout( + predicate::str::contains(operator_policy_name).and(predicate::str::contains("op-foo")), + ); + run_succeeds(["delete", "operator_policy", "--name", operator_policy_name]); + run_succeeds(["list", "operator_policies"]) + .stdout(predicate::str::contains(operator_policy_name).not()); + + Ok(()) +} + +#[test] +fn test_policies_declare_list_and_delete() -> Result<(), Box> { + let policy_name = "test_policies_declare_list_and_delete"; + + run_succeeds([ + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20}", + ]); + + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + run_succeeds(["operator_policies", "delete", "--name", policy_name]); + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} + +#[test] +fn test_policies_in() -> Result<(), Box> { + let vh1 = "rabbitmqadmin.test_policies_in.1"; + run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh1]); + + let vh2 = "rabbitmqadmin.test_policies_in.2"; + run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh2]); + + let policy_name = "test_policies_in"; + run_succeeds([ + "--vhost", + vh1, + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "98", + "--definition", + "{\"max-length\": 20}", + ]); + + run_succeeds(["--vhost", vh1, "operator_policies", "list_in"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("98"))); + run_succeeds(["--vhost", vh2, "operator_policies", "list_in"]) + .stdout(predicate::str::contains(policy_name).not()); + run_succeeds([ + "--vhost", + vh1, + "operator_policies", + "delete", + "--name", + policy_name, + ]); + run_succeeds(["--vhost", vh1, "operator_policies", "list_in"]) + .stdout(predicate::str::contains(policy_name).not()); + + run_succeeds(["delete", "vhost", "--name", vh1]); + run_succeeds(["delete", "vhost", "--name", vh2]); + + Ok(()) +} + +#[test] +fn test_policies_in_with_entity_type() -> Result<(), Box> { + let vh = "rabbitmqadmin.vh.operator_policies.2"; + run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh]); + + let policy_name = "test_policies_in_with_entity_type"; + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "98", + "--definition", + "{\"max-length\": 20}", + ]); + + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "list_in", + "--apply-to", + "queues", + ]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("98"))); + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "list_in", + "--apply-to", + "exchanges", + ]) + .stdout(predicate::str::contains(policy_name).not()); + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "list_in", + "--apply-to", + "streams", + ]) + .stdout(predicate::str::contains(policy_name).not()); + run_succeeds([ + "--vhost", + "/", + "operator_policies", + "list_in", + "--apply-to", + "queues", + ]) + .stdout(predicate::str::contains(policy_name).not()); + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "delete", + "--name", + policy_name, + ]); + run_succeeds(["--vhost", vh, "operator_policies", "list_in"]) + .stdout(predicate::str::contains(policy_name).not()); + + run_succeeds(["delete", "vhost", "--name", vh]); + + Ok(()) +} + +#[test] +fn test_operator_policies_matching_objects() -> Result<(), Box> { + let vh = "rabbitmqadmin.vh.operator_policies.11"; + + run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh]); + + let policy_name = "rabbitmqadmin.operator_policies.11"; + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "^q-.*", + "--apply-to", + "queues", + "--priority", + "47", + "--definition", + "{\"max-length\": 20}", + ]); + + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "list_matching_object", + "--name", + "q-abc", + "--type", + "queues", + ]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "list_matching_object", + "--name", + "q-abc", + "--type", + "exchanges", + ]) + .stdout(predicate::str::contains(policy_name).not()); + + run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); + + Ok(()) +} + +#[test] +fn test_policies_declare_list_update_definition_and_delete() +-> Result<(), Box> { + let policy_name = "test_policies_declare_list_update_definition_and_delete"; + + run_succeeds([ + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20}", + ]); + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + + run_succeeds([ + "operator_policies", + "update_definition", + "--name", + policy_name, + "--definition-key", + "max-length", + "--new-value", + "131", + ]); + + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("131"))); + + run_succeeds(["operator_policies", "delete", "--name", policy_name]); + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} + +#[test] +fn test_policies_individual_policy_key_manipulation() -> Result<(), Box> { + let policy_name = "test_policies_individual_policy_key_manipulation"; + + run_succeeds([ + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20, \"max-length-bytes\": 99999999}", + ]); + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + + run_succeeds([ + "operator_policies", + "update_definition", + "--name", + policy_name, + "--definition-key", + "max-length", + "--new-value", + "131", + ]); + + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("131"))); + + run_succeeds([ + "operator_policies", + "delete_definition_key", + "--name", + policy_name, + "--definition-key", + "max-length", + ]); + + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("99999999"))); + + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains("131").not()); + + run_succeeds(["operator_policies", "delete", "--name", policy_name]); + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} + +#[test] +fn test_policies_bulk_policy_key_manipulation() -> Result<(), Box> { + let vh1 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.1"; + let vh2 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.2"; + + run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh1]); + run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh2]); + + let policy1_name = "test_policies_bulk_policy_key_manipulation-1"; + let policy2_name = "test_policies_bulk_policy_key_manipulation-2"; + + run_succeeds([ + "--vhost", + vh1, + "operator_policies", + "declare", + "--name", + policy1_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 20, \"max-length-bytes\": 99999999}", + ]); + run_succeeds([ + "--vhost", + vh2, + "operator_policies", + "declare", + "--name", + policy2_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 120, \"max-length-bytes\": 333333333}", + ]); + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy1_name).and(predicate::str::contains("20"))); + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy1_name).and(predicate::str::contains("333333333"))); + + run_succeeds([ + "--vhost", + vh2, + "operator_policies", + "update_definitions_of_all_in", + "--definition-key", + "max-length", + "--new-value", + "272", + ]); + + run_succeeds(["operator_policies", "list"]).stdout( + predicate::str::contains(policy1_name) + .and(predicate::str::contains("272")) + .and(predicate::str::contains("120").not()), + ); + + run_succeeds([ + "--vhost", + vh1, + "operator_policies", + "delete_definition_key_from_all_in", + "--definition-key", + "max-length", + ]); + + run_succeeds([ + "--vhost", + vh2, + "operator_policies", + "delete_definition_key_from_all_in", + "--definition-key", + "max-length", + ]); + + run_succeeds(["operator_policies", "list"]) + .stdout(predicate::str::contains(policy1_name).and(predicate::str::contains("333333333"))); + + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains("272").not()); + + run_succeeds([ + "--vhost", + vh1, + "operator_policies", + "delete", + "--name", + policy1_name, + ]); + run_succeeds([ + "--vhost", + vh2, + "operator_policies", + "delete", + "--name", + policy2_name, + ]); + run_succeeds(["operator_policies", "list"]).stdout( + predicate::str::contains(policy1_name) + .not() + .and(predicate::str::contains(policy2_name).not()), + ); + + Ok(()) +} + +#[test] +fn test_operator_policies_patch_definition() -> Result<(), Box> { + let vh = "rabbitmqadmin.test_operator_policies_patch_definition.1"; + run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); + run_succeeds(["declare", "vhost", "--name", vh]); + + let policy_name = "test_operator_policies_patch_definition.ad6f7d"; + + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "123", + "--definition", + "{\"max-length\": 923, \"max-length-bytes\": 99999999}", + ]); + run_succeeds(["--vhost", vh, "operator_policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("923"))); + + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "patch", + "--name", + policy_name, + "--definition", + "{\"max-length\": 875, \"max-length-bytes\": 12355242124}", + ]); + + run_succeeds(["operator_policies", "list"]).stdout( + predicate::str::contains(policy_name) + .and(predicate::str::contains("12355242124")) + .and(predicate::str::contains("875")), + ); + + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains("99999999").not()); + + run_succeeds([ + "--vhost", + vh, + "operator_policies", + "delete", + "--name", + policy_name, + ]); + run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); + + Ok(()) +} From 52217cec1d0a0bf8944d33141bef15086ed85442 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 3 Jun 2025 20:55:04 +0400 Subject: [PATCH 105/320] cargo update --- Cargo.lock | 97 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2568b8..ba3dbd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,9 +224,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytecount" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytes" @@ -236,9 +236,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.24" +version = "1.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" dependencies = [ "jobserver", "libc", @@ -291,18 +291,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -769,22 +769,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ + "base64", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -934,6 +940,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -995,9 +1011,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", "windows-targets 0.53.0", @@ -1181,9 +1197,9 @@ checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags", "cfg-if", @@ -1213,9 +1229,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.108" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -1329,9 +1345,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" dependencies = [ "proc-macro2", "syn", @@ -1583,9 +1599,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" dependencies = [ "base64", "bytes", @@ -1613,23 +1629,21 @@ dependencies = [ "quinn", "rustls", "rustls-native-certs", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", ] [[package]] @@ -1718,15 +1732,6 @@ dependencies = [ "security-framework 3.2.0", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -1790,7 +1795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1913,9 +1918,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2229,6 +2234,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" From 5924ec3805d8d1c1f81e322b2f4e1d7df04bd559 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 6 Jun 2025 19:09:26 +0400 Subject: [PATCH 106/320] Use HTTP API client 0.32.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 45c6ee6..97ed0b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.31.0", features = [ +rabbitmq_http_client = { version = "0.32.0", features = [ "blocking", "tabled", ] } From 701f25662678705efd9a36ed693b314feba4fc9d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 9 Jun 2025 23:31:08 +0400 Subject: [PATCH 107/320] New command: 'policies declare_override' an override policy is a policy with a different name, a higher priority and a tweaked definition that still has the same pattern and matches the same kind of objects. These overrides are useful when automating Blue-Green deployments [1]. 1. https://www.rabbitmq.com/docs/blue-green-upgrade --- Cargo.lock | 107 ++++++++++++++----------------- src/cli.rs | 23 ++++++- src/commands.rs | 49 ++++++++++++-- src/config.rs | 4 +- src/main.rs | 4 ++ tests/operator_policies_tests.rs | 2 +- tests/policies_tests.rs | 49 ++++++++++++++ 7 files changed, 172 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba3dbd0..b42f759 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -71,33 +71,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "bytecount" @@ -236,9 +236,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.25" +version = "1.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" dependencies = [ "jobserver", "libc", @@ -291,18 +291,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmake" @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "core-foundation" @@ -655,9 +655,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" @@ -736,9 +736,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", @@ -769,9 +769,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64", "bytes", @@ -1456,9 +1456,9 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43d945d7f9bfa53662536a301e91938f0ee7beb349245c956954d7dd114e400" +checksum = "defb7107ed3aa3477268d45bc3549ad9aa443ab68dda35b490678032ecb7bf86" dependencies = [ "backtrace", "percent-encoding", @@ -1867,9 +1867,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -1912,9 +1912,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" @@ -2180,9 +2180,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -2192,18 +2192,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", @@ -2215,9 +2215,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" @@ -2276,9 +2276,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -2494,7 +2494,7 @@ dependencies = [ "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -2527,13 +2527,13 @@ checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ + "windows-link", "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] @@ -2545,15 +2545,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.2" diff --git a/src/cli.rs b/src/cli.rs index 3556dac..e092a6b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1775,7 +1775,7 @@ fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] { +fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 11] { let declare_cmd = Command::new("declare") .visible_aliases(vec!["update", "set"]) .about("Creates or updates a policy") @@ -1814,6 +1814,26 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] .required(true), ); + let declare_override_cmd = Command::new("declare_override") + .about("Declares a new policy from an existing one, with a higher priority, and merges a set of keys into the new overriding policy definition") + .arg( + Arg::new("name") + .long("name") + .help("the name of the policy to create an override for") + .required(true), + ) + .arg( + Arg::new("override_name") + .long("override-name") + .help("the name of the new overriding policy. If omitted, an 'override' suffix will be added to the original name.") + .required(false), + ) + .arg( + Arg::new("definition") + .long("definition") + .help("additional definitions to merge into the new overriding policy"), + ); + let list_cmd = Command::new("list") .long_about("Lists policies") .after_help(color_print::cformat!( @@ -1927,6 +1947,7 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] [ declare_cmd, + declare_override_cmd, delete_cmd, delete_definition_key_cmd, delete_definition_key_from_all_in_cmd, diff --git a/src/commands.rs b/src/commands.rs index d946ab4..8c43293 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1041,8 +1041,8 @@ pub fn declare_operator_policy( vhost: &str, command_args: &ArgMatches, ) -> ClientResult<()> { - let name = command_args.get_one::("name").unwrap(); - let pattern = command_args.get_one::("pattern").unwrap(); + let name = command_args.get_one::("name").cloned().unwrap(); + let pattern = command_args.get_one::("pattern").cloned().unwrap(); let apply_to = command_args .get_one::("apply_to") .cloned() @@ -1058,8 +1058,8 @@ pub fn declare_operator_policy( let params = requests::PolicyParams { vhost, - name, - pattern, + name: &name, + pattern: &pattern, apply_to: apply_to.clone(), priority: priority.parse::().unwrap(), definition: parsed_definition, @@ -1068,6 +1068,35 @@ pub fn declare_operator_policy( client.declare_operator_policy(¶ms) } +pub fn declare_policy_override( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + let original_pol_name = command_args.get_one::("name").cloned().unwrap(); + let override_pol_name = command_args + .get_one::("override_name") + .cloned() + .unwrap_or(override_policy_name(&original_pol_name)); + + let existing_policy = client.get_policy(vhost, &original_pol_name)?; + + let new_priority = existing_policy.priority + 100; + let definition = command_args.get_one::("definition").unwrap(); + + let parsed_definition = serde_json::from_str::(definition) + .unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON: {}", definition, err); + process::exit(1); + }); + + let overridden = + existing_policy.with_overrides(&override_pol_name, new_priority, &parsed_definition); + dbg!(&overridden); + let params = PolicyParams::from(&overridden); + client.declare_policy(¶ms) +} + pub fn update_policy_definition( client: APIClient, vhost: &str, @@ -1691,6 +1720,18 @@ pub fn import_vhost_definitions( } } +const POLICY_LENGTH_LIMIT: usize = 255; +const OVERRIDE_POLICY_PREFIX: &str = "overrides."; + +fn override_policy_name(original_policy_name: &str) -> String { + let n = POLICY_LENGTH_LIMIT - OVERRIDE_POLICY_PREFIX.len(); + + let mut s = original_policy_name.to_owned(); + s.truncate(n); + + format!("{}{}", OVERRIDE_POLICY_PREFIX, s) +} + fn read_definitions(path: Option<&str>, use_stdin: Option) -> io::Result { match (path, use_stdin) { (_, Some(true)) => { diff --git a/src/config.rs b/src/config.rs index bb73daa..c7ad1dd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -432,7 +432,7 @@ impl SharedSettings { } } -fn from_local_path(path: &Path) -> Result { +fn from_local_path(path: &Path) -> Result, ConfigFileError> { let expanded_s = shellexpand::tilde(&path.to_string_lossy()).to_string(); let expanded_path = PathBuf::from(&expanded_s); if expanded_path.exists() { @@ -442,7 +442,7 @@ fn from_local_path(path: &Path) -> Result { } } -fn read_from_local_path(path: &PathBuf) -> Result { +fn read_from_local_path(path: &PathBuf) -> Result, ConfigFileError> { let contents = std::fs::read_to_string(path)?; toml::from_str(&contents).map_err(ConfigFileError::from) } diff --git a/src/main.rs b/src/main.rs index b486eb7..ad2327c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -733,6 +733,10 @@ fn dispatch_common_subcommand( let result = commands::declare_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("policies", "declare_override") => { + let result = commands::declare_policy_override(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("policies", "delete") => { let result = commands::delete_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/operator_policies_tests.rs b/tests/operator_policies_tests.rs index 78bcb6e..8cec1a1 100644 --- a/tests/operator_policies_tests.rs +++ b/tests/operator_policies_tests.rs @@ -45,7 +45,7 @@ fn test_list_operator_policies() -> Result<(), Box> { #[test] fn test_operator_policies() -> Result<(), Box> { - let operator_policy_name = "test_operator_policy"; + let operator_policy_name = "test_operator_policies.1"; run_succeeds([ "declare", diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index b89105e..d7a1747 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -597,3 +597,52 @@ fn test_policies_patch_definition() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_policies_declare_override() -> Result<(), Box> { + let policy_name = "test_list_policies_override.1"; + let override_name = "overrides.test_list_policies_override.a"; + + run_succeeds([ + "policies", + "declare", + "--name", + policy_name, + "--pattern", + "foo-.*", + "--apply-to", + "queues", + "--priority", + "12", + "--definition", + "{\"max-length\": 12345}", + ]); + + run_succeeds([ + "policies", + "declare_override", + "--name", + policy_name, + "--override-name", + override_name, + "--definition", + "{\"max-length\": 23456, \"max-length-bytes\": 99999999}", + ]); + + run_succeeds(["policies", "list"]) + .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("12345"))); + run_succeeds(["policies", "list"]).stdout( + predicate::str::contains(override_name) + .and(predicate::str::contains("23456")) + .and(predicate::str::contains("112")) + .and(predicate::str::contains("99999999")), + ); + + run_succeeds(["delete", "policy", "--name", policy_name]); + run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + run_succeeds(["policies", "delete", "--name", override_name]); + run_succeeds(["policies", "list"]).stdout(predicate::str::contains(override_name).not()); + + Ok(()) +} From 548e9f6ca699e470d5f46596d783521e009ee08f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 10 Jun 2025 00:06:08 +0400 Subject: [PATCH 108/320] Remove a dbg message --- src/commands.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands.rs b/src/commands.rs index 8c43293..8023844 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1092,7 +1092,6 @@ pub fn declare_policy_override( let overridden = existing_policy.with_overrides(&override_pol_name, new_priority, &parsed_definition); - dbg!(&overridden); let params = PolicyParams::from(&overridden); client.declare_policy(¶ms) } From 5dc5b3d37be9c94534689dd22fe5b87ceaf3d608 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 10 Jun 2025 15:14:12 +0400 Subject: [PATCH 109/320] Try testing against RabbitMQ 4.0.x and 4.1.x --- .github/workflows/ci.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f7df5d9..dfa8f55 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,6 +41,9 @@ jobs: build: strategy: matrix: + rabbitmq-series: + - "4.0" + - "4.1" rust-version: - stable - beta @@ -52,7 +55,7 @@ jobs: services: rabbitmq: - image: rabbitmq:4-management + image: rabbitmq:${{ matrix.rabbitmq-series }}-management ports: - 15672:15672 - 5672:5672 From 851108773a2e5d3d481e9337d2940a3141c76ac5 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 10 Jun 2025 18:51:00 +0400 Subject: [PATCH 110/320] Adapt to HTTP API client 0.33.0 for RabbitMQ 4.1.1 compatibility in one health check command. --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 2 +- src/tables.rs | 8 ++++++++ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b42f759..feba61b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -604,7 +604,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1108,9 +1108,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -1122,7 +1122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -1456,9 +1456,9 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defb7107ed3aa3477268d45bc3549ad9aa443ab68dda35b490678032ecb7bf86" +checksum = "d7661b61badd45e3b0e420291b05d75c70685e987ab939e73106c45ce8c05396" dependencies = [ "backtrace", "percent-encoding", @@ -1662,9 +1662,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -1946,9 +1946,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" dependencies = [ "proc-macro2", "quote", @@ -2303,9 +2303,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "untrusted" @@ -2368,9 +2368,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" diff --git a/Cargo.toml b/Cargo.toml index 97ed0b2..f8ac142 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.32.0", features = [ +rabbitmq_http_client = { version = "0.33.0", features = [ "blocking", "tabled", ] } diff --git a/src/tables.rs b/src/tables.rs index 4e978a9..2d49c62 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -328,6 +328,9 @@ pub fn failure_details(error: &HttpClientError) -> Table { HealthCheckFailureDetails::NoActiveProtocolListener(details) => { details.reason.clone() } + HealthCheckFailureDetails::NoActiveProtocolListeners(details) => { + details.reason.clone() + } }; data.push(RowOfTwo { key: "reason", @@ -486,6 +489,7 @@ pub fn health_check_failure( HealthCheckFailureDetails::NodeIsQuorumCritical(ref details) => details.reason.clone(), HealthCheckFailureDetails::NoActivePortListener(ref details) => details.reason.clone(), HealthCheckFailureDetails::NoActiveProtocolListener(ref details) => details.reason.clone(), + HealthCheckFailureDetails::NoActiveProtocolListeners(ref details) => details.reason.clone(), }; let code_str = format!("{}", status_code); @@ -535,6 +539,10 @@ pub fn health_check_failure( details.inactive_protocol.to_string().as_str(), ]); } + HealthCheckFailureDetails::NoActiveProtocolListeners(details) => tb.push_record([ + "inactive protocols", + details.inactive_protocols.join(", ").as_str(), + ]), }; tb.build() From b4b2d8e47fa34252affd18fa02dd29ab04e34433 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 11 Jun 2025 18:07:16 +0400 Subject: [PATCH 111/320] New command: 'policies declare_blanket' --- Cargo.lock | 14 ++++++------- src/cli.rs | 27 ++++++++++++++++++++++++- src/commands.rs | 44 +++++++++++++++++++++++++++++++++++++++++ src/constants.rs | 2 ++ src/main.rs | 4 ++++ tests/policies_tests.rs | 33 +++++++++++++++++++++++++++++++ 6 files changed, 115 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index feba61b..351e4d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1071,9 +1071,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mimalloc" @@ -1599,9 +1599,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.19" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "base64", "bytes", @@ -1617,13 +1617,11 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "mime_guess", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -2702,9 +2700,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] diff --git a/src/cli.rs b/src/cli.rs index e092a6b..844c03b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1775,7 +1775,7 @@ fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 11] { +fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] { let declare_cmd = Command::new("declare") .visible_aliases(vec!["update", "set"]) .about("Creates or updates a policy") @@ -1834,6 +1834,30 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 11] .help("additional definitions to merge into the new overriding policy"), ); + let declare_blanket_cmd = Command::new("declare_blanket") + .about("Creates a low priority blanket policy, a policy that matches all objects not matched by any other policy") + .after_help(color_print::cformat!("Doc guide:: {}", POLICY_GUIDE_URL)) + .arg( + Arg::new("name") + .long("name") + .help("blanket policy name") + .required(true), + ) + .arg( + Arg::new("apply_to") + .long("apply-to") + .alias("applies-to") + .help("entities to apply to (queues, classic_queues, quorum_queues, streams, exchanges, all)") + .value_parser(value_parser!(PolicyTarget)) + .required(true), + ) + .arg( + Arg::new("definition") + .long("definition") + .help("policy definition") + .required(true), + ); + let list_cmd = Command::new("list") .long_about("Lists policies") .after_help(color_print::cformat!( @@ -1948,6 +1972,7 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 11] [ declare_cmd, declare_override_cmd, + declare_blanket_cmd, delete_cmd, delete_definition_key_cmd, delete_definition_key_from_all_in_cmd, diff --git a/src/commands.rs b/src/commands.rs index 8023844..66a91d0 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -31,6 +31,7 @@ use std::fs; use std::io; use std::process; +use crate::constants::DEFAULT_BLANKET_POLICY_PRIORITY; use rabbitmq_http_client::commons::BindingDestinationType; use rabbitmq_http_client::commons::QueueType; use rabbitmq_http_client::responses::PolicyDefinitionOps; @@ -1096,6 +1097,49 @@ pub fn declare_policy_override( client.declare_policy(¶ms) } +pub fn declare_blanket_policy( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> ClientResult<()> { + // find the lowest policy priority in the target virtual host + let existing_policies = client.list_policies_in(vhost)?; + let min_priority = existing_policies + .iter() + .fold(0, |acc, p| if p.priority < acc { p.priority } else { acc }); + + // blanket policy priority should be the lowest in the virtual host + let priority = [min_priority - 1, DEFAULT_BLANKET_POLICY_PRIORITY] + .iter() + .min() + .cloned() + .unwrap(); + + let name = command_args.get_one::("name").cloned().unwrap(); + let apply_to = command_args + .get_one::("apply_to") + .cloned() + .unwrap(); + let definition = command_args.get_one::("definition").unwrap(); + + let parsed_definition = serde_json::from_str::(definition) + .unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON: {}", definition, err); + process::exit(1); + }); + + let params = requests::PolicyParams { + vhost, + name: &name, + pattern: ".*", + apply_to, + priority: priority as i32, + definition: parsed_definition, + }; + + client.declare_policy(¶ms) +} + pub fn update_policy_definition( client: APIClient, vhost: &str, diff --git a/src/constants.rs b/src/constants.rs index aab1ab1..623ed58 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -34,3 +34,5 @@ pub const DEFAULT_CONFIG_FILE_PATH: &str = "~/.rabbitmqadmin.conf"; pub const DEFAULT_CONFIG_SECTION_NAME: &str = "default"; pub const TANZU_COMMAND_PREFIX: &str = "tanzu"; + +pub const DEFAULT_BLANKET_POLICY_PRIORITY: i16 = -20; diff --git a/src/main.rs b/src/main.rs index ad2327c..220c27a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -737,6 +737,10 @@ fn dispatch_common_subcommand( let result = commands::declare_policy_override(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("policies", "declare_blanket") => { + let result = commands::declare_blanket_policy(client, &vhost, second_level_args); + res_handler.no_output_on_success(result); + } ("policies", "delete") => { let result = commands::delete_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index d7a1747..4550b55 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -646,3 +646,36 @@ fn test_policies_declare_override() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_policies_declare_blanket() -> Result<(), Box> { + let policy_name = "test_policies_declare_blanket.1"; + + run_succeeds([ + "policies", + "declare_blanket", + "--name", + policy_name, + "--apply-to", + "queues", + "--definition", + "{\"max-length\": 787876}", + ]); + + run_succeeds(["policies", "list"]).stdout( + predicate::str::contains(policy_name) + // default blanket policy priority + .and(predicate::str::contains("787876")), + ); + + let client = api_client(); + let pol = client.get_policy("/", policy_name).unwrap(); + + assert_eq!(pol.pattern, ".*"); + assert!(pol.priority < 0); + + run_succeeds(["delete", "policy", "--name", policy_name]); + run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + + Ok(()) +} From a39c0b6ebb5dcfcc48fdae6c17c62ad497f933ae Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 11 Jun 2025 18:09:20 +0400 Subject: [PATCH 112/320] Change log update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ca079..5d457f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ used by `rabbitmqctl` and the latter reflects the fact that the command can be used to update an existing policy, in particular, to override its definition * `policies patch` is a new command that updates a policy definition by merging the provided definition with the existing one + * `policies declare_override` is a new command that declares a policy that overrides another policy + * `policies declare_blanket` is a new command that declares a low priority policy that matches all objects not matched + by any other policies * `parameters list_all` is a new command that lists all runtime parameters across all virtual hosts * `parameters list_in` is a new command that lists runtime parameters of a given component (type) in a specific virtual host From 5680a64d1c800d7648004077337d6dc19d11d572 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 11 Jun 2025 22:51:51 +0400 Subject: [PATCH 113/320] Adapt new 'policies' commands based on core team's feedback --- CHANGELOG.md | 4 +++ src/cli.rs | 53 +++++++++++++++++----------- src/commands.rs | 60 +++++++++++++++++++------------- src/main.rs | 16 ++++----- tests/operator_policies_tests.rs | 48 +++++++++++++------------ tests/policies_tests.rs | 22 ++++++------ 6 files changed, 118 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d457f0..4630667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ used by `rabbitmqctl` and the latter reflects the fact that the command can be used to update an existing policy, in particular, to override its definition * `policies patch` is a new command that updates a policy definition by merging the provided definition with the existing one + * `policies delete_definition_keys` is a new command that removes keys from a policy definition + * `policies delete_definition_keys_from_all_in` is a new command that removes definition keys from all policies in a virtual host + * `policies update_definition` is a new command that updates a policy definition key; for multi-key updates, see `policies patch + * `policies update_definitions_of_all_in` is a new command that updates a definition key for all policies in a virtual host * `policies declare_override` is a new command that declares a policy that overrides another policy * `policies declare_blanket` is a new command that declares a low priority policy that matches all objects not matched by any other policies diff --git a/src/cli.rs b/src/cli.rs index 844c03b..b3c582f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1663,8 +1663,8 @@ fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .required(true), ); - let delete_definition_key_cmd = Command::new("delete_definition_key") - .about("Deletes a definition key from an operator policy, unless it is the only key") + let delete_definition_key_cmd = Command::new("delete_definition_keys") + .about("Deletes definition keys from an operator policy, unless it is the only key") .arg( Arg::new("name") .long("name") @@ -1672,17 +1672,23 @@ fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .required(true), ) .arg( - Arg::new("definition_key") - .long("definition-key") - .help("definition key"), + Arg::new("definition_keys") + .long("definition-keys") + .num_args(1..) + .value_delimiter(',') + .action(ArgAction::Append) + .help("comma-separated definition keys"), ); - let delete_definition_key_from_all_in_cmd = Command::new("delete_definition_key_from_all_in") + let delete_definition_key_from_all_in_cmd = Command::new("delete_definition_keys_from_all_in") .about("Deletes a definition key from all operator policies in a virtual host, unless it is the only key") .arg( - Arg::new("definition_key") - .long("definition-key") - .help("definition key") + Arg::new("definition_keys") + .long("definition-keys") + .num_args(1..) + .value_delimiter(',') + .action(ArgAction::Append) + .help("comma-separated definition keys") ); let list_in_cmd = Command::new("list_in") @@ -1872,7 +1878,7 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .required(true), ); - let delete_definition_key_cmd = Command::new("delete_definition_key") + let delete_definition_keys_cmd = Command::new("delete_definition_keys") .about("Deletes a definition key from a policy, unless it is the only key") .arg( Arg::new("name") @@ -1881,17 +1887,24 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .required(true), ) .arg( - Arg::new("definition_key") - .long("definition-key") - .help("definition key"), + Arg::new("definition_keys") + .long("definition-keys") + .num_args(1..) + .value_delimiter(',') + .action(ArgAction::Append) + .help("comma-separated definition keys"), ); - let delete_definition_key_from_all_in_cmd = Command::new("delete_definition_key_from_all_in") - .about("Deletes a definition key from all policies in a virtual host, unless it is the only key") + let delete_definition_keys_from_all_in_cmd = Command::new("delete_definition_keys_from_all_in") + .about("Deletes definition keys from all policies in a virtual host, unless it is the only policy key") .arg( - Arg::new("definition_key") - .long("definition-key") - .help("definition key") + Arg::new("definition_keys") + .long("definition-keys") + .help("comma-separated definition keys") + .num_args(1..) + .value_delimiter(',') + .action(ArgAction::Append) + .required(true) ); let list_in_cmd = Command::new("list_in") @@ -1974,8 +1987,8 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] declare_override_cmd, declare_blanket_cmd, delete_cmd, - delete_definition_key_cmd, - delete_definition_key_from_all_in_cmd, + delete_definition_keys_cmd, + delete_definition_keys_from_all_in_cmd, list_cmd, list_in_cmd, list_matching_cmd, diff --git a/src/commands.rs b/src/commands.rs index 66a91d0..304066f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1286,37 +1286,43 @@ pub fn update_all_operator_policy_definitions_in( Ok(()) } -pub fn delete_policy_definition( +pub fn delete_policy_definition_keys( client: APIClient, vhost: &str, command_args: &ArgMatches, ) -> ClientResult<()> { let name = command_args.get_one::("name").cloned().unwrap(); - let key = command_args - .get_one::("definition_key") - .cloned() - .unwrap(); + let keys = command_args + .get_many::("definition_keys") + .unwrap() + .into_iter() + .map(String::from) + .collect::>(); + let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); let pol = client.get_policy(vhost, &name)?; - let updated_pol = pol.without_keys(vec![&key]); + let updated_pol = pol.without_keys(str_keys); let params = PolicyParams::from(&updated_pol); client.declare_policy(¶ms) } -pub fn delete_policy_definition_key_in( +pub fn delete_policy_definition_keys_in( client: APIClient, vhost: &str, command_args: &ArgMatches, ) -> ClientResult<()> { let pols = client.list_policies_in(vhost)?; - let key = command_args - .get_one::("definition_key") - .cloned() - .unwrap(); + let keys = command_args + .get_many::("definition_keys") + .unwrap() + .into_iter() + .map(String::from) + .collect::>(); + let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); for pol in pols { - let updated_pol = pol.without_keys(vec![&key]); + let updated_pol = pol.without_keys(str_keys.clone()); let params = PolicyParams::from(&updated_pol); client.declare_policy(¶ms)? @@ -1325,37 +1331,43 @@ pub fn delete_policy_definition_key_in( Ok(()) } -pub fn delete_operator_policy_definition( +pub fn delete_operator_policy_definition_keys( client: APIClient, vhost: &str, command_args: &ArgMatches, ) -> ClientResult<()> { let name = command_args.get_one::("name").cloned().unwrap(); - let key = command_args - .get_one::("definition_key") - .cloned() - .unwrap(); + let keys = command_args + .get_many::("definition_keys") + .unwrap() + .into_iter() + .map(String::from) + .collect::>(); + let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); let pol = client.get_operator_policy(vhost, &name)?; - let updated_pol = pol.without_keys(vec![&key]); + let updated_pol = pol.without_keys(str_keys); let params = PolicyParams::from(&updated_pol); client.declare_operator_policy(¶ms) } -pub fn delete_operator_policy_definition_key_in( +pub fn delete_operator_policy_definition_keys_in( client: APIClient, vhost: &str, command_args: &ArgMatches, ) -> ClientResult<()> { let pols = client.list_operator_policies_in(vhost)?; - let key = command_args - .get_one::("definition_key") - .cloned() - .unwrap(); + let keys = command_args + .get_many::("definition_keys") + .unwrap() + .into_iter() + .map(String::from) + .collect::>(); + let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); for pol in pols { - let updated_pol = pol.without_keys(vec![&key]); + let updated_pol = pol.without_keys(str_keys.clone()); let params = PolicyParams::from(&updated_pol); client.declare_operator_policy(¶ms)? diff --git a/src/main.rs b/src/main.rs index 220c27a..09d35c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -649,13 +649,13 @@ fn dispatch_common_subcommand( let result = commands::delete_operator_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("operator_policies", "delete_definition_key") => { + ("operator_policies", "delete_definition_keys") => { let result = - commands::delete_operator_policy_definition(client, &vhost, second_level_args); + commands::delete_operator_policy_definition_keys(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("operator_policies", "delete_definition_key_from_all_in") => { - let result = commands::delete_operator_policy_definition_key_in( + ("operator_policies", "delete_definition_keys_from_all_in") => { + let result = commands::delete_operator_policy_definition_keys_in( client, &vhost, second_level_args, @@ -745,13 +745,13 @@ fn dispatch_common_subcommand( let result = commands::delete_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("policies", "delete_definition_key") => { - let result = commands::delete_policy_definition(client, &vhost, second_level_args); + ("policies", "delete_definition_keys") => { + let result = commands::delete_policy_definition_keys(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } - ("policies", "delete_definition_key_from_all_in") => { + ("policies", "delete_definition_keys_from_all_in") => { let result = - commands::delete_policy_definition_key_in(client, &vhost, second_level_args); + commands::delete_policy_definition_keys_in(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } ("policies", "list") => { diff --git a/tests/operator_policies_tests.rs b/tests/operator_policies_tests.rs index 8cec1a1..79bfd5d 100644 --- a/tests/operator_policies_tests.rs +++ b/tests/operator_policies_tests.rs @@ -73,7 +73,7 @@ fn test_operator_policies() -> Result<(), Box> { } #[test] -fn test_policies_declare_list_and_delete() -> Result<(), Box> { +fn test_operator_policies_declare_list_and_delete() -> Result<(), Box> { let policy_name = "test_policies_declare_list_and_delete"; run_succeeds([ @@ -100,7 +100,7 @@ fn test_policies_declare_list_and_delete() -> Result<(), Box Result<(), Box> { +fn test_operator_policies_in() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_policies_in.1"; run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh1]); @@ -149,7 +149,7 @@ fn test_policies_in() -> Result<(), Box> { } #[test] -fn test_policies_in_with_entity_type() -> Result<(), Box> { +fn test_operator_policies_in_with_entity_type() -> Result<(), Box> { let vh = "rabbitmqadmin.vh.operator_policies.2"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); @@ -278,7 +278,7 @@ fn test_operator_policies_matching_objects() -> Result<(), Box Result<(), Box> { let policy_name = "test_policies_declare_list_update_definition_and_delete"; @@ -320,7 +320,8 @@ fn test_policies_declare_list_update_definition_and_delete() } #[test] -fn test_policies_individual_policy_key_manipulation() -> Result<(), Box> { +fn test_operator_policies_individual_policy_key_manipulation() +-> Result<(), Box> { let policy_name = "test_policies_individual_policy_key_manipulation"; run_succeeds([ @@ -335,7 +336,7 @@ fn test_policies_individual_policy_key_manipulation() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { - let vh1 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.1"; - let vh2 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.2"; +fn test_operator_policies_bulk_policy_keys_manipulation() -> Result<(), Box> +{ + let vh1 = "rabbitmqadmin.test_policies_bulk_policy_keys_manipulation.1"; + let vh2 = "rabbitmqadmin.test_policies_bulk_policy_keys_manipulation.2"; run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh1]); run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh2]); - let policy1_name = "test_policies_bulk_policy_key_manipulation-1"; - let policy2_name = "test_policies_bulk_policy_key_manipulation-2"; + let policy1_name = "test_policies_bulk_policy_keys_manipulation-1"; + let policy2_name = "test_policies_bulk_policy_keys_manipulation-2"; run_succeeds([ "--vhost", @@ -401,7 +404,7 @@ fn test_policies_bulk_policy_key_manipulation() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { "--priority", "123", "--definition", - "{\"max-length\": 20, \"max-length-bytes\": 99999999}", + "{\"max-length\": 20, \"max-length-bytes\": 4823748374}", ]); run_succeeds(["--vhost", vh1, "policies", "list"]) .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); @@ -588,7 +588,7 @@ fn test_policies_patch_definition() -> Result<(), Box> { .and(predicate::str::contains("29")), ); - run_succeeds(["policies", "list"]).stdout(predicate::str::contains("99999999").not()); + run_succeeds(["policies", "list"]).stdout(predicate::str::contains("4823748374").not()); run_succeeds(["--vhost", vh1, "policies", "delete", "--name", policy_name]); run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); From e6ecbfe40675b5ee1416f5eed1c5a6739a180346 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 11 Jun 2025 22:52:32 +0400 Subject: [PATCH 114/320] cargo clippy --all-features --fix --- src/commands.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 304066f..0be69d5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1295,7 +1295,6 @@ pub fn delete_policy_definition_keys( let keys = command_args .get_many::("definition_keys") .unwrap() - .into_iter() .map(String::from) .collect::>(); let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); @@ -1316,7 +1315,6 @@ pub fn delete_policy_definition_keys_in( let keys = command_args .get_many::("definition_keys") .unwrap() - .into_iter() .map(String::from) .collect::>(); let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); @@ -1340,7 +1338,6 @@ pub fn delete_operator_policy_definition_keys( let keys = command_args .get_many::("definition_keys") .unwrap() - .into_iter() .map(String::from) .collect::>(); let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); @@ -1361,7 +1358,6 @@ pub fn delete_operator_policy_definition_keys_in( let keys = command_args .get_many::("definition_keys") .unwrap() - .into_iter() .map(String::from) .collect::>(); let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); From 0ed22614f8f82dc488d06af86f863ad256df3771 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 11 Jun 2025 23:09:23 +0400 Subject: [PATCH 115/320] Fix a test --- tests/operator_policies_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/operator_policies_tests.rs b/tests/operator_policies_tests.rs index 79bfd5d..4de7a53 100644 --- a/tests/operator_policies_tests.rs +++ b/tests/operator_policies_tests.rs @@ -360,8 +360,8 @@ fn test_operator_policies_individual_policy_key_manipulation() "delete_definition_keys", "--name", policy_name, - "--definition-key", - "max-length", + "--definition-keys", + "max-length,abc,def", ]); run_succeeds(["operator_policies", "list"]).stdout( From bb7c0af310bea3cd0b666faebee5f6b4c74f3234 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 12 Jun 2025 14:34:36 +0400 Subject: [PATCH 116/320] Cosmetics --- tests/operator_policies_tests.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/operator_policies_tests.rs b/tests/operator_policies_tests.rs index 4de7a53..cdb3a39 100644 --- a/tests/operator_policies_tests.rs +++ b/tests/operator_policies_tests.rs @@ -18,7 +18,7 @@ use crate::test_helpers::*; #[test] fn test_list_operator_policies() -> Result<(), Box> { - let policy_name = "test_policy"; + let policy_name = "test_list_operator_policies"; run_succeeds([ "operator_policies", @@ -74,7 +74,7 @@ fn test_operator_policies() -> Result<(), Box> { #[test] fn test_operator_policies_declare_list_and_delete() -> Result<(), Box> { - let policy_name = "test_policies_declare_list_and_delete"; + let policy_name = "test_operator_policies_declare_list_and_delete"; run_succeeds([ "operator_policies", @@ -101,15 +101,15 @@ fn test_operator_policies_declare_list_and_delete() -> Result<(), Box Result<(), Box> { - let vh1 = "rabbitmqadmin.test_policies_in.1"; + let vh1 = "rabbitmqadmin.test_operator_policies_in.1"; run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh1]); - let vh2 = "rabbitmqadmin.test_policies_in.2"; + let vh2 = "rabbitmqadmin.test_operator_policies_in.2"; run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh2]); - let policy_name = "test_policies_in"; + let policy_name = "test_operator_policies_in"; run_succeeds([ "--vhost", vh1, @@ -154,7 +154,7 @@ fn test_operator_policies_in_with_entity_type() -> Result<(), Box Result<(), Box Result<(), Box> { - let policy_name = "test_policies_declare_list_update_definition_and_delete"; + let policy_name = "test_operator_policies_declare_list_update_definition_and_delete"; run_succeeds([ "operator_policies", @@ -322,7 +322,7 @@ fn test_operator_policies_declare_list_update_definition_and_delete() #[test] fn test_operator_policies_individual_policy_key_manipulation() -> Result<(), Box> { - let policy_name = "test_policies_individual_policy_key_manipulation"; + let policy_name = "test_operator_policies_individual_policy_key_manipulation"; run_succeeds([ "operator_policies", @@ -379,16 +379,16 @@ fn test_operator_policies_individual_policy_key_manipulation() #[test] fn test_operator_policies_bulk_policy_keys_manipulation() -> Result<(), Box> { - let vh1 = "rabbitmqadmin.test_policies_bulk_policy_keys_manipulation.1"; - let vh2 = "rabbitmqadmin.test_policies_bulk_policy_keys_manipulation.2"; + let vh1 = "rabbitmqadmin.test_operator_policies_bulk_policy_keys_manipulation.1"; + let vh2 = "rabbitmqadmin.test_operator_policies_bulk_policy_keys_manipulation.2"; run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh1]); run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh2]); - let policy1_name = "test_policies_bulk_policy_keys_manipulation-1"; - let policy2_name = "test_policies_bulk_policy_keys_manipulation-2"; + let policy1_name = "test_operator_policies_bulk_policy_keys_manipulation-1"; + let policy2_name = "test_operator_policies_bulk_policy_keys_manipulation-2"; run_succeeds([ "--vhost", From 2d4cd5584b75207e86dd9301a827a8c9d613a875 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 12 Jun 2025 14:52:58 +0400 Subject: [PATCH 117/320] Update dependencies --- Cargo.lock | 56 ++++++++++++++++++++++-------------------------------- Cargo.toml | 8 ++++---- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 351e4d8..64f2f54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,19 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.3", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -437,7 +424,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.1", ] [[package]] @@ -1016,7 +1003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.53.1", ] [[package]] @@ -1256,12 +1243,12 @@ dependencies = [ [[package]] name = "papergrid" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30268a8d20c2c0d126b2b6610ab405f16517f6ba9f244d8c59ac2c512a8a1ce7" +checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1" dependencies = [ - "ahash", "bytecount", + "fnv", "unicode-width", ] @@ -1456,9 +1443,9 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rabbitmq_http_client" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7661b61badd45e3b0e420291b05d75c70685e987ab939e73106c45ce8c05396" +checksum = "bb65a75e4797c2178db90ef382557a71664c5383c109734ccf86346f9d2a1f48" dependencies = [ "backtrace", "percent-encoding", @@ -1478,7 +1465,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.2.0" +version = "2.3.0" dependencies = [ "assert_cmd", "clap", @@ -2002,9 +1989,9 @@ dependencies = [ [[package]] name = "tabled" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228d124371171cd39f0f454b58f73ddebeeef3cef3207a82ffea1c29465aea43" +checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d" dependencies = [ "papergrid", "tabled_derive", @@ -2340,12 +2327,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "wait-timeout" version = "0.2.1" @@ -2519,9 +2500,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b" [[package]] name = "windows-registry" @@ -2570,6 +2551,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b36e9ed89376c545e20cbf5a13c306b49106b21b9d1d4f9cb9a1cb6b1e9ee06a" +dependencies = [ + "windows-targets 0.53.1", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2588,9 +2578,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "30357ec391cde730f8fbfcdc29adc47518b06504528df977ab5af02ef23fdee9" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", diff --git a/Cargo.toml b/Cargo.toml index f8ac142..64cc492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.2.0" +version = "2.3.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" @@ -10,20 +10,20 @@ license = "MIT OR Apache-2.0" clap = { version = "4", features = ["help", "color", "cargo", "env"] } url = "2" sysexits = "0.9" -reqwest = { version = "0.12.12", features = [ +reqwest = { version = "0.12.20", features = [ "blocking", "json", "multipart", "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.33.0", features = [ +rabbitmq_http_client = { version = "0.34.0", features = [ "blocking", "tabled", ] } serde = { version = "1.0", features = ["derive", "std"] } serde_json = "1" -tabled = "0.19" +tabled = "0.20" toml = "0.8" color-print = "0.3" thiserror = "2" From 16831a52540eedfca32a8fa953ac5b27bda7baef Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 12 Jun 2025 14:54:25 +0400 Subject: [PATCH 118/320] 2.2.0 --- CHANGELOG.md | 7 ++++++- README.md | 36 ++++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4630667..508b42b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.2.0 (in development) +## v2.3.0 (in development) + +No changes yet. + + +## v2.2.0 (Jun 12, 2025) ### Enhancements diff --git a/README.md b/README.md index 0e82425..c7981e5 100644 --- a/README.md +++ b/README.md @@ -68,25 +68,37 @@ which will output a list of command groups: Usage: rabbitmqadmin [OPTIONS] Commands: - show Overview, memory footprint breakdown, and more - list Lists objects - declare Creates or declares objects - delete Deletes objects - purge Purges queues - policies Operations on policies - health_check Runs health checks + bindings Operations on bindings + channels Operations on channels close Closes connections - rebalance Rebalancing of leader replicas + connections Operations on connections + declare Creates or declares objects definitions Operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc) + delete Deletes objects + deprecated_features Operations on deprecated features + exchanges Operations on exchanges export See 'definitions export' - import See 'definitions import' feature_flags Operations on feature flags - deprecated_features Operations on deprecated features - publish Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. + federation Operations on federation upstreams and links get Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments. + global_parameters Operations on global runtime parameters + health_check Runs health checks + import See 'definitions import' + list Lists objects + nodes Node operations + operator_policies Operations on operator policies + parameters Operations on runtime parameters + policies Operations on policies + publish Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. + purge Purges queues + queues Operations on queues + rebalance Rebalancing of leader replicas + show Overview, memory footprint breakdown, and more shovels Operations on shovels - federation Operations on federation upstreams and links + streams Operations on streams tanzu Tanzu RabbitMQ-specific commands + users Operations on users + vhosts Virtual host operations help Print this message or the help of the given subcommand(s) ``` From d403af9b491029d76c919f723fa8d05cb43a8c98 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 12 Jun 2025 15:04:09 +0400 Subject: [PATCH 119/320] Set version back to 2.2.0 for now --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 64cc492..92ff9d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.3.0" +version = "2.2.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 7a4e2a01d9f03159246e974dd2f1953c4fbf117a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 12 Jun 2025 15:18:52 +0400 Subject: [PATCH 120/320] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 92ff9d2..64cc492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.2.0" +version = "2.3.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From eb73c7ee2a3b7de960dc4bcf6986940c5f9eba3d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 16 Jun 2025 20:53:17 +0400 Subject: [PATCH 121/320] cargo update --- Cargo.lock | 53 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64f2f54..1fb5469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.26" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -424,7 +424,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.1", + "windows-sys 0.60.2", ] [[package]] @@ -992,9 +992,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "libloading" @@ -1003,14 +1003,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.1", + "windows-targets 0.53.2", ] [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" dependencies = [ "cc", "libc", @@ -1064,9 +1064,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" dependencies = [ "libmimalloc-sys", ] @@ -1332,9 +1332,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", "syn", @@ -1691,9 +1691,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "aws-lc-rs", "log", @@ -1888,12 +1888,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" @@ -1931,9 +1928,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.102" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -2500,9 +2497,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" @@ -2553,11 +2550,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.60.1" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b36e9ed89376c545e20cbf5a13c306b49106b21b9d1d4f9cb9a1cb6b1e9ee06a" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.1", + "windows-targets 0.53.2", ] [[package]] @@ -2578,9 +2575,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.1" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30357ec391cde730f8fbfcdc29adc47518b06504528df977ab5af02ef23fdee9" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", From d5ecd89263ee4dbcce15c6466187aef78e158940 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 16 Jun 2025 20:56:29 +0400 Subject: [PATCH 122/320] Closes #58 * 'tls' specified in rabbitmqadmin.conf is now respected by the code paths that load the config file * 'port' no longer has a CLI parser default * 'scheme' now relies on the 'tls' setting value first and foremost --- src/cli.rs | 4 +--- src/config.rs | 30 +++++++++++++++++++++++------- src/constants.rs | 1 - src/main.rs | 3 ++- src/tables.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index b3c582f..3f6a1eb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -384,8 +384,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .env("RABBITMQADMIN_TARGET_PORT") .help("HTTP API port to use when connecting") .required(false) - .value_parser(value_parser!(u16)) - .default_value(DEFAULT_PORT_STR), + .value_parser(value_parser!(u16)), ) // --base-uri .arg( @@ -460,7 +459,6 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { Arg::new("tls-ca-cert-file") .long("tls-ca-cert-file") .required(false) - .requires("tls") .help("Local path to a CA certificate file in the PEM format") .value_parser(value_parser!(PathBuf)), ) diff --git a/src/config.rs b/src/config.rs index c7ad1dd..5b9ee02 100644 --- a/src/config.rs +++ b/src/config.rs @@ -159,7 +159,11 @@ impl SharedSettings { || config_file_defaults.non_interactive; let quiet = cli_args.get_one::("quiet").cloned().unwrap_or(false) || config_file_defaults.quiet; - let scheme = if should_use_tls { "https" } else { "http" }; + let scheme = if should_use_tls { + "https" + } else { + config_file_defaults.scheme.as_str() + }; let hostname = cli_args .get_one::("host") .cloned() @@ -228,7 +232,11 @@ impl SharedSettings { .unwrap_or(false) || default_non_interactive(); let quiet = cli_args.get_one::("quiet").cloned().unwrap_or(false) || default_quiet(); - let scheme = if should_use_tls { "https" } else { "http" }; + let scheme = if should_use_tls { + "https".to_owned() + } else { + default_scheme() + }; let hostname = cli_args .get_one::("host") .cloned() @@ -271,7 +279,7 @@ impl SharedSettings { non_interactive, quiet, base_uri: None, - scheme: scheme.to_string(), + scheme, hostname: Some(hostname), port: Some(port), path_prefix: path_prefix.clone(), @@ -298,7 +306,11 @@ impl SharedSettings { let quiet = cli_args.get_one::("quiet").cloned().unwrap_or(false) || config_file_defaults.quiet; - let scheme = url.scheme().to_string(); + let scheme = if should_use_tls { + HTTPS_SCHEME.to_owned() + } else { + config_file_defaults.scheme.clone() + }; let hostname = url.host_str().unwrap_or(DEFAULT_HOST).to_string(); let port = url .port() @@ -341,7 +353,7 @@ impl SharedSettings { non_interactive, quiet, base_uri: Some(url.to_string()), - scheme: scheme.to_string(), + scheme, hostname: Some(hostname), port: Some(port), path_prefix, @@ -364,7 +376,11 @@ impl SharedSettings { .cloned() .unwrap_or(default_quiet()); - let scheme = url.scheme().to_string(); + let scheme = if should_use_tls { + "https".to_owned() + } else { + url.scheme().to_string() + }; let hostname = url.host_str().unwrap_or(DEFAULT_HOST).to_string(); let port = url .port() @@ -403,7 +419,7 @@ impl SharedSettings { non_interactive, quiet, base_uri: Some(url.to_string()), - scheme: scheme.to_string(), + scheme, hostname: Some(hostname), port: Some(port), path_prefix, diff --git a/src/constants.rs b/src/constants.rs index 623ed58..592a270 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -14,7 +14,6 @@ pub const DEFAULT_SCHEME: &str = "http"; pub const HTTPS_SCHEME: &str = "https"; pub const DEFAULT_HOST: &str = "localhost"; -pub const DEFAULT_PORT_STR: &str = "15672"; pub const DEFAULT_HTTPS_PORT: u16 = 15671; pub const DEFAULT_HTTP_PORT: u16 = 15672; // This path prefix that precedes diff --git a/src/main.rs b/src/main.rs index 09d35c2..0fd79b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -994,7 +994,8 @@ fn dispatch_tanzu_subcommand( } fn should_use_tls(shared_settings: &SharedSettings) -> bool { - shared_settings.scheme.to_lowercase() == "https" + shared_settings.tls + || shared_settings.scheme.to_lowercase() == "https" || shared_settings.port.unwrap_or(DEFAULT_HTTPS_PORT) == DEFAULT_HTTPS_PORT } diff --git a/src/tables.rs b/src/tables.rs index 2d49c62..d2c8ce7 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -1,3 +1,4 @@ +use std::error::Error; // Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -415,7 +416,7 @@ pub fn failure_details(error: &HttpClientError) -> Table { backtrace: _, } => { let reason = format!("HTTP API request failed: {}", error); - let data = vec![ + let mut data = vec![ RowOfTwo { key: "result", value: "request failed", @@ -426,6 +427,45 @@ pub fn failure_details(error: &HttpClientError) -> Table { }, ]; + let underlying_error1 = match error.source() { + Some(source) => source.to_string(), + None => "(none)".to_string(), + }; + let underlying_error2 = match error.source() { + Some(err) => match err.source() { + None => "(none)".to_string(), + Some(err2) => { + format!("{}", err2) + } + }, + None => "(none)".to_string(), + }; + let underlying_error3 = match error.source() { + Some(err) => match err.source() { + None => "(none)".to_string(), + Some(err2) => match err2.source() { + None => "(none)".to_string(), + Some(err3) => { + format!("{}", err3) + } + }, + }, + None => "(none)".to_string(), + }; + + data.push(RowOfTwo { + key: "underlying error", + value: &underlying_error1, + }); + data.push(RowOfTwo { + key: "underlying error", + value: &underlying_error2, + }); + data.push(RowOfTwo { + key: "underlying error", + value: &underlying_error3, + }); + let tb = Table::builder(data); tb.build() } From 385b6e482e860bc181629006f2ce809f97ee7b9d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 16 Jun 2025 21:07:30 +0400 Subject: [PATCH 123/320] Cosmetics, references #58 --- src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5b9ee02..fd3879f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -160,9 +160,9 @@ impl SharedSettings { let quiet = cli_args.get_one::("quiet").cloned().unwrap_or(false) || config_file_defaults.quiet; let scheme = if should_use_tls { - "https" + HTTPS_SCHEME.to_owned() } else { - config_file_defaults.scheme.as_str() + config_file_defaults.scheme.to_owned() }; let hostname = cli_args .get_one::("host") @@ -212,7 +212,7 @@ impl SharedSettings { non_interactive, quiet, base_uri: None, - scheme: scheme.to_string(), + scheme, hostname: Some(hostname), port: Some(port), path_prefix: path_prefix.clone(), @@ -233,7 +233,7 @@ impl SharedSettings { || default_non_interactive(); let quiet = cli_args.get_one::("quiet").cloned().unwrap_or(false) || default_quiet(); let scheme = if should_use_tls { - "https".to_owned() + HTTPS_SCHEME.to_owned() } else { default_scheme() }; From 7f4c34ffe6363dd4701f5e4fbc80c9b88e05a4da Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 20 Jun 2025 13:08:07 +0400 Subject: [PATCH 124/320] 2.2.1 --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 508b42b..8e2f5da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ No changes yet. +## v2.2.1 (Jun 19, 2025) + +### Bug Fixes + + * Several `rabbitmqadmin.conf` settings were not merged correctly with + the command line arguments. + + GitHub issue: [#58](https://github.com/rabbitmq/rabbitmqadmin-ng/issues/58) + + ## v2.2.0 (Jun 12, 2025) ### Enhancements diff --git a/Cargo.toml b/Cargo.toml index 64cc492..19e924c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.3.0" +version = "2.2.1" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 3ab5eaeb75fd5196b70f1af18b07c161a2455ed4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 20 Jun 2025 13:09:05 +0400 Subject: [PATCH 125/320] cargo update --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fb5469..6a14ffa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,9 +115,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" @@ -473,12 +473,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -992,9 +992,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.173" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -1414,9 +1414,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", @@ -1437,9 +1437,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" @@ -1465,7 +1465,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.3.0" +version = "2.2.1" dependencies = [ "assert_cmd", "clap", @@ -2735,18 +2735,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", From b5522cc3c7be59b4b179d1176cdd9e55921ac3ab Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 20 Jun 2025 13:18:14 +0400 Subject: [PATCH 126/320] Back to dev version --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e2f5da..b7f0d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ No changes yet. -## v2.2.1 (Jun 19, 2025) +## v2.2.1 (Jun 20, 2025) ### Bug Fixes diff --git a/Cargo.toml b/Cargo.toml index 19e924c..64cc492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.2.1" +version = "2.3.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From b6e15a058e941db61b68fffa51bcae45d88f6d22 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 22 Jun 2025 09:11:50 +0400 Subject: [PATCH 127/320] README: update an example --- Cargo.lock | 2 +- README.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a14ffa..e2c7ea9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1465,7 +1465,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.2.1" +version = "2.3.0" dependencies = [ "assert_cmd", "clap", diff --git a/README.md b/README.md index c7981e5..3e91740 100644 --- a/README.md +++ b/README.md @@ -158,11 +158,11 @@ will output a table that looks like this: ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Product name │ RabbitMQ │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ Product version │ 4.1.0 │ +│ Product version │ 4.1.1 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ RabbitMQ version │ 4.1.0 │ +│ RabbitMQ version │ 4.1.1 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ Erlang version │ 27.3.3 │ +│ Erlang version │ 27.3.4 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Erlang details │ Erlang/OTP 27 [erts-15.2.5] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ @@ -219,12 +219,28 @@ The output of the above command will not include any table borders and will is m as a result: ``` - key - Product name RabbitMQ - Product version 4.1.0 - RabbitMQ version 4.1.0 - Erlang version 27.3.3 - Erlang details Erlang/OTP 27 [erts-15.2.5 [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] +key +Product name RabbitMQ +Product version 4.1.1 +RabbitMQ version 4.1.1 +Erlang version 27.3.4 +Erlang details Erlang/OTP 27 [erts-15.2.7] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] +Connections (total) 0 +AMQP 0-9-1 channels (total) 0 +Queues and streams (total) 3 +Consumers (total) 0 +Messages (total) 0 +Messages ready for delivery (total) 0 +Messages delivered but unacknowledged by consumers (total) 0 +Publishing (ingress) rate (global) +Publishing confirm rate (global) +Consumer delivery (egress) rate (global) +Consumer delivery in automatic acknowledgement mode rate (global) +Consumer acknowledgement rate (global) +Unroutable messages: returned-to-publisher rate (global) +Unroutable messages: dropped rate (global) +Cluster tags "az": "us-east-3","environment": "production","region": "us-east", +Node tags "environment": "production","instance": "xlarge.m3", ``` ### Retrieving Basic Node Information @@ -554,6 +570,98 @@ passwords: `password-1`, `password-2`, and so forth), use the `obfuscate_usernam rabbitmqadmin definitions export --file /path/to/definitions.file.json --transformations obfuscate_usernames ``` +### Declare a Policy + +```shell +rabbitmqadmin --vhost "vh-1" policies declare \ + --name "policy-name-1" \ + --pattern '^cq.1\..+' \ + --apply-to "queues" \ + --priority 10 \ + --definition '{"max-length": 1000000}' +``` + +### Delete a Policy + +```shell +rabbitmqadmin --vhost "vh-1" policies delete --name "policy-name-1" +``` + +### List All Policies + +```shell +rabbitmqadmin policies list +``` + +### List Policies in A Virtual Host + +```shell +rabbitmqadmin --vhost "vh-1" policies list_in +``` + +### List Policies Matching an Object + +```shell +rabbitmqadmin --vhost "vh-1" policies list_matching_object --name "cq.1" --type "classic_queue" + +rabbitmqadmin --vhost "vh-1" policies list_matching_object --name "qq.1" --type "quorum_queue" + +rabbitmqadmin --vhost "vh-1" policies list_matching_object --name "topics.events" --type "exchange" +``` + +### Patch (Perform a Partial Update on) a Policy + +```shell +rabbitmqadmin --vhost "vh-1" policies patch \ + --name "policy-name-1" \ + --definition '{"max-length": 7777777, "max-length-bytes": 3333333333}' +``` + +### Remove One Or More Policy Definition Keys + +```shell +rabbitmqadmin policies delete_definition_keys \ + --name "policy-name-2" \ + --definition-keys max-length-bytes,max-length +``` + +### Declare an [Override Policy](https://www.rabbitmq.com/docs/policies#override) + +[Override policies](https://www.rabbitmq.com/docs/policies#override) are temporarily declared +policies that match the same objects as an existing policy but have a higher priority +and a slightly different definition. + +This is a potentially safer alternative to patching policies, say, during [Blue-Green deployment migrations](https://www.rabbitmq.com/docs/blue-green-upgrade). + +Override policies are meant to be relatively short lived. + +```shell +rabbitmqadmin --vhost "vh-1" policies declare_override \ + --name "policy-name-1" \ + --override-name "tmp.overrides.policy-name-1" \ + --apply-to "queues" \ + --definition '{"federation-upstream-set": "all"}' +``` + +### Declare a [Blanket Policy](https://www.rabbitmq.com/docs/policies#blanket) + +A [blanket policy](https://www.rabbitmq.com/docs/policies#blanket) is a policy with a negative priority that +matches all names. That is, it is a policy that matches everything not matched by other policies (that usually +will have positive priorities). + +Blanket policies are most useful in combination with override policies +covered above during [Blue-Green deployment migrations](https://www.rabbitmq.com/docs/blue-green-upgrade). + +Blanket policies are meant to be relatively short lived. + +```shell +rabbitmqadmin --vhost "vh-1" policies declare_blanket \ + --name "blanket-queuues" \ + --apply-to "queues" \ + --definition '{"federation-upstream-set": "all"}' +``` + + ### Import Definition To import definitions from the standard input, use `definitions import --stdin`: From e9062270dcf8cf21bba799f5deb58e91e6819750 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 24 Jun 2025 00:38:58 +0400 Subject: [PATCH 128/320] cargo update --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2c7ea9..2f0d354 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1332,9 +1332,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", "syn", @@ -1928,9 +1928,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", From 25b3d8ceee0efd8540a508d604ea8168125c17dd Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 28 Jun 2025 17:33:55 +0400 Subject: [PATCH 129/320] Use RabbitMQ HTTP API client 0.35.0 --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f0d354..9d8b55a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytecount" @@ -913,9 +913,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -1018,9 +1018,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags", "libc", @@ -1443,9 +1443,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb65a75e4797c2178db90ef382557a71664c5383c109734ccf86346f9d2a1f48" +checksum = "8bf2ef0090ab9feafeb58926ff6fc2f5b3118af93ed3ffc9078d4356e28dbd3c" dependencies = [ "backtrace", "percent-encoding", @@ -2503,9 +2503,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result", diff --git a/Cargo.toml b/Cargo.toml index 64cc492..0a04f68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.20", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.34.0", features = [ +rabbitmq_http_client = { version = "0.35.0", features = [ "blocking", "tabled", ] } From e2c392ee726804976b4a8471ad9acaf1b78a10dc Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 28 Jun 2025 17:34:27 +0400 Subject: [PATCH 130/320] Update change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f0d4f..197b890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v2.3.0 (in development) -No changes yet. + * RabbitMQ HTTP API client was upgraded to `0.35.0` ## v2.2.1 (Jun 20, 2025) From a97ce99168c8a0621b737ed42a1abcd4831d0094 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 29 Jun 2025 21:23:02 +0400 Subject: [PATCH 131/320] Change log edits --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 197b890..0405af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## v2.3.0 (in development) - * RabbitMQ HTTP API client was upgraded to `0.35.0` + * RabbitMQ HTTP API client was upgraded to [`0.35.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.35.0) to fix a `connections list` command + panic. ## v2.2.1 (Jun 20, 2025) From 73e30e4425f9c65002b3c374449dda7636b5bf1d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 30 Jun 2025 18:04:47 +0400 Subject: [PATCH 132/320] 2.3.0 has shipped --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0405af7..3097ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.3.0 (in development) +## v2.4.0 (in development) + +No changes yet. + + +## v2.3.0 (Jun 30, 2025) * RabbitMQ HTTP API client was upgraded to [`0.35.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.35.0) to fix a `connections list` command panic. From 7575619023564ca0ef07cc566e7d452fc419ccfe Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 30 Jun 2025 18:05:18 +0400 Subject: [PATCH 133/320] Bump dev version to 2.4.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0a04f68..8a3b029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.3.0" +version = "2.4.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 2cd39ea5948d919310e9f020153e6b87e9e74063 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 1 Jul 2025 17:12:46 +0300 Subject: [PATCH 134/320] Commit Cargo.lock after a dev version bump --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9d8b55a..2336bd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1465,7 +1465,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.3.0" +version = "2.4.0" dependencies = [ "assert_cmd", "clap", From 3144585304fec9c2de98de293a9aa378fd469d88 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 1 Jul 2025 20:32:12 +0300 Subject: [PATCH 135/320] cargo update --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2336bd2..c20f306 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,9 +623,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -1586,9 +1586,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.20" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64", "bytes", From 9b00bd32e5689f25f61aaaa39f3d0dbc01b08c6b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 4 Jul 2025 13:08:03 -0400 Subject: [PATCH 136/320] Bump HTTP API client to 0.36.0, cargo update --- Cargo.lock | 25 +++++++++++++++++++------ Cargo.toml | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c20f306..752d98d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c" dependencies = [ "jobserver", "libc", @@ -921,6 +921,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1443,9 +1454,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf2ef0090ab9feafeb58926ff6fc2f5b3118af93ed3ffc9078d4356e28dbd3c" +checksum = "44d7f5efb4ff99b1c868042d995d74f2f3cee9b9afd85a89d392db4a15ed994c" dependencies = [ "backtrace", "percent-encoding", @@ -2114,15 +2125,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", "windows-sys 0.52.0", ] diff --git a/Cargo.toml b/Cargo.toml index 8a3b029..f1acb02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.20", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.35.0", features = [ +rabbitmq_http_client = { version = "0.36.0", features = [ "blocking", "tabled", ] } From 523aff611be61eaff67d45966d1b50cdc79a71f0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 4 Jul 2025 13:08:24 -0400 Subject: [PATCH 137/320] 2.4.0 release preparation --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3097ca2..d6cf88b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,22 @@ # rabbitmqadmin-ng Change Log -## v2.4.0 (in development) +## v2.5.0 (in development) No changes yet. +## v2.4.0 (Jul 4, 2025) + +### Bug Fixes + + * `connections list` failed to deserialize a list of connections that included direct connections + (as in the Erlang AMQP 0-9-1 client), namely local connections of shovels and federation links. + + GitHub issue: [#68](https://github.com/rabbitmq/rabbitmqadmin-ng/issues/68) + +### Upgrades + + * RabbitMQ HTTP API client was upgraded to [`0.36.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.36.0) + ## v2.3.0 (Jun 30, 2025) From 75d3e1d28276c1dd6b7372a04b783e8892fda0c0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 4 Jul 2025 13:28:48 -0400 Subject: [PATCH 138/320] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f1acb02..8bdc3f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.4.0" +version = "2.5.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 737c38773c8ce8680dd0fd4968615ecde6428ae6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 7 Jul 2025 10:24:25 -0400 Subject: [PATCH 139/320] Bump dev version --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 752d98d..e36a78c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.4.0" +version = "2.5.0" dependencies = [ "assert_cmd", "clap", From a60794248c9915f2708600ab4b7aa003769a76f8 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 7 Jul 2025 13:11:27 -0400 Subject: [PATCH 140/320] cargo update --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e36a78c..feb2593 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.28" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -2125,9 +2125,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.0" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", From 7ddb437553ffa811a56e4208cd2b527331b9038a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 10 Jul 2025 10:31:17 -0400 Subject: [PATCH 141/320] Make sure 'export definitions' CLI interface matches that of 'definitions export' 'export definitions' only exists for better compatibility with the original rabbitmqadmin, v1. So it has fallen behind the evolution of the 'definitions' command group. --- src/cli.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 3f6a1eb..d9a5e6b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2420,17 +2420,29 @@ fn exchanges_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] } fn export_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { let definitions = Command::new("definitions") - .about("Prefer 'definitions export'") + .about("Export cluster-wide definitions") .after_help(color_print::cformat!( "Doc guide: {}", DEFINITION_GUIDE_URL )) .arg( Arg::new("file") + .group("output") .long("file") - .help("output path") + .help("output file path") + .required(false) + .default_value("-") + .conflicts_with("stdout"), + ) + .arg( + Arg::new("stdout") + .group("output") + .long("stdout") + .help("print result to the standard output stream") .required(false) - .default_value("-"), + .num_args(0) + .action(ArgAction::SetTrue) + .conflicts_with("file"), ) .arg( Arg::new("transformations") @@ -2442,10 +2454,22 @@ A comma-separated list of names of the definition transformations to apply. Supported transformations: + * no_op * strip_cmq_keys_from_policies * drop_empty_policies + * obfuscate_usernames + * exclude_users + * exclude_permissions + * exclude_runtime_parameters + * exclude_policies -Example use: --transformations strip_cmq_keys_from_policies,drop_empty_policies +Examples: + + * --transformations strip_cmq_keys_from_policies,drop_empty_policies + * --transformations exclude_users,exclude_permissions + * --transformations obfuscate_usernames + * --transformations exclude_runtime_parameters,exclude_policies + * --transformations no_op "#, ) .num_args(1..) From 038da6acd6c0f853d38e8b204272e47eec6d0498 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 10 Jul 2025 11:37:26 -0400 Subject: [PATCH 142/320] Change log update --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6cf88b..8da0cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ ## v2.5.0 (in development) -No changes yet. +### Bug Fixes + + * `export definitions` CLI interface was unintentionally different from that of `definitions export`. + Note that `export definitions` only exists for better backwards compatibility with `rabbitmqadmin` v1, + use `definitions export` when possible. + ## v2.4.0 (Jul 4, 2025) From df8a0936c6245dbe9c88e26c07f40311670ffa71 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 00:30:33 -0400 Subject: [PATCH 143/320] HTTP API client 0.37.0 --- Cargo.lock | 32 ++++++++++++++++---------------- Cargo.toml | 2 +- src/commands.rs | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index feb2593..fd142e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +checksum = "08b5d4e069cbc868041a64bd68dc8cb39a0d79585cd6c5a24caa8c2d622121be" dependencies = [ "aws-lc-sys", "zeroize", @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ "bindgen", "cc", @@ -278,18 +278,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -756,9 +756,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64", "bytes", @@ -1454,9 +1454,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d7f5efb4ff99b1c868042d995d74f2f3cee9b9afd85a89d392db4a15ed994c" +checksum = "133ee28f0c1964c4f047e0047087bc81ff310b0c6d63b17d05c575a96d900ec3" dependencies = [ "backtrace", "percent-encoding", @@ -1702,9 +1702,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "aws-lc-rs", "log", @@ -1740,9 +1740,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "aws-lc-rs", "ring", diff --git a/Cargo.toml b/Cargo.toml index 8bdc3f1..54e71be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.20", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.36.0", features = [ +rabbitmq_http_client = { version = "0.37.0", features = [ "blocking", "tabled", ] } diff --git a/src/commands.rs b/src/commands.rs index 0be69d5..d023c9c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -34,7 +34,7 @@ use std::process; use crate::constants::DEFAULT_BLANKET_POLICY_PRIORITY; use rabbitmq_http_client::commons::BindingDestinationType; use rabbitmq_http_client::commons::QueueType; -use rabbitmq_http_client::responses::PolicyDefinitionOps; +use rabbitmq_http_client::responses::OptionalArgumentSourceOps; use rabbitmq_http_client::transformers::TransformationChain; use rabbitmq_http_client::{password_hashing, requests, responses}; use serde_json::Value; From 64bf50859930f7f8387b2f2298a1ef923a41fd5f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 00:32:26 -0400 Subject: [PATCH 144/320] Document a new definitions transformation: prepare_for_quorum_queue_migration --- src/cli.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index d9a5e6b..797fabb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2184,6 +2184,7 @@ A comma-separated list of names of the definition transformations to apply. Supported transformations: * no_op + * prepare_for_quorum_queue_migration * strip_cmq_keys_from_policies * drop_empty_policies * obfuscate_usernames @@ -2194,6 +2195,7 @@ Supported transformations: Examples: + * --transformations prepare_for_quorum_queue_migration,drop_empty_policies * --transformations strip_cmq_keys_from_policies,drop_empty_policies * --transformations exclude_users,exclude_permissions * --transformations obfuscate_usernames @@ -2455,6 +2457,7 @@ A comma-separated list of names of the definition transformations to apply. Supported transformations: * no_op + * prepare_for_quorum_queue_migration * strip_cmq_keys_from_policies * drop_empty_policies * obfuscate_usernames @@ -2465,6 +2468,7 @@ Supported transformations: Examples: + * --transformations prepare_for_quorum_queue_migration,drop_empty_policies * --transformations strip_cmq_keys_from_policies,drop_empty_policies * --transformations exclude_users,exclude_permissions * --transformations obfuscate_usernames From 9e308d7fae2da0785348f482a4790e84097448e7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 00:36:05 -0400 Subject: [PATCH 145/320] 2.5.0 --- CHANGELOG.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da0cfe..f935694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,24 @@ # rabbitmqadmin-ng Change Log -## v2.5.0 (in development) +## v2.6.0 (in development) + +No changes yet. + + +## v2.5.0 (Jul 11, 2025) + +### Enhancements + + * `definitions export` now supports a new transformation: `prepare_for_quorum_queue_migration`. + + ```shell + rabbitmqadmin definitions export --transformations prepare_for_quorum_queue_migration,drop_empty_policies --stdout + ``` + + This one not only strips off the CMQ-related keys + but also handles an incompatible `"overflow"`/`"x-overflow"` key value + and `"queue-mode"`/`"x-queue-mode"` keys, both not supported + by quorum queues. ### Bug Fixes From 3203468b681693a3b96fe40952025e69c01bf6dd Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 01:41:35 -0400 Subject: [PATCH 146/320] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 54e71be..f291468 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.5.0" +version = "2.6.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 655b8f8433e854388ebbbd586a470d1e40e898cb Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 15:07:10 -0400 Subject: [PATCH 147/320] Introduce 'passwords', add --hashing-algorithm to 'users declare' --- Cargo.lock | 10 ++++------ Cargo.toml | 2 +- src/cli.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/commands.rs | 32 ++++++++++++++++++++++++-------- src/main.rs | 4 ++++ src/output.rs | 22 ++++++++++++++++++++++ src/tables.rs | 29 +++++++++++++++++++++++++++++ tests/users_tests.rs | 27 ++++++++++++++++++++++++++- 8 files changed, 150 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd142e4..c140cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1454,9 +1454,7 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133ee28f0c1964c4f047e0047087bc81ff310b0c6d63b17d05c575a96d900ec3" +version = "0.38.0" dependencies = [ "backtrace", "percent-encoding", @@ -1476,7 +1474,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.5.0" +version = "2.6.0" dependencies = [ "assert_cmd", "clap", @@ -2700,9 +2698,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index f291468..579378c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.20", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.37.0", features = [ +rabbitmq_http_client = { version = "0.38.0", features = [ "blocking", "tabled", ] } diff --git a/src/cli.rs b/src/cli.rs index 797fabb..c47f357 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -23,6 +23,7 @@ use rabbitmq_http_client::commons::{ BindingDestinationType, ExchangeType, MessageTransferAcknowledgementMode, PolicyTarget, QueueType, SupportedProtocol, }; +use rabbitmq_http_client::password_hashing::HashingAlgorithm; use rabbitmq_http_client::requests::FederationResourceCleanupMode; pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { @@ -210,6 +211,15 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { )) .subcommand_value_name("runtime_parameter") .subcommands(parameters_subcommands(pre_flight_settings.clone())); + let passwords_group = Command::new("passwords") + .about("Operations on passwords") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + PASSWORD_GUIDE_URL + )) + .subcommands(passwords_subcommands(pre_flight_settings.clone())); let policies_group = Command::new("policies") .about("Operations on policies") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -321,6 +331,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { nodes_group, operator_policies_group, parameters_group, + passwords_group, policies_group, publish_group, purge_group, @@ -2679,6 +2690,16 @@ pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] .required(false) .default_value(""), ) + .arg( + Arg::new("hashing_algorithm") + .long("hashing-algorithm") + .required(false) + .conflicts_with("password_hash") + .requires("password") + .value_parser(value_parser!(HashingAlgorithm)) + .default_value("SHA256") + .help("The hashing algorithm to use: SHA256 or SHA512"), + ) .arg( Arg::new("tags") .long("tags") @@ -2745,6 +2766,25 @@ pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +pub fn passwords_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { + let hash_password = Command::new("salt_and_hash") + .arg( + Arg::new("password") + .required(true) + .help("A cleartext password value to hash"), + ) + .arg( + Arg::new("hashing_algorithm") + .long("hashing-algorithm") + .required(false) + .value_parser(value_parser!(HashingAlgorithm)) + .default_value("SHA256") + .help("The hashing algorithm to use: SHA256 or SHA512"), + ); + + [hash_password].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("message") .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) diff --git a/src/commands.rs b/src/commands.rs index d023c9c..f47aba3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,13 +13,17 @@ // limitations under the License. #![allow(clippy::result_large_err)] +use crate::constants::DEFAULT_BLANKET_POLICY_PRIORITY; use clap::ArgMatches; use rabbitmq_http_client::blocking_api::Client; use rabbitmq_http_client::blocking_api::Result as ClientResult; use rabbitmq_http_client::commons; +use rabbitmq_http_client::commons::BindingDestinationType; +use rabbitmq_http_client::commons::QueueType; use rabbitmq_http_client::commons::{ExchangeType, SupportedProtocol}; use rabbitmq_http_client::commons::{MessageTransferAcknowledgementMode, UserLimitTarget}; use rabbitmq_http_client::commons::{PolicyTarget, VirtualHostLimitTarget}; +use rabbitmq_http_client::password_hashing::{HashingAlgorithm, HashingError}; use rabbitmq_http_client::requests::{ Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, @@ -27,17 +31,13 @@ use rabbitmq_http_client::requests::{ FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, }; -use std::fs; -use std::io; -use std::process; - -use crate::constants::DEFAULT_BLANKET_POLICY_PRIORITY; -use rabbitmq_http_client::commons::BindingDestinationType; -use rabbitmq_http_client::commons::QueueType; use rabbitmq_http_client::responses::OptionalArgumentSourceOps; use rabbitmq_http_client::transformers::TransformationChain; use rabbitmq_http_client::{password_hashing, requests, responses}; use serde_json::Value; +use std::fs; +use std::io; +use std::process; type APIClient<'a> = Client<&'a str, &'a str, &'a str>; @@ -910,8 +910,12 @@ pub fn declare_user(client: APIClient, command_args: &ArgMatches) -> ClientResul } let password_hash = if provided_hash.is_empty() { + let hashing_algo = command_args + .get_one::("hashing_algorithm") + .unwrap(); let salt = password_hashing::salt(); - password_hashing::base64_encoded_salted_password_hash_sha256(&salt, password) + let hash = hashing_algo.salt_and_hash(&salt, password).unwrap(); + String::from_utf8_lossy(hash.as_slice()).to_string() } else { provided_hash.to_string() }; @@ -924,6 +928,18 @@ pub fn declare_user(client: APIClient, command_args: &ArgMatches) -> ClientResul client.create_user(¶ms) } +pub fn salt_and_hash_password(command_args: &ArgMatches) -> Result { + let password = command_args.get_one::("password").cloned().unwrap(); + let hashing_algo = command_args + .get_one::("hashing_algorithm") + .unwrap(); + + let salt = password_hashing::salt(); + let password_hash = hashing_algo.salt_and_hash(&salt, &password)?; + + Ok(String::from_utf8_lossy(password_hash.as_slice()).to_string()) +} + pub fn declare_permissions( client: APIClient, vhost: &str, diff --git a/src/main.rs b/src/main.rs index 0fd79b0..87be3fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -729,6 +729,10 @@ fn dispatch_common_subcommand( let result = commands::declare_parameter(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } + ("passwords", "salt_and_hash") => { + let result = commands::salt_and_hash_password(second_level_args); + res_handler.show_salted_and_hashed_value(result) + } ("policies", "declare") => { let result = commands::declare_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/src/output.rs b/src/output.rs index 8d0ddfb..dd7ef80 100644 --- a/src/output.rs +++ b/src/output.rs @@ -17,6 +17,7 @@ use crate::tables; use clap::ArgMatches; use rabbitmq_http_client::blocking_api::{HttpClientError, Result as ClientResult}; use rabbitmq_http_client::error::Error as ClientError; +use rabbitmq_http_client::password_hashing::HashingError; use rabbitmq_http_client::responses::{ NodeMemoryBreakdown, Overview, SchemaDefinitionSyncStatus, WarmStandbyReplicationStatus, }; @@ -200,6 +201,20 @@ impl<'a> ResultHandler<'a> { } } + pub fn show_salted_and_hashed_value(&mut self, result: Result) { + match result { + Ok(value) => { + self.exit_code = Some(ExitCode::Ok); + + let mut table = tables::show_salted_and_hashed_value(value); + self.table_styler.apply(&mut table); + + println!("{}", table); + } + Err(error) => self.report_hashing_error(&error), + } + } + pub fn tabular_result(&mut self, result: ClientResult>) where T: fmt::Debug + Tabled, @@ -381,6 +396,13 @@ impl<'a> ResultHandler<'a> { let code = client_error_to_exit_code(error); self.exit_code = Some(code); } + + fn report_hashing_error(&mut self, error: &HashingError) { + let mut table = tables::hashing_error_details(error); + self.table_styler.apply(&mut table); + eprintln!("{}", table); + self.exit_code = Some(ExitCode::DataErr); + } } // We cannot implement From for two types in other crates, so… diff --git a/src/tables.rs b/src/tables.rs index d2c8ce7..c33f7cf 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -14,6 +14,7 @@ use std::error::Error; // limitations under the License. use rabbitmq_http_client::blocking_api::HttpClientError; use rabbitmq_http_client::formatting::*; +use rabbitmq_http_client::password_hashing::HashingError; use rabbitmq_http_client::responses::{ ClusterAlarmCheckDetails, HealthCheckFailureDetails, NodeMemoryBreakdown, Overview, QuorumCriticalityCheckDetails, SchemaDefinitionSyncStatus, @@ -188,6 +189,17 @@ pub fn churn_overview(ov: Overview) -> Table { t } +pub fn show_salted_and_hashed_value(value: String) -> Table { + let data = vec![RowOfTwo { + key: "value", + value: value.as_str(), + }]; + let tb = Table::builder(data); + let mut t = tb.build(); + t.with(Panel::header("Result")); + t +} + pub fn schema_definition_sync_status(status: SchemaDefinitionSyncStatus) -> Table { let operating_mode_s = &status.operating_mode.into(); let state_s = &status.state.into(); @@ -519,6 +531,23 @@ fn generic_failed_request_details( tb.build() } +pub fn hashing_error_details(error: &HashingError) -> Table { + let details = format!("{}", error); + let data = vec![ + RowOfTwo { + key: "result", + value: "hashing failed", + }, + RowOfTwo { + key: "details", + value: details.as_str(), + }, + ]; + + let tb = Table::builder(data); + tb.build() +} + pub fn health_check_failure( path: &str, status_code: StatusCode, diff --git a/tests/users_tests.rs b/tests/users_tests.rs index 5050eb6..3383a96 100644 --- a/tests/users_tests.rs +++ b/tests/users_tests.rs @@ -22,8 +22,8 @@ fn test_list_users() -> Result<(), Box> { let username = "test_list_users"; let password = "pa$$w0rd"; run_succeeds([ + "users", "declare", - "user", "--name", username, "--password", @@ -84,3 +84,28 @@ fn test_list_users_with_table_styles() -> Result<(), Box> Ok(()) } + +#[test] +fn test_create_user() -> Result<(), Box> { + let username = "test_create_user.1"; + let password = "pa$$w0rd///8*9"; + run_succeeds([ + "users", + "declare", + "--name", + username, + "--password", + password, + "--hashing-algorithm", + "sha256", + "--tags", + "administrator", + ]); + + run_succeeds(["--username", username, "--password", password, "users", "list"]).stdout(predicate::str::contains(username)); + run_succeeds(["users", "delete", "--name", username]); + + run_succeeds(["list", "users"]).stdout(predicate::str::contains(username).not()); + + Ok(()) +} \ No newline at end of file From 9f4916490383ee042ae400ec0aa6d3063c4094d9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 15:12:01 -0400 Subject: [PATCH 148/320] Commit a Cargo.lock update --- Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c140cd2..ed11e83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1455,6 +1455,8 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756e7eb66ebc5b18da7c7bf07ddfb987f3356be0a009e04dc835067abe7bd0fc" dependencies = [ "backtrace", "percent-encoding", From 3ae245f582479ce27e021f315968f5f46e65bc47 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 15:12:18 -0400 Subject: [PATCH 149/320] Wording --- src/tables.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tables.rs b/src/tables.rs index c33f7cf..87bc782 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -191,7 +191,7 @@ pub fn churn_overview(ov: Overview) -> Table { pub fn show_salted_and_hashed_value(value: String) -> Table { let data = vec![RowOfTwo { - key: "value", + key: "password hash", value: value.as_str(), }]; let tb = Table::builder(data); From a767d8b32585ead28d5091d8afd68c3c3b5b05b7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 15:20:54 -0400 Subject: [PATCH 150/320] Use --hashing-algorithm sha512 in a test --- src/commands.rs | 2 +- tests/users_tests.rs | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index f47aba3..21385d1 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -915,7 +915,7 @@ pub fn declare_user(client: APIClient, command_args: &ArgMatches) -> ClientResul .unwrap(); let salt = password_hashing::salt(); let hash = hashing_algo.salt_and_hash(&salt, password).unwrap(); - String::from_utf8_lossy(hash.as_slice()).to_string() + String::from_utf8(hash).unwrap().to_string() } else { provided_hash.to_string() }; diff --git a/tests/users_tests.rs b/tests/users_tests.rs index 3383a96..17fb940 100644 --- a/tests/users_tests.rs +++ b/tests/users_tests.rs @@ -86,9 +86,12 @@ fn test_list_users_with_table_styles() -> Result<(), Box> } #[test] -fn test_create_user() -> Result<(), Box> { - let username = "test_create_user.1"; - let password = "pa$$w0rd///8*9"; +fn test_create_user_using_sha256_for_hashing() -> Result<(), Box> { + let username = "test_create_user_using_sha256_for_hashing.1"; + let password = "pa$$w0rd_9w798f__sd8f7"; + + run_succeeds(["users", "delete", "--name", username, "--idempotently"]); + run_succeeds([ "users", "declare", @@ -107,5 +110,33 @@ fn test_create_user() -> Result<(), Box> { run_succeeds(["list", "users"]).stdout(predicate::str::contains(username).not()); + Ok(()) +} + +#[test] +fn test_create_user_using_sha512_for_hashing() -> Result<(), Box> { + let username = "test_create_user_using_sha512_for_hashing.1"; + let password = "pa$$w0rd///8*9"; + + run_succeeds(["users", "delete", "--name", username, "--idempotently"]); + + run_succeeds([ + "users", + "declare", + "--name", + username, + "--password", + password, + "--hashing-algorithm", + "sha512", + "--tags", + "administrator", + ]); + // unless the node is also configured to use SHA-512, we cannot try this + // password the same way we do in the SHA-256 version, by passing in --username and --password + run_succeeds(["users", "delete", "--name", username]); + + run_succeeds(["list", "users"]).stdout(predicate::str::contains(username).not()); + Ok(()) } \ No newline at end of file From 987fc429f41d4b21f75a872d5f76c386d10f9d6f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 16:07:26 -0400 Subject: [PATCH 151/320] cargo fmt --- tests/users_tests.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/users_tests.rs b/tests/users_tests.rs index 17fb940..a204469 100644 --- a/tests/users_tests.rs +++ b/tests/users_tests.rs @@ -105,7 +105,15 @@ fn test_create_user_using_sha256_for_hashing() -> Result<(), Box Result<(), Box Date: Fri, 11 Jul 2025 16:14:47 -0400 Subject: [PATCH 152/320] Make sure 'declare user' also supports --hashing-algorithm --- src/cli.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index c47f357..53fdef1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -696,6 +696,16 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] .required(false) .default_value(""), ) + .arg( + Arg::new("hashing_algorithm") + .long("hashing-algorithm") + .required(false) + .conflicts_with("password_hash") + .requires("password") + .value_parser(value_parser!(HashingAlgorithm)) + .default_value("SHA256") + .help("The hashing algorithm to use: SHA256 or SHA512"), + ) .arg( Arg::new("tags") .long("tags") From a09a7e0a8df80d55a285998566157fad94ef81b5 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 23:38:42 -0400 Subject: [PATCH 153/320] 2.6.0 --- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++++- README.md | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f935694..ce1f357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,44 @@ # rabbitmqadmin-ng Change Log -## v2.6.0 (in development) +## v2.7.0 (in development) No changes yet. +## v2.6.0 (Jul 11, 2025) + +### Enhancements + + * New command, `passwords salt_and_hash`, that implements the [password salting and hashing algorithm](https://www.rabbitmq.com/docs/passwords#computing-password-hash) + used by RabbitMQ's internal authentication backend: + + ```shell + rabbitmqadmin passwords salt_and_hash "sEkr37^va1ue" + # => ┌───────────────┬──────────────────────────────────────────────────┐ + # => │ Result │ + # => ├───────────────┼──────────────────────────────────────────────────┤ + # => │ key │ value │ + # => ├───────────────┼──────────────────────────────────────────────────┤ + # => │ password hash │ vRZC0bF0Ut4+6pmcQRSu87S/wRXdHRalgY5DV/5KDd5SzK69 │ + # => └───────────────┴──────────────────────────────────────────────────┘ + ``` + + This value can be passed as a `--password-hash` when creating a user with the `users declare` + command. + + * `users declare` now supports a new argument, `--hashing-algorithm`, that accepts two + possible values: `sha256` (the default) and `sha512`: + + ```shell + # RabbitMQ nodes must also be configured to use SHA-512 password hashing, + # or this user won't be able to authenticate against them + rabbitmqadmin users declare --username "username43742" --password "example_%^4@8s7" --hashing-algorithm "sha512" + ``` + + Target RabbitMQ nodes must be [configured](https://www.rabbitmq.com/docs/passwords#changing-algorithm) to use the same hashing algorithm (SHA-256 is + used by default). + + ## v2.5.0 (Jul 11, 2025) ### Enhancements diff --git a/README.md b/README.md index 3e91740..25e25f8 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Commands: nodes Node operations operator_policies Operations on operator policies parameters Operations on runtime parameters + passwords Operations on passwords policies Operations on policies publish Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. purge Purges queues From 963b612232a2a724b80e6a26272a04f2419686af Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 23:42:17 -0400 Subject: [PATCH 154/320] Change log cosmetics --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce1f357..89e1548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ No changes yet. -## v2.6.0 (Jul 11, 2025) +## v2.6.0 (Jul 12, 2025) ### Enhancements From a52ec939632ccdeecde6fa3983da3f7c4ee2d4e6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 11 Jul 2025 23:42:36 -0400 Subject: [PATCH 155/320] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 579378c..cc6aedc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.6.0" +version = "2.7.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 30cf7e0cfa4cbabf43144a554b2818d7c98c04d9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Jul 2025 04:25:27 -0400 Subject: [PATCH 156/320] Make sure current version is mentioned in --help a custom .help_template would be too much, appending the version to .about gets the job done. --- Cargo.lock | 2 +- src/cli.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed11e83..8ed9faa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.6.0" +version = "2.7.0" dependencies = [ "assert_cmd", "clap", diff --git a/src/cli.rs b/src/cli.rs index 53fdef1..ebd6003 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -347,8 +347,8 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { Command::new("rabbitmqadmin") .version(clap::crate_version!()) - .author("RabbitMQ Core Team") - .about("rabbitmqadmin gen 2") + .author("The RabbitMQ Core Team") + .about(format!("rabbitmqadmin gen 2, version: {}", clap::crate_version!())) .long_about(format!( "RabbitMQ CLI that uses the HTTP API. Version: {}", clap::crate_version!() From 54eebdeaf0481aef2098c09c8a30da9e1d19f2a7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Jul 2025 04:27:50 -0400 Subject: [PATCH 157/320] Change log update --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e1548..129f68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## v2.7.0 (in development) -No changes yet. +### Bug Fixes + + * Tool version was unintentionally missing from `--help` output ## v2.6.0 (Jul 12, 2025) From 14917727be34ff2555f3a0830df0d4edade62997 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 13 Jul 2025 04:29:43 -0400 Subject: [PATCH 158/320] Change log: clarify --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 129f68b..7dd1541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Bug Fixes - * Tool version was unintentionally missing from `--help` output + * Tool version was unintentionally missing from `-h` output (but present in its long counterpart, `--help`) ## v2.6.0 (Jul 12, 2025) From b0770926e19c6d80a15c4737a0c3ab570e131894 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 14 Jul 2025 14:38:33 -0400 Subject: [PATCH 159/320] Bump HTTP API client to 0.39.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ed9faa..aaa7712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1454,9 +1454,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756e7eb66ebc5b18da7c7bf07ddfb987f3356be0a009e04dc835067abe7bd0fc" +checksum = "87926e8ac68f95f53d97fade565f2ff96b3b60d1f5dea16da89536ea68893555" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index cc6aedc..bff3f78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.20", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.38.0", features = [ +rabbitmq_http_client = { version = "0.39.0", features = [ "blocking", "tabled", ] } From 78f96775e25de3b0a9567142be85f3cc1b746064 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 14 Jul 2025 20:40:54 -0400 Subject: [PATCH 160/320] Bump dependencies --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bff3f78..9d2e10d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" clap = { version = "4", features = ["help", "color", "cargo", "env"] } url = "2" sysexits = "0.9" -reqwest = { version = "0.12.20", features = [ +reqwest = { version = "0.12.22", features = [ "blocking", "json", "multipart", @@ -27,7 +27,7 @@ tabled = "0.20" toml = "0.8" color-print = "0.3" thiserror = "2" -shellexpand = "3.0" +shellexpand = "3.1" log = "0.4" rustls = { version = "0.23", features = ["aws_lc_rs"] } From 87d5e7b44b2b5a3a1c6f8cdb11376c6c476a1660 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 14 Jul 2025 20:41:11 -0400 Subject: [PATCH 161/320] Add .github/dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..90a0d1c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + reviewers: + - "michaelklishin" + assignees: + - "michaelklishin" From 50959f1852e552ca1e755c68aa7818a134811665 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 14 Jul 2025 22:01:52 -0400 Subject: [PATCH 162/320] Refactoring --- src/commands.rs | 2 +- src/main.rs | 151 ++++++++++++++++++++++++++---------------------- 2 files changed, 84 insertions(+), 69 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 21385d1..ff5e7bf 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -39,7 +39,7 @@ use std::fs; use std::io; use std::process; -type APIClient<'a> = Client<&'a str, &'a str, &'a str>; +type APIClient = Client; pub fn show_overview(client: APIClient) -> ClientResult { client.overview() diff --git a/src/main.rs b/src/main.rs index 87be3fa..ff26fc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,7 +48,7 @@ use reqwest::blocking::Client as HTTPClient; use rustls::crypto::CryptoProvider; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -type APIClient<'a> = GenericAPIClient<&'a str, &'a str, &'a str>; +type APIClient = GenericAPIClient; fn main() { let pre_flight_settings = match pre_flight::is_non_interactive() { @@ -61,22 +61,39 @@ fn main() { let parser = cli::parser(pre_flight_settings); let cli = parser.get_matches(); + + let (common_settings, endpoint) = resolve_run_configuration(&cli); + + match configure_http_api_client(&cli, &common_settings, &endpoint.clone()) { + Ok(client) => { + let exit_code = dispatch_command(&cli, client, &common_settings, endpoint); + process::exit(exit_code.into()) + } + Err(err) => { + let mut res_handler = ResultHandler::new(&common_settings, &cli); + res_handler.report_pre_command_run_error(&err); + let code = res_handler.exit_code.unwrap_or(ExitCode::DataErr); + process::exit(code.into()) + } + } +} + +fn resolve_run_configuration(cli: &ArgMatches) -> (SharedSettings, String) { let default_config_file_path = PathBuf::from(DEFAULT_CONFIG_FILE_PATH); let config_file_path = cli .get_one::("config_file_path") .cloned() .unwrap_or(PathBuf::from(DEFAULT_CONFIG_FILE_PATH)); let uses_default_config_file_path = config_file_path == default_config_file_path; - // config file entries are historically called nodes let node_alias = cli .get_one::("node_alias") .cloned() .or(Some(DEFAULT_NODE_ALIAS.to_string())); - let cf_ss = SharedSettings::from_config_file(&config_file_path, node_alias.clone()); // If the default config file path is used and the function above - // reports that it is not found, continue. Otherwise exit. + // reports that it is not found, continue. Otherwise, exit. + let cf_ss = SharedSettings::from_config_file(&config_file_path, node_alias.clone()); if cf_ss.is_err() && !uses_default_config_file_path { eprintln!( "Encountered an error when trying to load configuration for node alias '{}' in configuration file '{}'", @@ -86,82 +103,80 @@ fn main() { eprintln!("Underlying error: {}", cf_ss.unwrap_err()); process::exit(ExitCode::DataErr.into()) } + let common_settings = if let Ok(val) = cf_ss { - SharedSettings::from_args_with_defaults(&cli, &val) + SharedSettings::from_args_with_defaults(cli, &val) } else { - SharedSettings::from_args(&cli) + SharedSettings::from_args(cli) }; let endpoint = common_settings.endpoint(); - let httpc_result = build_http_client(&cli, &common_settings); - match httpc_result { - Ok(httpc) => { - // SharedSettings considers not just one but multiple ways to obtain - // the value if it wasn't passed on the command line, so these are - // safe to unwrap() - let username = common_settings.username.clone().unwrap(); - let password = common_settings.password.clone().unwrap(); - let client = build_rabbitmq_http_api_client(httpc, &endpoint, &username, &password); - - if let Some((first_level, first_level_args)) = cli.subcommand() { - if let Some((second_level, second_level_args)) = first_level_args.subcommand() { - // this is a Tanzu RabbitMQ-specific command, these are grouped under "tanzu" - if first_level == TANZU_COMMAND_PREFIX { - if let Some((third_level, third_level_args)) = - second_level_args.subcommand() - { - let pair = (second_level, third_level); - - // let vhost = virtual_host(&common_settings, second_level_args); - - let mut res_handler = - ResultHandler::new(&common_settings, second_level_args); - let exit_code = dispatch_tanzu_subcommand( - pair, - third_level_args, - client, - &mut res_handler, - ); - - process::exit(exit_code.into()) - } - } else { - // this is a common (OSS and Tanzu) command - let pair = (first_level, second_level); - - let vhost = virtual_host(&common_settings, second_level_args); + (common_settings, endpoint) +} - let mut res_handler = - ResultHandler::new(&common_settings, second_level_args); - let exit_code = dispatch_common_subcommand( - pair, - second_level_args, - client, - common_settings.endpoint(), - vhost, - &mut res_handler, - ); +fn configure_http_api_client<'a>( + cli: &'a ArgMatches, + common_settings: &'a SharedSettings, + endpoint: &'a str, +) -> Result { + let httpc = build_http_client(cli, common_settings)?; + // Due to how SharedSettings are computed, these should safe to unwrap() + let username = common_settings.username.clone().unwrap(); + let password = common_settings.password.clone().unwrap(); + let client = build_rabbitmq_http_api_client( + httpc, + endpoint.to_owned(), + username.clone(), + password.clone(), + ); + Ok(client) +} - process::exit(exit_code.into()) - } +fn dispatch_command( + cli: &ArgMatches, + client: APIClient, + common_settings: &SharedSettings, + endpoint: String, +) -> ExitCode { + if let Some((first_level, first_level_args)) = cli.subcommand() { + if let Some((second_level, second_level_args)) = first_level_args.subcommand() { + // this is a Tanzu RabbitMQ-specific command, these are grouped under "tanzu" + if first_level == TANZU_COMMAND_PREFIX { + if let Some((third_level, third_level_args)) = second_level_args.subcommand() { + let pair = (second_level, third_level); + let mut res_handler = ResultHandler::new(common_settings, second_level_args); + return dispatch_tanzu_subcommand( + pair, + third_level_args, + client, + &mut res_handler, + ); } + } else { + // this is a common (OSS and Tanzu) command + let pair = (first_level, second_level); + let vhost = virtual_host(common_settings, second_level_args); + let mut res_handler = ResultHandler::new(common_settings, second_level_args); + return dispatch_common_subcommand( + pair, + second_level_args, + client, + endpoint, + vhost, + &mut res_handler, + ); } } - Err(err) => { - let mut res_handler = ResultHandler::new(&common_settings, &cli); - res_handler.report_pre_command_run_error(&err); - let code = res_handler.exit_code.unwrap_or(ExitCode::DataErr); - process::exit(code.into()) - } } + ExitCode::Usage } -fn build_rabbitmq_http_api_client<'a>( +fn build_rabbitmq_http_api_client( httpc: HTTPClient, - endpoint: &'a str, - username: &'a str, - password: &'a str, -) -> APIClient<'a> { + endpoint: String, + username: String, + password: String, +) -> APIClient { ClientBuilder::new() .with_endpoint(endpoint) .with_basic_auth_credentials(username, password) @@ -275,7 +290,7 @@ fn load_private_key(filename: &str) -> Result, CommandRun fn dispatch_common_subcommand( pair: (&str, &str), second_level_args: &ArgMatches, - client: APIClient<'_>, + client: APIClient, endpoint: String, vhost: String, res_handler: &mut ResultHandler, @@ -957,7 +972,7 @@ fn dispatch_common_subcommand( fn dispatch_tanzu_subcommand( pair: (&str, &str), third_level_args: &ArgMatches, - client: APIClient<'_>, + client: APIClient, res_handler: &mut ResultHandler, ) -> ExitCode { match &pair { From ba96dc6260e9461e2314237eecd9665c288b3ec1 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 14 Jul 2025 22:32:35 -0400 Subject: [PATCH 163/320] Refactoring --- src/tables.rs | 106 +++++++++++++++++++------------------------------- 1 file changed, 41 insertions(+), 65 deletions(-) diff --git a/src/tables.rs b/src/tables.rs index 87bc782..31b57d0 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -24,6 +24,30 @@ use tabled::settings::Panel; use tabled::{Table, Tabled}; use url::Url; +fn build_table_with_header(data: Vec, header: &str) -> Table { + let mut table = Table::builder(data).build(); + table.with(Panel::header(header)); + table +} + +fn build_simple_table(data: Vec) -> Table { + Table::builder(data).build() +} + +fn build_request_failure_table(result: &str, reason: &str) -> Table { + let data = vec![ + RowOfTwo { + key: "result", + value: result, + }, + RowOfTwo { + key: "reason", + value: reason, + }, + ]; + build_simple_table(data) +} + #[derive(Debug, Tabled)] struct OverviewRow<'a> { key: &'a str, @@ -144,10 +168,7 @@ pub fn overview(ov: Overview) -> Table { value: display_tag_map_option(&ov.node_tags), }, ]; - let tb = Table::builder(data); - let mut t = tb.build(); - t.with(Panel::header("Overview")); - t + build_table_with_header(data, "Overview") } pub fn churn_overview(ov: Overview) -> Table { @@ -181,12 +202,10 @@ pub fn churn_overview(ov: Overview) -> Table { value: ov.churn_rates.queue_deleted.to_string(), }, ]; - let tb = Table::builder(data); - let mut t = tb.build(); - t.with(Panel::header( + build_table_with_header( + data, "Entity (connections, queues, etc) churn over the most recent sampling period", - )); - t + ) } pub fn show_salted_and_hashed_value(value: String) -> Table { @@ -194,10 +213,7 @@ pub fn show_salted_and_hashed_value(value: String) -> Table { key: "password hash", value: value.as_str(), }]; - let tb = Table::builder(data); - let mut t = tb.build(); - t.with(Panel::header("Result")); - t + build_table_with_header(data, "Result") } pub fn schema_definition_sync_status(status: SchemaDefinitionSyncStatus) -> Table { @@ -257,10 +273,7 @@ pub fn schema_definition_sync_status(status: SchemaDefinitionSyncStatus) -> Tabl data.push(row) } - let tb = Table::builder(data); - let mut t = tb.build(); - t.with(Panel::header("Schema Definition Sync Status")); - t + build_table_with_header(data, "Schema Definition Sync Status") } pub fn failure_details(error: &HttpClientError) -> Table { @@ -365,9 +378,7 @@ pub fn failure_details(error: &HttpClientError) -> Table { value: status_code_s.as_str(), }, ]; - - let tb = Table::builder(data); - tb.build() + build_simple_table(data) } HttpClientError::MultipleMatchingBindings => { let data = vec![ @@ -384,44 +395,17 @@ pub fn failure_details(error: &HttpClientError) -> Table { value: "multiple bindings found between the source and destination, please specify a --routing-key of the target binding", }, ]; - - let tb = Table::builder(data); - tb.build() + build_simple_table(data) } HttpClientError::InvalidHeaderValue { .. } => { - let reason = "invalid HTTP request header value"; - let data = vec![ - RowOfTwo { - key: "result", - value: "request failed", - }, - RowOfTwo { - key: "reason", - value: reason, - }, - ]; - - let tb = Table::builder(data); - tb.build() + build_request_failure_table("request failed", "invalid HTTP request header value") } HttpClientError::IncompatibleBody { error, .. } => { let reason = format!( "response body is not compatible with the requested data type: {}", error ); - let data = vec![ - RowOfTwo { - key: "result", - value: "request failed", - }, - RowOfTwo { - key: "reason", - value: &reason, - }, - ]; - - let tb = Table::builder(data); - tb.build() + build_request_failure_table("request failed", &reason) } HttpClientError::RequestError { error, @@ -493,8 +477,7 @@ pub fn failure_details(error: &HttpClientError) -> Table { }, ]; - let tb = Table::builder(data); - tb.build() + build_simple_table(data) } } } @@ -527,25 +510,20 @@ fn generic_failed_request_details( }, ]; - let tb = Table::builder(data); - tb.build() + build_simple_table(data) } pub fn hashing_error_details(error: &HashingError) -> Table { - let details = format!("{}", error); - let data = vec![ + build_simple_table(vec![ RowOfTwo { key: "result", value: "hashing failed", }, RowOfTwo { key: "details", - value: details.as_str(), + value: &error.to_string(), }, - ]; - - let tb = Table::builder(data); - tb.build() + ]) } pub fn health_check_failure( @@ -760,8 +738,7 @@ pub(crate) fn memory_breakdown_in_bytes(breakdown: NodeMemoryBreakdown) -> Table ]; // Note: this is descending ordering data.sort_by(|a, b| b.value.cmp(a.value)); - let tb = Table::builder(data); - tb.build() + build_simple_table(data) } pub(crate) fn memory_breakdown_in_percent(mut breakdown: NodeMemoryBreakdown) -> Table { @@ -954,6 +931,5 @@ pub(crate) fn memory_breakdown_in_percent(mut breakdown: NodeMemoryBreakdown) -> ]; // Note: this is descending ordering data.sort_by(|a, b| b.comparable.total_cmp(&a.comparable)); - let tb = Table::builder(data); - tb.build() + build_simple_table(data) } From d0d2179f8acd9f15ef94da26cfefcba117d9f6e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 03:33:01 +0000 Subject: [PATCH 164/320] Bump toml from 0.8.23 to 0.9.2 Bumps [toml](https://github.com/toml-rs/toml) from 0.8.23 to 0.9.2. - [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v0.9.2) --- updated-dependencies: - dependency-name: toml dependency-version: 0.9.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 37 ++++++++++++++++--------------------- Cargo.toml | 2 +- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaa7712..cf91379 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1863,9 +1863,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -2175,44 +2175,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ + "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tower" @@ -2703,9 +2701,6 @@ name = "winnow" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" -dependencies = [ - "memchr", -] [[package]] name = "wit-bindgen-rt" diff --git a/Cargo.toml b/Cargo.toml index 9d2e10d..16782ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ rabbitmq_http_client = { version = "0.39.0", features = [ serde = { version = "1.0", features = ["derive", "std"] } serde_json = "1" tabled = "0.20" -toml = "0.8" +toml = "0.9" color-print = "0.3" thiserror = "2" shellexpand = "3.1" From bed165c40a1cd2d3f41f452ce2a97ebf5bdc0ca5 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 15 Jul 2025 00:55:25 -0400 Subject: [PATCH 165/320] Improve TLS support * Make sure that the 'tls' settings in the config file is not unintentionally drowned out by its respective CLI flag's default * Support CA certificate, client certificate and client private key settings in rabbitmqadmin.conf References #72. --- CHANGELOG.md | 25 +++++++++++++++- README.md | 6 ++++ src/cli.rs | 13 ++++---- src/config.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++---- src/main.rs | 41 +++++++++++++------------ 5 files changed, 136 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd1541..af5f1c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,33 @@ ## v2.7.0 (in development) +### Enhancements + + * `rabbitmqadmin.conf` now supports more TLS-related settings: `ca_certificate_bundle_path` (corresponds to `--tls-ca-cert-file` on the command line), + `client_certificate_file_path` (corresponds to `--tls-cert-file`), and `client_private_key_file_path` (corresponds to `--tls-key-file`). + + As the names suggest, they are used to configure the CA certificate bundle file path, the client certificate file path, + and the client private key file path, respectively: + + ```toml + [production] + hostname = "(redacted)" + port = 15671 + username = "user-efe1f4d763f6" + password = "(redacted)" + tls = true + ca_certificate_bundle_path = "/path/to/ca_certificate.pem" + client_certificate_file_path = "/path/to/client_certificate.pem" + client_private_key_file_path = "/path/to/client_key.pem" + ``` + + To learn more, see [RabbitMQ's TLS guide](https://www.rabbitmq.com/docs/ssl). + ### Bug Fixes * Tool version was unintentionally missing from `-h` output (but present in its long counterpart, `--help`) - + * The `tls` setting in `rabbitmqadmin.conf`, a `--use-tls` equivalent, was not respected when connecting to a node + in certain cases ## v2.6.0 (Jul 12, 2025) diff --git a/README.md b/README.md index 25e25f8..dc0d9e3 100644 --- a/README.md +++ b/README.md @@ -1004,8 +1004,14 @@ password = "staging-1d20cfbd9d" [production] hostname = "(redacted)" port = 15671 + username = "user-efe1f4d763f6" password = "(redacted)" + +tls = true +ca_certificate_bundle_path = "/path/to/ca_certificate.pem" +client_certificate_file_path = "/path/to/client_certificate.pem" +client_private_key_file_path = "/path/to/client_key.pem" ``` diff --git a/src/cli.rs b/src/cli.rs index ebd6003..70d21bb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -462,12 +462,11 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .help("use TLS (HTTPS) for HTTP API requests ") .env("RABBITMQADMIN_USE_TLS") .value_parser(value_parser!(bool)) - .action(ArgAction::SetTrue) - .requires("tls-ca-cert-file"), + .action(ArgAction::SetTrue), ) // --tls-ca-cert-file .arg( - Arg::new("tls-ca-cert-file") + Arg::new("ca_certificate_bundle_path") .long("tls-ca-cert-file") .required(false) .help("Local path to a CA certificate file in the PEM format") @@ -475,19 +474,19 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { ) // --tls-cert-file .arg( - Arg::new("tls-cert-file") + Arg::new("client_certificate_file_path") .long("tls-cert-file") .required(false) - .requires("tls-key-file") + .requires("tls") .help("Local path to a client certificate file in the PEM format") .value_parser(value_parser!(PathBuf)), ) // --tls-key-file .arg( - Arg::new("tls-key-file") + Arg::new("client_private_key_file_path") .long("tls-key-file") .required(false) - .requires("tls-cert-file") + .requires("tls") .help("Local path to a client private key file in the PEM format") .value_parser(value_parser!(PathBuf)), ) diff --git a/src/config.rs b/src/config.rs index fd3879f..e8306bd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -63,7 +63,7 @@ pub enum ConfigFileError { MissingConfigSection(String), #[error(transparent)] IoError(#[from] std::io::Error), - #[error("failed to deserialize config file. Make sure it is valid TOML")] + #[error("failed to deserialize config file. Make sure it is valid TOML. Details: {0}")] DeserializationError(#[from] toml::de::Error), } @@ -103,6 +103,13 @@ pub struct SharedSettings { #[serde(skip_serializing_if = "Option::is_none")] pub table_style: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub ca_certificate_bundle_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub client_certificate_file_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub client_private_key_file_path: Option, } impl SharedSettings { @@ -148,10 +155,9 @@ impl SharedSettings { pub fn new_with_defaults(cli_args: &ArgMatches, config_file_defaults: &Self) -> Self { let default_hostname = DEFAULT_HOST.to_string(); - let should_use_tls = cli_args - .get_one::("tls") - .cloned() - .unwrap_or(config_file_defaults.tls); + let should_use_tls = + cli_args.get_one::("tls").cloned().unwrap_or(false) || config_file_defaults.tls; + let non_interactive = cli_args .get_one::("non_interactive") .cloned() @@ -207,8 +213,27 @@ impl SharedSettings { .or(Some(TableStyle::default())) .unwrap_or_default(); + let ca_certificate_bundle_path = cli_args + .get_one::("ca_certificate_bundle_path") + .cloned() + .or(config_file_defaults.ca_certificate_bundle_path.clone()); + + let client_certificate_file_path = cli_args + .get_one::("client_certificate_file_path") + .cloned() + .or(config_file_defaults.client_certificate_file_path.clone()); + + let client_private_key_file_path = cli_args + .get_one::("client_private_key_file_path") + .cloned() + .or(config_file_defaults.client_private_key_file_path.clone()); + Self { tls: should_use_tls, + ca_certificate_bundle_path, + client_certificate_file_path, + client_private_key_file_path, + non_interactive, quiet, base_uri: None, @@ -274,8 +299,24 @@ impl SharedSettings { .or(Some(TableStyle::default())) .unwrap_or_default(); + let ca_certificate_bundle_path = cli_args + .get_one::("ca_certificate_bundle_path") + .cloned(); + + let client_certificate_file_path = cli_args + .get_one::("client_certificate_file_path") + .cloned(); + + let client_private_key_file_path = cli_args + .get_one::("client_private_key_file_path") + .cloned(); + Self { tls: should_use_tls, + ca_certificate_bundle_path, + client_certificate_file_path, + client_private_key_file_path, + non_interactive, quiet, base_uri: None, @@ -348,8 +389,24 @@ impl SharedSettings { .or(Some(TableStyle::default())) .unwrap_or_default(); + let ca_certificate_bundle_path = cli_args + .get_one::("ca_certificate_bundle_path") + .cloned(); + + let client_certificate_file_path = cli_args + .get_one::("client_certificate_file_path") + .cloned(); + + let client_private_key_file_path = cli_args + .get_one::("client_private_key_file_path") + .cloned(); + Self { tls: should_use_tls, + ca_certificate_bundle_path, + client_certificate_file_path, + client_private_key_file_path, + non_interactive, quiet, base_uri: Some(url.to_string()), @@ -414,8 +471,24 @@ impl SharedSettings { .or(Some(TableStyle::default())) .unwrap_or_default(); + let ca_certificate_bundle_path = cli_args + .get_one::("ca_certificate_bundle_path") + .cloned(); + + let client_certificate_file_path = cli_args + .get_one::("client_certificate_file_path") + .cloned(); + + let client_private_key_file_path = cli_args + .get_one::("client_private_key_file_path") + .cloned(); + Self { tls: should_use_tls, + ca_certificate_bundle_path, + client_certificate_file_path, + client_private_key_file_path, + non_interactive, quiet, base_uri: Some(url.to_string()), diff --git a/src/main.rs b/src/main.rs index ff26fc5..cc6c4cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,7 @@ fn main() { match configure_http_api_client(&cli, &common_settings, &endpoint.clone()) { Ok(client) => { - let exit_code = dispatch_command(&cli, client, &common_settings, endpoint); + let exit_code = dispatch_command(&cli, client, &common_settings); process::exit(exit_code.into()) } Err(err) => { @@ -116,13 +116,13 @@ fn resolve_run_configuration(cli: &ArgMatches) -> (SharedSettings, String) { fn configure_http_api_client<'a>( cli: &'a ArgMatches, - common_settings: &'a SharedSettings, + merged_settings: &'a SharedSettings, endpoint: &'a str, ) -> Result { - let httpc = build_http_client(cli, common_settings)?; + let httpc = build_http_client(cli, merged_settings)?; // Due to how SharedSettings are computed, these should safe to unwrap() - let username = common_settings.username.clone().unwrap(); - let password = common_settings.password.clone().unwrap(); + let username = merged_settings.username.clone().unwrap(); + let password = merged_settings.password.clone().unwrap(); let client = build_rabbitmq_http_api_client( httpc, endpoint.to_owned(), @@ -135,8 +135,7 @@ fn configure_http_api_client<'a>( fn dispatch_command( cli: &ArgMatches, client: APIClient, - common_settings: &SharedSettings, - endpoint: String, + merged_settings: &SharedSettings, ) -> ExitCode { if let Some((first_level, first_level_args)) = cli.subcommand() { if let Some((second_level, second_level_args)) = first_level_args.subcommand() { @@ -144,7 +143,7 @@ fn dispatch_command( if first_level == TANZU_COMMAND_PREFIX { if let Some((third_level, third_level_args)) = second_level_args.subcommand() { let pair = (second_level, third_level); - let mut res_handler = ResultHandler::new(common_settings, second_level_args); + let mut res_handler = ResultHandler::new(merged_settings, second_level_args); return dispatch_tanzu_subcommand( pair, third_level_args, @@ -155,13 +154,13 @@ fn dispatch_command( } else { // this is a common (OSS and Tanzu) command let pair = (first_level, second_level); - let vhost = virtual_host(common_settings, second_level_args); - let mut res_handler = ResultHandler::new(common_settings, second_level_args); + let vhost = virtual_host(merged_settings, second_level_args); + let mut res_handler = ResultHandler::new(merged_settings, second_level_args); return dispatch_common_subcommand( pair, second_level_args, client, - endpoint, + merged_settings.endpoint(), vhost, &mut res_handler, ); @@ -192,12 +191,12 @@ fn build_http_client( if should_use_tls(common_settings) { let _ = CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider()); - let ca_cert_pem_file = cli.get_one::("tls-ca-cert-file"); - - let maybe_client_cert_pem_file = cli.get_one::("tls-cert-file"); - let maybe_client_key_pem_file = cli.get_one::("tls-key-file"); + let ca_cert_pem_file = common_settings.ca_certificate_bundle_path.clone(); + let maybe_client_cert_pem_file = common_settings.client_certificate_file_path.clone(); + let maybe_client_key_pem_file = common_settings.client_private_key_file_path.clone(); let ca_certs = ca_cert_pem_file + .clone() .map(|path| load_certs(&path.to_string_lossy())) .unwrap()?; @@ -209,15 +208,18 @@ fn build_http_client( .tls_info(true) .tls_sni(true) .min_tls_version(reqwest::tls::Version::TLS_1_2) + .tls_built_in_native_certs(true) .tls_built_in_root_certs(true) .danger_accept_invalid_certs(disable_peer_verification) .danger_accept_invalid_hostnames(disable_peer_verification); - // --tls-ca-cert-file + // local certificate store let mut store = rustls::RootCertStore::empty(); + for c in ca_certs { store.add(c).map_err(|err| { - let readable_path = maybe_client_cert_pem_file + let readable_path = ca_cert_pem_file + .clone() .unwrap() .to_string_lossy() .to_string(); @@ -230,8 +232,8 @@ fn build_http_client( // --tls-cert-file, --tls-key-file if maybe_client_cert_pem_file.is_some() && maybe_client_key_pem_file.is_some() { - let client_cert_pem_file = maybe_client_cert_pem_file.unwrap(); - let client_key_pem_file = maybe_client_key_pem_file.unwrap(); + let client_cert_pem_file = maybe_client_cert_pem_file.clone().unwrap(); + let client_key_pem_file = maybe_client_key_pem_file.clone().unwrap(); let client_cert = fs::read(client_cert_pem_file)?; let client_key = fs::read(client_key_pem_file)?; @@ -239,6 +241,7 @@ fn build_http_client( let concatenated = [&client_cert[..], &client_key[..]].concat(); let client_id = Identity::from_pem(&concatenated).map_err(|err| { let readable_path = maybe_client_key_pem_file + .clone() .unwrap() .to_string_lossy() .to_string(); From 29a06c9a0c48756c21607e654bb73155803857e1 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 15 Jul 2025 01:26:26 -0400 Subject: [PATCH 166/320] 2.7.0 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af5f1c9..22eff8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # rabbitmqadmin-ng Change Log -## v2.7.0 (in development) +## v2.8.0 (in development) + +No changes yet. + +## v2.7.0 (Jul 15, 2025) ### Enhancements From b6a8ddf1fa9c29055cb9e5b7a1c5cca64f87209d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 15 Jul 2025 01:46:45 -0400 Subject: [PATCH 167/320] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 16782ab..6ebccfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.7.0" +version = "2.8.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 319749124d12d3969e25668c28e9e7c6c982d98e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 15 Jul 2025 03:18:08 -0400 Subject: [PATCH 168/320] Commit updated Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index cf91379..834b2eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.7.0" +version = "2.8.0" dependencies = [ "assert_cmd", "clap", From 7ffcc5e886cf536e0100315c1c65ec10c1637b32 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 16 Jul 2025 21:37:59 -0400 Subject: [PATCH 169/320] cargo update --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 834b2eb..1ae880c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1689,15 +1689,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2028,7 +2028,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] From e60d0404145d91dfcaa10e8eb982d46e3bad114a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 16 Jul 2025 21:41:11 -0400 Subject: [PATCH 170/320] Improve handling of missing (or impossible to load/parse) --tls-ca-cert-file on the command line --- src/main.rs | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index cc6c4cb..46d9aa5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -195,10 +195,7 @@ fn build_http_client( let maybe_client_cert_pem_file = common_settings.client_certificate_file_path.clone(); let maybe_client_key_pem_file = common_settings.client_private_key_file_path.clone(); - let ca_certs = ca_cert_pem_file - .clone() - .map(|path| load_certs(&path.to_string_lossy())) - .unwrap()?; + let ca_certs_path_opt = ca_cert_pem_file.clone(); let disable_peer_verification = *cli.get_one::("insecure").unwrap_or(&false); @@ -216,18 +213,22 @@ fn build_http_client( // local certificate store let mut store = rustls::RootCertStore::empty(); - for c in ca_certs { - store.add(c).map_err(|err| { - let readable_path = ca_cert_pem_file - .clone() - .unwrap() - .to_string_lossy() - .to_string(); - CommandRunError::CertificateStoreRejectedCertificate { - local_path: readable_path, - cause: err, - } - })?; + if let Some(ca_certs_path) = ca_certs_path_opt { + let ca_certs = load_certs(&ca_certs_path.to_string_lossy())?; + + for c in ca_certs { + store.add(c).map_err(|err| { + let readable_path = ca_cert_pem_file + .clone() + .unwrap() + .to_string_lossy() + .to_string(); + CommandRunError::CertificateStoreRejectedCertificate { + local_path: readable_path, + cause: err, + } + })?; + } } // --tls-cert-file, --tls-key-file @@ -266,15 +267,13 @@ fn build_http_client( type CertificateChain = Vec>; fn load_certs(filename: &str) -> Result { - let results = CertificateDer::pem_file_iter(filename) - .map_err(|err| { - let readable_path = filename.to_string(); - CommandRunError::CertificateFileCouldNotBeLoaded2 { - local_path: readable_path, - cause: err, - } - }) - .unwrap(); + let results = CertificateDer::pem_file_iter(filename).map_err(|err| { + let readable_path = filename.to_string(); + CommandRunError::CertificateFileCouldNotBeLoaded2 { + local_path: readable_path, + cause: err, + } + })?; let certs = results.map(|it| it.unwrap()).collect::(); Ok(certs) } From 318af30b0e3243a54e4ff7c8d512afb8fb0530ca Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 16 Jul 2025 21:56:06 -0400 Subject: [PATCH 171/320] Improve error handling in areas related to TLS --- src/errors.rs | 12 ++++ src/main.rs | 168 ++++++++++++++++++++++++++++++++++++++++++++------ src/output.rs | 5 ++ 3 files changed, 165 insertions(+), 20 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 8101b59..1e8c10b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -47,6 +47,18 @@ pub enum CommandRunError { local_path: String, cause: rustls::Error, }, + #[error("TLS certificate file at {local_path} does not exist or is not readable")] + CertificateFileNotFound { local_path: String }, + #[error( + "TLS certificate file at {local_path} could not be parsed, is empty or contains no valid certificates" + )] + CertificateFileEmpty { local_path: String }, + #[error("TLS certificate file at {local_path} contains invalid PEM data: {details}")] + CertificateFileInvalidPem { local_path: String, details: String }, + #[error("TLS private key file at {local_path} contains an unsupported key type or format")] + PrivateKeyFileUnsupported { local_path: String }, + #[error("TLS certificate and private key files do not match")] + CertificateKeyMismatch { cert_path: String, key_path: String }, #[error("API responded with a client error: status code of {status_code}")] ClientError { status_code: StatusCode, diff --git a/src/main.rs b/src/main.rs index 46d9aa5..3c0656d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -214,17 +214,50 @@ fn build_http_client( let mut store = rustls::RootCertStore::empty(); if let Some(ca_certs_path) = ca_certs_path_opt { - let ca_certs = load_certs(&ca_certs_path.to_string_lossy())?; + let ca_certs_path_str = ca_certs_path.to_string_lossy(); - for c in ca_certs { - store.add(c).map_err(|err| { + // Load CA certificates with improved error handling + let ca_certs = load_certs(&ca_certs_path_str).map_err(|err| { + // Add context about this being a CA certificate bundle + match err { + CommandRunError::CertificateFileNotFound { local_path } => { + CommandRunError::CertificateFileNotFound { + local_path: format!("CA certificate bundle at {}", local_path), + } + } + CommandRunError::CertificateFileEmpty { local_path } => { + CommandRunError::CertificateFileEmpty { + local_path: format!("CA certificate bundle at {}", local_path), + } + } + CommandRunError::CertificateFileInvalidPem { + local_path, + details, + } => CommandRunError::CertificateFileInvalidPem { + local_path: format!("CA certificate bundle at {}", local_path), + details, + }, + other => other, + } + })?; + + for (index, cert) in ca_certs.into_iter().enumerate() { + store.add(cert).map_err(|err| { let readable_path = ca_cert_pem_file .clone() .unwrap() .to_string_lossy() .to_string(); + + // Provide more context about which certificate failed + let detailed_path = if index == 0 { + readable_path + } else { + format!("{} (certificate #{} in bundle)", readable_path, index + 1) + }; + CommandRunError::CertificateStoreRejectedCertificate { - local_path: readable_path, + local_path: detailed_path, cause: err, } })?; @@ -236,19 +269,42 @@ fn build_http_client( let client_cert_pem_file = maybe_client_cert_pem_file.clone().unwrap(); let client_key_pem_file = maybe_client_key_pem_file.clone().unwrap(); - let client_cert = fs::read(client_cert_pem_file)?; - let client_key = fs::read(client_key_pem_file)?; + let cert_path = client_cert_pem_file.to_string_lossy().to_string(); + let key_path = client_key_pem_file.to_string_lossy().to_string(); + + // Validate both files exist and are readable + validate_certificate_file(&cert_path)?; + validate_certificate_file(&key_path)?; + + let client_cert = fs::read(&client_cert_pem_file).map_err(|err| { + CommandRunError::CertificateFileCouldNotBeLoaded2 { + local_path: cert_path.clone(), + cause: rustls::pki_types::pem::Error::Io(err), + } + })?; + + let client_key = fs::read(&client_key_pem_file).map_err(|err| { + CommandRunError::CertificateFileCouldNotBeLoaded2 { + local_path: key_path.clone(), + cause: rustls::pki_types::pem::Error::Io(err), + } + })?; let concatenated = [&client_cert[..], &client_key[..]].concat(); let client_id = Identity::from_pem(&concatenated).map_err(|err| { - let readable_path = maybe_client_key_pem_file - .clone() - .unwrap() - .to_string_lossy() - .to_string(); - CommandRunError::CertificateFileCouldNotBeLoaded1 { - local_path: readable_path, - cause: err, + // Try to determine if it's a key/cert mismatch or other issue + if err.to_string().contains("private key") + || err.to_string().contains("certificate") + { + CommandRunError::CertificateKeyMismatch { + cert_path: cert_path.clone(), + key_path: key_path.clone(), + } + } else { + CommandRunError::CertificateFileCouldNotBeLoaded1 { + local_path: cert_path, + cause: err, + } } })?; @@ -266,25 +322,97 @@ fn build_http_client( type CertificateChain = Vec>; +fn validate_certificate_file(path: &str) -> Result<(), CommandRunError> { + let path_buf = std::path::Path::new(path); + + if !path_buf.exists() { + return Err(CommandRunError::CertificateFileNotFound { + local_path: path.to_string(), + }); + } + + if !path_buf.is_file() { + return Err(CommandRunError::CertificateFileNotFound { + local_path: path.to_string(), + }); + } + + // Check if file is readable + match fs::metadata(path) { + Ok(metadata) => { + if metadata.len() == 0 { + return Err(CommandRunError::CertificateFileEmpty { + local_path: path.to_string(), + }); + } + } + Err(_) => { + return Err(CommandRunError::CertificateFileNotFound { + local_path: path.to_string(), + }); + } + } + + Ok(()) +} + fn load_certs(filename: &str) -> Result { + validate_certificate_file(filename)?; + let results = CertificateDer::pem_file_iter(filename).map_err(|err| { let readable_path = filename.to_string(); - CommandRunError::CertificateFileCouldNotBeLoaded2 { + let details = match err { + rustls::pki_types::pem::Error::NoItemsFound => { + "Invalid PEM format or structure".to_string() + } + rustls::pki_types::pem::Error::IllegalSectionStart { .. } => { + "Invalid PEM format or structure".to_string() + } + rustls::pki_types::pem::Error::MissingSectionEnd { .. } => { + "Invalid PEM format or structure".to_string() + } + _ => format!("Failed to load a PEM file at {}: {}", filename, err), + }; + CommandRunError::CertificateFileInvalidPem { local_path: readable_path, - cause: err, + details, } })?; - let certs = results.map(|it| it.unwrap()).collect::(); + + let certs = results + .map(|result| { + result.map_err(|err| CommandRunError::CertificateFileInvalidPem { + local_path: filename.to_string(), + details: format!("Failed to parse certificate: {}", err), + }) + }) + .collect::>()?; + + if certs.is_empty() { + return Err(CommandRunError::CertificateFileEmpty { + local_path: filename.to_string(), + }); + } + Ok(certs) } #[allow(dead_code)] fn load_private_key(filename: &str) -> Result, CommandRunError> { + validate_certificate_file(filename)?; + PrivateKeyDer::from_pem_file(filename).map_err(|err| { let readable_path = filename.to_string(); - CommandRunError::CertificateFileCouldNotBeLoaded2 { - local_path: readable_path, - cause: err, + match err { + rustls::pki_types::pem::Error::NoItemsFound => { + CommandRunError::CertificateFileInvalidPem { + local_path: readable_path, + details: "Invalid PEM format in private key file".to_string(), + } + } + _ => CommandRunError::PrivateKeyFileUnsupported { + local_path: readable_path, + }, } }) } diff --git a/src/output.rs b/src/output.rs index dd7ef80..0390587 100644 --- a/src/output.rs +++ b/src/output.rs @@ -379,6 +379,11 @@ impl<'a> ResultHandler<'a> { CommandRunError::UnknownCommandTarget { .. } => ExitCode::Usage, CommandRunError::CertificateFileCouldNotBeLoaded1 { .. } => ExitCode::DataErr, CommandRunError::CertificateFileCouldNotBeLoaded2 { .. } => ExitCode::DataErr, + CommandRunError::CertificateFileNotFound { .. } => ExitCode::DataErr, + CommandRunError::CertificateFileEmpty { .. } => ExitCode::DataErr, + CommandRunError::CertificateFileInvalidPem { .. } => ExitCode::DataErr, + CommandRunError::PrivateKeyFileUnsupported { .. } => ExitCode::DataErr, + CommandRunError::CertificateKeyMismatch { .. } => ExitCode::DataErr, CommandRunError::IoError { .. } => ExitCode::DataErr, _ => ExitCode::Usage, }; From 6047a6433ea490628f78a3028a358924583e4d6f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 17 Jul 2025 11:51:30 -0400 Subject: [PATCH 172/320] Change log updates --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22eff8f..d4efaa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # rabbitmqadmin-ng Change Log -## v2.8.0 (in development) +## v2.7.1 (in development) -No changes yet. +### Bug Fixes + + * Improved handling of missing or impossible to load/parse `--tls-ca-cert-file` on the command line. + + The tool now properly handles cases where a [CA certificate](https://www.rabbitmq.com/docs/ssl#peer-verification) file path is not provided, making + CA certificate loading optional rather than required, which prevents crashes when TLS is used + without a custom CA certificate bundle. ## v2.7.0 (Jul 15, 2025) From 768ad2aea5612c8c3f23856a1121eff7c3b7fc56 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 17 Jul 2025 13:12:48 -0400 Subject: [PATCH 173/320] Upgrade HTTP API client to 0.40.0 --- Cargo.lock | 6 +++--- Cargo.toml | 4 ++-- src/commands.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ae880c..bf9061c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1454,9 +1454,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.39.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87926e8ac68f95f53d97fade565f2ff96b3b60d1f5dea16da89536ea68893555" +checksum = "abfe00e15e479464bb01cc03f0dd3e68d837dad72c6ae89f271856df88d9f78e" dependencies = [ "backtrace", "percent-encoding", @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.8.0" +version = "2.7.1" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 6ebccfa..85f7bc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.8.0" +version = "2.7.1" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" @@ -17,7 +17,7 @@ reqwest = { version = "0.12.22", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.39.0", features = [ +rabbitmq_http_client = { version = "0.40.0", features = [ "blocking", "tabled", ] } diff --git a/src/commands.rs b/src/commands.rs index ff5e7bf..d7de7cf 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -915,7 +915,7 @@ pub fn declare_user(client: APIClient, command_args: &ArgMatches) -> ClientResul .unwrap(); let salt = password_hashing::salt(); let hash = hashing_algo.salt_and_hash(&salt, password).unwrap(); - String::from_utf8(hash).unwrap().to_string() + String::from_utf8(hash.into()).unwrap().to_string() } else { provided_hash.to_string() }; @@ -937,7 +937,7 @@ pub fn salt_and_hash_password(command_args: &ArgMatches) -> Result Date: Thu, 17 Jul 2025 13:12:54 -0400 Subject: [PATCH 174/320] Change log updates --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4efaa2..f3c5241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,16 @@ The tool now properly handles cases where a [CA certificate](https://www.rabbitmq.com/docs/ssl#peer-verification) file path is not provided, making CA certificate loading optional rather than required, which prevents crashes when TLS is used - without a custom CA certificate bundle. + without a custom CA certificate bundle + + * `show overview` could panic when run against a freshly booted RabbitMQ node that did not have certain + metrics/rates initialized and available. Now those metrics will use the default values for their types, + such as `0` and `0.0` for the counters, gauges, rates + +### Upgrades + +* RabbitMQ HTTP API client was upgraded to [`0.40.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.40.0) + ## v2.7.0 (Jul 15, 2025) From 604ef8d44f71f032322fa589e295224e3f406930 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 17 Jul 2025 13:14:42 -0400 Subject: [PATCH 175/320] README: cosmetics --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dc0d9e3..034a188 100644 --- a/README.md +++ b/README.md @@ -159,9 +159,9 @@ will output a table that looks like this: ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Product name │ RabbitMQ │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ Product version │ 4.1.1 │ +│ Product version │ 4.1.2 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ RabbitMQ version │ 4.1.1 │ +│ RabbitMQ version │ 4.1.2 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Erlang version │ 27.3.4 │ ├───────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤ @@ -222,8 +222,8 @@ as a result: ``` key Product name RabbitMQ -Product version 4.1.1 -RabbitMQ version 4.1.1 +Product version 4.1.2 +RabbitMQ version 4.1.2 Erlang version 27.3.4 Erlang details Erlang/OTP 27 [erts-15.2.7] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] Connections (total) 0 From 8f57f7ac0eb44d14b106c7fb896c2b8e719a7bea Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 17 Jul 2025 13:25:31 -0400 Subject: [PATCH 176/320] Back to dev version --- CHANGELOG.md | 7 ++++++- Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c5241..90198c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.7.1 (in development) +## v2.8.0 (in development) + +No changes yet. + + +## v2.7.1 (Jul 17, 2025) ### Bug Fixes diff --git a/Cargo.toml b/Cargo.toml index 85f7bc3..b98dafa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.7.1" +version = "2.8.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 426bd8acf014ed9ed9295aa20984772096de0d9c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 18 Jul 2025 14:10:48 -0800 Subject: [PATCH 177/320] cargo update --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf9061c..c74fe2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.29" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.7.1" +version = "2.8.0" dependencies = [ "assert_cmd", "clap", @@ -1851,9 +1851,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", From 56125b2c9841f6c0d8d117c897d1e8e6376f2e34 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 19 Jul 2025 17:17:22 -0700 Subject: [PATCH 178/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c74fe2a..51e3f40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,9 +1029,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ "bitflags", "libc", From 83219d60529f1a9d3931c95203d25b595321aeda Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 22 Jul 2025 13:52:17 -0700 Subject: [PATCH 179/320] cargo update --- Cargo.lock | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51e3f40..de73727 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08b5d4e069cbc868041a64bd68dc8cb39a0d79585cd6c5a24caa8c2d622121be" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" dependencies = [ "aws-lc-sys", "zeroize", @@ -756,9 +756,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", @@ -772,7 +772,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -923,9 +923,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags", "cfg-if", @@ -1395,7 +1395,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2", + "socket2 0.5.10", "thiserror", "tokio", "tracing", @@ -1432,7 +1432,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -1498,9 +1498,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", @@ -1919,6 +1919,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2136,7 +2146,7 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2", + "socket2 0.5.10", "windows-sys 0.52.0", ] From 88838961def69d52b86a61e00f63b38b06486ec0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 24 Jul 2025 07:10:09 -0700 Subject: [PATCH 180/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de73727..dc91e7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1343,9 +1343,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", "syn", From 4bb6d8b5a85dbfe5d98166af535b41104a962e17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 00:29:47 +0000 Subject: [PATCH 181/320] Bump rustls from 0.23.29 to 0.23.30 Bumps [rustls](https://github.com/rustls/rustls) from 0.23.29 to 0.23.30. - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.23.29...v/0.23.30) --- updated-dependencies: - dependency-name: rustls dependency-version: 0.23.30 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc91e7a..649246a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1702,9 +1702,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "069a8df149a16b1a12dcc31497c3396a173844be3cac4bd40c9e7671fef96671" dependencies = [ "aws-lc-rs", "log", From 586d971e63f61eab8bb12a6636d11c128242b97f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 2 Aug 2025 19:47:57 -0400 Subject: [PATCH 182/320] Extract a function --- src/main.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3c0656d..81b67a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,6 +50,8 @@ use rustls::pki_types::{CertificateDer, PrivateKeyDer}; type APIClient = GenericAPIClient; +type CertificateChain = Vec>; + fn main() { let pre_flight_settings = match pre_flight::is_non_interactive() { true => PreFlightSettings::non_interactive(), @@ -276,19 +278,8 @@ fn build_http_client( validate_certificate_file(&cert_path)?; validate_certificate_file(&key_path)?; - let client_cert = fs::read(&client_cert_pem_file).map_err(|err| { - CommandRunError::CertificateFileCouldNotBeLoaded2 { - local_path: cert_path.clone(), - cause: rustls::pki_types::pem::Error::Io(err), - } - })?; - - let client_key = fs::read(&client_key_pem_file).map_err(|err| { - CommandRunError::CertificateFileCouldNotBeLoaded2 { - local_path: key_path.clone(), - cause: rustls::pki_types::pem::Error::Io(err), - } - })?; + let client_cert = read_pem_file(&client_cert_pem_file, &cert_path)?; + let client_key = read_pem_file(&client_key_pem_file, &key_path)?; let concatenated = [&client_cert[..], &client_key[..]].concat(); let client_id = Identity::from_pem(&concatenated).map_err(|err| { @@ -320,7 +311,12 @@ fn build_http_client( } } -type CertificateChain = Vec>; +fn read_pem_file(buf: &PathBuf, file_path: &String) -> Result, CommandRunError> { + fs::read(&buf).map_err(|err| CommandRunError::CertificateFileCouldNotBeLoaded2 { + local_path: file_path.clone(), + cause: rustls::pki_types::pem::Error::Io(err), + }) +} fn validate_certificate_file(path: &str) -> Result<(), CommandRunError> { let path_buf = std::path::Path::new(path); From 3b727c97a1a6e694cb805b50b8447b09d9834008 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 2 Aug 2025 19:48:31 -0400 Subject: [PATCH 183/320] cargo clippy --all-features --fix --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 81b67a1..cbb014d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -312,7 +312,7 @@ fn build_http_client( } fn read_pem_file(buf: &PathBuf, file_path: &String) -> Result, CommandRunError> { - fs::read(&buf).map_err(|err| CommandRunError::CertificateFileCouldNotBeLoaded2 { + fs::read(buf).map_err(|err| CommandRunError::CertificateFileCouldNotBeLoaded2 { local_path: file_path.clone(), cause: rustls::pki_types::pem::Error::Io(err), }) From e218d7a3368e71855a4ffa3eff43f0cd4734c882 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 3 Aug 2025 03:24:27 -0400 Subject: [PATCH 184/320] cargo update --- Cargo.lock | 57 +++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 649246a..fbca681 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.30" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -278,18 +278,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstream", "anstyle", @@ -1014,7 +1014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -1029,9 +1029,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags", "libc", @@ -1557,9 +1557,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -1658,9 +1658,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -1702,9 +1702,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.30" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069a8df149a16b1a12dcc31497c3396a173844be3cac4bd40c9e7671fef96671" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "aws-lc-rs", "log", @@ -1851,9 +1851,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -1980,9 +1980,9 @@ dependencies = [ [[package]] name = "sysexits" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198f60d1f7f003f168507691e42d082df109ef0f05c6fd006e22528371a5f1b4" +checksum = "fff8b58f22cae39a24a26289da003ac87ddce0c024359c382d9ea0c48d08bc86" [[package]] name = "system-configuration" @@ -2135,9 +2135,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -2146,8 +2146,8 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2 0.5.10", - "windows-sys 0.52.0", + "socket2 0.6.0", + "windows-sys 0.59.0", ] [[package]] @@ -2185,9 +2185,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" dependencies = [ "indexmap", "serde", @@ -2575,7 +2575,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -2596,10 +2596,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", From b77328399b7cdd76ba68d9fab54bf9cfa446a139 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 3 Aug 2025 03:25:16 -0400 Subject: [PATCH 185/320] Fix a clippy warning --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index cbb014d..30bf248 100644 --- a/src/main.rs +++ b/src/main.rs @@ -311,9 +311,9 @@ fn build_http_client( } } -fn read_pem_file(buf: &PathBuf, file_path: &String) -> Result, CommandRunError> { +fn read_pem_file(buf: &PathBuf, file_path: &str) -> Result, CommandRunError> { fs::read(buf).map_err(|err| CommandRunError::CertificateFileCouldNotBeLoaded2 { - local_path: file_path.clone(), + local_path: file_path.to_owned(), cause: rustls::pki_types::pem::Error::Io(err), }) } From a981be339a91bad1bf1b491c015f90088df7d549 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 4 Aug 2025 15:48:31 -0400 Subject: [PATCH 186/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fbca681..fd28ff9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2172,9 +2172,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", From 22523eb4311d9798753c60698a9b257311a3d4fb Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 5 Aug 2025 12:56:23 -0400 Subject: [PATCH 187/320] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 034a188..6b82b1c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ that target the [HTTP API](https://www.rabbitmq.com/docs/management#http-api). If you are migrating from the original `rabbitqadmin`, please see [Breaking or Potentially Breaking Changes](#breaking-or-potentially-breaking-changes) to learn about the breaking changes in the command line interface. -The general "shape and feel" of the interface is still very similar to `rabbitmqadmin` v1. +The general "shape and feel" of the interface is still very similar to `rabbitmqadmin` v1. However, this generation +is significantly more powerful, in particular, when it comes to [Blue-Green Deployment upgrades and migrations](https://www.rabbitmq.com/blog/2025/07/29/latest-benefits-of-rmq-and-migrating-to-qq-along-the-way) +from RabbitMQ 3.13.x to 4.x. ## Supported RabbitMQ Series From 5cae8bb5f57672d1103ab2ba4c224001df68dcf4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 6 Aug 2025 23:44:00 -0400 Subject: [PATCH 188/320] shovels: correctly fetch --source-queue --- src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.rs b/src/commands.rs index d7de7cf..bbb60d2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -378,7 +378,7 @@ pub fn declare_amqp091_shovel( let destination_queue: String; let destination_exchange: String; let destination_params = if destination_queue_opt.is_some() { - destination_queue = destination_exchange_opt.unwrap(); + destination_queue = destination_queue_opt.unwrap(); if predeclared_destination { Amqp091ShovelDestinationParams::predeclared_queue_destination( &destination_uri, From af29a54d317ced24b41c4439e52ccef5d5d9f412 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 6 Aug 2025 23:50:07 -0400 Subject: [PATCH 189/320] 2.7.2 --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90198c4..558c955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ No changes yet. +## v2.7.2 (Aug 6, 2025) + +### Bug Fixes + + * `shovels declare_amqp091` panicked when the `--source-exchange` argument was not provided, + even if `--source-queue` was + ## v2.7.1 (Jul 17, 2025) diff --git a/Cargo.toml b/Cargo.toml index b98dafa..d6b93fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.8.0" +version = "2.7.2" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From a438542974fb963174dc30d7b760616366b1a265 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 7 Aug 2025 00:01:54 -0400 Subject: [PATCH 190/320] cargo update --- Cargo.lock | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd28ff9..6e03461 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -73,22 +73,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -278,18 +278,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65" dependencies = [ "anstream", "anstyle", @@ -623,9 +623,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.8.0" +version = "2.7.2" dependencies = [ "assert_cmd", "clap", @@ -1725,7 +1725,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework 3.3.0", ] [[package]] @@ -1786,9 +1786,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -2185,9 +2185,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ "indexmap", "serde", @@ -2209,9 +2209,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ "winnow", ] @@ -2812,9 +2812,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", From e4fb3ed387be31e51c03300c76176cbb2808807f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 7 Aug 2025 00:11:01 -0400 Subject: [PATCH 191/320] Remove a couple of time-sensitive tests that fail on RabbitMQ 4.1.3. --- tests/deprecated_feature_tests.rs | 72 ------------------------------- 1 file changed, 72 deletions(-) diff --git a/tests/deprecated_feature_tests.rs b/tests/deprecated_feature_tests.rs index 33d9f66..6f7a3ed 100644 --- a/tests/deprecated_feature_tests.rs +++ b/tests/deprecated_feature_tests.rs @@ -24,81 +24,9 @@ fn test_list_all_deprecated_features() -> Result<(), Box> Ok(()) } -#[test] -fn test_list_deprecated_features_in_use() -> Result<(), Box> { - let vh = "test_list_deprecated_features_in_use"; - let q = "test_list_deprecated_features_in_use.cq.transient.1"; - - delete_vhost(vh).expect("failed to delete a virtual host"); - - // there are no deprecated features in use at this point - run_succeeds(["deprecated_features", "list_used"]) - .stdout(predicate::str::contains("transient_nonexcl_queues").not()); - - run_succeeds(["declare", "vhost", "--name", vh]); - run_succeeds([ - "-V", - vh, - "declare", - "queue", - "--name", - q, - "--type", - "classic", - "--durable", - "false", - ]); - - await_queue_metric_emission(); - - // now there is: a non-exclusive transient queue - run_succeeds(["list", "deprecated_features_in_use"]) - .stdout(predicate::str::contains("transient_nonexcl_queues")); - - delete_vhost(vh).expect("failed to delete a virtual host"); - - Ok(()) -} - #[test] fn test_list_all_deprecated_features_via_alias() -> Result<(), Box> { run_succeeds(["list", "deprecated_features"]).stdout(predicate::str::contains("ram_node_type")); Ok(()) } - -#[test] -fn test_list_deprecated_features_in_use_via_alias() -> Result<(), Box> { - let vh = "test_list_deprecated_features_in_use_via_alias"; - let q = "test_list_deprecated_features_in_use_via_alias.cq.transient.1"; - - delete_vhost(vh).expect("failed to delete a virtual host"); - - // there are no deprecated features in use at this point - run_succeeds(["list", "deprecated_features_in_use"]) - .stdout(predicate::str::contains("transient_nonexcl_queues").not()); - - run_succeeds(["declare", "vhost", "--name", vh]); - run_succeeds([ - "-V", - vh, - "declare", - "queue", - "--name", - q, - "--type", - "classic", - "--durable", - "false", - ]); - - await_queue_metric_emission(); - - // now there is: a non-exclusive transient queue - run_succeeds(["list", "deprecated_features_in_use"]) - .stdout(predicate::str::contains("transient_nonexcl_queues")); - - delete_vhost(vh).expect("failed to delete a virtual host"); - - Ok(()) -} From b1145de856fc377c067dff6577a1b9fc3d12364a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 7 Aug 2025 00:26:44 -0400 Subject: [PATCH 192/320] Back to dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d6b93fb..b98dafa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.7.2" +version = "2.8.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 7bc5067494ef182279836abcf85bc09a9d73be06 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 7 Aug 2025 00:28:58 -0400 Subject: [PATCH 193/320] Commit a Cargo.lock change --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6e03461..5c5fe1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.7.2" +version = "2.8.0" dependencies = [ "assert_cmd", "clap", From 3140fb4a8c2960f3581807a82099aeae2ce9db21 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 11 Aug 2025 16:08:45 -0400 Subject: [PATCH 194/320] Bump HTTP API client --- Cargo.lock | 40 ++++++++++++++++++++-------------------- Cargo.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c5fe1f..f44a34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.31" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "jobserver", "libc", @@ -278,18 +278,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.43" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" +checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.43" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -617,9 +617,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" @@ -642,9 +642,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heck" @@ -1003,9 +1003,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" @@ -1375,9 +1375,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0" dependencies = [ "unicode-ident", ] @@ -1454,9 +1454,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfe00e15e479464bb01cc03f0dd3e68d837dad72c6ae89f271856df88d9f78e" +checksum = "f7261e1a11912f6018b6adea722fe0fbb064ef832f1e22fa54c13726f75e6a48" dependencies = [ "backtrace", "percent-encoding", @@ -1752,9 +1752,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1899,9 +1899,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" diff --git a/Cargo.toml b/Cargo.toml index b98dafa..cc8d1da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.22", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.40.0", features = [ +rabbitmq_http_client = { version = "0.41.0", features = [ "blocking", "tabled", ] } From fcd964ff6b8acf40b230b1a219ef968d732234c6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 11 Aug 2025 16:09:11 -0400 Subject: [PATCH 195/320] Introduce 'shovels list' for listing only the shovels in a particular virtual host. --- src/cli.rs | 19 ++++++++++++++++--- src/commands.rs | 4 ++++ src/main.rs | 4 ++++ tests/shovel_tests.rs | 18 ++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 70d21bb..7dacfcc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2861,7 +2861,7 @@ pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { +pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { let list_all_cmd = Command::new("list_all") .long_about("Lists shovels in all virtual hosts") .after_help(color_print::cformat!( @@ -2869,6 +2869,13 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4 SHOVEL_GUIDE_URL )); + let list_cmd = Command::new("list") + .long_about("Lists shovels in a specific virtual host") + .after_help(color_print::cformat!( + "Doc guide: {}", + SHOVEL_GUIDE_URL + )); + let declare_091_cmd = Command::new("declare_amqp091") .long_about( "Declares a dynamic shovel that uses AMQP 0-9-1 for both source and destination", @@ -3003,8 +3010,14 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4 .required(true), ); - [list_all_cmd, declare_091_cmd, declare_10_cmd, delete_cmd] - .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + [ + list_all_cmd, + list_cmd, + declare_091_cmd, + declare_10_cmd, + delete_cmd, + ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { diff --git a/src/commands.rs b/src/commands.rs index bbb60d2..9cf4c81 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -248,6 +248,10 @@ pub fn list_shovels(client: APIClient) -> ClientResult> { client.list_shovels() } +pub fn list_shovels_in(client: APIClient, vhost: &str) -> ClientResult> { + client.list_shovels_in(vhost) +} + pub fn declare_amqp10_shovel( client: APIClient, vhost: &str, diff --git a/src/main.rs b/src/main.rs index 30bf248..6024436 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1035,6 +1035,10 @@ fn dispatch_common_subcommand( let result = commands::list_shovels(client); res_handler.tabular_result(result) } + ("shovels", "list") => { + let result = commands::list_shovels_in(client, &vhost); + res_handler.tabular_result(result) + } ("streams", "declare") => { let result = commands::declare_stream(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index 8d50a54..e724f93 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -15,6 +15,7 @@ mod test_helpers; use crate::test_helpers::*; +use predicates::boolean::PredicateBooleanExt; use predicates::prelude::predicate; #[test] @@ -150,6 +151,23 @@ fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box Date: Mon, 11 Aug 2025 16:10:02 -0400 Subject: [PATCH 196/320] Change log updates --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 558c955..ca35195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,21 @@ # rabbitmqadmin-ng Change Log -## v2.8.0 (in development) +## v2.9.0 (in development) No changes yet. + +## v2.8.0 (Aug 11, 2025) + +### Bug Fixes + + * `shovels list_all` + +### Upgrades + +* RabbitMQ HTTP API client was upgraded to [`0.41.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.41.0) + + ## v2.7.2 (Aug 6, 2025) ### Bug Fixes From 67e36bbd96141b99b6d3d27d0d9237aa26eb382e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 11 Aug 2025 16:16:46 -0400 Subject: [PATCH 197/320] Back to dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cc8d1da..9819090 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.8.0" +version = "2.9.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 48554c05949d5dfd184f64c522d15a32e682a9d2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 11 Aug 2025 16:19:10 -0400 Subject: [PATCH 198/320] Commit Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index f44a34f..0b4adda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.8.0" +version = "2.9.0" dependencies = [ "assert_cmd", "clap", From c5bfc182947addf00ed233aac54ae7048de9fde0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 11 Aug 2025 16:25:03 -0400 Subject: [PATCH 199/320] Change log updates --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca35195..9ba1ad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,11 @@ No changes yet. ### Bug Fixes - * `shovels list_all` + * `shovels list_all` panicked when one of the shovels was in `terminated` state + +### Enhancements + + * `shovels list` is a new command that lists shovels in a particular virtual host ### Upgrades From 61b228c23e949647f0bc0a3fcbe4df9ba93d1c42 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 11 Aug 2025 19:40:44 -0400 Subject: [PATCH 200/320] Release notes: a typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba1ad7..ed32a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ No changes yet. ### Bug Fixes - * `shovels list_all` panicked when one of the shovels was in `terminated` state + * `shovels list_all` panicked when one of the shovels was in the `terminated` state ### Enhancements From 50e3d098e4a80d7cc7994133fb6e117b124c77d7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 12 Aug 2025 11:39:17 -0400 Subject: [PATCH 201/320] Depend on the latest reqwest release available --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b4adda..38d289a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1375,9 +1375,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] @@ -1597,9 +1597,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", @@ -2059,18 +2059,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9819090..e9f579f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" clap = { version = "4", features = ["help", "color", "cargo", "env"] } url = "2" sysexits = "0.9" -reqwest = { version = "0.12.22", features = [ +reqwest = { version = "0.12", features = [ "blocking", "json", "multipart", From 6dab680d5b40736068ddc3cc50d250d1f838c367 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 14 Aug 2025 02:31:04 -0400 Subject: [PATCH 202/320] Bump HTTP API client --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e9f579f..b9c9337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.41.0", features = [ +rabbitmq_http_client = { version = "0.42.0", features = [ "blocking", "tabled", ] } From 2cd8271257cbee3c17c7a32d0d2a27dd075b5ae2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 14 Aug 2025 02:31:18 -0400 Subject: [PATCH 203/320] cargo update --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38d289a..4235406 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.44" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", ] @@ -1454,9 +1454,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7261e1a11912f6018b6adea722fe0fbb064ef832f1e22fa54c13726f75e6a48" +checksum = "6d39d0633452a94813c0faad771b52f10d089e611ab15958b28e040094d6dd20" dependencies = [ "backtrace", "percent-encoding", @@ -1527,9 +1527,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -1537,9 +1537,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -1949,9 +1949,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" dependencies = [ "proc-macro2", "quote", From 19714e89137b9dbcc7ae1831dd8a7c0620f89b1e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 14 Aug 2025 15:31:36 -0400 Subject: [PATCH 204/320] 2.8.1 --- CHANGELOG.md | 12 ++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed32a21..67d6b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ No changes yet. +## v2.8.1 (Aug 14, 2025) + +### Bug Fixes + + * `shovels list` and `shovels list_all` panicked when target cluster had at least one + static shovel + +### Upgrades + + * RabbitMQ HTTP API client was upgraded to [`0.42.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.42.0) + + ## v2.8.0 (Aug 11, 2025) ### Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index 4235406..10ccbcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.9.0" +version = "2.8.1" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index b9c9337..8bc2b65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.9.0" +version = "2.8.1" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From e80c3b1790e89f2230b95b2f88bcbbedcdf89a55 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 14 Aug 2025 15:33:02 -0400 Subject: [PATCH 205/320] Back to dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8bc2b65..b9c9337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.8.1" +version = "2.9.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 9958c3d9963fbf04af78e289308aa6b54ec04bf7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 15 Aug 2025 13:22:17 -0400 Subject: [PATCH 206/320] Commit Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 10ccbcf..4235406 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.8.1" +version = "2.9.0" dependencies = [ "assert_cmd", "clap", From 29c65039f37cd30ca4e4da6d7bea03c46ee3528a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:51:48 +0000 Subject: [PATCH 207/320] Bump thiserror from 2.0.14 to 2.0.15 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.14 to 2.0.15. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.14...2.0.15) --- updated-dependencies: - dependency-name: thiserror dependency-version: 2.0.15 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4235406..c24598e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2059,18 +2059,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" dependencies = [ "proc-macro2", "quote", From 8e94a2c24af0109b97610c343145d9fe4ef3ea09 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 19 Aug 2025 20:44:25 -0400 Subject: [PATCH 208/320] Make 'definitions export' compatible with RabbitMQ 3.10.0 --- CHANGELOG.md | 12 ++++++++++++ Cargo.lock | 52 +++++++++++++++++++++++++++------------------------- Cargo.toml | 4 ++-- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67d6b9f..d7f308a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ No changes yet. +## v2.8.2 (Aug 19, 2025) + +### Enhancements + + * `definitions export` is now compatible with RabbitMQ 3.10.0, a series that has + reached end of life (EOL) in late 2023 + +### Upgrades + + * RabbitMQ HTTP API client was upgraded to [`0.43.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.43.0) + + ## v2.8.1 (Aug 14, 2025) ### Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index c24598e..ca9bccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" [[package]] name = "bstr" @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.32" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "jobserver", "libc", @@ -243,9 +243,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -703,19 +703,21 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1343,9 +1345,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -1375,9 +1377,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1454,9 +1456,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.42.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d39d0633452a94813c0faad771b52f10d089e611ab15958b28e040094d6dd20" +checksum = "62709cb4f2905d117fa28771408f92f72a40e954e0573d6341d5eb16ffa6b832" dependencies = [ "backtrace", "percent-encoding", @@ -1476,7 +1478,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.9.0" +version = "2.8.2" dependencies = [ "assert_cmd", "clap", @@ -1851,9 +1853,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -1949,9 +1951,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.105" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -2031,15 +2033,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2120,9 +2122,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] diff --git a/Cargo.toml b/Cargo.toml index b9c9337..231c4eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.9.0" +version = "2.8.2" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.42.0", features = [ +rabbitmq_http_client = { version = "0.43.0", features = [ "blocking", "tabled", ] } From f11596da4808726782ae0991f88bc0edd56c0316 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 01:47:02 +0000 Subject: [PATCH 209/320] Bump thiserror from 2.0.15 to 2.0.16 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.15 to 2.0.16. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.15...2.0.16) --- updated-dependencies: - dependency-name: thiserror dependency-version: 2.0.16 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca9bccc..a806ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2061,18 +2061,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", From fcaafb363c5cae6b0676d34253017b5608413f4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 01:49:33 +0000 Subject: [PATCH 210/320] Bump url from 2.5.4 to 2.5.7 Bumps [url](https://github.com/servo/rust-url) from 2.5.4 to 2.5.7. - [Release notes](https://github.com/servo/rust-url/releases) - [Commits](https://github.com/servo/rust-url/commits) --- updated-dependencies: - dependency-name: url dependency-version: 2.5.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca9bccc..11e8dc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,9 +519,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -894,9 +894,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1267,9 +1267,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -2320,13 +2320,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] From 3f508eabd61f87ab5334ece3612a20308a7093f0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 25 Aug 2025 23:05:29 -0400 Subject: [PATCH 211/320] cargo update --- Cargo.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 257b1aa..2fa517f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bstr" @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.33" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", @@ -925,9 +925,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", @@ -973,9 +973,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -1570,9 +1570,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -1582,9 +1582,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -1593,9 +1593,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" @@ -2712,9 +2712,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] name = "wit-bindgen-rt" From 1224ddc137d7fd1cb1ab2a6c4caedb3767574f58 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 25 Aug 2025 23:41:39 -0400 Subject: [PATCH 212/320] 2.9.0 --- CHANGELOG.md | 14 +++++++++++++- Cargo.toml | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7f308a..6d07a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,22 @@ # rabbitmqadmin-ng Change Log -## v2.9.0 (in development) +## v2.10.0 (in development) No changes yet. +## v2.9.0 (Aug 25, 2025) + +### Enhancements + + * RabbitMQ 4.2 forward compatibility: `shovels list_all` and `shovels list` now can render + [local shovel](https://github.com/rabbitmq/rabbitmq-server/pull/14256) rows + +### Upgrades + +* RabbitMQ HTTP API client was upgraded to [`0.44.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.44.0) + + ## v2.8.2 (Aug 19, 2025) ### Enhancements diff --git a/Cargo.toml b/Cargo.toml index 231c4eb..dbfc1bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.8.2" +version = "2.9.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.43.0", features = [ +rabbitmq_http_client = { version = "0.44.0", features = [ "blocking", "tabled", ] } From 8c66030db9dc3e7c8fd5d918b3aabac481e87d0b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 25 Aug 2025 23:50:54 -0400 Subject: [PATCH 213/320] 2.9.0, for real --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fa517f..174b13a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1456,9 +1456,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62709cb4f2905d117fa28771408f92f72a40e954e0573d6341d5eb16ffa6b832" +checksum = "c499b254a994d614a5d25e39b28d86d4012cd648dd3032d2ab3dc5a8a026b05c" dependencies = [ "backtrace", "percent-encoding", @@ -1478,7 +1478,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.8.2" +version = "2.9.0" dependencies = [ "assert_cmd", "clap", From 59c7fa7940e24db638f5dd1dcf80e1e00c809f98 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 25 Aug 2025 23:58:22 -0400 Subject: [PATCH 214/320] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dbfc1bb..b257760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.9.0" +version = "2.10.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From 3ee31a4f1137c127e121c9ef2a1f964744013f2f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 26 Aug 2025 00:02:41 -0400 Subject: [PATCH 215/320] Commit Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 174b13a..c6eef86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1478,7 +1478,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.9.0" +version = "2.10.0" dependencies = [ "assert_cmd", "clap", From c743ca05492f5b3d0aaeb7ff61e1b30c55f172da Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 28 Aug 2025 18:47:43 -0400 Subject: [PATCH 216/320] cargo update --- Cargo.lock | 69 ++++++++++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6eef86..e5dd6de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,18 +278,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -605,7 +605,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.3+wasi-0.2.4", "wasm-bindgen", ] @@ -774,7 +774,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2", "system-configuration", "tokio", "tower-service", @@ -1021,9 +1021,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" dependencies = [ "cc", "libc", @@ -1077,9 +1077,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mimalloc" -version = "0.1.47" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" dependencies = [ "libmimalloc-sys", ] @@ -1291,9 +1291,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -1386,9 +1386,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -1397,7 +1397,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.5.10", + "socket2", "thiserror", "tokio", "tracing", @@ -1406,9 +1406,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", @@ -1427,16 +1427,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1911,16 +1911,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -2148,7 +2138,7 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2 0.6.0", + "socket2", "windows-sys 0.59.0", ] @@ -2374,11 +2364,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.3+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -2717,13 +2707,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" [[package]] name = "writeable" From cec3b74b4b286b633512d3b914fe9386eef8249c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 1 Sep 2025 03:47:23 -0400 Subject: [PATCH 217/320] www.rabbitmq.com => rabbitmq.com in doc guide URLs --- src/static_urls.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/static_urls.rs b/src/static_urls.rs index f521a05..596f929 100644 --- a/src/static_urls.rs +++ b/src/static_urls.rs @@ -15,7 +15,7 @@ #![allow(dead_code)] #![allow(unused_variables)] -pub(crate) const RABBITMQADMIN_DOC_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/management-cli"; +pub(crate) const RABBITMQADMIN_DOC_GUIDE_URL: &str = "https://rabbitmq.com/docs/management-cli"; pub(crate) const RABBITMQ_DOC_GUIDES_URL: &str = "https://rabbitmq.com/docs/"; pub(crate) const GITHUB_DISCUSSIONS_URL: &str = "https://github.com/rabbitmq/rabbitmq-server/discussions"; @@ -42,7 +42,7 @@ pub(crate) const DEPRECATED_FEATURE_GUIDE_URL: &str = pub(crate) const ACCESS_CONTROL_GUIDE_URL: &str = "https://rabbitmq.com/docs/access-control"; pub(crate) const HTTP_API_ACCESS_PERMISSIONS_GUIDE_URL: &str = "https://rabbitmq.com/docs/management#permissions"; -pub(crate) const MEMORY_FOOTPRINT_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/memory-use"; +pub(crate) const MEMORY_FOOTPRINT_GUIDE_URL: &str = "https://rabbitmq.com/docs/memory-use"; pub(crate) const DEFINITION_GUIDE_URL: &str = "https://rabbitmq.com/docs/definitions"; pub(crate) const CONSUMER_GUIDE_URL: &str = "https://rabbitmq.com/docs/consumers"; pub(crate) const POLLING_CONSUMER_GUIDE_URL: &str = "https://rabbitmq.com/docs/consumers#polling"; @@ -50,13 +50,13 @@ pub(crate) const PUBLISHER_GUIDE_URL: &str = "https://rabbitmq.com/docs/publishe%20pub(crate)%20const%20CLUSTERING_GUIDE_URL:%20&str%20="https://rabbitmq.com/docs/clustering"; pub(crate) const PEER_DISCOVERY_GUIDE_URL: &str = "https://rabbitmq.com/docs/cluster-formation"; pub(crate) const VIRTUAL_HOST_GUIDE_URL: &str = "https://rabbitmq.com/docs/vhosts"; -pub(crate) const VIRTUAL_HOST_LIMIT_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/vhosts#limits"; +pub(crate) const VIRTUAL_HOST_LIMIT_GUIDE_URL: &str = "https://rabbitmq.com/docs/vhosts#limits"; pub(crate) const VIRTUAL_HOST_DEFAULT_QUEUE_TYPE_GUIDE_URL: &str = - "https://www.rabbitmq.com/docs/vhosts#default-queue-type"; + "https://rabbitmq.com/docs/vhosts#default-queue-type"; pub(crate) const RUNTIME_PARAMETER_GUIDE_URL: &str = "https://rabbitmq.com/docs/parameters"; -pub(crate) const POLICY_GUIDE_URL: &str = "https://www.rabbitmq.com/docs/policies"; +pub(crate) const POLICY_GUIDE_URL: &str = "https://rabbitmq.com/docs/policies"; pub(crate) const OPERATOR_POLICY_GUIDE_URL: &str = - "https://www.rabbitmq.com/docs/parameters#operator-policies"; + "https://rabbitmq.com/docs/parameters#operator-policies"; pub(crate) const USER_LIMIT_GUIDE_URL: &str = "https://rabbitmq.com/docs/user-limits"; pub(crate) const PASSWORD_GUIDE_URL: &str = "https://rabbitmq.com/docs/passwords"; pub(crate) const SHOVEL_GUIDE_URL: &str = "https://rabbitmq.com/docs/shovel"; @@ -65,4 +65,4 @@ pub(crate) const FEDERATED_QUEUES_GUIDE_URL: &str = "https://rabbitmq.com/docs/f%20pub(crate)%20const%20FEDERATED_EXCHANGES_GUIDE_URL:%20&str%20="https://rabbitmq.com/docs/federated-exchanges"; pub(crate) const FEDERATION_REFERENCE_URL: &str = "https://rabbitmq.com/docs/federation-reference"; -pub(crate) const COMMERCIAL_OFFERINGS_GUIDE_URL: &str = "https://www.rabbitmq.com/contact"; +pub(crate) const COMMERCIAL_OFFERINGS_GUIDE_URL: &str = "https://rabbitmq.com/contact"; From c02b85a546fef196f6c1084502fdc31fee598820 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 2 Sep 2025 00:02:07 -0400 Subject: [PATCH 218/320] Bump HTTP API client to 0.45.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5dd6de..5864da4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1456,9 +1456,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c499b254a994d614a5d25e39b28d86d4012cd648dd3032d2ab3dc5a8a026b05c" +checksum = "e13e5074ac289e9525fcb423fe564d939e78c16f06c17d4e0da4533f185d224c" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index b257760..ca7933d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.44.0", features = [ +rabbitmq_http_client = { version = "0.45.0", features = [ "blocking", "tabled", ] } From 997ad5447288306fa253a493c80e7a2b45e2cfaa Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 2 Sep 2025 02:25:34 -0400 Subject: [PATCH 219/320] Change log update --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d07a5a..8a8c9db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## v2.10.0 (in development) -No changes yet. +### Upgrades + +* RabbitMQ HTTP API client was upgraded to [`0.45.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.45.0) ## v2.9.0 (Aug 25, 2025) From 2407aada9226caca3b64674b19e0b9cdc89c60fa Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 2 Sep 2025 02:28:39 -0400 Subject: [PATCH 220/320] cargo update --- Cargo.lock | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5864da4..6b14797 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.3" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "bstr" @@ -223,10 +223,11 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.34" +version = "1.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -392,9 +393,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", "serde", @@ -487,6 +488,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" + [[package]] name = "float-cmp" version = "0.10.0" @@ -2071,12 +2078,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "8ca967379f9d8eb8058d86ed467d81d03e81acd45757e4ca341c24affbe8e8e3" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", @@ -2086,15 +2092,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "a9108bb380861b07264b950ded55a44a14a4adc68b9f5efd85aafc3aa4d40a68" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "7182799245a7264ce590b349d90338f1c1affad93d2639aed5f8f69c090b334c" dependencies = [ "num-conv", "time-core", From 0a4bdd7e1233a2741cdcedb7394910aa681dc373 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 2 Sep 2025 16:42:18 -0400 Subject: [PATCH 221/320] README updates --- README.md | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/README.md b/README.md index 6b82b1c..c2b8950 100644 --- a/README.md +++ b/README.md @@ -806,6 +806,214 @@ To list all [federation links](https://www.rabbitmq.com/docs/federation) across rabbitmqadmin federation list_all_links ``` +### Create a User + +```shell +# Salt and hash a cleartext password value, and output the resultign hash. +# See https://www.rabbitmq.com/docs/passwords to learn more. +rabbitmqadmin passwords salt_and_hash "cleartext value" +``` + +```shell +rabbitmqadmin users declare --name "new-user" --password "secure-password" --tags "monitoring,management" +``` + +```shell +# Create user with administrator tag using pre-hashed password +# (use 'rabbitmqadmin passwords salt_and_hash' to generate the hash) +rabbitmqadmin users declare --name "admin-user" --password-hash "{value produced by 'rabbitmqadmin passwords salt_and_hash'}" --tags "administrator" +``` + +```shell +# If RabbitMQ nodes are configured to use SHA512 for passwords, add `--hashing-algorithm`. +# See https://www.rabbitmq.com/docs/passwords to learn more. +rabbitmqadmin users declare --name "secure-user" --password-hash "{SHA512-hashed-password}" --hashing-algorithm "SHA512" --tags "monitoring" +``` + +### Delete a User + +```shell +rabbitmqadmin users delete --name "user-to-delete" +``` + +```shell +# Idempotent deletion (won't fail if user doesn't exist) +rabbitmqadmin users delete --name "user-to-delete" --idempotently +``` + +### Grant Permissions to a User + +```shell +rabbitmqadmin users permissions --name "app-user" --configure ".*" --write ".*" --read ".*" +``` + +```shell +rabbitmqadmin --vhost "production" users permissions --name "app-user" --configure "^amq\.gen.*|^aliveness-test$" --write ".*" --read ".*" +``` + +### Create a Binding + +```shell +rabbitmqadmin --vhost "events" bindings declare --source "events.topic" --destination-type "queue" --destination "events.processing" --routing-key "user.created" +``` + +```shell +rabbitmqadmin --vhost "events" bindings declare --source "events.fanout" --destination-type "exchange" --destination "events.archived" --routing-key "" --arguments '{"x-match": "all"}' +``` + +### Delete a Binding + +```shell +rabbitmqadmin --vhost "events" bindings delete --source "events.topic" --destination-type "queue" --destination "events.processing" --routing-key "user.created" +``` + +### List Connections + +```shell +rabbitmqadmin connections list +``` + +```shell +# List connections for a specific user +rabbitmqadmin connections list --user "app-user" +``` + +### Close Connections + +```shell +# Close a specific connection by name +rabbitmqadmin connections close --name "connection-name" +``` + +```shell +# Close all connections from a specific user +rabbitmqadmin connections close --user "problem-user" --reason "Maintenance window" +``` + +### List Channels + +```shell +rabbitmqadmin channels list +``` + +```shell +# List channels in a specific virtual host +rabbitmqadmin --vhost "production" channels list +``` + +### Run Health Checks + +```shell +# Check for local alarms +rabbitmqadmin health_check local_alarms +``` + +```shell +# Check for cluster-wide alarms +rabbitmqadmin health_check cluster_wide_alarms +``` + +```shell +# Check if node is quorum critical +rabbitmqadmin health_check node_is_quorum_critical +``` + +```shell +# Check for deprecated features in use +rabbitmqadmin health_check deprecated_features_in_use +``` + +```shell +# Check if a port listener is running +rabbitmqadmin health_check port_listener --port 5672 +``` + +```shell +# Check if a protocol listener is running +rabbitmqadmin health_check protocol_listener --protocol "amqp" +``` + +### Set Runtime Parameters + +```shell +rabbitmqadmin --vhost "events" parameters declare --component "federation-upstream" --name "upstream-1" --value '{"uri": "amqp://remote-server", "ack-mode": "on-publish"}' +``` + +```shell +rabbitmqadmin parameters delete --component "federation-upstream" --name "upstream-1" +``` + +### Set Global Parameters + +```shell +rabbitmqadmin global_parameters declare --name "cluster_name" --value '"production-cluster"' +``` + +```shell +rabbitmqadmin global_parameters delete --name "cluster_name" +``` + +### Declare Operator Policies + +```shell +rabbitmqadmin --vhost "production" operator_policies declare --name "ha-policy" --pattern "^ha\." --definition '{"ha-mode": "exactly", "ha-params": 3}' --priority 1 --apply-to "queues" +``` + +### List Operator Policies + +```shell +rabbitmqadmin operator_policies list +``` + +### Delete Operator Policies + +```shell +rabbitmqadmin --vhost "production" operator_policies delete --name "ha-policy" +``` + +### Manage Passwords + +```shell +# Change user password +rabbitmqadmin passwords change --name "app-user" --new-password "new-secure-password" +``` + +### Rebalance Quorum Queue Leaders + +```shell +# Rebalances leader members (replicas) for all quorum queue +rabbitmqadmin rebalance all +``` + +### Stream Operations + +```shell +# List streams +rabbitmqadmin streams list +``` + +```shell +# Declare a stream +rabbitmqadmin --vhost "logs" streams declare --name "application.logs" --max-age "7d" --max-length-bytes "10GB" +``` + +```shell +# Delete a stream +rabbitmqadmin --vhost "logs" streams delete --name "old.stream" +``` + +### Node Operations + +```shell +# List cluster nodes +rabbitmqadmin nodes list +``` + +```shell +# Show node information +rabbitmqadmin nodes show --name "rabbit@server1" +``` + ## Subcommand and Long Option Inference From 7129f913cb13e2f0be822b5dc2ab9907957a3203 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 6 Sep 2025 18:58:48 -0400 Subject: [PATCH 222/320] Bump HTTP API client to 0.46.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b14797..c374c00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1463,9 +1463,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13e5074ac289e9525fcb423fe564d939e78c16f06c17d4e0da4533f185d224c" +checksum = "d0628a14cd8ac9d6b3a670f583b3c1db6b403db18a1d695546450cf707a86f17" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index ca7933d..5506e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.45.0", features = [ +rabbitmq_http_client = { version = "0.46.0", features = [ "blocking", "tabled", ] } From 42d2ececd369415142f30821f2e4444920d66fe2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 6 Sep 2025 19:00:53 -0400 Subject: [PATCH 223/320] cargo update --- .gitignore | 1 + Cargo.lock | 112 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 65 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index d26635b..c00fe18 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea/* .fleet/* profile.json +.tool-versions diff --git a/Cargo.lock b/Cargo.lock index c374c00..a1a45eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.35" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ "find-msvc-tools", "jobserver", @@ -263,7 +263,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -279,18 +279,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.46" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.46" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -425,7 +425,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -490,9 +490,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" [[package]] name = "float-cmp" @@ -612,7 +612,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasi 0.14.4+wasi-0.2.4", "wasm-bindgen", ] @@ -990,9 +990,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" dependencies = [ "once_cell", "wasm-bindgen", @@ -1066,9 +1066,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru-slab" @@ -2078,9 +2078,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.42" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca967379f9d8eb8058d86ed467d81d03e81acd45757e4ca341c24affbe8e8e3" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" dependencies = [ "deranged", "num-conv", @@ -2092,15 +2092,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9108bb380861b07264b950ded55a44a14a4adc68b9f5efd85aafc3aa4d40a68" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7182799245a7264ce590b349d90338f1c1affad93d2639aed5f8f69c090b334c" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -2370,30 +2370,31 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.3+wasi-0.2.4" +version = "0.14.4+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" dependencies = [ "bumpalo", "log", @@ -2405,9 +2406,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" dependencies = [ "cfg-if", "js-sys", @@ -2418,9 +2419,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2428,9 +2429,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" dependencies = [ "proc-macro2", "quote", @@ -2441,18 +2442,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" dependencies = [ "js-sys", "wasm-bindgen", @@ -2488,7 +2489,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -2521,13 +2522,19 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-registry" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -2538,7 +2545,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2547,7 +2554,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2577,6 +2584,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2599,7 +2615,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2714,9 +2730,9 @@ checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] name = "wit-bindgen" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "writeable" @@ -2750,18 +2766,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", From 37857c138fd78c4ffeffe66d56a1934d055df79f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 7 Sep 2025 02:21:37 -0400 Subject: [PATCH 224/320] Bump HTTP API client to 0.48.0 --- CHANGELOG.md | 2 +- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a8c9db..b01a72d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Upgrades -* RabbitMQ HTTP API client was upgraded to [`0.45.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.45.0) +* RabbitMQ HTTP API client was upgraded to [`0.48.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.48.0) ## v2.9.0 (Aug 25, 2025) diff --git a/Cargo.lock b/Cargo.lock index a1a45eb..38aab0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1463,9 +1463,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.46.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0628a14cd8ac9d6b3a670f583b3c1db6b403db18a1d695546450cf707a86f17" +checksum = "bafce1d74c5d3f7602f1e6ac2c840f0f81ddb51c3d7f42b953e402e29ac8676f" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 5506e6d..47b01a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.46.0", features = [ +rabbitmq_http_client = { version = "0.48.0", features = [ "blocking", "tabled", ] } From af253b8e64201b0cdd4e9d4faf9d89ae48511606 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 7 Sep 2025 05:04:07 -0400 Subject: [PATCH 225/320] Bump HTTP API client to 0.49.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38aab0b..859f290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1463,9 +1463,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.48.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafce1d74c5d3f7602f1e6ac2c840f0f81ddb51c3d7f42b953e402e29ac8676f" +checksum = "df594ee601ed4dcdded73d9dc72b54d234ad702efdef409ae35824fb7993ab27" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 47b01a2..a32217c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.48.0", features = [ +rabbitmq_http_client = { version = "0.49.0", features = [ "blocking", "tabled", ] } From a6619edcdcb99960bb6010ec87a482c588b6331d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 00:33:59 +0000 Subject: [PATCH 226/320] Bump sysexits from 0.9.1 to 0.10.0 Bumps [sysexits](https://github.com/sorairolake/sysexits-rs) from 0.9.1 to 0.10.0. - [Changelog](https://github.com/sorairolake/sysexits-rs/blob/develop/CHANGELOG.adoc) - [Commits](https://github.com/sorairolake/sysexits-rs/compare/v0.9.1...v0.10.0) --- updated-dependencies: - dependency-name: sysexits dependency-version: 0.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 859f290..8c2f373 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1979,9 +1979,9 @@ dependencies = [ [[package]] name = "sysexits" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff8b58f22cae39a24a26289da003ac87ddce0c024359c382d9ea0c48d08bc86" +checksum = "bf9154bb31a0b747e214520c60cb9a842df871bf6a5fea5be4352b59d6c432ab" [[package]] name = "system-configuration" diff --git a/Cargo.toml b/Cargo.toml index a32217c..3a8132e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" [dependencies] clap = { version = "4", features = ["help", "color", "cargo", "env"] } url = "2" -sysexits = "0.9" +sysexits = "0.10" reqwest = { version = "0.12", features = [ "blocking", "json", From e2c24708facdc2e2113424343d180efd2f64ec64 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 7 Sep 2025 23:42:24 -0400 Subject: [PATCH 227/320] cargo update --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c2f373..6065455 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1734,7 +1734,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.3.0", + "security-framework 3.4.0", ] [[package]] @@ -1795,9 +1795,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -1808,9 +1808,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", From 22590d2dad7bbbc2513cbff3802b6f594bbab61a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 11 Sep 2025 13:40:25 -0400 Subject: [PATCH 228/320] Wording --- src/config.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index e8306bd..b71fe0f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,15 +55,15 @@ impl PreFlightSettings { #[derive(Error, Debug)] pub enum ConfigFileError { - #[error("provided config file at '{0}' does not exist")] + #[error("the provided config file at '{0}' does not exist")] MissingFile(PathBuf), #[error( - "provided configuration section (--node) '{0}' was not found in the configuration file" + "specified configuration section (--node) '{0}' was not found in the configuration file" )] MissingConfigSection(String), #[error(transparent)] IoError(#[from] std::io::Error), - #[error("failed to deserialize config file. Make sure it is valid TOML. Details: {0}")] + #[error("failed to deserialize the config file. Make sure it is valid TOML. Details: {0}")] DeserializationError(#[from] toml::de::Error), } From 57cf1231c53c52ddaa211930af62a6b74898289b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 13 Sep 2025 01:31:50 -0400 Subject: [PATCH 229/320] Fix 'cargo test' by updating an output assertion --- Cargo.lock | 195 +++++++++++----------------- tests/combined_integration_tests.rs | 2 +- 2 files changed, 78 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6065455..62b6afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -121,9 +115,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" dependencies = [ "aws-lc-sys", "zeroize", @@ -131,9 +125,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" dependencies = [ "bindgen", "cc", @@ -165,25 +159,22 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bindgen" -version = "0.69.5" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools", - "lazy_static", - "lazycell", "log", "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash 1.1.0", + "rustc-hash", "shlex", "syn", - "which", ] [[package]] @@ -223,9 +214,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.36" +version = "1.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" dependencies = [ "find-msvc-tools", "jobserver", @@ -256,14 +247,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", - "windows-link 0.1.3", + "windows-link 0.2.0", ] [[package]] @@ -474,12 +464,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -612,7 +602,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.4+wasi-0.2.4", + "wasi 0.14.5+wasi-0.2.4", "wasm-bindgen", ] @@ -659,15 +649,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "http" version = "1.3.1" @@ -791,9 +772,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -922,9 +903,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" dependencies = [ "equivalent", "hashbrown", @@ -965,9 +946,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -998,18 +979,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.175" @@ -1048,15 +1017,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1402,7 +1365,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "socket2", "thiserror", @@ -1422,7 +1385,7 @@ dependencies = [ "lru-slab", "rand", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "rustls-pki-types", "slab", @@ -1671,12 +1634,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -1685,28 +1642,15 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys", + "windows-sys 0.61.0", ] [[package]] @@ -1749,9 +1693,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" dependencies = [ "aws-lc-rs", "ring", @@ -1773,11 +1717,11 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -2030,15 +1974,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix", + "windows-sys 0.61.0", ] [[package]] @@ -2298,9 +2242,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-width" @@ -2370,9 +2314,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.4+wasi-0.2.4" +version = "0.14.5+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" +checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.0+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" dependencies = [ "wit-bindgen", ] @@ -2469,29 +2422,17 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-link 0.2.0", + "windows-result 0.4.0", + "windows-strings 0.5.0", ] [[package]] @@ -2535,8 +2476,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -2548,6 +2489,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -2557,6 +2507,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/tests/combined_integration_tests.rs b/tests/combined_integration_tests.rs index 3fb51ce..934f956 100644 --- a/tests/combined_integration_tests.rs +++ b/tests/combined_integration_tests.rs @@ -63,7 +63,7 @@ fn combined_integration_test2() -> Result<(), Box> { vh, ]) .stderr( - predicate::str::contains("provided configuration section (--node)").and( + predicate::str::contains("specified configuration section (--node)").and( predicate::str::contains("was not found in the configuration file"), ), ); From ca7eba38013f24941aa03dff7b958b650b083de5 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 13 Sep 2025 19:54:36 -0400 Subject: [PATCH 230/320] Bump rabbitmq_http_client to 0.50.0 --- CHANGELOG.md | 2 +- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b01a72d..555042d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Upgrades -* RabbitMQ HTTP API client was upgraded to [`0.48.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.48.0) +* RabbitMQ HTTP API client was upgraded to [`0.50.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.50.0) ## v2.9.0 (Aug 25, 2025) diff --git a/Cargo.lock b/Cargo.lock index 62b6afc..7373c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1426,9 +1426,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df594ee601ed4dcdded73d9dc72b54d234ad702efdef409ae35824fb7993ab27" +checksum = "d2622f55fa1eb0516f6ec57d98b1f33c969b077fb38887e8317e4f35c150b024" dependencies = [ "backtrace", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 3a8132e..bb3dff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.49.0", features = [ +rabbitmq_http_client = { version = "0.50.0", features = [ "blocking", "tabled", ] } From 826216c79d0536ada9fbfc999d1f37d31f1e0267 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 13 Sep 2025 20:02:28 -0400 Subject: [PATCH 231/320] Adapt to rabbitmq_http_api client 0.50.0 --- Cargo.lock | 24 +++++++++++++++++------- src/tanzu_commands.rs | 8 ++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7373c80..5fb7795 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1762,10 +1762,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.220" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "ceecad4c782e936ac90ecfd6b56532322e3262b14320abf30ce89a92ffdbfe22" dependencies = [ + "serde_core", "serde_derive", ] @@ -1791,11 +1792,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.220" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddba47394f3b862d6ff6efdbd26ca4673e3566a307880a0ffb98f274bbe0ec32" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.220" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "60e1f3b1761e96def5ec6d04a6e7421c0404fa3cf5c0155f1e2848fae3d8cc08" dependencies = [ "proc-macro2", "quote", @@ -1804,14 +1814,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "56177480b00303e689183f110b4e727bb4211d692c62d4fcd16d02be93077d40" dependencies = [ "itoa", "memchr", "ryu", - "serde", + "serde_core", ] [[package]] diff --git a/src/tanzu_commands.rs b/src/tanzu_commands.rs index 95dc688..285fb1d 100644 --- a/src/tanzu_commands.rs +++ b/src/tanzu_commands.rs @@ -28,21 +28,21 @@ pub fn sds_status_on_node( } pub fn sds_enable_cluster_wide(client: APIClient) -> ClientResult<()> { - client.enable_schema_definition_sync() + client.enable_schema_definition_sync_on_node(None) } pub fn sds_disable_cluster_wide(client: APIClient) -> ClientResult<()> { - client.disable_schema_definition_sync() + client.disable_schema_definition_sync_on_node(None) } pub fn sds_enable_on_node(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { let node = command_args.get_one::("node").unwrap(); - client.enable_schema_definition_sync_on_node(node) + client.enable_schema_definition_sync_on_node(Some(node)) } pub fn sds_disable_on_node(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { let node = command_args.get_one::("node").unwrap(); - client.disable_schema_definition_sync_on_node(node) + client.disable_schema_definition_sync_on_node(Some(node)) } pub fn wsr_status(client: APIClient) -> ClientResult { From dd531061029c8a320d39b99ffbdafa8387a08b54 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 16 Sep 2025 18:58:50 -0400 Subject: [PATCH 232/320] cargo update --- CHANGELOG.md | 2 +- Cargo.lock | 101 ++++++++++++++++++++++++++------------------------- Cargo.toml | 2 +- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 555042d..a8fce5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Upgrades -* RabbitMQ HTTP API client was upgraded to [`0.50.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.50.0) +* RabbitMQ HTTP API client was upgraded to [`0.51.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.51.0) ## v2.9.0 (Aug 25, 2025) diff --git a/Cargo.lock b/Cargo.lock index 5fb7795..286aacb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,7 +602,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.5+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", @@ -903,9 +903,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.1" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" dependencies = [ "equivalent", "hashbrown", @@ -971,9 +971,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "6247da8b8658ad4e73a186e747fcc5fc2a29f979d6fe6269127fdb5fd08298d0" dependencies = [ "once_cell", "wasm-bindgen", @@ -1007,9 +1007,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", @@ -1426,9 +1426,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.50.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2622f55fa1eb0516f6ec57d98b1f33c969b077fb38887e8317e4f35c150b024" +checksum = "b4785ccfa2812e0861bd9eaa6bdd8b59891229bf7bb1339b56a30f145d9dcef6" dependencies = [ "backtrace", "percent-encoding", @@ -1693,9 +1693,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.5" +version = "0.103.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" dependencies = [ "aws-lc-rs", "ring", @@ -1762,9 +1762,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.220" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceecad4c782e936ac90ecfd6b56532322e3262b14320abf30ce89a92ffdbfe22" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ "serde_core", "serde_derive", @@ -1794,18 +1794,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.220" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddba47394f3b862d6ff6efdbd26ca4673e3566a307880a0ffb98f274bbe0ec32" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.220" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e1f3b1761e96def5ec6d04a6e7421c0404fa3cf5c0155f1e2848fae3d8cc08" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -1814,23 +1814,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56177480b00303e689183f110b4e727bb4211d692c62d4fcd16d02be93077d40" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", + "serde", "serde_core", ] [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2137,12 +2138,12 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -2152,11 +2153,11 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2324,27 +2325,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.5+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" -version = "1.0.0+wasi-0.2.4" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "4ad224d2776649cfb4f4471124f8176e54c1cca67a88108e30a0cd98b90e7ad3" dependencies = [ "cfg-if", "once_cell", @@ -2355,9 +2356,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "3a1364104bdcd3c03f22b16a3b1c9620891469f5e9f09bc38b2db121e593e732" dependencies = [ "bumpalo", "log", @@ -2369,9 +2370,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.51" +version = "0.4.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" +checksum = "9c0a08ecf5d99d5604a6666a70b3cde6ab7cc6142f5e641a8ef48fc744ce8854" dependencies = [ "cfg-if", "js-sys", @@ -2382,9 +2383,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "0d7ab4ca3e367bb1ed84ddbd83cc6e41e115f8337ed047239578210214e36c76" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2392,9 +2393,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "4a518014843a19e2dbbd0ed5dfb6b99b23fb886b14e6192a00803a3e14c552b0" dependencies = [ "proc-macro2", "quote", @@ -2405,18 +2406,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "255eb0aa4cc2eea3662a00c2bbd66e93911b7361d5e0fcd62385acfd7e15dcee" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.78" +version = "0.3.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "50462a022f46851b81d5441d1a6f5bac0b21a1d72d64bd4906fbdd4bf7230ec7" dependencies = [ "js-sys", "wasm-bindgen", @@ -2699,9 +2700,9 @@ checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] name = "wit-bindgen" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" diff --git a/Cargo.toml b/Cargo.toml index bb3dff8..26b5bb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.50.0", features = [ +rabbitmq_http_client = { version = "0.51.0", features = [ "blocking", "tabled", ] } From ef9da7d4d1b298203afdc62c2cdb20ea19838b42 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 16 Sep 2025 19:32:11 -0400 Subject: [PATCH 233/320] Refactoring --- src/commands.rs | 250 ++++++++++++++---------------------------------- src/errors.rs | 45 ++++----- src/main.rs | 48 +++++----- src/output.rs | 41 ++++---- src/tables.rs | 21 ++-- 5 files changed, 142 insertions(+), 263 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 9cf4c81..04f5e18 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -34,6 +34,7 @@ use rabbitmq_http_client::requests::{ use rabbitmq_http_client::responses::OptionalArgumentSourceOps; use rabbitmq_http_client::transformers::TransformationChain; use rabbitmq_http_client::{password_hashing, requests, responses}; +use serde::de::DeserializeOwned; use serde_json::Value; use std::fs; use std::io; @@ -74,8 +75,7 @@ pub fn list_user_limits( client: APIClient, command_args: &ArgMatches, ) -> ClientResult> { - let user = command_args.get_one::("user"); - match user { + match command_args.get_one::("user") { None => client.list_all_user_limits(), Some(username) => client.list_user_limits(username), } @@ -119,13 +119,10 @@ pub fn list_policies_in_and_applying_to( apply_to: PolicyTarget, ) -> ClientResult> { let policies = client.list_policies_in(vhost)?; - let filtered = policies - .iter() - .filter(|&pol| apply_to.does_apply_to(pol.apply_to.clone())) - .cloned() - .collect(); - - Ok(filtered) + Ok(policies + .into_iter() + .filter(|pol| apply_to.does_apply_to(pol.apply_to.clone())) + .collect()) } pub fn list_matching_policies_in( @@ -135,13 +132,10 @@ pub fn list_matching_policies_in( typ: PolicyTarget, ) -> ClientResult> { let candidates = list_policies_in_and_applying_to(client, vhost, typ.clone())?; - let matching = candidates - .iter() - .filter(|&pol| pol.does_match_name(vhost, name, typ.clone())) - .cloned() - .collect(); - - Ok(matching) + Ok(candidates + .into_iter() + .filter(|pol| pol.does_match_name(vhost, name, typ.clone())) + .collect()) } pub fn list_operator_policies(client: APIClient) -> ClientResult> { @@ -161,13 +155,10 @@ pub fn list_operator_policies_in_and_applying_to( apply_to: PolicyTarget, ) -> ClientResult> { let policies = client.list_operator_policies_in(vhost)?; - let filtered = policies - .iter() - .filter(|&pol| apply_to.does_apply_to(pol.apply_to.clone())) - .cloned() - .collect(); - - Ok(filtered) + Ok(policies + .into_iter() + .filter(|pol| apply_to.does_apply_to(pol.apply_to.clone())) + .collect()) } pub fn list_matching_operator_policies_in( @@ -177,13 +168,10 @@ pub fn list_matching_operator_policies_in( typ: PolicyTarget, ) -> ClientResult> { let candidates = list_operator_policies_in_and_applying_to(client, vhost, typ.clone())?; - let matching = candidates - .iter() - .filter(|&pol| pol.does_match_name(vhost, name, typ.clone())) - .cloned() - .collect(); - - Ok(matching) + Ok(candidates + .into_iter() + .filter(|pol| pol.does_match_name(vhost, name, typ.clone())) + .collect()) } pub fn list_queues(client: APIClient, vhost: &str) -> ClientResult> { @@ -214,8 +202,7 @@ pub fn list_parameters( vhost: &str, command_args: &ArgMatches, ) -> ClientResult> { - let component = command_args.get_one::("component"); - match component { + match command_args.get_one::("component") { None => { let mut r = client.list_runtime_parameters()?; r.retain(|p| p.vhost == vhost); @@ -758,10 +745,7 @@ pub fn declare_exchange( exchange_type, durable, auto_delete, - arguments: serde_json::from_str::(arguments).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", arguments, err); - process::exit(1); - }), + arguments: parse_json_from_arg(arguments), }; client.declare_exchange(vhost, ¶ms) @@ -779,11 +763,7 @@ pub fn declare_binding( let destination = command_args.get_one::("destination").unwrap(); let routing_key = command_args.get_one::("routing_key").unwrap(); let arguments = command_args.get_one::("arguments").unwrap(); - let parsed_arguments = - serde_json::from_str::(arguments).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", arguments, err); - process::exit(1); - }); + let parsed_arguments = parse_json_from_arg(arguments); match destination_type { BindingDestinationType::Queue => client.bind_queue( @@ -906,11 +886,17 @@ pub fn declare_user(client: APIClient, command_args: &ArgMatches) -> ClientResul let provided_hash = command_args.get_one::("password_hash").unwrap(); let tags = command_args.get_one::("tags").unwrap(); - if password.is_empty() && provided_hash.is_empty() - || !password.is_empty() && !provided_hash.is_empty() - { + let has_password = !password.is_empty(); + let has_hash = !provided_hash.is_empty(); + + if !has_password && !has_hash { eprintln!("Please provide either --password or --password-hash"); - process::exit(1) + process::exit(1); + } + + if has_password && has_hash { + eprintln!("Please provide either --password or --password-hash"); + process::exit(1); } let password_hash = if provided_hash.is_empty() { @@ -919,9 +905,9 @@ pub fn declare_user(client: APIClient, command_args: &ArgMatches) -> ClientResul .unwrap(); let salt = password_hashing::salt(); let hash = hashing_algo.salt_and_hash(&salt, password).unwrap(); - String::from_utf8(hash.into()).unwrap().to_string() + String::from_utf8(hash.into()).unwrap() } else { - provided_hash.to_string() + provided_hash.to_owned() }; let params = requests::UserParams { @@ -985,11 +971,7 @@ pub fn declare_queue( .unwrap_or(false); let arguments = command_args.get_one::("arguments").unwrap(); - let parsed_args = - serde_json::from_str::(arguments).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", arguments, err); - process::exit(1); - }); + let parsed_args = parse_json_from_arg(arguments); let params = requests::QueueParams::new(name, queue_type, durable, auto_delete, parsed_args); @@ -1008,11 +990,7 @@ pub fn declare_stream( .get_one::("max_segment_length_bytes") .cloned(); let arguments = command_args.get_one::("arguments").unwrap(); - let parsed_args = - serde_json::from_str::(arguments).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", arguments, err); - process::exit(1); - }); + let parsed_args = parse_json_from_arg(arguments); let params = requests::StreamParams { name, @@ -1039,11 +1017,7 @@ pub fn declare_policy( let priority = command_args.get_one::("priority").unwrap(); let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = serde_json::from_str::(definition) - .unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", definition, err); - process::exit(1); - }); + let parsed_definition = parse_json_from_arg(definition); let params = requests::PolicyParams { vhost, @@ -1071,11 +1045,7 @@ pub fn declare_operator_policy( let priority = command_args.get_one::("priority").unwrap(); let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = serde_json::from_str::(definition) - .unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", definition, err); - process::exit(1); - }); + let parsed_definition = parse_json_from_arg(definition); let params = requests::PolicyParams { vhost, @@ -1105,11 +1075,7 @@ pub fn declare_policy_override( let new_priority = existing_policy.priority + 100; let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = serde_json::from_str::(definition) - .unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", definition, err); - process::exit(1); - }); + let parsed_definition = parse_json_from_arg(definition); let overridden = existing_policy.with_overrides(&override_pol_name, new_priority, &parsed_definition); @@ -1142,11 +1108,7 @@ pub fn declare_blanket_policy( .unwrap(); let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = serde_json::from_str::(definition) - .unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", definition, err); - process::exit(1); - }); + let parsed_definition = parse_json_from_arg(definition); let params = requests::PolicyParams { vhost, @@ -1174,10 +1136,7 @@ pub fn update_policy_definition( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON value: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg::(&value); update_policy_definition_with(&client, vhost, &name, &key, &parsed_value) } @@ -1196,10 +1155,7 @@ pub fn update_operator_policy_definition( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON value: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg::(&value); update_operator_policy_definition_with(&client, vhost, &name, &key, &parsed_value) } @@ -1214,10 +1170,7 @@ pub fn patch_policy_definition( .get_one::("definition") .cloned() .unwrap(); - let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON value: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg::(&value); let mut pol = client.get_policy(vhost, &name)?; let patch = parsed_value.as_object().unwrap(); @@ -1243,10 +1196,7 @@ pub fn update_all_policy_definitions_in( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON value: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg::(&value); for pol in pols { update_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? @@ -1265,10 +1215,7 @@ pub fn patch_operator_policy_definition( .get_one::("definition") .cloned() .unwrap(); - let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON value: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg::(&value); let mut pol = client.get_operator_policy(vhost, &name)?; let patch = parsed_value.as_object().unwrap(); @@ -1294,10 +1241,7 @@ pub fn update_all_operator_policy_definitions_in( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = serde_json::from_str::(&value).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON value: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg::(&value); for pol in pols { update_operator_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? @@ -1428,11 +1372,7 @@ pub fn declare_parameter( let component = command_args.get_one::("component").unwrap(); let name = command_args.get_one::("name").unwrap(); let value = command_args.get_one::("value").unwrap(); - let parsed_value = serde_json::from_str::(value) - .unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg(value); let params = requests::RuntimeParameterDefinition { vhost, @@ -1449,11 +1389,7 @@ pub fn declare_global_parameter(client: APIClient, command_args: &ArgMatches) -> let value = command_args.get_one::("value").unwrap(); // TODO: global runtime parameter values can be regular strings (not JSON documents) // but we don't support that yet in the HTTP API client. - let parsed_value = serde_json::from_str::(value) - .unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", value, err); - process::exit(1); - }); + let parsed_value = parse_json_from_arg(value); let params = requests::GlobalRuntimeParameterDefinition { name, @@ -1491,11 +1427,7 @@ pub fn delete_binding( let destination = command_args.get_one::("destination").unwrap(); let routing_key = command_args.get_one::("routing_key").unwrap(); let arguments = command_args.get_one::("arguments").unwrap(); - let parsed_arguments = - serde_json::from_str::(arguments).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", arguments, err); - process::exit(1); - }); + let parsed_arguments = parse_json_from_arg(arguments); client .delete_binding( @@ -1609,10 +1541,7 @@ pub fn export_cluster_wide_definitions( if transformations.len() == 0 { export_cluster_wide_definitions_without_transformations(client, command_args) } else { - let transformations = transformations - .into_iter() - .map(String::from) - .collect::>(); + let transformations = transformations.map(String::from).collect(); export_and_transform_cluster_wide_definitions(client, command_args, transformations) } @@ -1706,44 +1635,8 @@ pub fn export_vhost_definitions( } pub fn import_definitions(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { - let path = command_args - .get_one::("file") - .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')); - let use_stdin = command_args.get_one::("stdin").copied(); - let definitions = read_definitions(path, use_stdin); - match definitions { - Ok(defs) => { - let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { - match path { - None => { - eprintln!("could not parse the value read from the standard input: {}", err); - } - Some(val) => { - eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err); - } - } - process::exit(1) - }); - client.import_definitions(defs_json) - } - Err(err) => { - match path { - None => { - eprintln!( - "could not parse the value read from the standard input: {}", - err - ); - } - Some(val) => { - eprintln!( - "`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", - val, err - ); - } - } - process::exit(1) - } - } + let defs_json = read_and_parse_definitions(command_args); + client.import_definitions(defs_json) } pub fn import_vhost_definitions( @@ -1751,26 +1644,28 @@ pub fn import_vhost_definitions( vhost: &str, command_args: &ArgMatches, ) -> ClientResult<()> { + let defs_json = read_and_parse_definitions(command_args); + client.import_vhost_definitions(vhost, defs_json) +} + +fn read_and_parse_definitions(command_args: &ArgMatches) -> Value { let path = command_args .get_one::("file") .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')); let use_stdin = command_args.get_one::("stdin").copied(); let definitions = read_definitions(path, use_stdin); match definitions { - Ok(defs) => { - let defs_json = serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { - match path { - None => { - eprintln!("could not parse the value read from the standard input: {}", err); - } - Some(val) => { - eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err); - } + Ok(defs) => serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { + match path { + None => { + eprintln!("could not parse the value read from the standard input: {}", err); } - process::exit(1) - }); - client.import_vhost_definitions(vhost, defs_json) - } + Some(val) => { + eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err); + } + } + process::exit(1) + }), Err(err) => { match path { None => { @@ -1850,11 +1745,7 @@ pub fn publish_message( let routing_key = command_args.get_one::("routing_key").unwrap(); let payload = command_args.get_one::("payload").unwrap(); let properties = command_args.get_one::("properties").unwrap(); - let parsed_properties = serde_json::from_str::(properties) - .unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", properties, err); - process::exit(1); - }); + let parsed_properties = parse_json_from_arg(properties); client.publish_message(vhost, exchange, routing_key, payload, parsed_properties) } @@ -1869,3 +1760,10 @@ pub fn get_messages( let ack_mode = command_args.get_one::("ack_mode").unwrap(); client.get_messages(vhost, queue, count.parse::().unwrap(), ack_mode) } + +fn parse_json_from_arg(arg_value: &str) -> T { + serde_json::from_str(arg_value).unwrap_or_else(|err| { + eprintln!("`{}` is not a valid JSON: {}", arg_value, err); + process::exit(1); + }) +} diff --git a/src/errors.rs b/src/errors.rs index 1e8c10b..3ed301c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -107,34 +107,27 @@ impl From for CommandRunError { impl From for CommandRunError { fn from(value: HttpClientError) -> Self { + use ApiClientError::*; match value { - ApiClientError::UnsupportedArgumentValue { property } => { - Self::UnsupportedArgumentValue { property } - } - ApiClientError::ClientErrorResponse { status_code, url, body, headers, .. } => { - Self::ClientError { status_code, url, body, headers } - }, - ApiClientError::ServerErrorResponse { status_code, url, body, headers, .. } => { - Self::ServerError { status_code, url, body, headers } - }, - ApiClientError::HealthCheckFailed { path, details, status_code } => { - Self::HealthCheckFailed { health_check_path: path, details, status_code } - }, - ApiClientError::NotFound => Self::NotFound, - ApiClientError::MultipleMatchingBindings => Self::ConflictingOptions { - message: "multiple bindings match, cannot determine which binding to delete without explicitly provided binding properties".to_owned() - }, - ApiClientError::InvalidHeaderValue { error } => { - Self::InvalidHeaderValue { error } - }, - ApiClientError::RequestError { error, .. } => Self::RequestError { error }, - ApiClientError::Other => Self::Other, - ApiClientError::MissingProperty { argument } => { - Self::MissingArgumentValue { property: argument } - }, - ApiClientError::IncompatibleBody { error, .. } => { - Self::IncompatibleBody { error } + UnsupportedArgumentValue { property } => Self::UnsupportedArgumentValue { property }, + ClientErrorResponse { status_code, url, body, headers, .. } => { + Self::ClientError { status_code, url, body, headers } + } + ServerErrorResponse { status_code, url, body, headers, .. } => { + Self::ServerError { status_code, url, body, headers } + } + HealthCheckFailed { path, details, status_code } => { + Self::HealthCheckFailed { health_check_path: path, details, status_code } + } + NotFound => Self::NotFound, + MultipleMatchingBindings => Self::ConflictingOptions { + message: "multiple bindings match, cannot determine which binding to delete without explicitly provided binding properties".to_owned() }, + InvalidHeaderValue { error } => Self::InvalidHeaderValue { error }, + RequestError { error, .. } => Self::RequestError { error }, + Other => Self::Other, + MissingProperty { argument } => Self::MissingArgumentValue { property: argument }, + IncompatibleBody { error, .. } => Self::IncompatibleBody { error }, } } } diff --git a/src/main.rs b/src/main.rs index 6024436..bf263a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,12 +53,13 @@ type APIClient = GenericAPIClient; type CertificateChain = Vec>; fn main() { - let pre_flight_settings = match pre_flight::is_non_interactive() { - true => PreFlightSettings::non_interactive(), - false => PreFlightSettings { + let pre_flight_settings = if pre_flight::is_non_interactive() { + PreFlightSettings::non_interactive() + } else { + PreFlightSettings { infer_subcommands: pre_flight::should_infer_subcommands(), infer_long_options: pre_flight::should_infer_long_options(), - }, + } }; let parser = cli::parser(pre_flight_settings); @@ -91,7 +92,7 @@ fn resolve_run_configuration(cli: &ArgMatches) -> (SharedSettings, String) { let node_alias = cli .get_one::("node_alias") .cloned() - .or(Some(DEFAULT_NODE_ALIAS.to_string())); + .or_else(|| Some(DEFAULT_NODE_ALIAS.to_string())); // If the default config file path is used and the function above // reports that it is not found, continue. Otherwise, exit. @@ -106,11 +107,9 @@ fn resolve_run_configuration(cli: &ArgMatches) -> (SharedSettings, String) { process::exit(ExitCode::DataErr.into()) } - let common_settings = if let Ok(val) = cf_ss { - SharedSettings::from_args_with_defaults(cli, &val) - } else { - SharedSettings::from_args(cli) - }; + let common_settings = cf_ss + .map(|val| SharedSettings::from_args_with_defaults(cli, &val)) + .unwrap_or_else(|_| SharedSettings::from_args(cli)); let endpoint = common_settings.endpoint(); (common_settings, endpoint) @@ -141,32 +140,29 @@ fn dispatch_command( ) -> ExitCode { if let Some((first_level, first_level_args)) = cli.subcommand() { if let Some((second_level, second_level_args)) = first_level_args.subcommand() { - // this is a Tanzu RabbitMQ-specific command, these are grouped under "tanzu" - if first_level == TANZU_COMMAND_PREFIX { + return if first_level == TANZU_COMMAND_PREFIX { + // this is a Tanzu RabbitMQ-specific command, these are grouped under "tanzu" if let Some((third_level, third_level_args)) = second_level_args.subcommand() { let pair = (second_level, third_level); let mut res_handler = ResultHandler::new(merged_settings, second_level_args); - return dispatch_tanzu_subcommand( - pair, - third_level_args, - client, - &mut res_handler, - ); + dispatch_tanzu_subcommand(pair, third_level_args, client, &mut res_handler) + } else { + ExitCode::Usage } } else { // this is a common (OSS and Tanzu) command let pair = (first_level, second_level); let vhost = virtual_host(merged_settings, second_level_args); let mut res_handler = ResultHandler::new(merged_settings, second_level_args); - return dispatch_common_subcommand( + dispatch_common_subcommand( pair, second_level_args, client, merged_settings.endpoint(), vhost, &mut res_handler, - ); - } + ) + }; } } ExitCode::Usage @@ -1154,23 +1150,23 @@ fn virtual_host(shared_settings: &SharedSettings, command_flags: &ArgMatches) -> if command_flags.try_contains_id("vhost").is_ok() { // if the command-specific flag is not set to default, // use it, otherwise use the global/shared --vhost flag value - let fallback = String::from(DEFAULT_VHOST); - let command_vhost: &str = command_flags + let fallback = DEFAULT_VHOST.to_string(); + let command_vhost = command_flags .get_one::("vhost") .unwrap_or(&fallback); if command_vhost != DEFAULT_VHOST { - String::from(command_vhost) + command_vhost.clone() } else { shared_settings .virtual_host .clone() - .unwrap_or(DEFAULT_VHOST.to_string()) + .unwrap_or_else(|| DEFAULT_VHOST.to_string()) } } else { shared_settings .virtual_host .clone() - .unwrap_or(DEFAULT_VHOST.to_string()) + .unwrap_or_else(|| DEFAULT_VHOST.to_string()) } } diff --git a/src/output.rs b/src/output.rs index 0390587..baba2f8 100644 --- a/src/output.rs +++ b/src/output.rs @@ -156,10 +156,10 @@ impl<'a> ResultHandler<'a> { pub fn new(common_args: &'a SharedSettings, command_args: &ArgMatches) -> Self { let non_interactive = common_args.non_interactive; let quiet = common_args.quiet; - let idempotently = match command_args.try_get_one::("idempotently") { - Ok(val) => val.cloned().unwrap_or(false), - Err(_) => false, - }; + let idempotently = command_args + .try_get_one::("idempotently") + .map(|val| val.cloned().unwrap_or(false)) + .unwrap_or(false); let table_styler = TableStyler::new(common_args); @@ -323,26 +323,21 @@ impl<'a> ResultHandler<'a> { Ok(_) => { self.exit_code = Some(ExitCode::Ok); } - Err(error) => match error { - ClientError::ClientErrorResponse { - status_code: http_code, - .. - } if http_code == StatusCode::NOT_FOUND => { - if self.idempotently { - self.exit_code = Some(ExitCode::Ok) - } else { - self.report_command_run_error(&error) - } - } - ClientError::NotFound => { - if self.idempotently { - self.exit_code = Some(ExitCode::Ok) - } else { - self.report_command_run_error(&error) - } + Err(error) => { + let is_not_found = matches!( + error, + ClientError::ClientErrorResponse { + status_code: StatusCode::NOT_FOUND, + .. + } | ClientError::NotFound + ); + + if is_not_found && self.idempotently { + self.exit_code = Some(ExitCode::Ok) + } else { + self.report_command_run_error(&error) } - _ => self.report_command_run_error(&error), - }, + } } } diff --git a/src/tables.rs b/src/tables.rs index 31b57d0..c798851 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -245,32 +245,29 @@ pub fn schema_definition_sync_status(status: SchemaDefinitionSyncStatus) -> Tabl let last_connection_time_s: String; if let Some(stamp) = &status.last_connection_completion_timestamp { - last_connection_time_s = stamp.clone().to_string().clone(); - let row = RowOfTwo { + last_connection_time_s = stamp.to_string(); + data.push(RowOfTwo { key: "last connection time time", value: &last_connection_time_s, - }; - data.push(row) + }) } let last_sync_request_s: String; if let Some(stamp) = &status.last_sync_request_timestamp { - last_sync_request_s = stamp.clone().to_string().clone(); - let row = RowOfTwo { + last_sync_request_s = stamp.to_string(); + data.push(RowOfTwo { key: "last sync request time", value: &last_sync_request_s, - }; - data.push(row) + }) } let sync_duration_s: String; if let Some(stamp) = &status.last_sync_duration { - sync_duration_s = stamp.clone().to_string().clone(); - let row = RowOfTwo { + sync_duration_s = stamp.to_string(); + data.push(RowOfTwo { key: "last sync duration (in ms)", value: &sync_duration_s, - }; - data.push(row) + }) } build_table_with_header(data, "Schema Definition Sync Status") From a68b1527cd7ef03bb86e65570b18fe36ddf6bdd0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 16 Sep 2025 20:05:28 -0400 Subject: [PATCH 234/320] Refactor command fns to use Result in more scenarios instead of terminating the process when, say, a JSON document failed to parse or a definitions file could not be read. --- src/commands.rs | 243 +++++++++++++++++++++++++----------------------- src/errors.rs | 4 + src/main.rs | 128 +++++++++++++++---------- src/output.rs | 11 ++- 4 files changed, 220 insertions(+), 166 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 04f5e18..9a48050 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -14,6 +14,7 @@ #![allow(clippy::result_large_err)] use crate::constants::DEFAULT_BLANKET_POLICY_PRIORITY; +use crate::errors::CommandRunError; use clap::ArgMatches; use rabbitmq_http_client::blocking_api::Client; use rabbitmq_http_client::blocking_api::Result as ClientResult; @@ -722,7 +723,7 @@ pub fn declare_exchange( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { // the flag is required let name = command_args.get_one::("name").unwrap(); // these are optional @@ -745,17 +746,17 @@ pub fn declare_exchange( exchange_type, durable, auto_delete, - arguments: parse_json_from_arg(arguments), + arguments: parse_json_from_arg(arguments)?, }; - client.declare_exchange(vhost, ¶ms) + client.declare_exchange(vhost, ¶ms).map_err(Into::into) } pub fn declare_binding( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let source = command_args.get_one::("source").unwrap(); let destination_type = command_args .get_one::("destination_type") @@ -763,23 +764,27 @@ pub fn declare_binding( let destination = command_args.get_one::("destination").unwrap(); let routing_key = command_args.get_one::("routing_key").unwrap(); let arguments = command_args.get_one::("arguments").unwrap(); - let parsed_arguments = parse_json_from_arg(arguments); + let parsed_arguments = parse_json_from_arg(arguments)?; match destination_type { - BindingDestinationType::Queue => client.bind_queue( - vhost, - destination, - source, - Some(routing_key), - parsed_arguments, - ), - BindingDestinationType::Exchange => client.bind_exchange( - vhost, - destination, - source, - Some(routing_key), - parsed_arguments, - ), + BindingDestinationType::Queue => client + .bind_queue( + vhost, + destination, + source, + Some(routing_key), + parsed_arguments, + ) + .map_err(Into::into), + BindingDestinationType::Exchange => client + .bind_exchange( + vhost, + destination, + source, + Some(routing_key), + parsed_arguments, + ) + .map_err(Into::into), } } @@ -955,7 +960,7 @@ pub fn declare_queue( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { // the flag is required let name = command_args.get_one::("name").unwrap(); let queue_type = command_args.get_one::("type").cloned().unwrap(); @@ -970,19 +975,18 @@ pub fn declare_queue( .cloned() .unwrap_or(false); let arguments = command_args.get_one::("arguments").unwrap(); - - let parsed_args = parse_json_from_arg(arguments); + let parsed_args = parse_json_from_arg(arguments)?; let params = requests::QueueParams::new(name, queue_type, durable, auto_delete, parsed_args); - client.declare_queue(vhost, ¶ms) + client.declare_queue(vhost, ¶ms).map_err(Into::into) } pub fn declare_stream( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").unwrap(); let expiration = command_args.get_one::("expiration").unwrap(); let max_length_bytes = command_args.get_one::("max_length_bytes").cloned(); @@ -990,7 +994,7 @@ pub fn declare_stream( .get_one::("max_segment_length_bytes") .cloned(); let arguments = command_args.get_one::("arguments").unwrap(); - let parsed_args = parse_json_from_arg(arguments); + let parsed_args = parse_json_from_arg(arguments)?; let params = requests::StreamParams { name, @@ -1000,14 +1004,14 @@ pub fn declare_stream( arguments: parsed_args, }; - client.declare_stream(vhost, ¶ms) + client.declare_stream(vhost, ¶ms).map_err(Into::into) } pub fn declare_policy( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").unwrap(); let pattern = command_args.get_one::("pattern").unwrap(); let apply_to = command_args @@ -1017,9 +1021,9 @@ pub fn declare_policy( let priority = command_args.get_one::("priority").unwrap(); let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = parse_json_from_arg(definition); + let parsed_definition = parse_json_from_arg(definition)?; - let params = requests::PolicyParams { + let params = PolicyParams { vhost, name, pattern, @@ -1028,14 +1032,14 @@ pub fn declare_policy( definition: parsed_definition, }; - client.declare_policy(¶ms) + client.declare_policy(¶ms).map_err(Into::into) } pub fn declare_operator_policy( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").cloned().unwrap(); let pattern = command_args.get_one::("pattern").cloned().unwrap(); let apply_to = command_args @@ -1045,9 +1049,9 @@ pub fn declare_operator_policy( let priority = command_args.get_one::("priority").unwrap(); let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = parse_json_from_arg(definition); + let parsed_definition = parse_json_from_arg(definition)?; - let params = requests::PolicyParams { + let params = PolicyParams { vhost, name: &name, pattern: &pattern, @@ -1056,40 +1060,44 @@ pub fn declare_operator_policy( definition: parsed_definition, }; - client.declare_operator_policy(¶ms) + client.declare_operator_policy(¶ms).map_err(Into::into) } pub fn declare_policy_override( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let original_pol_name = command_args.get_one::("name").cloned().unwrap(); let override_pol_name = command_args .get_one::("override_name") .cloned() .unwrap_or(override_policy_name(&original_pol_name)); - let existing_policy = client.get_policy(vhost, &original_pol_name)?; + let existing_policy = client + .get_policy(vhost, &original_pol_name) + .map_err(CommandRunError::from)?; let new_priority = existing_policy.priority + 100; let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = parse_json_from_arg(definition); + let parsed_definition = parse_json_from_arg(definition)?; let overridden = existing_policy.with_overrides(&override_pol_name, new_priority, &parsed_definition); let params = PolicyParams::from(&overridden); - client.declare_policy(¶ms) + client.declare_policy(¶ms).map_err(Into::into) } pub fn declare_blanket_policy( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { // find the lowest policy priority in the target virtual host - let existing_policies = client.list_policies_in(vhost)?; + let existing_policies = client + .list_policies_in(vhost) + .map_err(CommandRunError::from)?; let min_priority = existing_policies .iter() .fold(0, |acc, p| if p.priority < acc { p.priority } else { acc }); @@ -1108,9 +1116,9 @@ pub fn declare_blanket_policy( .unwrap(); let definition = command_args.get_one::("definition").unwrap(); - let parsed_definition = parse_json_from_arg(definition); + let parsed_definition = parse_json_from_arg(definition)?; - let params = requests::PolicyParams { + let params = PolicyParams { vhost, name: &name, pattern: ".*", @@ -1119,14 +1127,14 @@ pub fn declare_blanket_policy( definition: parsed_definition, }; - client.declare_policy(¶ms) + client.declare_policy(¶ms).map_err(Into::into) } pub fn update_policy_definition( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").cloned().unwrap(); let key = command_args .get_one::("definition_key") @@ -1136,16 +1144,16 @@ pub fn update_policy_definition( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value); + let parsed_value = parse_json_from_arg::(&value)?; - update_policy_definition_with(&client, vhost, &name, &key, &parsed_value) + update_policy_definition_with(&client, vhost, &name, &key, &parsed_value).map_err(Into::into) } pub fn update_operator_policy_definition( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").cloned().unwrap(); let key = command_args .get_one::("definition_key") @@ -1155,39 +1163,44 @@ pub fn update_operator_policy_definition( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value); + let parsed_value = parse_json_from_arg::(&value)?; update_operator_policy_definition_with(&client, vhost, &name, &key, &parsed_value) + .map_err(Into::into) } pub fn patch_policy_definition( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").cloned().unwrap(); let value = command_args .get_one::("definition") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value); + let parsed_value = parse_json_from_arg::(&value)?; - let mut pol = client.get_policy(vhost, &name)?; + let mut pol = client + .get_policy(vhost, &name) + .map_err(CommandRunError::from)?; let patch = parsed_value.as_object().unwrap(); for (k, v) in patch.iter() { pol.insert_definition_key(k.clone(), v.clone()); } let params = PolicyParams::from(&pol); - client.declare_policy(¶ms) + client.declare_policy(¶ms).map_err(Into::into) } pub fn update_all_policy_definitions_in( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { - let pols = client.list_policies_in(vhost)?; +) -> Result<(), CommandRunError> { + let pols = client + .list_policies_in(vhost) + .map_err(CommandRunError::from)?; let key = command_args .get_one::("definition_key") .cloned() @@ -1196,7 +1209,7 @@ pub fn update_all_policy_definitions_in( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value); + let parsed_value = parse_json_from_arg::(&value)?; for pol in pols { update_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? @@ -1209,30 +1222,34 @@ pub fn patch_operator_policy_definition( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").cloned().unwrap(); let value = command_args .get_one::("definition") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value); + let parsed_value = parse_json_from_arg::(&value)?; - let mut pol = client.get_operator_policy(vhost, &name)?; + let mut pol = client + .get_operator_policy(vhost, &name) + .map_err(CommandRunError::from)?; let patch = parsed_value.as_object().unwrap(); for (k, v) in patch.iter() { pol.insert_definition_key(k.clone(), v.clone()); } let params = PolicyParams::from(&pol); - client.declare_operator_policy(¶ms) + client.declare_operator_policy(¶ms).map_err(Into::into) } pub fn update_all_operator_policy_definitions_in( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { - let pols = client.list_operator_policies_in(vhost)?; +) -> Result<(), CommandRunError> { + let pols = client + .list_operator_policies_in(vhost) + .map_err(CommandRunError::from)?; let key = command_args .get_one::("definition_key") .cloned() @@ -1241,7 +1258,7 @@ pub fn update_all_operator_policy_definitions_in( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value); + let parsed_value = parse_json_from_arg::(&value)?; for pol in pols { update_operator_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? @@ -1368,11 +1385,11 @@ pub fn declare_parameter( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let component = command_args.get_one::("component").unwrap(); let name = command_args.get_one::("name").unwrap(); let value = command_args.get_one::("value").unwrap(); - let parsed_value = parse_json_from_arg(value); + let parsed_value = parse_json_from_arg(value)?; let params = requests::RuntimeParameterDefinition { vhost, @@ -1381,22 +1398,27 @@ pub fn declare_parameter( value: parsed_value, }; - client.upsert_runtime_parameter(¶ms) + client.upsert_runtime_parameter(¶ms).map_err(Into::into) } -pub fn declare_global_parameter(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { +pub fn declare_global_parameter( + client: APIClient, + command_args: &ArgMatches, +) -> Result<(), CommandRunError> { let name = command_args.get_one::("name").unwrap(); let value = command_args.get_one::("value").unwrap(); // TODO: global runtime parameter values can be regular strings (not JSON documents) // but we don't support that yet in the HTTP API client. - let parsed_value = parse_json_from_arg(value); + let parsed_value = parse_json_from_arg(value)?; let params = requests::GlobalRuntimeParameterDefinition { name, value: parsed_value, }; - client.upsert_global_runtime_parameter(¶ms) + client + .upsert_global_runtime_parameter(¶ms) + .map_err(Into::into) } pub fn delete_queue(client: APIClient, vhost: &str, command_args: &ArgMatches) -> ClientResult<()> { @@ -1421,13 +1443,13 @@ pub fn delete_binding( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { let source = command_args.get_one::("source").unwrap(); let destination_type = command_args.get_one::("destination_type").unwrap(); let destination = command_args.get_one::("destination").unwrap(); let routing_key = command_args.get_one::("routing_key").unwrap(); let arguments = command_args.get_one::("arguments").unwrap(); - let parsed_arguments = parse_json_from_arg(arguments); + let parsed_arguments = parse_json_from_arg(arguments)?; client .delete_binding( @@ -1439,6 +1461,7 @@ pub fn delete_binding( parsed_arguments, ) .map(|_| ()) + .map_err(Into::into) } pub fn delete_exchange( @@ -1634,56 +1657,45 @@ pub fn export_vhost_definitions( } } -pub fn import_definitions(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { - let defs_json = read_and_parse_definitions(command_args); - client.import_definitions(defs_json) +pub fn import_definitions( + client: APIClient, + command_args: &ArgMatches, +) -> Result<(), CommandRunError> { + let defs_json = read_and_parse_definitions(command_args)?; + client.import_definitions(defs_json).map_err(Into::into) } pub fn import_vhost_definitions( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { - let defs_json = read_and_parse_definitions(command_args); - client.import_vhost_definitions(vhost, defs_json) +) -> Result<(), CommandRunError> { + let defs_json = read_and_parse_definitions(command_args)?; + client + .import_vhost_definitions(vhost, defs_json) + .map_err(Into::into) } -fn read_and_parse_definitions(command_args: &ArgMatches) -> Value { +fn read_and_parse_definitions(command_args: &ArgMatches) -> Result { let path = command_args .get_one::("file") .map(|s| s.trim_ascii().trim_matches('\'').trim_matches('"')); let use_stdin = command_args.get_one::("stdin").copied(); - let definitions = read_definitions(path, use_stdin); - match definitions { - Ok(defs) => serde_json::from_str(defs.as_str()).unwrap_or_else(|err| { - match path { - None => { - eprintln!("could not parse the value read from the standard input: {}", err); - } - Some(val) => { - eprintln!("`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", val, err); - } - } - process::exit(1) - }), - Err(err) => { - match path { - None => { - eprintln!( - "could not parse the value read from the standard input: {}", - err - ); - } - Some(val) => { - eprintln!( - "`{}` does not exist, is not a readable file, or is not a valid JSON file: {}", - val, err - ); - } - } - process::exit(1) - } - } + let definitions = read_definitions(path, use_stdin).map_err(|err| { + let message = match path { + None => format!("could not read from standard input: {}", err), + Some(val) => format!("`{}` does not exist or is not readable: {}", val, err), + }; + CommandRunError::DefinitionsFileLoadingError { message } + })?; + + serde_json::from_str(definitions.as_str()).map_err(|err| { + let message = match path { + None => format!("could not parse JSON from standard input: {}", err), + Some(val) => format!("`{}` is not a valid JSON file: {}", val, err), + }; + CommandRunError::DefinitionsFileLoadingError { message } + }) } const POLICY_LENGTH_LIMIT: usize = 255; @@ -1740,14 +1752,16 @@ pub fn publish_message( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult { +) -> Result { let exchange = command_args.get_one::("exchange").unwrap(); let routing_key = command_args.get_one::("routing_key").unwrap(); let payload = command_args.get_one::("payload").unwrap(); let properties = command_args.get_one::("properties").unwrap(); - let parsed_properties = parse_json_from_arg(properties); + let parsed_properties = parse_json_from_arg(properties)?; - client.publish_message(vhost, exchange, routing_key, payload, parsed_properties) + client + .publish_message(vhost, exchange, routing_key, payload, parsed_properties) + .map_err(Into::into) } pub fn get_messages( @@ -1761,9 +1775,8 @@ pub fn get_messages( client.get_messages(vhost, queue, count.parse::().unwrap(), ack_mode) } -fn parse_json_from_arg(arg_value: &str) -> T { - serde_json::from_str(arg_value).unwrap_or_else(|err| { - eprintln!("`{}` is not a valid JSON: {}", arg_value, err); - process::exit(1); +fn parse_json_from_arg(input: &str) -> Result { + serde_json::from_str(input).map_err(|err| CommandRunError::JsonParseError { + message: format!("`{}` is not a valid JSON: {}", input, err), }) } diff --git a/src/errors.rs b/src/errors.rs index 3ed301c..ce4c793 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -95,6 +95,10 @@ pub enum CommandRunError { IncompatibleBody { error: ConversionError }, #[error("encountered an error when performing an HTTP request")] RequestError { error: reqwest::Error }, + #[error("Failed to parse JSON argument: {message}")] + JsonParseError { message: String }, + #[error("Failed to read definitions file: {message}")] + DefinitionsFileLoadingError { message: String }, #[error("an unspecified error")] Other, } diff --git a/src/main.rs b/src/main.rs index bf263a8..25e843f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -435,19 +435,21 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("close", "connection") => { - let result = commands::close_connection(client, second_level_args); + let result = commands::close_connection(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("close", "user_connections") => { - let result = commands::close_user_connections(client, second_level_args); + let result = + commands::close_user_connections(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("connections", "close") => { - let result = commands::close_connection(client, second_level_args); + let result = commands::close_connection(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("connections", "close_of_user") => { - let result = commands::close_user_connections(client, second_level_args); + let result = + commands::close_user_connections(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("connections", "list") => { @@ -475,7 +477,8 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("declare", "permissions") => { - let result = commands::declare_permissions(client, &vhost, second_level_args); + let result = commands::declare_permissions(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("declare", "policy") => { @@ -491,27 +494,31 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("declare", "user") => { - let result = commands::declare_user(client, second_level_args); + let result = commands::declare_user(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("declare", "user_limit") => { - let result = commands::declare_user_limit(client, second_level_args); + let result = + commands::declare_user_limit(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("declare", "vhost") => { - let result = commands::declare_vhost(client, second_level_args); + let result = commands::declare_vhost(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("declare", "vhost_limit") => { - let result = commands::declare_vhost_limit(client, &vhost, second_level_args); + let result = commands::declare_vhost_limit(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("definitions", "export") => { - let result = commands::export_cluster_wide_definitions(client, second_level_args); + let result = commands::export_cluster_wide_definitions(client, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("definitions", "export_from_vhost") => { - let result = commands::export_vhost_definitions(client, &vhost, second_level_args); + let result = commands::export_vhost_definitions(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("definitions", "import") => { @@ -531,19 +538,23 @@ fn dispatch_common_subcommand( res_handler.delete_operation_result(result); } ("delete", "operator_policy") => { - let result = commands::delete_operator_policy(client, &vhost, second_level_args); + let result = commands::delete_operator_policy(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("delete", "parameter") => { - let result = commands::delete_parameter(client, &vhost, second_level_args); + let result = + commands::delete_parameter(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("delete", "permissions") => { - let result = commands::delete_permissions(client, &vhost, second_level_args); + let result = + commands::delete_permissions(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("delete", "policy") => { - let result = commands::delete_policy(client, &vhost, second_level_args); + let result = + commands::delete_policy(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("delete", "queue") => { @@ -551,7 +562,8 @@ fn dispatch_common_subcommand( res_handler.delete_operation_result(result); } ("delete", "shovel") => { - let result = commands::delete_shovel(client, &vhost, second_level_args); + let result = + commands::delete_shovel(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("delete", "stream") => { @@ -563,7 +575,7 @@ fn dispatch_common_subcommand( res_handler.delete_operation_result(result); } ("delete", "user_limit") => { - let result = commands::delete_user_limit(client, second_level_args); + let result = commands::delete_user_limit(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("delete", "vhost") => { @@ -571,7 +583,8 @@ fn dispatch_common_subcommand( res_handler.delete_operation_result(result); } ("delete", "vhost_limit") => { - let result = commands::delete_vhost_limit(client, &vhost, second_level_args); + let result = + commands::delete_vhost_limit(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("deprecated_features", "list") => { @@ -583,7 +596,8 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result.map(|val| val.0)) } ("export", "definitions") => { - let result = commands::export_cluster_wide_definitions(client, second_level_args); + let result = commands::export_cluster_wide_definitions(client, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("exchanges", "bind") => { @@ -607,11 +621,12 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("feature_flags", "enable") => { - let result = commands::enable_feature_flag(client, second_level_args); + let result = + commands::enable_feature_flag(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("feature_flags", "enable_all") => { - let result = commands::enable_all_stable_feature_flags(client); + let result = commands::enable_all_stable_feature_flags(client).map_err(Into::into); res_handler.no_output_on_success(result); } ("feature_flags", "list") => { @@ -619,7 +634,8 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result.map(|val| val.0)) } ("federation", "declare_upstream") => { - let result = commands::declare_federation_upstream(client, &vhost, second_level_args); + let result = commands::declare_federation_upstream(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("federation", "declare_upstream_for_exchanges") => { @@ -627,7 +643,8 @@ fn dispatch_common_subcommand( client, &vhost, second_level_args, - ); + ) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("federation", "declare_upstream_for_queues") => { @@ -635,11 +652,13 @@ fn dispatch_common_subcommand( client, &vhost, second_level_args, - ); + ) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("federation", "delete_upstream") => { - let result = commands::delete_federation_upstream(client, &vhost, second_level_args); + let result = commands::delete_federation_upstream(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("federation", "list_all_links") => { @@ -655,7 +674,8 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("global_parameters", "clear") => { - let result = commands::delete_global_parameter(client, second_level_args); + let result = + commands::delete_global_parameter(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("global_parameters", "list") => { @@ -783,12 +803,14 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("operator_policies", "delete") => { - let result = commands::delete_operator_policy(client, &vhost, second_level_args); + let result = commands::delete_operator_policy(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("operator_policies", "delete_definition_keys") => { let result = - commands::delete_operator_policy_definition_keys(client, &vhost, second_level_args); + commands::delete_operator_policy_definition_keys(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("operator_policies", "delete_definition_keys_from_all_in") => { @@ -796,7 +818,8 @@ fn dispatch_common_subcommand( client, &vhost, second_level_args, - ); + ) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("operator_policies", "list") => { @@ -846,7 +869,8 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("parameters", "clear") => { - let result = commands::delete_parameter(client, &vhost, second_level_args); + let result = + commands::delete_parameter(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("parameters", "list_all") => { @@ -883,16 +907,19 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("policies", "delete") => { - let result = commands::delete_policy(client, &vhost, second_level_args); + let result = + commands::delete_policy(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("policies", "delete_definition_keys") => { - let result = commands::delete_policy_definition_keys(client, &vhost, second_level_args); + let result = commands::delete_policy_definition_keys(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("policies", "delete_definition_keys_from_all_in") => { let result = - commands::delete_policy_definition_keys_in(client, &vhost, second_level_args); + commands::delete_policy_definition_keys_in(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("policies", "list") => { @@ -936,10 +963,11 @@ fn dispatch_common_subcommand( } ("publish", "message") => { let result = commands::publish_message(client, &vhost, second_level_args); - res_handler.single_value_result(result) + res_handler.single_value_output_with_result(result) } ("purge", "queue") => { - let result = commands::purge_queue(client, &vhost, second_level_args); + let result = + commands::purge_queue(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("queues", "declare") => { @@ -955,15 +983,16 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("queues", "purge") => { - let result = commands::purge_queue(client, &vhost, second_level_args); + let result = + commands::purge_queue(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("queues", "rebalance") => { - let result = commands::rebalance_queues(client); + let result = commands::rebalance_queues(client).map_err(Into::into); res_handler.no_output_on_success(result); } ("rebalance", "queues") => { - let result = commands::rebalance_queues(client); + let result = commands::rebalance_queues(client).map_err(Into::into); res_handler.no_output_on_success(result); } ("show", "churn") => { @@ -1015,16 +1044,19 @@ fn dispatch_common_subcommand( res_handler.report_pre_command_run_error(&err) } else { - let result = commands::declare_amqp091_shovel(client, &vhost, second_level_args); + let result = commands::declare_amqp091_shovel(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } } ("shovels", "declare_amqp10") => { - let result = commands::declare_amqp10_shovel(client, &vhost, second_level_args); + let result = commands::declare_amqp10_shovel(client, &vhost, second_level_args) + .map_err(Into::into); res_handler.no_output_on_success(result); } ("shovels", "delete") => { - let result = commands::delete_shovel(client, &vhost, second_level_args); + let result = + commands::delete_shovel(client, &vhost, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("shovels", "list_all") => { @@ -1052,7 +1084,7 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("users", "declare") => { - let result = commands::declare_user(client, second_level_args); + let result = commands::declare_user(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("users", "delete") => { @@ -1072,7 +1104,7 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("vhosts", "declare") => { - let result = commands::declare_vhost(client, second_level_args); + let result = commands::declare_vhost(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); } ("vhosts", "delete") => { @@ -1107,19 +1139,21 @@ fn dispatch_tanzu_subcommand( res_handler.schema_definition_sync_status_result(result) } ("sds", "enable_cluster_wide") => { - let result = tanzu_commands::sds_enable_cluster_wide(client); + let result = tanzu_commands::sds_enable_cluster_wide(client).map_err(Into::into); res_handler.no_output_on_success(result) } ("sds", "disable_cluster_wide") => { - let result = tanzu_commands::sds_disable_cluster_wide(client); + let result = tanzu_commands::sds_disable_cluster_wide(client).map_err(Into::into); res_handler.no_output_on_success(result) } ("sds", "enable_on_node") => { - let result = tanzu_commands::sds_enable_on_node(client, third_level_args); + let result = + tanzu_commands::sds_enable_on_node(client, third_level_args).map_err(Into::into); res_handler.no_output_on_success(result) } ("sds", "disable_on_node") => { - let result = tanzu_commands::sds_disable_on_node(client, third_level_args); + let result = + tanzu_commands::sds_disable_on_node(client, third_level_args).map_err(Into::into); res_handler.no_output_on_success(result) } ("wsr", "status") => { diff --git a/src/output.rs b/src/output.rs index baba2f8..7a3ea13 100644 --- a/src/output.rs +++ b/src/output.rs @@ -232,13 +232,16 @@ impl<'a> ResultHandler<'a> { } } - pub fn single_value_result(&mut self, result: ClientResult) { + pub fn single_value_output_with_result( + &mut self, + result: Result, + ) { match result { Ok(output) => { self.exit_code = Some(ExitCode::Ok); println!("{}", output) } - Err(error) => self.report_command_run_error(&error), + Err(error) => self.report_pre_command_run_error(&error), } } @@ -309,12 +312,12 @@ impl<'a> ResultHandler<'a> { } } - pub fn no_output_on_success(&mut self, result: ClientResult) { + pub fn no_output_on_success(&mut self, result: Result) { match result { Ok(_) => { self.exit_code = Some(ExitCode::Ok); } - Err(error) => self.report_command_run_error(&error), + Err(error) => self.report_pre_command_run_error(&error), } } From 0827b5033fc31d43bb7157bed3b7acf4b7c6eebf Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 17 Sep 2025 23:44:22 -0400 Subject: [PATCH 235/320] Bump rabbitmq_http_client to 0.52.0 --- Cargo.lock | 40 ++++++++++++++++++++-------------------- Cargo.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 286aacb..8a1a97c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -971,9 +971,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.79" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6247da8b8658ad4e73a186e747fcc5fc2a29f979d6fe6269127fdb5fd08298d0" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" dependencies = [ "once_cell", "wasm-bindgen", @@ -1426,9 +1426,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.51.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4785ccfa2812e0861bd9eaa6bdd8b59891229bf7bb1339b56a30f145d9dcef6" +checksum = "63d41327bcdf04bef7015cc5c3181cce5d39d1194ff4b62f33b6bf4541ed6bc0" dependencies = [ "backtrace", "percent-encoding", @@ -2115,9 +2115,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" dependencies = [ "rustls", "tokio", @@ -2343,9 +2343,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad224d2776649cfb4f4471124f8176e54c1cca67a88108e30a0cd98b90e7ad3" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" dependencies = [ "cfg-if", "once_cell", @@ -2356,9 +2356,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1364104bdcd3c03f22b16a3b1c9620891469f5e9f09bc38b2db121e593e732" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" dependencies = [ "bumpalo", "log", @@ -2370,9 +2370,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.52" +version = "0.4.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0a08ecf5d99d5604a6666a70b3cde6ab7cc6142f5e641a8ef48fc744ce8854" +checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" dependencies = [ "cfg-if", "js-sys", @@ -2383,9 +2383,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d7ab4ca3e367bb1ed84ddbd83cc6e41e115f8337ed047239578210214e36c76" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2393,9 +2393,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a518014843a19e2dbbd0ed5dfb6b99b23fb886b14e6192a00803a3e14c552b0" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", @@ -2406,18 +2406,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.102" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "255eb0aa4cc2eea3662a00c2bbd66e93911b7361d5e0fcd62385acfd7e15dcee" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.79" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50462a022f46851b81d5441d1a6f5bac0b21a1d72d64bd4906fbdd4bf7230ec7" +checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 26b5bb4..aa582dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.51.0", features = [ +rabbitmq_http_client = { version = "0.52.0", features = [ "blocking", "tabled", ] } From 8998f1c9180229dbb0bebecbc3301927c84f48e1 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 17 Sep 2025 23:45:17 -0400 Subject: [PATCH 236/320] 'definitions export_from_vhost' now supports --transformations Closes #82. --- src/cli.rs | 35 ++++++++++++++++++++ src/commands.rs | 55 ++++++++++++++++++++++++++++--- src/main.rs | 3 +- tests/definitions_export_tests.rs | 51 ++++++++++++++++++++++++++-- 4 files changed, 136 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 7dacfcc..8bd96ef 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2253,6 +2253,41 @@ Examples: .num_args(0) .action(ArgAction::SetTrue) .conflicts_with("file"), + ) + .arg( + Arg::new("transformations") + .long("transformations") + .short('t') + .long_help( + r#" +A comma-separated list of names of the definition transformations to apply. + +Supported transformations: + + * no_op + * prepare_for_quorum_queue_migration + * strip_cmq_keys_from_policies + * drop_empty_policies + * obfuscate_usernames + * exclude_users + * exclude_permissions + * exclude_runtime_parameters + * exclude_policies + +Examples: + + * --transformations prepare_for_quorum_queue_migration,drop_empty_policies + * --transformations strip_cmq_keys_from_policies,drop_empty_policies + * --transformations exclude_users,exclude_permissions + * --transformations obfuscate_usernames + * --transformations exclude_runtime_parameters,exclude_policies + * --transformations no_op + "#, + ) + .num_args(1..) + .value_delimiter(',') + .action(ArgAction::Append) + .required(false), ); let import_cmd = Command::new("import") diff --git a/src/commands.rs b/src/commands.rs index 9a48050..0fa87b8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -33,7 +33,7 @@ use rabbitmq_http_client::requests::{ RuntimeParameterDefinition, }; use rabbitmq_http_client::responses::OptionalArgumentSourceOps; -use rabbitmq_http_client::transformers::TransformationChain; +use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; use rabbitmq_http_client::{password_hashing, requests, responses}; use serde::de::DeserializeOwned; use serde_json::Value; @@ -1638,7 +1638,54 @@ pub fn export_vhost_definitions( client: APIClient, vhost: &str, command_args: &ArgMatches, -) -> ClientResult<()> { +) -> Result<(), CommandRunError> { + let transformations = command_args + .get_many::("transformations") + .unwrap_or_default(); + + if transformations.len() == 0 { + export_vhost_definitions_without_transformations(client, vhost, command_args) + } else { + let transformations = transformations.map(String::from).collect(); + + export_and_transform_vhost_definitions(client, vhost, command_args, transformations) + } +} + +fn export_and_transform_vhost_definitions( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, + transformations: Vec, +) -> Result<(), CommandRunError> { + match client.export_vhost_definitions_as_data(vhost) { + Ok(mut defs0) => { + let chain = VirtualHostTransformationChain::from(transformations); + chain.apply(&mut defs0); + + let json = serde_json::to_string_pretty(&defs0).unwrap(); + + let path = command_args.get_one::("file").unwrap(); + match path.as_str() { + "-" => { + println!("{}", &json); + Ok(()) + } + file => { + fs::write(file, &json)?; + Ok(()) + } + } + } + Err(err) => Err(err.into()), + } +} + +fn export_vhost_definitions_without_transformations( + client: APIClient, + vhost: &str, + command_args: &ArgMatches, +) -> Result<(), CommandRunError> { match client.export_vhost_definitions(vhost) { Ok(definitions) => { let path = command_args.get_one::("file").unwrap(); @@ -1648,12 +1695,12 @@ pub fn export_vhost_definitions( Ok(()) } file => { - _ = fs::write(file, &definitions); + fs::write(file, &definitions)?; Ok(()) } } } - Err(err) => Err(err), + Err(err) => Err(err.into()), } } diff --git a/src/main.rs b/src/main.rs index 25e843f..4fe2d96 100644 --- a/src/main.rs +++ b/src/main.rs @@ -517,8 +517,7 @@ fn dispatch_common_subcommand( res_handler.no_output_on_success(result); } ("definitions", "export_from_vhost") => { - let result = commands::export_vhost_definitions(client, &vhost, second_level_args) - .map_err(Into::into); + let result = commands::export_vhost_definitions(client, &vhost, second_level_args); res_handler.no_output_on_success(result); } ("definitions", "import") => { diff --git a/tests/definitions_export_tests.rs b/tests/definitions_export_tests.rs index c8ccd30..dbc9d0d 100644 --- a/tests/definitions_export_tests.rs +++ b/tests/definitions_export_tests.rs @@ -78,7 +78,7 @@ fn test_export_cluster_wide_definitions_with_transformations_case1() run_succeeds(["--vhost", vh, "definitions", "export"]).stdout(predicate::str::contains(p1)); // These two cannot be tested on 4.x: empty definitions will be rejected - // by validation and CMQ keys are no longer recognized as known/valid. + // by validation, and CMQ keys are no longer recognized as known/valid. // But at least we can test the code path this way. run_succeeds([ "--vhost", @@ -86,7 +86,54 @@ fn test_export_cluster_wide_definitions_with_transformations_case1() "definitions", "export", "--transformations", - "strip_cmq_keys_from_policies,drop_empty_policies", + "prepare_for_quorum_queue_migration,strip_cmq_keys_from_policies,drop_empty_policies", + ]) + .stdout(predicate::str::contains(p1)); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_export_vhost_definitions_with_transformations_case1() +-> Result<(), Box> { + let vh = "test_export_vhost_definitions.transformations.1"; + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); + + let p1 = "test_export_vhost_definitions.transformations.1"; + run_succeeds([ + "--vhost", + vh, + "declare", + "policy", + "--name", + p1, + "--pattern", + "^matching\\..+", + "--apply-to", + "classic_queues", + "--priority", + "10", + "--definition", + "{\"max-length\": 10}", + ]); + + let q = "qq.test_export_vhost_definitions.transformations.1"; + run_succeeds([ + "-V", vh, "declare", "queue", "--name", q, "--type", "quorum", + ]); + + run_succeeds(["--vhost", vh, "definitions", "export_from_vhost"]) + .stdout(predicate::str::contains(p1)); + run_succeeds([ + "--vhost", + vh, + "definitions", + "export_from_vhost", + "--transformations", + "prepare_for_quorum_queue_migration,strip_cmq_keys_from_policies,drop_empty_policies", ]) .stdout(predicate::str::contains(p1)); From a69706d0fc39128791a24348b8f4f7ddb089b9fb Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 17 Sep 2025 23:51:31 -0400 Subject: [PATCH 237/320] Update --transformations docs --- src/cli.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 8bd96ef..abd0e5b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2203,7 +2203,6 @@ A comma-separated list of names of the definition transformations to apply. Supported transformations: - * no_op * prepare_for_quorum_queue_migration * strip_cmq_keys_from_policies * drop_empty_policies @@ -2212,6 +2211,9 @@ Supported transformations: * exclude_permissions * exclude_runtime_parameters * exclude_policies + * no_op + +All unknown transformations will be ignored (will be replaced with a `no_op`). Examples: @@ -2264,23 +2266,17 @@ A comma-separated list of names of the definition transformations to apply. Supported transformations: - * no_op * prepare_for_quorum_queue_migration * strip_cmq_keys_from_policies * drop_empty_policies - * obfuscate_usernames - * exclude_users - * exclude_permissions - * exclude_runtime_parameters - * exclude_policies + * no_op + +All unknown transformations will be ignored (will be replaced with a `no_op`). Examples: * --transformations prepare_for_quorum_queue_migration,drop_empty_policies * --transformations strip_cmq_keys_from_policies,drop_empty_policies - * --transformations exclude_users,exclude_permissions - * --transformations obfuscate_usernames - * --transformations exclude_runtime_parameters,exclude_policies * --transformations no_op "#, ) From 6f5f05ebfc4ffa1f76a3f6c009e67ce56ecc5833 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 17 Sep 2025 23:58:34 -0400 Subject: [PATCH 238/320] Change log updates for 0.52.0 --- CHANGELOG.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8fce5b..6d9927f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,25 @@ ## v2.10.0 (in development) +### Enhancements + +* `definitions export_from_vhost` now supports `--transformations`: + + ```shell + # previously only 'definitions export' supported --transformations + rabbitmqadmin --vhost "my-vhost" definitions export_from_vhost \ + --transformations prepare_for_quorum_queue_migration,drop_empty_policies \ + --file "my-vhost.definitions.json" + ``` + +### Bug Fixes + + * The `prepare_for_quorum_queue_migration` transformation did not remove CMQ-related keys + such as `x-ha-mode` from [optional queue arguments](https://www.rabbitmq.com/docs/queues#optional-arguments) + ### Upgrades -* RabbitMQ HTTP API client was upgraded to [`0.51.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.51.0) +* RabbitMQ HTTP API client was upgraded to [`0.52.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.52.0) ## v2.9.0 (Aug 25, 2025) From e73201b12e0687ded42583a786fc843cb7b225cf Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 18 Sep 2025 00:22:08 -0400 Subject: [PATCH 239/320] Bump dev version --- CHANGELOG.md | 7 ++++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9927f..f0ae13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.10.0 (in development) +## v2.11.0 (in development) + +No changes yet. + + +## v2.10.0 (Sep 18, 2025) ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index 8a1a97c..f740c3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1448,7 +1448,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.10.0" +version = "2.11.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index aa582dd..f934398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.10.0" +version = "2.11.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From fd38fc95d03e1412a62a017fb8088ab52e378825 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 20 Sep 2025 03:30:53 -0400 Subject: [PATCH 240/320] rabbitmq_http_client 0.55.0, cargo update --- Cargo.lock | 65 +++++++++++++++++++++++++++++++----------------------- Cargo.toml | 2 +- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f740c3c..b9f8b97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,9 +214,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.37" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" dependencies = [ "find-msvc-tools", "jobserver", @@ -269,18 +269,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -480,9 +480,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] name = "float-cmp" @@ -639,9 +639,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "heck" @@ -903,9 +903,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.3" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -1426,9 +1426,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d41327bcdf04bef7015cc5c3181cce5d39d1194ff4b62f33b6bf4541ed6bc0" +checksum = "68186dc6b34b3c7b7d7bf96da30eef58a0aa2b61bdcbcefa648d580ed21029c4" dependencies = [ "backtrace", "percent-encoding", @@ -1444,6 +1444,8 @@ dependencies = [ "thiserror", "time", "tokio", + "url", + "urlencoding", ] [[package]] @@ -1655,9 +1657,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "aws-lc-rs", "log", @@ -1827,9 +1829,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" dependencies = [ "serde_core", ] @@ -2033,11 +2035,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.43" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", @@ -2138,9 +2141,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" dependencies = [ "indexmap", "serde_core", @@ -2153,27 +2156,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" [[package]] name = "tower" @@ -2281,6 +2284,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index f934398..411f8f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.52.0", features = [ +rabbitmq_http_client = { version = "0.55.0", features = [ "blocking", "tabled", ] } From b6abfd74e017b2730d23277069a345c263a687ca Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 20 Sep 2025 03:31:50 -0400 Subject: [PATCH 241/320] Introduce 'federation disable_tls_peer_verification_for_all_upstreams' This command is only mean to be used in special situations where TLS client settings (e.g. a private key/certificate pair, a trusted CA bundle) were not configured but peer verification was still enabled for outgoing connection. --- CHANGELOG.md | 9 +++- src/cli.rs | 36 +++++++++++--- src/commands.rs | 113 +++++++++++++++++++++++++++++++++++------- src/errors.rs | 7 +-- src/main.rs | 4 ++ src/output.rs | 1 + src/static_urls.rs | 1 + src/tables.rs | 15 +++++- tests/shovel_tests.rs | 1 + 9 files changed, 155 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ae13f..1e29ee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,14 @@ ## v2.11.0 (in development) -No changes yet. +### Enhancements + +* `'federation disable_tls_peer_verification_for_all_upstreams'` + +### Upgrades + +* RabbitMQ HTTP API client was upgraded to [`0.55.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.55.0) + ## v2.10.0 (Sep 18, 2025) diff --git a/src/cli.rs b/src/cli.rs index abd0e5b..92a0c37 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -19,10 +19,7 @@ use super::tanzu_cli::tanzu_subcommands; use crate::config::PreFlightSettings; use crate::output::TableStyle; use clap::{Arg, ArgAction, ArgGroup, Command, value_parser}; -use rabbitmq_http_client::commons::{ - BindingDestinationType, ExchangeType, MessageTransferAcknowledgementMode, PolicyTarget, - QueueType, SupportedProtocol, -}; +use rabbitmq_http_client::commons::{BindingDestinationType, ChannelUseMode, ExchangeType, MessageTransferAcknowledgementMode, PolicyTarget, QueueType, SupportedProtocol}; use rabbitmq_http_client::password_hashing::HashingAlgorithm; use rabbitmq_http_client::requests::FederationResourceCleanupMode; @@ -3051,7 +3048,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5 .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { +fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7] { let list_all_upstreams = Command::new("list_all_upstreams") .long_about("Lists federation upstreams in all virtual hosts") .after_help(color_print::cformat!( @@ -3267,7 +3264,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 Arg::new("reconnect_delay") .long("reconnect-delay") .default_value("5") - .value_parser(value_parser!(u16)) + .value_parser(value_parser!(u32)) .help("Reconnection delay in seconds") ) .arg( @@ -3281,9 +3278,8 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 Arg::new("prefetch_count") .long("prefetch-count") .default_value("1000") - .value_parser(value_parser!(u16)) + .value_parser(value_parser!(u32)) .help("The prefetch value to use with internal consumers") - .value_parser(value_parser!(u16)) ) .arg( Arg::new("ack_mode") @@ -3316,6 +3312,12 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 .default_value("default") .value_parser(value_parser!(FederationResourceCleanupMode)) ) + .arg( + Arg::new("channel_use_mode") + .long("channel-use-mode") + .default_value("multiple") + .value_parser(value_parser!(ChannelUseMode)) + ) .arg( Arg::new("bind_nowait") .long("bind-using-nowait") @@ -3363,6 +3365,23 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 FEDERATION_REFERENCE_URL )); + let disable_tls_peer_verification_cmd = Command::new("disable_tls_peer_verification_for_all_upstreams") + // shorter, displayed in the federation group's help + .about(color_print::cstr!("Use only to undo incorrect URI changes. Disables TLS peer verification for all federation upstreams.")) + // longer, displayed in the command's help + .long_about(color_print::cstr!("Use only to undo incorrect URI changes. Disables TLS peer verification for all federation upstreams by updating their 'verify' parameter.")) + + .after_help(color_print::cformat!( + r#"Doc guides: + + * {} + * {} + * {}"#, + FEDERATION_GUIDE_URL, + TLS_GUIDE_URL, + "https://www.rabbitmq.com/docs/federation#tls-connections" + )); + [ list_all_upstreams, declare_upstream, @@ -3370,6 +3389,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 declare_upstream_for_queue_federation, delete_upstream, list_all_links, + disable_tls_peer_verification_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/commands.rs b/src/commands.rs index 0fa87b8..0916b9c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -19,19 +19,13 @@ use clap::ArgMatches; use rabbitmq_http_client::blocking_api::Client; use rabbitmq_http_client::blocking_api::Result as ClientResult; use rabbitmq_http_client::commons; -use rabbitmq_http_client::commons::BindingDestinationType; +use rabbitmq_http_client::commons::{BindingDestinationType, ChannelUseMode, TlsPeerVerificationMode}; use rabbitmq_http_client::commons::QueueType; use rabbitmq_http_client::commons::{ExchangeType, SupportedProtocol}; use rabbitmq_http_client::commons::{MessageTransferAcknowledgementMode, UserLimitTarget}; use rabbitmq_http_client::commons::{PolicyTarget, VirtualHostLimitTarget}; use rabbitmq_http_client::password_hashing::{HashingAlgorithm, HashingError}; -use rabbitmq_http_client::requests::{ - Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, - Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, - EnforcedLimitParams, ExchangeFederationParams, FEDERATION_UPSTREAM_COMPONENT, - FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, - RuntimeParameterDefinition, -}; +use rabbitmq_http_client::requests::{Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, EnforcedLimitParams, ExchangeFederationParams, FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, DEFAULT_FEDERATION_PREFETCH}; use rabbitmq_http_client::responses::OptionalArgumentSourceOps; use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; use rabbitmq_http_client::{password_hashing, requests, responses}; @@ -269,7 +263,7 @@ pub fn declare_amqp10_shovel( .cloned() .unwrap(); let reconnect_delay = command_args - .get_one::("reconnect_delay") + .get_one::("reconnect_delay") .cloned() .or(Some(5)); @@ -315,7 +309,7 @@ pub fn declare_amqp091_shovel( .cloned() .unwrap(); let reconnect_delay = command_args - .get_one::("reconnect_delay") + .get_one::("reconnect_delay") .cloned() .or(Some(5)); @@ -440,7 +434,7 @@ pub fn declare_federation_upstream( let name = command_args.get_one::("name").cloned().unwrap(); let uri = command_args.get_one::("uri").cloned().unwrap(); let reconnect_delay = command_args - .get_one::("reconnect_delay") + .get_one::("reconnect_delay") .cloned() .unwrap(); let trust_user_id = command_args @@ -448,7 +442,7 @@ pub fn declare_federation_upstream( .cloned() .unwrap(); let prefetch_count = command_args - .get_one::("prefetch_count") + .get_one::("prefetch_count") .cloned() .unwrap(); let ack_mode = command_args @@ -494,6 +488,10 @@ pub fn declare_federation_upstream( .get_one::("bind_nowait") .cloned() .unwrap_or_default(); + let channel_use_mode = command_args + .get_one::("channel_use_mode") + .cloned() + .unwrap_or_default(); let ttl = command_args.get_one::("ttl").cloned(); let message_ttl = command_args.get_one::("message_ttl").cloned(); let efp = Some(ExchangeFederationParams { @@ -515,6 +513,7 @@ pub fn declare_federation_upstream( prefetch_count, ack_mode, bind_using_nowait, + channel_use_mode, queue_federation: qfp, exchange_federation: efp, }; @@ -530,7 +529,7 @@ pub fn declare_federation_upstream_for_exchange_federation( let name = command_args.get_one::("name").cloned().unwrap(); let uri = command_args.get_one::("uri").cloned().unwrap(); let reconnect_delay = command_args - .get_one::("reconnect_delay") + .get_one::("reconnect_delay") .cloned() .unwrap(); let trust_user_id = command_args @@ -538,7 +537,7 @@ pub fn declare_federation_upstream_for_exchange_federation( .cloned() .unwrap(); let prefetch_count = command_args - .get_one::("prefetch_count") + .get_one::("prefetch_count") .cloned() .unwrap(); let ack_mode = command_args @@ -562,6 +561,10 @@ pub fn declare_federation_upstream_for_exchange_federation( .get_one::("bind_nowait") .cloned() .unwrap_or_default(); + let channel_use_mode = command_args + .get_one::("channel_use_mode") + .cloned() + .unwrap_or_default(); let ttl = command_args.get_one::("ttl").cloned(); let message_ttl = command_args.get_one::("message_ttl").cloned(); let efp = Some(ExchangeFederationParams { @@ -583,6 +586,7 @@ pub fn declare_federation_upstream_for_exchange_federation( prefetch_count, ack_mode, bind_using_nowait, + channel_use_mode, queue_federation: None, exchange_federation: efp, }; @@ -598,7 +602,7 @@ pub fn declare_federation_upstream_for_queue_federation( let name = command_args.get_one::("name").cloned().unwrap(); let uri = command_args.get_one::("uri").cloned().unwrap(); let reconnect_delay = command_args - .get_one::("reconnect_delay") + .get_one::("reconnect_delay") .cloned() .unwrap(); let trust_user_id = command_args @@ -606,13 +610,21 @@ pub fn declare_federation_upstream_for_queue_federation( .cloned() .unwrap(); let prefetch_count = command_args - .get_one::("prefetch_count") + .get_one::("prefetch_count") .cloned() .unwrap(); let ack_mode = command_args .get_one::("ack_mode") .cloned() .unwrap(); + let bind_using_nowait = command_args + .get_one::("bind_nowait") + .cloned() + .unwrap_or_default(); + let channel_use_mode = command_args + .get_one::("channel_use_mode") + .cloned() + .unwrap_or_default(); let queue_name = command_args.get_one::("queue_name").cloned(); let consumer_tag = command_args.get_one::("consumer_tag").cloned(); @@ -642,7 +654,8 @@ pub fn declare_federation_upstream_for_queue_federation( trust_user_id, prefetch_count, ack_mode, - bind_using_nowait: false, + bind_using_nowait, + channel_use_mode, queue_federation: qfp, exchange_federation: None, }; @@ -659,6 +672,56 @@ pub fn delete_federation_upstream( client.clear_runtime_parameter(FEDERATION_UPSTREAM_COMPONENT, vhost, &name) } +pub fn disable_tls_peer_verification_for_all_federation_upstreams( + client: APIClient, +) -> Result<(), CommandRunError> { + let upstreams = client.list_federation_upstreams()?; + + for upstream in upstreams { + let original_uri = &upstream.uri; + let updated_uri = disable_tls_peer_verification(original_uri)?; + + if original_uri != &updated_uri { + let upstream_params = FederationUpstreamParams { + name: &upstream.name, + vhost: &upstream.vhost, + uri: &updated_uri, + prefetch_count: upstream.prefetch_count.unwrap_or(DEFAULT_FEDERATION_PREFETCH), + reconnect_delay: upstream.reconnect_delay.unwrap_or(5), + ack_mode: upstream.ack_mode, + trust_user_id: upstream.trust_user_id.unwrap_or_default(), + bind_using_nowait: upstream.bind_using_nowait, + channel_use_mode: upstream.channel_use_mode, + queue_federation: if upstream.queue.is_some() { + Some(QueueFederationParams { + queue: upstream.queue.as_deref(), + consumer_tag: upstream.consumer_tag.as_deref(), + }) + } else { + None + }, + exchange_federation: if upstream.exchange.is_some() { + Some(ExchangeFederationParams { + exchange: upstream.exchange.as_deref(), + max_hops: upstream.max_hops.map(|h| h as u8), + queue_type: upstream.queue_type.unwrap_or(QueueType::Classic), + ttl: upstream.expires, + message_ttl: upstream.message_ttl, + resource_cleanup_mode: upstream.resource_cleanup_mode, + }) + } else { + None + }, + }; + + let param = RuntimeParameterDefinition::from(upstream_params); + client.upsert_runtime_parameter(¶m)?; + } + } + + Ok(()) +} + // // Feature flags // @@ -1733,7 +1796,7 @@ fn read_and_parse_definitions(command_args: &ArgMatches) -> Result format!("could not read from standard input: {}", err), Some(val) => format!("`{}` does not exist or is not readable: {}", val, err), }; - CommandRunError::DefinitionsFileLoadingError { message } + CommandRunError::FailureDuringExecution { message } })?; serde_json::from_str(definitions.as_str()).map_err(|err| { @@ -1741,7 +1804,7 @@ fn read_and_parse_definitions(command_args: &ArgMatches) -> Result format!("could not parse JSON from standard input: {}", err), Some(val) => format!("`{}` is not a valid JSON file: {}", val, err), }; - CommandRunError::DefinitionsFileLoadingError { message } + CommandRunError::FailureDuringExecution { message } }) } @@ -1827,3 +1890,15 @@ fn parse_json_from_arg(input: &str) -> Result Result { + use rabbitmq_http_client::uris::UriBuilder; + + let ub = UriBuilder::new(uri) + .map_err(|e| CommandRunError::FailureDuringExecution { message: format!("Could not parse a value as a URI '{}': {}", uri, e) })? + .with_tls_peer_verification(TlsPeerVerificationMode::Disabled); + + + ub.build() + .map_err(|e| CommandRunError::FailureDuringExecution { message: format!("Failed to reconstruct (modify) a URI: {}", e) }) +} diff --git a/src/errors.rs b/src/errors.rs index ce4c793..bff67bc 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -15,8 +15,8 @@ use rabbitmq_http_client::error::{ConversionError, Error as ApiClientError}; use rabbitmq_http_client::{blocking_api::HttpClientError, responses::HealthCheckFailureDetails}; use reqwest::{ - StatusCode, header::{HeaderMap, InvalidHeaderValue}, + StatusCode, }; use url::Url; @@ -97,8 +97,8 @@ pub enum CommandRunError { RequestError { error: reqwest::Error }, #[error("Failed to parse JSON argument: {message}")] JsonParseError { message: String }, - #[error("Failed to read definitions file: {message}")] - DefinitionsFileLoadingError { message: String }, + #[error("Command execution failed: {message}")] + FailureDuringExecution { message: String }, #[error("an unspecified error")] Other, } @@ -132,6 +132,7 @@ impl From for CommandRunError { Other => Self::Other, MissingProperty { argument } => Self::MissingArgumentValue { property: argument }, IncompatibleBody { error, .. } => Self::IncompatibleBody { error }, + ParsingError { message } => Self::FailureDuringExecution { message }, } } } diff --git a/src/main.rs b/src/main.rs index 4fe2d96..3ca4eef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -668,6 +668,10 @@ fn dispatch_common_subcommand( let result = commands::list_federation_upstreams(client); res_handler.tabular_result(result) } + ("federation", "disable_tls_peer_verification_for_all_upstreams") => { + let result = commands::disable_tls_peer_verification_for_all_federation_upstreams(client); + res_handler.no_output_on_success(result); + } ("get", "messages") => { let result = commands::get_messages(client, &vhost, second_level_args); res_handler.tabular_result(result) diff --git a/src/output.rs b/src/output.rs index 7a3ea13..dcada28 100644 --- a/src/output.rs +++ b/src/output.rs @@ -420,6 +420,7 @@ pub(crate) fn client_error_to_exit_code(error: &HttpClientError) -> ExitCode { ClientError::MultipleMatchingBindings => ExitCode::DataErr, ClientError::InvalidHeaderValue { error: _ } => ExitCode::DataErr, ClientError::IncompatibleBody { .. } => ExitCode::DataErr, + ClientError::ParsingError { .. } => ExitCode::DataErr, ClientError::RequestError { .. } => ExitCode::IoErr, ClientError::Other => ExitCode::Usage, } diff --git a/src/static_urls.rs b/src/static_urls.rs index 596f929..1564613 100644 --- a/src/static_urls.rs +++ b/src/static_urls.rs @@ -30,6 +30,7 @@ pub(crate) const STREAM_GUIDE_URL: &str = "https://rabbitmq.com/docs/streams"; pub(crate) const QUORUM_QUEUE_GUIDE_URL: &str = "https://rabbitmq.com/docs/quorum-queues"; pub(crate) const QUORUM_QUEUE_FAILURE_HANDLING_GUIDE_URL: &str = "https://rabbitmq.com/docs/quorum-queues#leader-election"; +pub(crate) const TLS_GUIDE_URL: &str = "https://rabbitmq.com/docs/ssl"; pub(crate) const UPGRADE_GUIDE_URL: &str = "https://rabbitmq.com/docs/upgrade"; pub(crate) const BLUE_GREEN_UPGRADE_GUIDE_URL: &str = "https://rabbitmq.com/docs/blue-green-upgrade"; diff --git a/src/tables.rs b/src/tables.rs index c798851..c20c68f 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -1,4 +1,3 @@ -use std::error::Error; // Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +18,7 @@ use rabbitmq_http_client::responses::{ ClusterAlarmCheckDetails, HealthCheckFailureDetails, NodeMemoryBreakdown, Overview, QuorumCriticalityCheckDetails, SchemaDefinitionSyncStatus, }; +use std::error::Error; use reqwest::StatusCode; use tabled::settings::Panel; use tabled::{Table, Tabled}; @@ -404,6 +404,19 @@ pub fn failure_details(error: &HttpClientError) -> Table { ); build_request_failure_table("request failed", &reason) } + HttpClientError::ParsingError { message } => { + let data = vec![ + RowOfTwo { + key: "result", + value: "request failed", + }, + RowOfTwo { + key: "reason", + value: message.as_str(), + }, + ]; + build_simple_table(data) + } HttpClientError::RequestError { error, backtrace: _, diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index e724f93..902cef1 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -19,6 +19,7 @@ use predicates::boolean::PredicateBooleanExt; use predicates::prelude::predicate; #[test] +#[ignore] fn test_shovel_declaration_without_source_uri() -> Result<(), Box> { let vh = "rust.shovels.0"; let name = "shovels.test_shovel_declaration_without_source_uri"; From bb4d8ebbcb1e9d976efb9358c9ebcb4eace98759 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 20 Sep 2025 03:46:55 -0400 Subject: [PATCH 242/320] Adapt for rabbitmq_http_client 0.55.0 --- src/cli.rs | 38 +++++++++++++++++++++++++++++--------- src/commands.rs | 25 +++++++++++++++++++------ src/errors.rs | 2 +- src/main.rs | 3 ++- src/tables.rs | 2 +- tests/shovel_tests.rs | 1 - 6 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 92a0c37..785ae13 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -19,7 +19,10 @@ use super::tanzu_cli::tanzu_subcommands; use crate::config::PreFlightSettings; use crate::output::TableStyle; use clap::{Arg, ArgAction, ArgGroup, Command, value_parser}; -use rabbitmq_http_client::commons::{BindingDestinationType, ChannelUseMode, ExchangeType, MessageTransferAcknowledgementMode, PolicyTarget, QueueType, SupportedProtocol}; +use rabbitmq_http_client::commons::{ + BindingDestinationType, ChannelUseMode, ExchangeType, MessageTransferAcknowledgementMode, + PolicyTarget, QueueType, SupportedProtocol, +}; use rabbitmq_http_client::password_hashing::HashingAlgorithm; use rabbitmq_http_client::requests::FederationResourceCleanupMode; @@ -2980,7 +2983,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5 Arg::new("reconnect_delay") .long("reconnect-delay") .default_value("5") - .value_parser(value_parser!(u16)), + .value_parser(value_parser!(u32)), ) .group( ArgGroup::new("destination") @@ -3022,7 +3025,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5 Arg::new("reconnect_delay") .long("reconnect-delay") .default_value("5") - .value_parser(value_parser!(u16)), + .value_parser(value_parser!(u32)), ); let delete_cmd = Command::new("delete") @@ -3092,7 +3095,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 Arg::new("reconnect_delay") .long("reconnect-delay") .default_value("5") - .value_parser(value_parser!(u16)) + .value_parser(value_parser!(u32)) .help("Reconnection delay in seconds") ) .arg( @@ -3106,9 +3109,9 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 Arg::new("prefetch_count") .long("prefetch-count") .default_value("1000") - .value_parser(value_parser!(u16)) + .value_parser(value_parser!(u32)) .help("The prefetch value to use with internal consumers") - .value_parser(value_parser!(u16)) + .value_parser(value_parser!(u32)) ) .arg( Arg::new("ack_mode") @@ -3158,6 +3161,12 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 .default_value("default") .value_parser(value_parser!(FederationResourceCleanupMode)) ) + .arg( + Arg::new("channel_use_mode") + .long("channel-use-mode") + .default_value("multiple") + .value_parser(value_parser!(ChannelUseMode)) + ) .arg( Arg::new("ttl") .long("ttl") @@ -3199,7 +3208,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 Arg::new("reconnect_delay") .long("reconnect-delay") .default_value("5") - .value_parser(value_parser!(u16)) + .value_parser(value_parser!(u32)) .help("Reconnection delay in seconds") ) .arg( @@ -3213,9 +3222,8 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 Arg::new("prefetch_count") .long("prefetch-count") .default_value("1000") - .value_parser(value_parser!(u16)) + .value_parser(value_parser!(u32)) .help("The prefetch value to use with internal consumers") - .value_parser(value_parser!(u16)) ) .arg( Arg::new("ack_mode") @@ -3224,6 +3232,18 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 .help("Accepted values are: on-confirm, on-publish, no-ack") .default_value("on-confirm"), ) + .arg( + Arg::new("bind_nowait") + .long("bind-using-nowait") + .default_value("false") + .value_parser(value_parser!(bool)) + ) + .arg( + Arg::new("channel_use_mode") + .long("channel-use-mode") + .default_value("multiple") + .value_parser(value_parser!(ChannelUseMode)) + ) .arg( Arg::new("queue_name") .long("queue-name") diff --git a/src/commands.rs b/src/commands.rs index 0916b9c..22e14de 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -19,13 +19,21 @@ use clap::ArgMatches; use rabbitmq_http_client::blocking_api::Client; use rabbitmq_http_client::blocking_api::Result as ClientResult; use rabbitmq_http_client::commons; -use rabbitmq_http_client::commons::{BindingDestinationType, ChannelUseMode, TlsPeerVerificationMode}; use rabbitmq_http_client::commons::QueueType; +use rabbitmq_http_client::commons::{ + BindingDestinationType, ChannelUseMode, TlsPeerVerificationMode, +}; use rabbitmq_http_client::commons::{ExchangeType, SupportedProtocol}; use rabbitmq_http_client::commons::{MessageTransferAcknowledgementMode, UserLimitTarget}; use rabbitmq_http_client::commons::{PolicyTarget, VirtualHostLimitTarget}; use rabbitmq_http_client::password_hashing::{HashingAlgorithm, HashingError}; -use rabbitmq_http_client::requests::{Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, EnforcedLimitParams, ExchangeFederationParams, FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, DEFAULT_FEDERATION_PREFETCH}; +use rabbitmq_http_client::requests::{ + Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, + Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, + DEFAULT_FEDERATION_PREFETCH, EnforcedLimitParams, ExchangeFederationParams, + FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, FederationUpstreamParams, + PolicyParams, QueueFederationParams, RuntimeParameterDefinition, +}; use rabbitmq_http_client::responses::OptionalArgumentSourceOps; use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; use rabbitmq_http_client::{password_hashing, requests, responses}; @@ -686,7 +694,9 @@ pub fn disable_tls_peer_verification_for_all_federation_upstreams( name: &upstream.name, vhost: &upstream.vhost, uri: &updated_uri, - prefetch_count: upstream.prefetch_count.unwrap_or(DEFAULT_FEDERATION_PREFETCH), + prefetch_count: upstream + .prefetch_count + .unwrap_or(DEFAULT_FEDERATION_PREFETCH), reconnect_delay: upstream.reconnect_delay.unwrap_or(5), ack_mode: upstream.ack_mode, trust_user_id: upstream.trust_user_id.unwrap_or_default(), @@ -1895,10 +1905,13 @@ fn disable_tls_peer_verification(uri: &str) -> Result { use rabbitmq_http_client::uris::UriBuilder; let ub = UriBuilder::new(uri) - .map_err(|e| CommandRunError::FailureDuringExecution { message: format!("Could not parse a value as a URI '{}': {}", uri, e) })? + .map_err(|e| CommandRunError::FailureDuringExecution { + message: format!("Could not parse a value as a URI '{}': {}", uri, e), + })? .with_tls_peer_verification(TlsPeerVerificationMode::Disabled); - ub.build() - .map_err(|e| CommandRunError::FailureDuringExecution { message: format!("Failed to reconstruct (modify) a URI: {}", e) }) + .map_err(|e| CommandRunError::FailureDuringExecution { + message: format!("Failed to reconstruct (modify) a URI: {}", e), + }) } diff --git a/src/errors.rs b/src/errors.rs index bff67bc..dd71740 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -15,8 +15,8 @@ use rabbitmq_http_client::error::{ConversionError, Error as ApiClientError}; use rabbitmq_http_client::{blocking_api::HttpClientError, responses::HealthCheckFailureDetails}; use reqwest::{ - header::{HeaderMap, InvalidHeaderValue}, StatusCode, + header::{HeaderMap, InvalidHeaderValue}, }; use url::Url; diff --git a/src/main.rs b/src/main.rs index 3ca4eef..fb6997e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -669,7 +669,8 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("federation", "disable_tls_peer_verification_for_all_upstreams") => { - let result = commands::disable_tls_peer_verification_for_all_federation_upstreams(client); + let result = + commands::disable_tls_peer_verification_for_all_federation_upstreams(client); res_handler.no_output_on_success(result); } ("get", "messages") => { diff --git a/src/tables.rs b/src/tables.rs index c20c68f..abc6637 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -18,8 +18,8 @@ use rabbitmq_http_client::responses::{ ClusterAlarmCheckDetails, HealthCheckFailureDetails, NodeMemoryBreakdown, Overview, QuorumCriticalityCheckDetails, SchemaDefinitionSyncStatus, }; -use std::error::Error; use reqwest::StatusCode; +use std::error::Error; use tabled::settings::Panel; use tabled::{Table, Tabled}; use url::Url; diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index 902cef1..e724f93 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -19,7 +19,6 @@ use predicates::boolean::PredicateBooleanExt; use predicates::prelude::predicate; #[test] -#[ignore] fn test_shovel_declaration_without_source_uri() -> Result<(), Box> { let vh = "rust.shovels.0"; let name = "shovels.test_shovel_declaration_without_source_uri"; From 5b082628e45fc78ffe19a1920c0a496030533e19 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 20 Sep 2025 04:03:06 -0400 Subject: [PATCH 243/320] Squash a clippy warning --- src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.rs b/src/commands.rs index 22e14de..73806e6 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -713,7 +713,7 @@ pub fn disable_tls_peer_verification_for_all_federation_upstreams( exchange_federation: if upstream.exchange.is_some() { Some(ExchangeFederationParams { exchange: upstream.exchange.as_deref(), - max_hops: upstream.max_hops.map(|h| h as u8), + max_hops: upstream.max_hops, queue_type: upstream.queue_type.unwrap_or(QueueType::Classic), ttl: upstream.expires, message_ttl: upstream.message_ttl, From 9220a2af5e62eec9c9f72977f84d10b457ec59a1 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 21 Sep 2025 04:56:47 -0400 Subject: [PATCH 244/320] rabbitmq_http_client 0.56.0 --- CHANGELOG.md | 2 +- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- src/commands.rs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e29ee3..bd88ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Upgrades -* RabbitMQ HTTP API client was upgraded to [`0.55.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.55.0) +* RabbitMQ HTTP API client was upgraded to [`0.56.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.56.0) diff --git a/Cargo.lock b/Cargo.lock index b9f8b97..ea63263 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1426,9 +1426,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.55.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68186dc6b34b3c7b7d7bf96da30eef58a0aa2b61bdcbcefa648d580ed21029c4" +checksum = "734c0eab28f7b2f33e9f22239f1a5434f1d167126f7fbeae702e4ff99fb1c521" dependencies = [ "backtrace", "percent-encoding", @@ -1764,9 +1764,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.225" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" dependencies = [ "serde_core", "serde_derive", @@ -1796,18 +1796,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 411f8f2..fa457d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.55.0", features = [ +rabbitmq_http_client = { version = "0.56.0", features = [ "blocking", "tabled", ] } diff --git a/src/commands.rs b/src/commands.rs index 73806e6..2f46934 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -34,7 +34,7 @@ use rabbitmq_http_client::requests::{ FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, }; -use rabbitmq_http_client::responses::OptionalArgumentSourceOps; + use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; use rabbitmq_http_client::{password_hashing, requests, responses}; use serde::de::DeserializeOwned; From f61c3a3603e0c89fdf6098ec3c64aed656f90d03 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 15:48:59 -0400 Subject: [PATCH 245/320] rabbitmq_http_client 0.57.0 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea63263..c53f101 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -987,12 +987,12 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link 0.2.0", ] [[package]] @@ -1426,9 +1426,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734c0eab28f7b2f33e9f22239f1a5434f1d167126f7fbeae702e4ff99fb1c521" +checksum = "cd35e3d6e15d83bfe4d93e81fe4c8b7f9c9cbfd3e370513c9ae3370f9a4c62af" dependencies = [ "backtrace", "percent-encoding", @@ -1680,7 +1680,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.4.0", + "security-framework 3.5.0", ] [[package]] @@ -1741,9 +1741,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" +checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" dependencies = [ "bitflags", "core-foundation 0.10.1", diff --git a/Cargo.toml b/Cargo.toml index fa457d5..591293f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.56.0", features = [ +rabbitmq_http_client = { version = "0.57.0", features = [ "blocking", "tabled", ] } From 4e8ad4885a9643bd4a53e14b7bf1753db27ed5a9 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 15:49:24 -0400 Subject: [PATCH 246/320] Tests for 'federation disable_tls_peer_verification_for_all_upstreams' --- ...eration_upstream_uri_modification_tests.rs | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 tests/federation_upstream_uri_modification_tests.rs diff --git a/tests/federation_upstream_uri_modification_tests.rs b/tests/federation_upstream_uri_modification_tests.rs new file mode 100644 index 0000000..6a93c0c --- /dev/null +++ b/tests/federation_upstream_uri_modification_tests.rs @@ -0,0 +1,280 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_helpers; + +use crate::test_helpers::*; +use predicates::prelude::*; + +#[test] +fn test_disable_tls_peer_verification_for_all_upstreams_basic() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_upstreams_basic"; + let upstream_name = "test_basic_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_exchanges", + "--name", + upstream_name, + "--uri", + &amqps_endpoint, + "--exchange-name", + "x.fanout", + "--queue-type", + "classic", + ]); + + run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(upstream_name)); + + run_succeeds([ + "federation", + "disable_tls_peer_verification_for_all_upstreams", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(upstream_name)) + .stdout(predicate::str::contains("verify=verify_none")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_param() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_param"; + let upstream_name = "test_existing_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let source_uri = format!( + "{}?key1=abc&verify=verify_peer&cacertfile=/path/to/ca_bundle.pem&key2=def&certfile=/path/to/client.pem&keyfile=/path/to/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", + amqps_endpoint + ); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_exchanges", + "--name", + upstream_name, + "--uri", + &source_uri, + "--exchange-name", + "x.fanout", + "--queue-type", + "classic", + ]); + await_metric_emission(500); + + run_succeeds([ + "federation", + "disable_tls_peer_verification_for_all_upstreams", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(upstream_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("key1=abc")) + .stdout(predicate::str::contains("key2=def")) + .stdout(predicate::str::contains( + "cacertfile=/path/to/ca_bundle.pem", + )) + .stdout(predicate::str::contains("certfile=/path/to/client.pem")) + .stdout(predicate::str::contains("keyfile=/path/to/client.key")) + .stdout(predicate::str::contains( + "server_name_indication=example.com", + )) + .stdout(predicate::str::contains("custom_param=value123")) + .stdout(predicate::str::contains("another_param=xyz")) + .stdout(predicate::str::contains("heartbeat=60")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic"; + let upstream_name = "test_queue_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_queues", + "--name", + upstream_name, + "--uri", + &amqps_endpoint, + "--queue-name", + "test.queue", + "--consumer-tag", + "test-consumer", + ]); + + run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(upstream_name)); + + run_succeeds([ + "federation", + "disable_tls_peer_verification_for_all_upstreams", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(upstream_name)) + .stdout(predicate::str::contains("verify=verify_none")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_params() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_params"; + let upstream_name = "test_queue_upstream_with_params"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let source_uri = format!( + "{}?queue_param=test123&verify=verify_peer&cacertfile=/etc/ssl/certs/ca.pem&consumer_tag_param=custom&prefetch=100&ack_mode=on-confirm", + amqps_endpoint + ); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_queues", + "--name", + upstream_name, + "--uri", + &source_uri, + "--queue-name", + "federated.queue", + "--consumer-tag", + "queue-consumer", + ]); + await_metric_emission(500); + + run_succeeds([ + "federation", + "disable_tls_peer_verification_for_all_upstreams", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(upstream_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("queue_param=test123")) + .stdout(predicate::str::contains("cacertfile=/etc/ssl/certs/ca.pem")) + .stdout(predicate::str::contains("consumer_tag_param=custom")) + .stdout(predicate::str::contains("prefetch=100")) + .stdout(predicate::str::contains("ack_mode=on-confirm")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_upstreams_mixed_federation"; + let exchange_upstream_name = "exchange_upstream"; + let queue_upstream_name = "queue_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let exchange_uri = format!( + "{}?exchange_param=value1&verify=verify_peer&certfile=/path/to/client.pem", + amqps_endpoint + ); + let queue_uri = format!( + "{}?queue_param=value2&verify=verify_peer&keyfile=/path/to/client.key", + amqps_endpoint + ); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_exchanges", + "--name", + exchange_upstream_name, + "--uri", + &exchange_uri, + "--exchange-name", + "x.federated", + "--queue-type", + "classic", + ]); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_queues", + "--name", + queue_upstream_name, + "--uri", + &queue_uri, + "--queue-name", + "q.federated", + "--consumer-tag", + "mixed-consumer", + ]); + await_metric_emission(500); + + run_succeeds([ + "federation", + "disable_tls_peer_verification_for_all_upstreams", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(exchange_upstream_name)) + .stdout(predicate::str::contains(queue_upstream_name)) + .stdout(predicate::str::contains("exchange_param=value1")) + .stdout(predicate::str::contains("queue_param=value2")) + .stdout(predicate::str::contains("certfile=/path/to/client.pem")) + .stdout(predicate::str::contains("keyfile=/path/to/client.key")) + .stdout(predicate::str::contains("verify=verify_none")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} From ecce04103c9776336f86e3943c1d7aa5e7c687a7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 17:11:35 -0400 Subject: [PATCH 247/320] Improve a few flaky tests --- tests/runtime_parameters_tests.rs | 19 ++++++++++++------- tests/shovel_tests.rs | 3 +++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index ef297df..f02ebb6 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -20,6 +20,8 @@ use crate::test_helpers::*; #[test] fn test_runtime_parameters_across_groups() -> Result<(), Box> { let vh = "test_runtime_parameters_across_groups"; + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); run_succeeds([ "-V", @@ -31,8 +33,9 @@ fn test_runtime_parameters_across_groups() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { let vh = "test_runtime_parameters_cmd_group"; + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["vhosts", "declare", "--name", vh]); run_succeeds([ "-V", @@ -84,11 +89,11 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "--name", "my-upstream", "--value", - "{\"uri\":\"amqp://target.hostname\",\"expires\":3600000}", + "{\"uri\":\"amqp://target.hostname\",\"ack-mode\":\"on-confirm\"}", ]); + await_metric_emission(200); - run_succeeds(["parameters", "list_all"]) - .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains("my-upstream")); run_succeeds([ "-V", @@ -98,7 +103,7 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "--component", "federation-upstream", ]) - .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + .stdout(predicate::str::contains("my-upstream")); run_succeeds([ "-V", @@ -108,7 +113,7 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "--component", "federation-upstream", ]) - .stdout(predicate::str::contains("my-upstream").and(predicate::str::contains("3600000"))); + .stdout(predicate::str::contains("my-upstream")); run_succeeds([ "-V", diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index e724f93..34bd9d9 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -127,6 +127,8 @@ fn test_shovel_declaration_with_overlapping_destination_types() #[test] fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box> { let vh = "rust.shovels.2"; + delete_vhost(vh).expect("failed to delete a virtual host"); + let name = "shovels.test_amqp091_shovel_declaration_and_deletion"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -150,6 +152,7 @@ fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box Date: Mon, 22 Sep 2025 17:16:26 -0400 Subject: [PATCH 248/320] Change log updates --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd88ba1..55144e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,15 @@ ### Enhancements -* `'federation disable_tls_peer_verification_for_all_upstreams'` +* `federation disable_tls_peer_verification_for_all_upstreams` is a new command that disables TLS peer verification + for all federation upstreams. + + **Important**: this command should **only** be used to undo incorrect federation upstream URI, for example, + if [peer verification](https://www.rabbitmq.com/docs/ssl#peer-verification) was enabled prematurely. ### Upgrades -* RabbitMQ HTTP API client was upgraded to [`0.56.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.56.0) +* RabbitMQ HTTP API client was upgraded to [`0.57.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.57.0) From a18f1c900bf9e3521f7f2b47eda13dd07ac115f2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 17:24:12 -0400 Subject: [PATCH 249/320] 2.11.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55144e8..a19478a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # rabbitmqadmin-ng Change Log -## v2.11.0 (in development) +## v2.11.0 (Sep 22, 2025) ### Enhancements From 35ab698721d2640fb0b03b989d766d2c1fe6c3ee Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 18:03:35 -0400 Subject: [PATCH 250/320] Reduce test interference --- tests/exchange_federation_tests.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/exchange_federation_tests.rs b/tests/exchange_federation_tests.rs index b567a97..ef89b0e 100644 --- a/tests/exchange_federation_tests.rs +++ b/tests/exchange_federation_tests.rs @@ -354,10 +354,7 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() ]); run_succeeds(["-V", vh, "federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(name).not()) - .stdout(predicate::str::contains(endpoint1.clone()).not()) - .stdout(predicate::str::contains(x).not()) - .stdout(predicate::str::contains(queue_type.to_string()).not()); + .stdout(predicate::str::contains(name).not()); delete_vhost(vh).expect("failed to delete a virtual host"); From b0dd690d2dc6417fc0a2cd2300840f12bd96c8d5 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 18:05:33 -0400 Subject: [PATCH 251/320] Introduce 'shovel disable_tls_peer_verification_for_all_source_uris' --- src/cli.rs | 23 +- src/commands.rs | 42 ++ src/main.rs | 4 + tests/shovel_source_uri_modification_tests.rs | 394 ++++++++++++++++++ 4 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 tests/shovel_source_uri_modification_tests.rs diff --git a/src/cli.rs b/src/cli.rs index 785ae13..c5b9ad3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2892,7 +2892,7 @@ pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { +pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { let list_all_cmd = Command::new("list_all") .long_about("Lists shovels in all virtual hosts") .after_help(color_print::cformat!( @@ -3041,12 +3041,29 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5 .required(true), ); + let disable_tls_peer_verification_cmd = Command::new("disable_tls_peer_verification_for_all_source_uris") + // shorter, displayed in the shovels group's help + .about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all shovels.")) + // longer, displayed in the command's help + .long_about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all shovels by updating their source and destination URIs' 'verify' parameter.")) + .after_help(color_print::cformat!( + r#"Doc guides: + + * {} + * {} + * {}"#, + SHOVEL_GUIDE_URL, + TLS_GUIDE_URL, + "https://www.rabbitmq.com/docs/shovel#tls-connections" + )); + [ list_all_cmd, list_cmd, declare_091_cmd, declare_10_cmd, delete_cmd, + disable_tls_peer_verification_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } @@ -3387,9 +3404,9 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 let disable_tls_peer_verification_cmd = Command::new("disable_tls_peer_verification_for_all_upstreams") // shorter, displayed in the federation group's help - .about(color_print::cstr!("Use only to undo incorrect URI changes. Disables TLS peer verification for all federation upstreams.")) + .about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all federation upstreams.")) // longer, displayed in the command's help - .long_about(color_print::cstr!("Use only to undo incorrect URI changes. Disables TLS peer verification for all federation upstreams by updating their 'verify' parameter.")) + .long_about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all federation upstreams by updating their 'verify' parameter.")) .after_help(color_print::cformat!( r#"Doc guides: diff --git a/src/commands.rs b/src/commands.rs index 2f46934..de745da 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -34,6 +34,7 @@ use rabbitmq_http_client::requests::{ FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, }; +use rabbitmq_http_client::requests::shovels::OwnedShovelParams; use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; use rabbitmq_http_client::{password_hashing, requests, responses}; @@ -732,6 +733,47 @@ pub fn disable_tls_peer_verification_for_all_federation_upstreams( Ok(()) } +pub fn disable_tls_peer_verification_for_all_shovels( + client: APIClient, +) -> Result<(), CommandRunError> { + // Get all runtime parameters of "shovel" component + let all_params = client.list_runtime_parameters()?; + let shovel_params: Vec<_> = all_params + .into_iter() + .filter(|p| p.component == "shovel") + .collect(); + + for param in shovel_params { + // Convert the runtime parameter to OwnedShovelParams for easier manipulation + let owned_params = match OwnedShovelParams::try_from(param.clone()) { + Ok(params) => params, + Err(_) => continue, // Skip malformed shovel parameters + }; + + let original_source_uri = &owned_params.source_uri; + let original_destination_uri = &owned_params.destination_uri; + + // Skip shovels with empty URIs + if original_source_uri.is_empty() || original_destination_uri.is_empty() { + continue; + } + + let updated_source_uri = disable_tls_peer_verification(original_source_uri)?; + let updated_destination_uri = disable_tls_peer_verification(original_destination_uri)?; + + if original_source_uri != &updated_source_uri || original_destination_uri != &updated_destination_uri { + let mut updated_params = owned_params; + updated_params.source_uri = updated_source_uri; + updated_params.destination_uri = updated_destination_uri; + + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + } + } + + Ok(()) +} + // // Feature flags // diff --git a/src/main.rs b/src/main.rs index fb6997e..7bf4133 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1071,6 +1071,10 @@ fn dispatch_common_subcommand( let result = commands::list_shovels_in(client, &vhost); res_handler.tabular_result(result) } + ("shovels", "disable_tls_peer_verification_for_all_source_uris") => { + let result = commands::disable_tls_peer_verification_for_all_shovels(client); + res_handler.no_output_on_success(result); + } ("streams", "declare") => { let result = commands::declare_stream(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/shovel_source_uri_modification_tests.rs b/tests/shovel_source_uri_modification_tests.rs new file mode 100644 index 0000000..ed87c00 --- /dev/null +++ b/tests/shovel_source_uri_modification_tests.rs @@ -0,0 +1,394 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_helpers; + +use crate::test_helpers::*; +use predicates::prelude::*; + +#[test] +fn test_disable_tls_peer_verification_for_all_shovels_basic() -> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_shovels_basic"; + let shovel_name = "test_basic_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_source = format!("amqps://localhost:5671/{}", vh); + let amqps_destination = format!("amqps://localhost:5671/{}", vh); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &amqps_source, + "--destination-uri", + &amqps_destination, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + + run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_source_uris", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param"; + let shovel_name = "test_existing_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_base = format!("amqps://localhost:5671/{}", vh); + let source_uri = format!( + "{}?key1=abc&verify=verify_peer&cacertfile=/path/to/ca_bundle.pem&key2=def&certfile=/path/to/client.pem&keyfile=/path/to/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", + amqps_base + ); + let dest_uri = format!( + "{}?dest_key1=xyz&verify=verify_peer&cacertfile=/path/to/dest_ca.pem&dest_key2=abc&certfile=/path/to/dest_client.pem&keyfile=/path/to/dest_client.key&server_name_indication=dest.example.com&dest_param=value456&another_dest_param=def&heartbeat=30", + amqps_base + ); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &source_uri, + "--destination-uri", + &dest_uri, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + await_metric_emission(500); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_source_uris", + ]); + + let output = run_succeeds(["parameters", "list_all"]); + let stdout = output.stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("key1=abc")) + .stdout(predicate::str::contains("key2=def")) + .stdout(predicate::str::contains("cacertfile=/path/to/ca_bundle.pem")) + .stdout(predicate::str::contains("certfile=/path/to/client.pem")) + .stdout(predicate::str::contains("keyfile=/path/to/client.key")) + .stdout(predicate::str::contains("server_name_indication=example.com")) + .stdout(predicate::str::contains("custom_param=value123")) + .stdout(predicate::str::contains("another_param=xyz")) + .stdout(predicate::str::contains("heartbeat=60")) + .stdout(predicate::str::contains("dest_key1=xyz")) + .stdout(predicate::str::contains("dest_key2=abc")) + .stdout(predicate::str::contains("cacertfile=/path/to/dest_ca.pem")) + .stdout(predicate::str::contains("certfile=/path/to/dest_client.pem")) + .stdout(predicate::str::contains("keyfile=/path/to/dest_client.key")) + .stdout(predicate::str::contains("server_name_indication=dest.example.com")) + .stdout(predicate::str::contains("dest_param=value456")) + .stdout(predicate::str::contains("another_dest_param=def")) + .stdout(predicate::str::contains("heartbeat=30")); + + let output_str = std::str::from_utf8(&stdout.get_output().stdout).unwrap(); + let lines: Vec<&str> = output_str.lines().collect(); + let mut shovel_section = String::new(); + let mut in_our_shovel = false; + + for line in lines { + if line.contains(&shovel_name) { + in_our_shovel = true; + } + if in_our_shovel { + shovel_section.push_str(line); + shovel_section.push('\n'); + if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + break; + } + } + } + + let src_uri_lines: Vec<&str> = shovel_section.lines() + .filter(|line| line.contains("src-uri")) + .collect(); + assert!(!src_uri_lines.is_empty(), "Could not find src-uri in shovel section"); + + let src_uri_content = src_uri_lines[0]; + let src_verify_count = src_uri_content.matches("verify=").count(); + assert_eq!(src_verify_count, 1, "Expected exactly 1 verify parameter in source URI, found {}", src_verify_count); + + assert!(src_uri_content.contains("verify=verify_none"), "Source URI should contain verify=verify_none"); + assert!(!src_uri_content.contains("verify=verify_peer"), "Source URI should not contain verify=verify_peer"); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_shovels_amqp10() -> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_shovels_amqp10"; + let shovel_name = "test_amqp10_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_source = format!("amqps://localhost:5671/{}?verify=verify_peer&cacertfile=/path/to/ca.pem", vh); + let amqps_destination = format!("amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", vh); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp10", + "--name", + shovel_name, + "--source-uri", + &amqps_source, + "--destination-uri", + &amqps_destination, + "--source-address", + "source.address", + "--destination-address", + "dest.address", + "--ack-mode", + "on-confirm", + ]); + + run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_source_uris", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("cacertfile=/path/to/ca.pem")) + .stdout(predicate::str::contains("certfile=/path/to/client.pem")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_shovels_mixed_protocols"; + let shovel_091_name = "test_091_shovel"; + let shovel_10_name = "test_10_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_base = format!("amqps://localhost:5671/{}", vh); + let uri_091_source = format!("{}?protocol_param=091&verify=verify_peer&certfile=/path/to/091.pem", amqps_base); + let uri_091_dest = format!("{}?protocol_param=091_dest&verify=verify_peer&keyfile=/path/to/091.key", amqps_base); + let uri_10_source = format!("{}?protocol_param=10&verify=verify_peer&cacertfile=/path/to/10.pem", amqps_base); + let uri_10_dest = format!("{}?protocol_param=10_dest&verify=verify_peer&server_name_indication=amqp10.example.com", amqps_base); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_091_name, + "--source-uri", + &uri_091_source, + "--destination-uri", + &uri_091_dest, + "--source-queue", + "q.091.source", + "--destination-queue", + "q.091.dest", + "--ack-mode", + "on-confirm", + ]); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp10", + "--name", + shovel_10_name, + "--source-uri", + &uri_10_source, + "--destination-uri", + &uri_10_dest, + "--source-address", + "addr.10.source", + "--destination-address", + "addr.10.dest", + "--ack-mode", + "on-confirm", + ]); + await_metric_emission(500); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_source_uris", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(shovel_091_name)) + .stdout(predicate::str::contains(shovel_10_name)) + .stdout(predicate::str::contains("protocol_param=091")) + .stdout(predicate::str::contains("protocol_param=091_dest")) + .stdout(predicate::str::contains("protocol_param=10")) + .stdout(predicate::str::contains("protocol_param=10_dest")) + .stdout(predicate::str::contains("certfile=/path/to/091.pem")) + .stdout(predicate::str::contains("keyfile=/path/to/091.key")) + .stdout(predicate::str::contains("cacertfile=/path/to/10.pem")) + .stdout(predicate::str::contains("server_name_indication=amqp10.example.com")) + .stdout(predicate::str::contains("verify=verify_none")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_shovels_no_shovels() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_shovels_no_shovels"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_source_uris", + ]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params"; + let shovel_name = "test_dummy_params_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_base = format!("amqps://localhost:5671/{}", vh); + let source_uri = format!("{}?abc=123&heartbeat=5&connection_timeout=30&dummy_param=test_value", amqps_base); + let dest_uri = format!("{}?xyz=456&heartbeat=10&channel_max=100&another_dummy=example", amqps_base); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &source_uri, + "--destination-uri", + &dest_uri, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + await_metric_emission(500); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_source_uris", + ]); + + let output = run_succeeds(["parameters", "list_all"]); + let stdout = output.stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("abc=123")) + .stdout(predicate::str::contains("heartbeat=5")) + .stdout(predicate::str::contains("connection_timeout=30")) + .stdout(predicate::str::contains("dummy_param=test_value")) + .stdout(predicate::str::contains("xyz=456")) + .stdout(predicate::str::contains("heartbeat=10")) + .stdout(predicate::str::contains("channel_max=100")) + .stdout(predicate::str::contains("another_dummy=example")); + + let output_str = std::str::from_utf8(&stdout.get_output().stdout).unwrap(); + let lines: Vec<&str> = output_str.lines().collect(); + let mut shovel_section = String::new(); + let mut in_our_shovel = false; + + for line in lines { + if line.contains(&shovel_name) { + in_our_shovel = true; + } + if in_our_shovel { + shovel_section.push_str(line); + shovel_section.push('\n'); + if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + break; + } + } + } + + let src_uri_lines: Vec<&str> = shovel_section.lines() + .filter(|line| line.contains("src-uri")) + .collect(); + assert!(!src_uri_lines.is_empty(), "Could not find src-uri in shovel section"); + + let src_uri_content = src_uri_lines[0]; + let src_verify_count = src_uri_content.matches("verify=").count(); + assert_eq!(src_verify_count, 1, "Expected exactly 1 verify parameter in source URI, found {}", src_verify_count); + + assert!(src_uri_content.contains("verify=verify_none"), "Source URI should contain verify=verify_none"); + assert!(!src_uri_content.contains("verify=verify_peer"), "Source URI should not contain verify=verify_peer"); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} \ No newline at end of file From 99cafa5dbfe59004dd1c24d64ae91dfd561a1a65 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 18:17:23 -0400 Subject: [PATCH 252/320] Introduce two new shovel commands to be used in case of an emergency. --- CHANGELOG.md | 21 +- src/cli.rs | 25 +- src/commands.rs | 45 ++- src/main.rs | 4 + ...ovel_destination_uri_modification_tests.rs | 300 ++++++++++++++++++ tests/shovel_source_uri_modification_tests.rs | 5 +- 6 files changed, 383 insertions(+), 17 deletions(-) create mode 100644 tests/shovel_destination_uri_modification_tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a19478a..d0cb252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # rabbitmqadmin-ng Change Log +## v2.12.0 (in development) + +### Enhancements + + * `shovel disable_tls_peer_verification_for_all_source_uris` is a new command that disables TLS peer verification + for all shovel source URIs. + + **Important**: this command should **only** be used to undo incorrect shovel source URIs, after a bad deployment, for example, + if [peer verification](https://www.rabbitmq.com/docs/ssl#peer-verification) was enabled before certificates and keys were + deployed. + + * `shovel disable_tls_peer_verification_for_all_source_uris` is a new command that disables TLS peer verification + for all shovel source URIs. + + **Important**: this command should **only** be used to undo incorrect shovel destination URIs (see above). + ## v2.11.0 (Sep 22, 2025) ### Enhancements @@ -7,8 +23,9 @@ * `federation disable_tls_peer_verification_for_all_upstreams` is a new command that disables TLS peer verification for all federation upstreams. - **Important**: this command should **only** be used to undo incorrect federation upstream URI, for example, - if [peer verification](https://www.rabbitmq.com/docs/ssl#peer-verification) was enabled prematurely. + **Important**: this command should **only** be used to correct federation upstream URI after a bad deployment, for example, + if [peer verification](https://www.rabbitmq.com/docs/ssl#peer-verification) was enabled before certificates and keys were + deployed. ### Upgrades diff --git a/src/cli.rs b/src/cli.rs index c5b9ad3..22e5601 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2892,7 +2892,7 @@ pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { +pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7] { let list_all_cmd = Command::new("list_all") .long_about("Lists shovels in all virtual hosts") .after_help(color_print::cformat!( @@ -3043,9 +3043,23 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 let disable_tls_peer_verification_cmd = Command::new("disable_tls_peer_verification_for_all_source_uris") // shorter, displayed in the shovels group's help - .about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all shovels.")) + .about(color_print::cstr!("Use only in case of emergency. Disables TLS peer verification for all shovels.")) // longer, displayed in the command's help - .long_about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all shovels by updating their source and destination URIs' 'verify' parameter.")) + .long_about(color_print::cstr!("Use only in case of emergency. Disables TLS peer verification for all shovels by updating their source and destination URIs' 'verify' parameter.")) + .after_help(color_print::cformat!( + r#"Doc guides: + + * {} + * {} + * {}"#, + SHOVEL_GUIDE_URL, + TLS_GUIDE_URL, + "https://www.rabbitmq.com/docs/shovel#tls-connections" + )); + + let disable_tls_peer_verification_dest_cmd = Command::new("disable_tls_peer_verification_for_all_destination_uris") + .about(color_print::cstr!("Use only in case of emergency. Disables TLS peer verification for all shovel destination URIs.")) + .long_about(color_print::cstr!("Use only in case of emergency. Disables TLS peer verification for all shovel destination URIs by updating their 'verify' parameter.")) .after_help(color_print::cformat!( r#"Doc guides: @@ -3064,6 +3078,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 declare_10_cmd, delete_cmd, disable_tls_peer_verification_cmd, + disable_tls_peer_verification_dest_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } @@ -3404,9 +3419,9 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 let disable_tls_peer_verification_cmd = Command::new("disable_tls_peer_verification_for_all_upstreams") // shorter, displayed in the federation group's help - .about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all federation upstreams.")) + .about(color_print::cstr!("Use only in case of emergency. Disables TLS peer verification for all federation upstreams.")) // longer, displayed in the command's help - .long_about(color_print::cstr!("Use only in emergency cases. Disables TLS peer verification for all federation upstreams by updating their 'verify' parameter.")) + .long_about(color_print::cstr!("Use only in case of emergency. Disables TLS peer verification for all federation upstreams by updating their 'verify' parameter.")) .after_help(color_print::cformat!( r#"Doc guides: diff --git a/src/commands.rs b/src/commands.rs index de745da..a4fa571 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -736,7 +736,6 @@ pub fn disable_tls_peer_verification_for_all_federation_upstreams( pub fn disable_tls_peer_verification_for_all_shovels( client: APIClient, ) -> Result<(), CommandRunError> { - // Get all runtime parameters of "shovel" component let all_params = client.list_runtime_parameters()?; let shovel_params: Vec<_> = all_params .into_iter() @@ -744,26 +743,56 @@ pub fn disable_tls_peer_verification_for_all_shovels( .collect(); for param in shovel_params { - // Convert the runtime parameter to OwnedShovelParams for easier manipulation let owned_params = match OwnedShovelParams::try_from(param.clone()) { Ok(params) => params, - Err(_) => continue, // Skip malformed shovel parameters + Err(_) => continue, }; let original_source_uri = &owned_params.source_uri; - let original_destination_uri = &owned_params.destination_uri; - // Skip shovels with empty URIs - if original_source_uri.is_empty() || original_destination_uri.is_empty() { + if original_source_uri.is_empty() { continue; } let updated_source_uri = disable_tls_peer_verification(original_source_uri)?; - let updated_destination_uri = disable_tls_peer_verification(original_destination_uri)?; - if original_source_uri != &updated_source_uri || original_destination_uri != &updated_destination_uri { + if original_source_uri != &updated_source_uri { let mut updated_params = owned_params; updated_params.source_uri = updated_source_uri; + + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + } + } + + Ok(()) +} + +pub fn disable_tls_peer_verification_for_all_destination_uris( + client: APIClient, +) -> Result<(), CommandRunError> { + let all_params = client.list_runtime_parameters()?; + let shovel_params: Vec<_> = all_params + .into_iter() + .filter(|p| p.component == "shovel") + .collect(); + + for param in shovel_params { + let owned_params = match OwnedShovelParams::try_from(param.clone()) { + Ok(params) => params, + Err(_) => continue, + }; + + let original_destination_uri = &owned_params.destination_uri; + + if original_destination_uri.is_empty() { + continue; + } + + let updated_destination_uri = disable_tls_peer_verification(original_destination_uri)?; + + if original_destination_uri != &updated_destination_uri { + let mut updated_params = owned_params; updated_params.destination_uri = updated_destination_uri; let param = RuntimeParameterDefinition::from(&updated_params); diff --git a/src/main.rs b/src/main.rs index 7bf4133..5aa837c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1075,6 +1075,10 @@ fn dispatch_common_subcommand( let result = commands::disable_tls_peer_verification_for_all_shovels(client); res_handler.no_output_on_success(result); } + ("shovels", "disable_tls_peer_verification_for_all_destination_uris") => { + let result = commands::disable_tls_peer_verification_for_all_destination_uris(client); + res_handler.no_output_on_success(result); + } ("streams", "declare") => { let result = commands::declare_stream(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/shovel_destination_uri_modification_tests.rs b/tests/shovel_destination_uri_modification_tests.rs new file mode 100644 index 0000000..f93b480 --- /dev/null +++ b/tests/shovel_destination_uri_modification_tests.rs @@ -0,0 +1,300 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_helpers; + +use crate::test_helpers::*; +use predicates::prelude::*; +use std::str; + +#[test] +fn test_disable_tls_peer_verification_for_all_destination_uris_basic() -> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_destination_uris_basic"; + let shovel_name = "test_basic_dest_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_source = format!("amqps://localhost:5671/{}", vh); + let amqps_destination = format!("amqps://localhost:5671/{}", vh); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &amqps_source, + "--destination-uri", + &amqps_destination, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + + run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_destination_uris", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param"; + let shovel_name = "test_existing_dest_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_base = format!("amqps://localhost:5671/{}", vh); + let source_uri = format!("{}?source_key=abc&heartbeat=60", amqps_base); + let dest_uri = format!( + "{}?dest_key1=xyz&verify=verify_peer&cacertfile=/path/to/dest_ca.pem&dest_key2=abc&certfile=/path/to/dest_client.pem&keyfile=/path/to/dest_client.key&server_name_indication=dest.example.com&dest_param=value456&another_dest_param=def&heartbeat=30", + amqps_base + ); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &source_uri, + "--destination-uri", + &dest_uri, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + await_metric_emission(500); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_destination_uris", + ]); + + let output = run_succeeds(["parameters", "list_all"]); + let stdout = output.stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("dest_key1=xyz")) + .stdout(predicate::str::contains("dest_key2=abc")) + .stdout(predicate::str::contains("cacertfile=/path/to/dest_ca.pem")) + .stdout(predicate::str::contains("certfile=/path/to/dest_client.pem")) + .stdout(predicate::str::contains("keyfile=/path/to/dest_client.key")) + .stdout(predicate::str::contains("server_name_indication=dest.example.com")) + .stdout(predicate::str::contains("dest_param=value456")) + .stdout(predicate::str::contains("another_dest_param=def")) + .stdout(predicate::str::contains("heartbeat=30")); + + let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); + let lines: Vec<&str> = output_str.lines().collect(); + let mut shovel_section = String::new(); + let mut in_our_shovel = false; + + for line in lines { + if line.contains(&shovel_name) { + in_our_shovel = true; + } + if in_our_shovel { + shovel_section.push_str(line); + shovel_section.push('\n'); + if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + break; + } + } + } + + let dest_uri_lines: Vec<&str> = shovel_section.lines() + .filter(|line| line.contains("dest-uri")) + .collect(); + assert!(!dest_uri_lines.is_empty(), "Could not find dest-uri in shovel section"); + + let dest_uri_content = dest_uri_lines[0]; + let dest_verify_count = dest_uri_content.matches("verify=").count(); + assert_eq!(dest_verify_count, 1, "Expected exactly 1 verify parameter in destination URI, found {}", dest_verify_count); + + assert!(dest_uri_content.contains("verify=verify_none"), "Destination URI should contain verify=verify_none"); + assert!(!dest_uri_content.contains("verify=verify_peer"), "Destination URI should not contain verify=verify_peer"); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() -> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_destination_uris_amqp10"; + let shovel_name = "test_amqp10_dest_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_source = format!("amqps://localhost:5671/{}", vh); + let amqps_destination = format!("amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", vh); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp10", + "--name", + shovel_name, + "--source-uri", + &amqps_source, + "--destination-uri", + &amqps_destination, + "--source-address", + "source.address", + "--destination-address", + "dest.address", + "--ack-mode", + "on-confirm", + ]); + + run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_destination_uris", + ]); + + run_succeeds(["parameters", "list_all"]) + .stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("certfile=/path/to/client.pem")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_params() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_params"; + let shovel_name = "test_dummy_dest_params_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_base = format!("amqps://localhost:5671/{}", vh); + let source_uri = format!("{}?source_abc=123&source_heartbeat=5", amqps_base); + let dest_uri = format!("{}?dest_xyz=456&dest_heartbeat=10&channel_max=100&another_dummy=example", amqps_base); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &source_uri, + "--destination-uri", + &dest_uri, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + await_metric_emission(500); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_destination_uris", + ]); + + let output = run_succeeds(["parameters", "list_all"]); + let stdout = output.stdout(predicate::str::contains(shovel_name)) + .stdout(predicate::str::contains("verify=verify_none")) + .stdout(predicate::str::contains("dest_xyz=456")) + .stdout(predicate::str::contains("dest_heartbeat=10")) + .stdout(predicate::str::contains("channel_max=100")) + .stdout(predicate::str::contains("another_dummy=example")); + + let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); + let lines: Vec<&str> = output_str.lines().collect(); + let mut shovel_section = String::new(); + let mut in_our_shovel = false; + + for line in lines { + if line.contains(&shovel_name) { + in_our_shovel = true; + } + if in_our_shovel { + shovel_section.push_str(line); + shovel_section.push('\n'); + if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + break; + } + } + } + + let dest_uri_lines: Vec<&str> = shovel_section.lines() + .filter(|line| line.contains("dest-uri")) + .collect(); + assert!(!dest_uri_lines.is_empty(), "Could not find dest-uri in shovel section"); + + let dest_uri_content = dest_uri_lines[0]; + let dest_verify_count = dest_uri_content.matches("verify=").count(); + assert_eq!(dest_verify_count, 1, "Expected exactly 1 verify parameter in destination URI, found {}", dest_verify_count); + + assert!(dest_uri_content.contains("verify=verify_none"), "Destination URI should contain verify=verify_none"); + assert!(!dest_uri_content.contains("verify=verify_peer"), "Destination URI should not contain verify=verify_peer"); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_disable_tls_peer_verification_for_all_destination_uris_no_shovels() +-> Result<(), Box> { + let vh = "test_disable_tls_peer_verification_for_all_destination_uris_no_shovels"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + run_succeeds([ + "shovels", + "disable_tls_peer_verification_for_all_destination_uris", + ]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} \ No newline at end of file diff --git a/tests/shovel_source_uri_modification_tests.rs b/tests/shovel_source_uri_modification_tests.rs index ed87c00..361da53 100644 --- a/tests/shovel_source_uri_modification_tests.rs +++ b/tests/shovel_source_uri_modification_tests.rs @@ -16,6 +16,7 @@ mod test_helpers; use crate::test_helpers::*; use predicates::prelude::*; +use std::str; #[test] fn test_disable_tls_peer_verification_for_all_shovels_basic() -> Result<(), Box> { @@ -129,7 +130,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param .stdout(predicate::str::contains("another_dest_param=def")) .stdout(predicate::str::contains("heartbeat=30")); - let output_str = std::str::from_utf8(&stdout.get_output().stdout).unwrap(); + let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); let lines: Vec<&str> = output_str.lines().collect(); let mut shovel_section = String::new(); let mut in_our_shovel = false; @@ -358,7 +359,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() .stdout(predicate::str::contains("channel_max=100")) .stdout(predicate::str::contains("another_dummy=example")); - let output_str = std::str::from_utf8(&stdout.get_output().stdout).unwrap(); + let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); let lines: Vec<&str> = output_str.lines().collect(); let mut shovel_section = String::new(); let mut in_our_shovel = false; From ff751dd5264972b1f0bd05bfcc53992c32b84c01 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 18:23:12 -0400 Subject: [PATCH 253/320] Rename a function --- src/commands.rs | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index a4fa571..499d16f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -733,7 +733,7 @@ pub fn disable_tls_peer_verification_for_all_federation_upstreams( Ok(()) } -pub fn disable_tls_peer_verification_for_all_shovels( +pub fn disable_tls_peer_verification_for_all_source_uris( client: APIClient, ) -> Result<(), CommandRunError> { let all_params = client.list_runtime_parameters()?; diff --git a/src/main.rs b/src/main.rs index 5aa837c..2ae2bea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1072,7 +1072,7 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("shovels", "disable_tls_peer_verification_for_all_source_uris") => { - let result = commands::disable_tls_peer_verification_for_all_shovels(client); + let result = commands::disable_tls_peer_verification_for_all_source_uris(client); res_handler.no_output_on_success(result); } ("shovels", "disable_tls_peer_verification_for_all_destination_uris") => { From c33f2309dc8eff88f1e767d593fcacad123e9fcd Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 22 Sep 2025 18:50:42 -0400 Subject: [PATCH 254/320] cargo fmt --- src/commands.rs | 2 +- ...ovel_destination_uri_modification_tests.rs | 91 +++++++++--- tests/shovel_source_uri_modification_tests.rs | 130 ++++++++++++++---- 3 files changed, 170 insertions(+), 53 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 499d16f..1b5b37b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -27,6 +27,7 @@ use rabbitmq_http_client::commons::{ExchangeType, SupportedProtocol}; use rabbitmq_http_client::commons::{MessageTransferAcknowledgementMode, UserLimitTarget}; use rabbitmq_http_client::commons::{PolicyTarget, VirtualHostLimitTarget}; use rabbitmq_http_client::password_hashing::{HashingAlgorithm, HashingError}; +use rabbitmq_http_client::requests::shovels::OwnedShovelParams; use rabbitmq_http_client::requests::{ Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, @@ -34,7 +35,6 @@ use rabbitmq_http_client::requests::{ FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, }; -use rabbitmq_http_client::requests::shovels::OwnedShovelParams; use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; use rabbitmq_http_client::{password_hashing, requests, responses}; diff --git a/tests/shovel_destination_uri_modification_tests.rs b/tests/shovel_destination_uri_modification_tests.rs index f93b480..899b370 100644 --- a/tests/shovel_destination_uri_modification_tests.rs +++ b/tests/shovel_destination_uri_modification_tests.rs @@ -19,7 +19,8 @@ use predicates::prelude::*; use std::str; #[test] -fn test_disable_tls_peer_verification_for_all_destination_uris_basic() -> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_destination_uris_basic() +-> Result<(), Box> { let vh = "test_disable_tls_peer_verification_for_all_destination_uris_basic"; let shovel_name = "test_basic_dest_shovel"; @@ -67,7 +68,8 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_basic() -> Result #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param"; + let vh = + "test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param"; let shovel_name = "test_existing_dest_shovel"; delete_vhost(vh).ok(); @@ -106,14 +108,19 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_ver ]); let output = run_succeeds(["parameters", "list_all"]); - let stdout = output.stdout(predicate::str::contains(shovel_name)) + let stdout = output + .stdout(predicate::str::contains(shovel_name)) .stdout(predicate::str::contains("verify=verify_none")) .stdout(predicate::str::contains("dest_key1=xyz")) .stdout(predicate::str::contains("dest_key2=abc")) .stdout(predicate::str::contains("cacertfile=/path/to/dest_ca.pem")) - .stdout(predicate::str::contains("certfile=/path/to/dest_client.pem")) + .stdout(predicate::str::contains( + "certfile=/path/to/dest_client.pem", + )) .stdout(predicate::str::contains("keyfile=/path/to/dest_client.key")) - .stdout(predicate::str::contains("server_name_indication=dest.example.com")) + .stdout(predicate::str::contains( + "server_name_indication=dest.example.com", + )) .stdout(predicate::str::contains("dest_param=value456")) .stdout(predicate::str::contains("another_dest_param=def")) .stdout(predicate::str::contains("heartbeat=30")); @@ -130,23 +137,39 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_ver if in_our_shovel { shovel_section.push_str(line); shovel_section.push('\n'); - if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + if line.contains("└─") + || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) + { break; } } } - let dest_uri_lines: Vec<&str> = shovel_section.lines() + let dest_uri_lines: Vec<&str> = shovel_section + .lines() .filter(|line| line.contains("dest-uri")) .collect(); - assert!(!dest_uri_lines.is_empty(), "Could not find dest-uri in shovel section"); + assert!( + !dest_uri_lines.is_empty(), + "Could not find dest-uri in shovel section" + ); let dest_uri_content = dest_uri_lines[0]; let dest_verify_count = dest_uri_content.matches("verify=").count(); - assert_eq!(dest_verify_count, 1, "Expected exactly 1 verify parameter in destination URI, found {}", dest_verify_count); + assert_eq!( + dest_verify_count, 1, + "Expected exactly 1 verify parameter in destination URI, found {}", + dest_verify_count + ); - assert!(dest_uri_content.contains("verify=verify_none"), "Destination URI should contain verify=verify_none"); - assert!(!dest_uri_content.contains("verify=verify_peer"), "Destination URI should not contain verify=verify_peer"); + assert!( + dest_uri_content.contains("verify=verify_none"), + "Destination URI should contain verify=verify_none" + ); + assert!( + !dest_uri_content.contains("verify=verify_peer"), + "Destination URI should not contain verify=verify_peer" + ); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -154,7 +177,8 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_ver } #[test] -fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() -> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() +-> Result<(), Box> { let vh = "test_disable_tls_peer_verification_for_all_destination_uris_amqp10"; let shovel_name = "test_amqp10_dest_shovel"; @@ -162,7 +186,10 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() -> Resul run_succeeds(["declare", "vhost", "--name", vh]); let amqps_source = format!("amqps://localhost:5671/{}", vh); - let amqps_destination = format!("amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", vh); + let amqps_destination = format!( + "amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", + vh + ); run_succeeds([ "-V", @@ -211,7 +238,10 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_ let amqps_base = format!("amqps://localhost:5671/{}", vh); let source_uri = format!("{}?source_abc=123&source_heartbeat=5", amqps_base); - let dest_uri = format!("{}?dest_xyz=456&dest_heartbeat=10&channel_max=100&another_dummy=example", amqps_base); + let dest_uri = format!( + "{}?dest_xyz=456&dest_heartbeat=10&channel_max=100&another_dummy=example", + amqps_base + ); run_succeeds([ "-V", @@ -239,7 +269,8 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_ ]); let output = run_succeeds(["parameters", "list_all"]); - let stdout = output.stdout(predicate::str::contains(shovel_name)) + let stdout = output + .stdout(predicate::str::contains(shovel_name)) .stdout(predicate::str::contains("verify=verify_none")) .stdout(predicate::str::contains("dest_xyz=456")) .stdout(predicate::str::contains("dest_heartbeat=10")) @@ -258,23 +289,39 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_ if in_our_shovel { shovel_section.push_str(line); shovel_section.push('\n'); - if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + if line.contains("└─") + || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) + { break; } } } - let dest_uri_lines: Vec<&str> = shovel_section.lines() + let dest_uri_lines: Vec<&str> = shovel_section + .lines() .filter(|line| line.contains("dest-uri")) .collect(); - assert!(!dest_uri_lines.is_empty(), "Could not find dest-uri in shovel section"); + assert!( + !dest_uri_lines.is_empty(), + "Could not find dest-uri in shovel section" + ); let dest_uri_content = dest_uri_lines[0]; let dest_verify_count = dest_uri_content.matches("verify=").count(); - assert_eq!(dest_verify_count, 1, "Expected exactly 1 verify parameter in destination URI, found {}", dest_verify_count); + assert_eq!( + dest_verify_count, 1, + "Expected exactly 1 verify parameter in destination URI, found {}", + dest_verify_count + ); - assert!(dest_uri_content.contains("verify=verify_none"), "Destination URI should contain verify=verify_none"); - assert!(!dest_uri_content.contains("verify=verify_peer"), "Destination URI should not contain verify=verify_peer"); + assert!( + dest_uri_content.contains("verify=verify_none"), + "Destination URI should contain verify=verify_none" + ); + assert!( + !dest_uri_content.contains("verify=verify_peer"), + "Destination URI should not contain verify=verify_peer" + ); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -297,4 +344,4 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_no_shovels() delete_vhost(vh).expect("failed to delete a virtual host"); Ok(()) -} \ No newline at end of file +} diff --git a/tests/shovel_source_uri_modification_tests.rs b/tests/shovel_source_uri_modification_tests.rs index 361da53..0a728c0 100644 --- a/tests/shovel_source_uri_modification_tests.rs +++ b/tests/shovel_source_uri_modification_tests.rs @@ -19,7 +19,8 @@ use predicates::prelude::*; use std::str; #[test] -fn test_disable_tls_peer_verification_for_all_shovels_basic() -> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_shovels_basic() +-> Result<(), Box> { let vh = "test_disable_tls_peer_verification_for_all_shovels_basic"; let shovel_name = "test_basic_shovel"; @@ -109,23 +110,32 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param ]); let output = run_succeeds(["parameters", "list_all"]); - let stdout = output.stdout(predicate::str::contains(shovel_name)) + let stdout = output + .stdout(predicate::str::contains(shovel_name)) .stdout(predicate::str::contains("verify=verify_none")) .stdout(predicate::str::contains("key1=abc")) .stdout(predicate::str::contains("key2=def")) - .stdout(predicate::str::contains("cacertfile=/path/to/ca_bundle.pem")) + .stdout(predicate::str::contains( + "cacertfile=/path/to/ca_bundle.pem", + )) .stdout(predicate::str::contains("certfile=/path/to/client.pem")) .stdout(predicate::str::contains("keyfile=/path/to/client.key")) - .stdout(predicate::str::contains("server_name_indication=example.com")) + .stdout(predicate::str::contains( + "server_name_indication=example.com", + )) .stdout(predicate::str::contains("custom_param=value123")) .stdout(predicate::str::contains("another_param=xyz")) .stdout(predicate::str::contains("heartbeat=60")) .stdout(predicate::str::contains("dest_key1=xyz")) .stdout(predicate::str::contains("dest_key2=abc")) .stdout(predicate::str::contains("cacertfile=/path/to/dest_ca.pem")) - .stdout(predicate::str::contains("certfile=/path/to/dest_client.pem")) + .stdout(predicate::str::contains( + "certfile=/path/to/dest_client.pem", + )) .stdout(predicate::str::contains("keyfile=/path/to/dest_client.key")) - .stdout(predicate::str::contains("server_name_indication=dest.example.com")) + .stdout(predicate::str::contains( + "server_name_indication=dest.example.com", + )) .stdout(predicate::str::contains("dest_param=value456")) .stdout(predicate::str::contains("another_dest_param=def")) .stdout(predicate::str::contains("heartbeat=30")); @@ -142,23 +152,39 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param if in_our_shovel { shovel_section.push_str(line); shovel_section.push('\n'); - if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + if line.contains("└─") + || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) + { break; } } } - let src_uri_lines: Vec<&str> = shovel_section.lines() + let src_uri_lines: Vec<&str> = shovel_section + .lines() .filter(|line| line.contains("src-uri")) .collect(); - assert!(!src_uri_lines.is_empty(), "Could not find src-uri in shovel section"); + assert!( + !src_uri_lines.is_empty(), + "Could not find src-uri in shovel section" + ); let src_uri_content = src_uri_lines[0]; let src_verify_count = src_uri_content.matches("verify=").count(); - assert_eq!(src_verify_count, 1, "Expected exactly 1 verify parameter in source URI, found {}", src_verify_count); + assert_eq!( + src_verify_count, 1, + "Expected exactly 1 verify parameter in source URI, found {}", + src_verify_count + ); - assert!(src_uri_content.contains("verify=verify_none"), "Source URI should contain verify=verify_none"); - assert!(!src_uri_content.contains("verify=verify_peer"), "Source URI should not contain verify=verify_peer"); + assert!( + src_uri_content.contains("verify=verify_none"), + "Source URI should contain verify=verify_none" + ); + assert!( + !src_uri_content.contains("verify=verify_peer"), + "Source URI should not contain verify=verify_peer" + ); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -166,15 +192,22 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param } #[test] -fn test_disable_tls_peer_verification_for_all_shovels_amqp10() -> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_shovels_amqp10() +-> Result<(), Box> { let vh = "test_disable_tls_peer_verification_for_all_shovels_amqp10"; let shovel_name = "test_amqp10_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_source = format!("amqps://localhost:5671/{}?verify=verify_peer&cacertfile=/path/to/ca.pem", vh); - let amqps_destination = format!("amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", vh); + let amqps_source = format!( + "amqps://localhost:5671/{}?verify=verify_peer&cacertfile=/path/to/ca.pem", + vh + ); + let amqps_destination = format!( + "amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", + vh + ); run_succeeds([ "-V", @@ -224,10 +257,22 @@ fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() run_succeeds(["declare", "vhost", "--name", vh]); let amqps_base = format!("amqps://localhost:5671/{}", vh); - let uri_091_source = format!("{}?protocol_param=091&verify=verify_peer&certfile=/path/to/091.pem", amqps_base); - let uri_091_dest = format!("{}?protocol_param=091_dest&verify=verify_peer&keyfile=/path/to/091.key", amqps_base); - let uri_10_source = format!("{}?protocol_param=10&verify=verify_peer&cacertfile=/path/to/10.pem", amqps_base); - let uri_10_dest = format!("{}?protocol_param=10_dest&verify=verify_peer&server_name_indication=amqp10.example.com", amqps_base); + let uri_091_source = format!( + "{}?protocol_param=091&verify=verify_peer&certfile=/path/to/091.pem", + amqps_base + ); + let uri_091_dest = format!( + "{}?protocol_param=091_dest&verify=verify_peer&keyfile=/path/to/091.key", + amqps_base + ); + let uri_10_source = format!( + "{}?protocol_param=10&verify=verify_peer&cacertfile=/path/to/10.pem", + amqps_base + ); + let uri_10_dest = format!( + "{}?protocol_param=10_dest&verify=verify_peer&server_name_indication=amqp10.example.com", + amqps_base + ); run_succeeds([ "-V", @@ -283,7 +328,9 @@ fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() .stdout(predicate::str::contains("certfile=/path/to/091.pem")) .stdout(predicate::str::contains("keyfile=/path/to/091.key")) .stdout(predicate::str::contains("cacertfile=/path/to/10.pem")) - .stdout(predicate::str::contains("server_name_indication=amqp10.example.com")) + .stdout(predicate::str::contains( + "server_name_indication=amqp10.example.com", + )) .stdout(predicate::str::contains("verify=verify_none")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -319,8 +366,14 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() run_succeeds(["declare", "vhost", "--name", vh]); let amqps_base = format!("amqps://localhost:5671/{}", vh); - let source_uri = format!("{}?abc=123&heartbeat=5&connection_timeout=30&dummy_param=test_value", amqps_base); - let dest_uri = format!("{}?xyz=456&heartbeat=10&channel_max=100&another_dummy=example", amqps_base); + let source_uri = format!( + "{}?abc=123&heartbeat=5&connection_timeout=30&dummy_param=test_value", + amqps_base + ); + let dest_uri = format!( + "{}?xyz=456&heartbeat=10&channel_max=100&another_dummy=example", + amqps_base + ); run_succeeds([ "-V", @@ -348,7 +401,8 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() ]); let output = run_succeeds(["parameters", "list_all"]); - let stdout = output.stdout(predicate::str::contains(shovel_name)) + let stdout = output + .stdout(predicate::str::contains(shovel_name)) .stdout(predicate::str::contains("verify=verify_none")) .stdout(predicate::str::contains("abc=123")) .stdout(predicate::str::contains("heartbeat=5")) @@ -371,25 +425,41 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() if in_our_shovel { shovel_section.push_str(line); shovel_section.push('\n'); - if line.contains("└─") || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) { + if line.contains("└─") + || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) + { break; } } } - let src_uri_lines: Vec<&str> = shovel_section.lines() + let src_uri_lines: Vec<&str> = shovel_section + .lines() .filter(|line| line.contains("src-uri")) .collect(); - assert!(!src_uri_lines.is_empty(), "Could not find src-uri in shovel section"); + assert!( + !src_uri_lines.is_empty(), + "Could not find src-uri in shovel section" + ); let src_uri_content = src_uri_lines[0]; let src_verify_count = src_uri_content.matches("verify=").count(); - assert_eq!(src_verify_count, 1, "Expected exactly 1 verify parameter in source URI, found {}", src_verify_count); + assert_eq!( + src_verify_count, 1, + "Expected exactly 1 verify parameter in source URI, found {}", + src_verify_count + ); - assert!(src_uri_content.contains("verify=verify_none"), "Source URI should contain verify=verify_none"); - assert!(!src_uri_content.contains("verify=verify_peer"), "Source URI should not contain verify=verify_peer"); + assert!( + src_uri_content.contains("verify=verify_none"), + "Source URI should contain verify=verify_none" + ); + assert!( + !src_uri_content.contains("verify=verify_peer"), + "Source URI should not contain verify=verify_peer" + ); delete_vhost(vh).expect("failed to delete a virtual host"); Ok(()) -} \ No newline at end of file +} From fe3c9f00d8d19986b4a2ccad3cd174c93898bf2d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 23 Sep 2025 18:00:32 -0400 Subject: [PATCH 255/320] Introduce 'federation enable_tls_peer_verification_for_all_upstreams' --- CHANGELOG.md | 20 ++ src/cli.rs | 38 ++- src/commands.rs | 97 +++++++ src/main.rs | 7 + ...eration_upstream_uri_modification_tests.rs | 267 +++++++++++++++++- 5 files changed, 421 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0cb252..78e9f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ ### Enhancements +* `federation enable_tls_peer_verification_for_all_upstreams` is a new command that enables TLS peer verification + for all federation upstreams: + + ```shell + # Note that the certificate and private key paths below refer + # to the files deployed to the target RabbitMQ node(s), not to the + # local files. + # + # As such, these arguments are command-specific and should not be confused + # with the global `--tls-ca-cert-file`, `--tls-cert-file`, and `--tls-key-file` + # arguments that are used by `rabbitmqadmin` itself to connect to the target node + # over the HTTP API. + rabbitmqadmin federation enable_tls_peer_verification_for_all_upstreams \ + --node-local-ca-certificate-bundle-path /path/to/node/local/ca_bundle.pem \ + --node-local-client-certificate-file-path /path/to/node/local/client_certificate.pem \ + --node-local-client-private-key-file-path /path/to/node/local/client_private_key.pem + ``` + + See [TLS guide](https://www.rabbitmq.com/docs/ssl#peer-verification) and [Federation guide](https://www.rabbitmq.com/docs/federation#tls-connections) to learn more. + * `shovel disable_tls_peer_verification_for_all_source_uris` is a new command that disables TLS peer verification for all shovel source URIs. diff --git a/src/cli.rs b/src/cli.rs index 22e5601..d57885d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3083,7 +3083,7 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7] { +fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 8] { let list_all_upstreams = Command::new("list_all_upstreams") .long_about("Lists federation upstreams in all virtual hosts") .after_help(color_print::cformat!( @@ -3426,6 +3426,41 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 .after_help(color_print::cformat!( r#"Doc guides: + * {} + * {} + * {}"#, + FEDERATION_GUIDE_URL, + TLS_GUIDE_URL, + "https://www.rabbitmq.com/docs/federation#tls-connections" + )); + + let enable_tls_peer_verification_cmd = Command::new("enable_tls_peer_verification_for_all_upstreams") + .about("Enables TLS peer verification for all federation upstreams with provided [RabbitMQ node-local] certificate paths.") + .long_about("Enables TLS peer verification for all federation upstreams by updating their 'verify' parameter and adding [RabbitMQ node-local] certificate and private key file paths.") + .arg( + Arg::new("node_local_ca_certificate_bundle_path") + .long("node-local-ca-certificate-bundle-path") + .help("Path to the CA certificate bundle file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .arg( + Arg::new("node_local_client_certificate_file_path") + .long("node-local-client-certificate-file-path") + .help("Path to the client certificate file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .arg( + Arg::new("node_local_client_private_key_file_path") + .long("node-local-client-private-key-file-path") + .help("Path to the client private key file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .after_help(color_print::cformat!( + r#"Doc guides: + * {} * {} * {}"#, @@ -3442,6 +3477,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 delete_upstream, list_all_links, disable_tls_peer_verification_cmd, + enable_tls_peer_verification_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/commands.rs b/src/commands.rs index 1b5b37b..cb21f95 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -733,6 +733,80 @@ pub fn disable_tls_peer_verification_for_all_federation_upstreams( Ok(()) } +pub fn enable_tls_peer_verification_for_all_federation_upstreams( + client: APIClient, + args: &ArgMatches, +) -> Result<(), CommandRunError> { + let ca_cert_path = args + .get_one::("node_local_ca_certificate_bundle_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_ca_certificate_bundle_path".to_string(), + })?; + let client_cert_path = args + .get_one::("node_local_client_certificate_file_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_client_certificate_file_path".to_string(), + })?; + let client_key_path = args + .get_one::("node_local_client_private_key_file_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_client_private_key_file_path".to_string(), + })?; + + let upstreams = client.list_federation_upstreams()?; + + for upstream in upstreams { + let original_uri = &upstream.uri; + let updated_uri = enable_tls_peer_verification( + original_uri, + ca_cert_path, + client_cert_path, + client_key_path, + )?; + + if original_uri != &updated_uri { + let upstream_params = FederationUpstreamParams { + name: &upstream.name, + vhost: &upstream.vhost, + uri: &updated_uri, + prefetch_count: upstream + .prefetch_count + .unwrap_or(DEFAULT_FEDERATION_PREFETCH), + reconnect_delay: upstream.reconnect_delay.unwrap_or(5), + ack_mode: upstream.ack_mode, + trust_user_id: upstream.trust_user_id.unwrap_or_default(), + bind_using_nowait: upstream.bind_using_nowait, + channel_use_mode: upstream.channel_use_mode, + queue_federation: if upstream.queue.is_some() { + Some(QueueFederationParams { + queue: upstream.queue.as_deref(), + consumer_tag: upstream.consumer_tag.as_deref(), + }) + } else { + None + }, + exchange_federation: if upstream.exchange.is_some() { + Some(ExchangeFederationParams { + exchange: upstream.exchange.as_deref(), + max_hops: upstream.max_hops, + queue_type: upstream.queue_type.unwrap_or(QueueType::Classic), + ttl: upstream.expires, + message_ttl: upstream.message_ttl, + resource_cleanup_mode: upstream.resource_cleanup_mode, + }) + } else { + None + }, + }; + + let param = RuntimeParameterDefinition::from(upstream_params); + client.upsert_runtime_parameter(¶m)?; + } + } + + Ok(()) +} + pub fn disable_tls_peer_verification_for_all_source_uris( client: APIClient, ) -> Result<(), CommandRunError> { @@ -1986,3 +2060,26 @@ fn disable_tls_peer_verification(uri: &str) -> Result { message: format!("Failed to reconstruct (modify) a URI: {}", e), }) } + +fn enable_tls_peer_verification( + uri: &str, + ca_cert_path: &str, + client_cert_path: &str, + client_key_path: &str, +) -> Result { + use rabbitmq_http_client::uris::UriBuilder; + + let ub = UriBuilder::new(uri) + .map_err(|e| CommandRunError::FailureDuringExecution { + message: format!("Could not parse a value as a URI '{}': {}", uri, e), + })? + .with_tls_peer_verification(TlsPeerVerificationMode::Enabled) + .with_ca_cert_file(ca_cert_path) + .with_client_cert_file(client_cert_path) + .with_client_key_file(client_key_path); + + ub.build() + .map_err(|e| CommandRunError::FailureDuringExecution { + message: format!("Failed to reconstruct (modify) a URI: {}", e), + }) +} diff --git a/src/main.rs b/src/main.rs index 2ae2bea..3253cf9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -673,6 +673,13 @@ fn dispatch_common_subcommand( commands::disable_tls_peer_verification_for_all_federation_upstreams(client); res_handler.no_output_on_success(result); } + ("federation", "enable_tls_peer_verification_for_all_upstreams") => { + let result = commands::enable_tls_peer_verification_for_all_federation_upstreams( + client, + second_level_args, + ); + res_handler.no_output_on_success(result); + } ("get", "messages") => { let result = commands::get_messages(client, &vhost, second_level_args); res_handler.tabular_result(result) diff --git a/tests/federation_upstream_uri_modification_tests.rs b/tests/federation_upstream_uri_modification_tests.rs index 6a93c0c..e11794c 100644 --- a/tests/federation_upstream_uri_modification_tests.rs +++ b/tests/federation_upstream_uri_modification_tests.rs @@ -43,14 +43,15 @@ fn test_disable_tls_peer_verification_for_all_upstreams_basic() "classic", ]); - run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(upstream_name)); + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(upstream_name)); run_succeeds([ "federation", "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["parameters", "list_all"]) + run_succeeds(["federation", "list_all_upstreams"]) .stdout(predicate::str::contains(upstream_name)) .stdout(predicate::str::contains("verify=verify_none")); @@ -95,7 +96,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_par "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["parameters", "list_all"]) + run_succeeds(["federation", "list_all_upstreams"]) .stdout(predicate::str::contains(upstream_name)) .stdout(predicate::str::contains("verify=verify_none")) .stdout(predicate::str::contains("key1=abc")) @@ -143,14 +144,15 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic() "test-consumer", ]); - run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(upstream_name)); + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(upstream_name)); run_succeeds([ "federation", "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["parameters", "list_all"]) + run_succeeds(["federation", "list_all_upstreams"]) .stdout(predicate::str::contains(upstream_name)) .stdout(predicate::str::contains("verify=verify_none")); @@ -195,7 +197,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_pa "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["parameters", "list_all"]) + run_succeeds(["federation", "list_all_upstreams"]) .stdout(predicate::str::contains(upstream_name)) .stdout(predicate::str::contains("verify=verify_none")) .stdout(predicate::str::contains("queue_param=test123")) @@ -265,7 +267,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["parameters", "list_all"]) + run_succeeds(["federation", "list_all_upstreams"]) .stdout(predicate::str::contains(exchange_upstream_name)) .stdout(predicate::str::contains(queue_upstream_name)) .stdout(predicate::str::contains("exchange_param=value1")) @@ -278,3 +280,254 @@ fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() Ok(()) } + +#[test] +fn test_enable_tls_peer_verification_for_all_upstreams_basic() +-> Result<(), Box> { + let vh = "test_enable_tls_peer_verification_for_all_upstreams_basic"; + let upstream_name = "test_enable_basic_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_exchanges", + "--name", + upstream_name, + "--uri", + &amqps_endpoint, + "--exchange-name", + "x.fanout", + "--queue-type", + "classic", + ]); + + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(upstream_name)); + + run_succeeds([ + "federation", + "enable_tls_peer_verification_for_all_upstreams", + "--node-local-ca-certificate-bundle-path", + "/etc/ssl/certs/ca_bundle.pem", + "--node-local-client-certificate-file-path", + "/etc/ssl/certs/client.pem", + "--node-local-client-private-key-file-path", + "/etc/ssl/private/client.key", + ]); + + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(upstream_name)) + .stdout(predicate::str::contains("verify=verify_peer")) + .stdout(predicate::str::contains( + "cacertfile=/etc/ssl/certs/ca_bundle.pem", + )) + .stdout(predicate::str::contains( + "certfile=/etc/ssl/certs/client.pem", + )) + .stdout(predicate::str::contains( + "keyfile=/etc/ssl/private/client.key", + )); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_enable_tls_peer_verification_for_all_upstreams_with_existing_params() +-> Result<(), Box> { + let vh = "test_enable_tls_peer_verification_for_all_upstreams_with_existing_params"; + let upstream_name = "test_enable_existing_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let source_uri = format!( + "{}?key1=abc&verify=verify_none&cacertfile=/old/path/ca.pem&key2=def&certfile=/old/path/client.pem&keyfile=/old/path/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", + amqps_endpoint + ); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_exchanges", + "--name", + upstream_name, + "--uri", + &source_uri, + "--exchange-name", + "x.fanout", + "--queue-type", + "classic", + ]); + await_metric_emission(500); + + run_succeeds([ + "federation", + "enable_tls_peer_verification_for_all_upstreams", + "--node-local-ca-certificate-bundle-path", + "/new/path/ca_bundle.pem", + "--node-local-client-certificate-file-path", + "/new/path/client.pem", + "--node-local-client-private-key-file-path", + "/new/path/client.key", + ]); + + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(upstream_name)) + .stdout(predicate::str::contains("verify=verify_peer")) + .stdout(predicate::str::contains("key1=abc")) + .stdout(predicate::str::contains("key2=def")) + .stdout(predicate::str::contains( + "cacertfile=/new/path/ca_bundle.pem", + )) + .stdout(predicate::str::contains("certfile=/new/path/client.pem")) + .stdout(predicate::str::contains("keyfile=/new/path/client.key")) + .stdout(predicate::str::contains( + "server_name_indication=example.com", + )) + .stdout(predicate::str::contains("custom_param=value123")) + .stdout(predicate::str::contains("another_param=xyz")) + .stdout(predicate::str::contains("heartbeat=60")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_enable_tls_peer_verification_for_all_upstreams_queue_federation() +-> Result<(), Box> { + let vh = "test_enable_tls_peer_verification_for_all_upstreams_queue_federation"; + let upstream_name = "test_enable_queue_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_queues", + "--name", + upstream_name, + "--uri", + &amqps_endpoint, + "--queue-name", + "test.queue", + "--consumer-tag", + "test-consumer", + ]); + + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(upstream_name)); + + run_succeeds([ + "federation", + "enable_tls_peer_verification_for_all_upstreams", + "--node-local-ca-certificate-bundle-path", + "/etc/ssl/ca.pem", + "--node-local-client-certificate-file-path", + "/etc/ssl/client.pem", + "--node-local-client-private-key-file-path", + "/etc/ssl/client.key", + ]); + + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(upstream_name)) + .stdout(predicate::str::contains("verify=verify_peer")) + .stdout(predicate::str::contains("cacertfile=/etc/ssl/ca.pem")) + .stdout(predicate::str::contains("certfile=/etc/ssl/client.pem")) + .stdout(predicate::str::contains("keyfile=/etc/ssl/client.key")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_enable_tls_peer_verification_for_all_upstreams_mixed_federation() +-> Result<(), Box> { + let vh = "test_enable_tls_peer_verification_for_all_upstreams_mixed_federation"; + let exchange_upstream_name = "enable_exchange_upstream"; + let queue_upstream_name = "enable_queue_upstream"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let exchange_uri = format!( + "{}?exchange_param=value1&verify=verify_none&old_cert=/old/path.pem", + amqps_endpoint + ); + let queue_uri = format!( + "{}?queue_param=value2&verify=verify_none&old_key=/old/key.pem", + amqps_endpoint + ); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_exchanges", + "--name", + exchange_upstream_name, + "--uri", + &exchange_uri, + "--exchange-name", + "x.federated", + "--queue-type", + "classic", + ]); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream_for_queues", + "--name", + queue_upstream_name, + "--uri", + &queue_uri, + "--queue-name", + "q.federated", + "--consumer-tag", + "mixed-consumer", + ]); + await_metric_emission(500); + + run_succeeds([ + "federation", + "enable_tls_peer_verification_for_all_upstreams", + "--node-local-ca-certificate-bundle-path", + "/path/to/ca.pem", + "--node-local-client-certificate-file-path", + "/path/to/client.pem", + "--node-local-client-private-key-file-path", + "/path/to/client.key", + ]); + + run_succeeds(["federation", "list_all_upstreams"]) + .stdout(predicate::str::contains(exchange_upstream_name)) + .stdout(predicate::str::contains(queue_upstream_name)) + .stdout(predicate::str::contains("exchange_param=value1")) + .stdout(predicate::str::contains("queue_param=value2")) + .stdout(predicate::str::contains("cacertfile=/path/to/ca.pem")) + .stdout(predicate::str::contains("certfile=/path/to/client.pem")) + .stdout(predicate::str::contains("keyfile=/path/to/client.key")) + .stdout(predicate::str::contains("verify=verify_peer")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} From 4f13e210f845168a71f364d1ba593f15ab5068c4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 23 Sep 2025 23:39:15 -0400 Subject: [PATCH 256/320] Bump dependencies --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c53f101..a37c5ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -981,9 +981,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libloading" @@ -1426,9 +1426,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.57.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd35e3d6e15d83bfe4d93e81fe4c8b7f9c9cbfd3e370513c9ae3370f9a4c62af" +checksum = "e2562d855c6713a7570ec103931221cad5f984ad8b149fd01f41ae71fe6c2285" dependencies = [ "backtrace", "percent-encoding", @@ -1987,9 +1987,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", diff --git a/Cargo.toml b/Cargo.toml index 591293f..40f5397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.57.0", features = [ +rabbitmq_http_client = { version = "0.58.0", features = [ "blocking", "tabled", ] } From e99233b2c54f8ecb7e3ffb9148c5178156c50588 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 23 Sep 2025 23:39:34 -0400 Subject: [PATCH 257/320] Make sure that all deletion commands support --idempotently --- CHANGELOG.md | 21 +++++ src/cli.rs | 135 +++++++++++++++++++++++------ src/commands.rs | 70 +++++++++++---- tests/bindings_tests.rs | 128 +++++++++++++++++++++++++++ tests/connections_tests.rs | 21 +++++ tests/exchange_federation_tests.rs | 53 +++++++++++ tests/queues_tests.rs | 29 +++++++ tests/runtime_parameters_tests.rs | 106 ++++++++++++++++++++++ tests/shovel_tests.rs | 113 +++++++++++++++++++++++- tests/streams_tests.rs | 43 +++++++++ 10 files changed, 671 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e9f35..648687f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,27 @@ **Important**: this command should **only** be used to undo incorrect shovel destination URIs (see above). +* All `delete_*` and `clear_*` commands now support the `--idempotently` flag (previously it was just a few): + - `bindings delete` + - `close connection` + - `close user_connections` + - `connections close` + - `connections close_of_user` + - `exchanges delete` + - `exchanges unbind` + - `federation delete_upstream` + - `global_parameters clear` + - `operator_policies delete` + - `parameters clear` + - `policies delete` + - `queues delete` + - `shovels delete` + - `streams delete` + - `users delete` + - `vhosts delete` + +* Updated `delete_binding` to use the new `BindingDeletionParams` struct API + ## v2.11.0 (Sep 22, 2025) ### Enhancements diff --git a/src/cli.rs b/src/cli.rs index d57885d..b48b13a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1206,7 +1206,8 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .required(false) .default_value("{}") .value_parser(value_parser!(String)), - ); + ) + .arg(idempotently_arg.clone()); let parameter_cmd = Command::new("parameter") .about("Clears a runtime parameter") .arg( @@ -1220,13 +1221,17 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .long("component") .help("component (eg. federation-upstream)") .required(true), - ); - let policy_cmd = Command::new("policy").about("Deletes a policy").arg( - Arg::new("name") - .long("name") - .help("policy name") - .required(true), - ); + ) + .arg(idempotently_arg.clone()); + let policy_cmd = Command::new("policy") + .about("Deletes a policy") + .arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ) + .arg(idempotently_arg.clone()); let operator_policy_cmd = Command::new("operator_policy") .about("Deletes an operator policy") .arg( @@ -1234,7 +1239,8 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { .long("name") .help("operator policy name") .required(true), - ); + ) + .arg(idempotently_arg.clone()); let vhost_limit_cmd = Command::new("vhost_limit") .about("delete a vhost limit") .arg( @@ -1297,6 +1303,13 @@ fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { } fn binding_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let declare_cmd = Command::new("declare") .about("Creates a binding between a source exchange and a destination (a queue or an exchange)") .arg( @@ -1365,7 +1378,8 @@ fn binding_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { .required(false) .default_value("{}") .value_parser(value_parser!(String)), - ); + ) + .arg(idempotently_arg.clone()); let list_cmd = Command::new("list").long_about("Lists bindings"); [declare_cmd, delete_cmd, list_cmd] @@ -1507,6 +1521,13 @@ fn streams_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { } fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let list_all_cmd = Command::new("list_all") .long_about("Lists all runtime parameters across all virtual hosts") .after_help(color_print::cformat!( @@ -1578,13 +1599,21 @@ fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5 .long("component") .help("component (eg. federation-upstream)") .required(true), - ); + ) + .arg(idempotently_arg.clone()); [clear_cmd, list_all_cmd, list_cmd, list_in_cmd, set_cmd] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let list_cmd = Command::new("list") .long_about("Lists global runtime parameters") .after_help(color_print::cformat!( @@ -1620,13 +1649,21 @@ fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .long("name") .help("parameter's name") .required(true), - ); + ) + .arg(idempotently_arg.clone()); [clear_cmd, list_cmd, set_cmd] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let declare_cmd = Command::new("declare") .visible_aliases(vec!["update", "set"]) .about("Creates or updates an operator policy") @@ -1679,7 +1716,8 @@ fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .long("name") .help("policy name") .required(true), - ); + ) + .arg(idempotently_arg.clone()); let delete_definition_key_cmd = Command::new("delete_definition_keys") .about("Deletes definition keys from an operator policy, unless it is the only key") @@ -1800,6 +1838,13 @@ fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Com } fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let declare_cmd = Command::new("declare") .visible_aliases(vec!["update", "set"]) .about("Creates or updates a policy") @@ -1889,12 +1934,15 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] POLICY_GUIDE_URL )); - let delete_cmd = Command::new("delete").about("Deletes a policy").arg( - Arg::new("name") - .long("name") - .help("policy name") - .required(true), - ); + let delete_cmd = Command::new("delete") + .about("Deletes a policy") + .arg( + Arg::new("name") + .long("name") + .help("policy name") + .required(true), + ) + .arg(idempotently_arg.clone()); let delete_definition_keys_cmd = Command::new("delete_definition_keys") .about("Deletes a definition key from a policy, unless it is the only key") @@ -2088,6 +2136,13 @@ fn rebalance_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] } fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let close_connection = Command::new("connection") .about("Closes a client connection") .arg( @@ -2095,7 +2150,8 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { .long("name") .help("connection name (identifying string)") .required(true), - ); + ) + .arg(idempotently_arg.clone()); let close_user_connections = Command::new("user_connections") .about("Closes all connections that authenticated with a specific username") .arg( @@ -2104,7 +2160,8 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { .long("username") .help("Name of the user whose connections to close") .required(true), - ); + ) + .arg(idempotently_arg.clone()); [close_connection, close_user_connections] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } @@ -2121,6 +2178,13 @@ fn channels_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] } fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let close_connection = Command::new("close") .about("Closes a client connection") .arg( @@ -2128,7 +2192,8 @@ fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; .long("name") .help("connection name (identifying string)") .required(true), - ); + ) + .arg(idempotently_arg.clone()); let close_user_connections = Command::new("close_of_user") .about("Closes all connections that are authenticated with a specific username") .arg( @@ -2137,7 +2202,8 @@ fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; .long("username") .help("Name of the user whose connections should be closed") .required(true), - ); + ) + .arg(idempotently_arg.clone()); let list_cmd = Command::new("list") .long_about("Lists client connections") .after_help(color_print::cformat!( @@ -2467,7 +2533,8 @@ fn exchanges_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] .required(false) .default_value("{}") .value_parser(value_parser!(String)), - ); + ) + .arg(idempotently_arg.clone()); [bind_cmd, declare_cmd, delete_cmd, list_cmd, unbind_cmd] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } @@ -2893,6 +2960,13 @@ pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { } pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let list_all_cmd = Command::new("list_all") .long_about("Lists shovels in all virtual hosts") .after_help(color_print::cformat!( @@ -3039,7 +3113,8 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 .long("name") .help("shovel name (identifier)") .required(true), - ); + ) + .arg(idempotently_arg.clone()); let disable_tls_peer_verification_cmd = Command::new("disable_tls_peer_verification_for_all_source_uris") // shorter, displayed in the shovels group's help @@ -3084,6 +3159,13 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 } fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 8] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + let list_all_upstreams = Command::new("list_all_upstreams") .long_about("Lists federation upstreams in all virtual hosts") .after_help(color_print::cformat!( @@ -3400,7 +3482,8 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 8 .long("name") .help("upstream name (identifier)") .required(true), - ); + ) + .arg(idempotently_arg.clone()); let list_all_links = Command::new("list_all_links") .long_about("List federation links in all virtual hosts") diff --git a/src/commands.rs b/src/commands.rs index cb21f95..456418a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -31,9 +31,9 @@ use rabbitmq_http_client::requests::shovels::OwnedShovelParams; use rabbitmq_http_client::requests::{ Amqp10ShovelDestinationParams, Amqp10ShovelParams, Amqp10ShovelSourceParams, Amqp091ShovelDestinationParams, Amqp091ShovelParams, Amqp091ShovelSourceParams, - DEFAULT_FEDERATION_PREFETCH, EnforcedLimitParams, ExchangeFederationParams, - FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, FederationUpstreamParams, - PolicyParams, QueueFederationParams, RuntimeParameterDefinition, + BindingDeletionParams, DEFAULT_FEDERATION_PREFETCH, EnforcedLimitParams, + ExchangeFederationParams, FEDERATION_UPSTREAM_COMPONENT, FederationResourceCleanupMode, + FederationUpstreamParams, PolicyParams, QueueFederationParams, RuntimeParameterDefinition, }; use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; @@ -416,8 +416,12 @@ pub fn delete_shovel( command_args: &ArgMatches, ) -> ClientResult<()> { let name = command_args.get_one::("name").cloned().unwrap(); + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); - client.delete_shovel(vhost, &name, true) + client.delete_shovel(vhost, &name, idempotently) } // @@ -678,7 +682,11 @@ pub fn delete_federation_upstream( command_args: &ArgMatches, ) -> ClientResult<()> { let name = command_args.get_one::("name").cloned().unwrap(); - client.clear_runtime_parameter(FEDERATION_UPSTREAM_COMPONENT, vhost, &name) + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); + client.clear_runtime_parameter(FEDERATION_UPSTREAM_COMPONENT, vhost, &name, idempotently) } pub fn disable_tls_peer_verification_for_all_federation_upstreams( @@ -1059,8 +1067,12 @@ pub fn delete_parameter( ) -> ClientResult<()> { let component = command_args.get_one::("component").unwrap(); let name = command_args.get_one::("name").unwrap(); + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); - client.clear_runtime_parameter(component, vhost, name) + client.clear_runtime_parameter(component, vhost, name, idempotently) } pub fn delete_global_parameter(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { @@ -1669,15 +1681,21 @@ pub fn delete_binding( let arguments = command_args.get_one::("arguments").unwrap(); let parsed_arguments = parse_json_from_arg(arguments)?; + let params = BindingDeletionParams { + virtual_host: vhost, + source, + destination, + destination_type: BindingDestinationType::from(destination_type.clone()), + routing_key, + arguments: parsed_arguments, + }; + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); + client - .delete_binding( - vhost, - source, - destination, - BindingDestinationType::from(destination_type.clone()), - routing_key, - parsed_arguments, - ) + .delete_binding(¶ms, idempotently) .map(|_| ()) .map_err(Into::into) } @@ -1703,7 +1721,11 @@ pub fn delete_policy( ) -> ClientResult<()> { // the flag is required let name = command_args.get_one::("name").unwrap(); - client.delete_policy(vhost, name) + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); + client.delete_policy(vhost, name, idempotently) } pub fn delete_operator_policy( @@ -1713,7 +1735,11 @@ pub fn delete_operator_policy( ) -> ClientResult<()> { // the flag is required let name = command_args.get_one::("name").unwrap(); - client.delete_operator_policy(vhost, name) + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); + client.delete_operator_policy(vhost, name, idempotently) } pub fn purge_queue(client: APIClient, vhost: &str, command_args: &ArgMatches) -> ClientResult<()> { @@ -1758,13 +1784,21 @@ pub fn health_check_protocol_listener( pub fn close_connection(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { // the flag is required let name = command_args.get_one::("name").unwrap(); - client.close_connection(name, Some("closed via rabbitmqadmin v2")) + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); + client.close_connection(name, Some("closed via rabbitmqadmin v2"), idempotently) } pub fn close_user_connections(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { // the flag is required let username = command_args.get_one::("username").unwrap(); - client.close_user_connections(username, Some("closed via rabbitmqadmin v2")) + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); + client.close_user_connections(username, Some("closed via rabbitmqadmin v2"), idempotently) } pub fn rebalance_queues(client: APIClient) -> ClientResult<()> { diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index 2284174..b8d44cd 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -199,3 +199,131 @@ fn test_bindings_list() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_bindings_delete_idempotently() -> Result<(), Box> { + let vh = "bindings.delete.idempotently.1"; + let source_ex = "test_source_exchange"; + let dest_queue = "test_dest_queue"; + let routing_key = "test.routing.key"; + + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); + + run_succeeds([ + "-V", + vh, + "bindings", + "delete", + "--source", + source_ex, + "--destination-type", + "queue", + "--destination", + dest_queue, + "--routing-key", + routing_key, + "--idempotently", + ]); + + run_succeeds([ + "-V", vh, "declare", "exchange", "--name", source_ex, "--type", "direct", + ]); + run_succeeds([ + "-V", vh, "declare", "queue", "--name", dest_queue, "--type", "classic", + ]); + + run_succeeds([ + "-V", + vh, + "bindings", + "declare", + "--source", + source_ex, + "--destination-type", + "queue", + "--destination", + dest_queue, + "--routing-key", + routing_key, + ]); + + run_succeeds([ + "-V", + vh, + "bindings", + "delete", + "--source", + source_ex, + "--destination-type", + "queue", + "--destination", + dest_queue, + "--routing-key", + routing_key, + ]); + + run_succeeds([ + "-V", + vh, + "bindings", + "delete", + "--source", + source_ex, + "--destination-type", + "queue", + "--destination", + dest_queue, + "--routing-key", + routing_key, + "--idempotently", + ]); + + run_succeeds([ + "-V", + vh, + "bindings", + "declare", + "--source", + source_ex, + "--destination-type", + "queue", + "--destination", + dest_queue, + "--routing-key", + routing_key, + ]); + run_succeeds([ + "-V", + vh, + "delete", + "binding", + "--source", + source_ex, + "--destination-type", + "queue", + "--destination", + dest_queue, + "--routing-key", + routing_key, + ]); + run_succeeds([ + "-V", + vh, + "delete", + "binding", + "--source", + source_ex, + "--destination-type", + "queue", + "--destination", + dest_queue, + "--routing-key", + routing_key, + "--idempotently", + ]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} diff --git a/tests/connections_tests.rs b/tests/connections_tests.rs index 2a0f26c..f3de04c 100644 --- a/tests/connections_tests.rs +++ b/tests/connections_tests.rs @@ -63,3 +63,24 @@ fn test_list_user_connections2() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_connections_close_idempotently() -> Result<(), Box> { + run_succeeds([ + "connections", + "close", + "--name", + "non-existent-connection-12345", + "--idempotently", + ]); + + run_succeeds([ + "connections", + "close_of_user", + "--username", + "non-existent-user-12345", + "--idempotently", + ]); + + Ok(()) +} diff --git a/tests/exchange_federation_tests.rs b/tests/exchange_federation_tests.rs index ef89b0e..7f2db8b 100644 --- a/tests/exchange_federation_tests.rs +++ b/tests/exchange_federation_tests.rs @@ -360,3 +360,56 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() Ok(()) } + +#[test] +fn test_federation_delete_upstream_idempotently() -> Result<(), Box> { + let vh = "federation.delete.upstream.idempotently.1"; + let upstream_name = "test_upstream_delete_idempotently"; + + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); + + run_succeeds([ + "-V", + vh, + "federation", + "delete_upstream", + "--name", + upstream_name, + "--idempotently", + ]); + + run_succeeds([ + "-V", + vh, + "federation", + "declare_upstream", + "--name", + upstream_name, + "--uri", + "amqp://guest@localhost", + ]); + + run_succeeds([ + "-V", + vh, + "federation", + "delete_upstream", + "--name", + upstream_name, + ]); + + run_succeeds([ + "-V", + vh, + "federation", + "delete_upstream", + "--name", + upstream_name, + "--idempotently", + ]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} diff --git a/tests/queues_tests.rs b/tests/queues_tests.rs index 7aab892..a4990d0 100644 --- a/tests/queues_tests.rs +++ b/tests/queues_tests.rs @@ -110,3 +110,32 @@ fn queues_lists() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_queues_delete_idempotently() -> Result<(), Box> { + let vh = "queues.delete.idempotently.1"; + let q = "test_queue_delete_idempotently"; + + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); + + run_succeeds(["-V", vh, "queues", "delete", "--name", q, "--idempotently"]); + + run_succeeds([ + "-V", vh, "declare", "queue", "--name", q, "--type", "classic", + ]); + + run_succeeds(["-V", vh, "queues", "delete", "--name", q]); + + run_succeeds(["-V", vh, "queues", "delete", "--name", q, "--idempotently"]); + + run_succeeds([ + "declare", "queue", "-V", vh, "--name", q, "--type", "classic", + ]); + run_succeeds(["delete", "queue", "-V", vh, "--name", q]); + run_succeeds(["delete", "queue", "-V", vh, "--name", q, "--idempotently"]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index f02ebb6..c033aa5 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -162,3 +162,109 @@ fn test_global_runtime_parameters_cmd_group() -> Result<(), Box Result<(), Box> { + let vh = "parameters.clear.idempotently.1"; + let param_name = "test_param_delete_idempotently"; + let component = "federation-upstream"; + + // Create vhost + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); + + // Try clearing a non-existent parameter with --idempotently (should succeed) + run_succeeds([ + "-V", + vh, + "parameters", + "clear", + "--name", + param_name, + "--component", + component, + "--idempotently", + ]); + + // Set the parameter + run_succeeds([ + "-V", + vh, + "parameters", + "set", + "--name", + param_name, + "--component", + component, + "--value", + r#"{"uri": "amqp://localhost"}"#, + ]); + + // Clear it normally + run_succeeds([ + "-V", + vh, + "parameters", + "clear", + "--name", + param_name, + "--component", + component, + ]); + + // Try clearing it again with --idempotently (should succeed) + run_succeeds([ + "-V", + vh, + "parameters", + "clear", + "--name", + param_name, + "--component", + component, + "--idempotently", + ]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_global_parameters_clear_idempotently() -> Result<(), Box> { + let param_name = "test_global_param_delete_idempotently"; + + // Set the global parameter first + run_succeeds([ + "global_parameters", + "set", + "--name", + param_name, + "--value", + r#"{"test": "value"}"#, + ]); + + // Clear it normally + run_succeeds(["global_parameters", "clear", "--name", param_name]); + + // Set it again + run_succeeds([ + "global_parameters", + "set", + "--name", + param_name, + "--value", + r#"{"test": "value2"}"#, + ]); + + // Clear it with --idempotently (should succeed) + run_succeeds([ + "global_parameters", + "clear", + "--name", + param_name, + "--idempotently", + ]); + + Ok(()) +} diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index 34bd9d9..ae7f842 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -115,9 +115,25 @@ fn test_shovel_declaration_with_overlapping_destination_types() ]) .stderr(predicate::str::contains("cannot be used with")); - run_succeeds(["-V", vh, "shovels", "delete", "--name", name]); + run_succeeds([ + "-V", + vh, + "shovels", + "delete", + "--name", + name, + "--idempotently", + ]); - run_succeeds(["-V", vh, "shovels", "delete", "--name", name]); + run_succeeds([ + "-V", + vh, + "shovels", + "delete", + "--name", + name, + "--idempotently", + ]); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -172,7 +188,15 @@ fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box Result<(), Box Result<(), Box> { + let vh = "shovels.delete.idempotently.1"; + let shovel_name = "test_shovel_delete_idempotently"; + + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); + + run_succeeds([ + "-V", + vh, + "shovels", + "delete", + "--name", + shovel_name, + "--idempotently", + ]); + + let amqp_endpoint = amqp_endpoint_with_vhost(vh); + let src_q = "test_src_queue"; + let dest_q = "test_dest_queue"; + + run_succeeds([ + "-V", vh, "declare", "queue", "--name", src_q, "--type", "classic", + ]); + run_succeeds([ + "-V", vh, "declare", "queue", "--name", dest_q, "--type", "classic", + ]); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &amqp_endpoint, + "--destination-uri", + &amqp_endpoint, + "--source-queue", + src_q, + "--destination-queue", + dest_q, + ]); + + run_succeeds(["-V", vh, "shovels", "delete", "--name", shovel_name]); + + run_succeeds([ + "-V", + vh, + "shovels", + "delete", + "--name", + shovel_name, + "--idempotently", + ]); + + run_succeeds([ + "-V", + vh, + "delete", + "shovel", + "--name", + shovel_name, + "--idempotently", + ]); delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/streams_tests.rs b/tests/streams_tests.rs index b6d0dd4..f207d58 100644 --- a/tests/streams_tests.rs +++ b/tests/streams_tests.rs @@ -132,3 +132,46 @@ fn streams_list() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_streams_delete_idempotently() -> Result<(), Box> { + let vh = "streams.delete.idempotently.1"; + let s = "test_stream_delete_idempotently"; + + delete_vhost(vh).expect("failed to delete a virtual host"); + run_succeeds(["declare", "vhost", "--name", vh]); + + run_succeeds(["-V", vh, "streams", "delete", "--name", s, "--idempotently"]); + + run_succeeds([ + "-V", + vh, + "declare", + "stream", + "--name", + s, + "--expiration", + "2D", + ]); + + run_succeeds(["-V", vh, "streams", "delete", "--name", s]); + + run_succeeds(["-V", vh, "streams", "delete", "--name", s, "--idempotently"]); + + run_succeeds([ + "declare", + "stream", + "-V", + vh, + "--name", + s, + "--expiration", + "2D", + ]); + run_succeeds(["delete", "stream", "-V", vh, "--name", s]); + run_succeeds(["delete", "stream", "-V", vh, "--name", s, "--idempotently"]); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} From 505836ce323840e3b84acbc8a0b8d6d0332b149a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 23 Sep 2025 23:50:54 -0400 Subject: [PATCH 258/320] 2.12.0 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 648687f..5914290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # rabbitmqadmin-ng Change Log -## v2.12.0 (in development) +## v2.13.0 (in development) + +No changes yet. + +## v2.12.0 (Sep 23, 2025) ### Enhancements From 09906690077c538dd84592e115b63d28ca27e766 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 23 Sep 2025 23:51:23 -0400 Subject: [PATCH 259/320] 2.12.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a37c5ef..ab8ece2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1450,7 +1450,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.11.0" +version = "2.12.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 40f5397..ca43aed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.11.0" +version = "2.12.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From a2dc80a835096d0630af344de4f20cf9e0b53783 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 24 Sep 2025 00:56:05 -0400 Subject: [PATCH 260/320] Bump dev version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab8ece2..1ce6ff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1450,7 +1450,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.12.0" +version = "2.13.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index ca43aed..96f509b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.12.0" +version = "2.13.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From cd03ad8fd5c3ab7d3375fed7c43baeddc7eacfbf Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 25 Sep 2025 16:53:25 -0400 Subject: [PATCH 261/320] cargo update --- Cargo.lock | 99 +++++++++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ce6ff8..fe8742f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,9 +115,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" dependencies = [ "aws-lc-sys", "zeroize", @@ -125,15 +125,16 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" +checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", + "libloading", ] [[package]] @@ -383,12 +384,12 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -415,7 +416,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -469,7 +470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -971,9 +972,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -987,12 +988,12 @@ checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libloading" -version = "0.8.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-link 0.2.0", + "windows-targets 0.53.4", ] [[package]] @@ -1041,9 +1042,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mimalloc" @@ -1542,9 +1543,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -1554,9 +1555,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -1652,7 +1653,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -1723,7 +1724,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -1995,7 +1996,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -2352,9 +2353,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -2365,9 +2366,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -2379,9 +2380,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.53" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -2392,9 +2393,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2402,9 +2403,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -2415,18 +2416,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -2444,9 +2445,9 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ "windows-implement", "windows-interface", @@ -2457,9 +2458,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" dependencies = [ "proc-macro2", "quote", @@ -2468,9 +2469,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" dependencies = [ "proc-macro2", "quote", @@ -2560,14 +2561,14 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.4", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" dependencies = [ "windows-link 0.2.0", ] @@ -2590,11 +2591,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ - "windows-link 0.1.3", + "windows-link 0.2.0", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", From f60aa47509b70da083d12339cea88f7c4997d514 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 25 Sep 2025 16:53:36 -0400 Subject: [PATCH 262/320] Introduce 'shovels enable_tls_peer_verification_for_all_{source,destination}_uris' --- .config/nextest.toml | 15 + src/cli.rs | 74 ++- src/commands.rs | 112 +++++ src/main.rs | 14 + tests/bindings_tests.rs | 2 +- tests/combined_integration_tests.rs | 8 +- tests/definitions_export_tests.rs | 6 +- tests/definitions_import_tests.rs | 2 +- tests/exchange_federation_tests.rs | 18 +- tests/exchanges_tests.rs | 6 +- ...eration_upstream_uri_modification_tests.rs | 320 ++++++++----- tests/operator_policies_tests.rs | 6 +- tests/policies_tests.rs | 2 +- tests/queue_federation_tests.rs | 16 +- tests/queues_tests.rs | 2 +- tests/runtime_parameters_tests.rs | 6 +- ...ovel_destination_uri_modification_tests.rs | 377 +++++++++------ tests/shovel_source_uri_modification_tests.rs | 451 ++++++++++-------- tests/shovel_tests.rs | 12 +- tests/streams_tests.rs | 2 +- tests/test_helpers.rs | 19 + tests/vhosts_tests.rs | 8 +- 22 files changed, 971 insertions(+), 507 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 0884983..004d650 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -51,3 +51,18 @@ test-group = 'sequential' filter = 'test(deprecated_features)' priority = 10 test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'binary(federation_upstream_uri_modification_tests)' +priority = 25 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'binary(shovel_source_uri_modification_tests)' +priority = 24 +test-group = 'sequential' + +[[profile.default.overrides]] +filter = 'binary(shovel_destination_uri_modification_tests)' +priority = 23 +test-group = 'sequential' diff --git a/src/cli.rs b/src/cli.rs index b48b13a..0a18e3b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2959,7 +2959,7 @@ pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7] { +pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 9] { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -3138,6 +3138,76 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 .after_help(color_print::cformat!( r#"Doc guides: + * {} + * {} + * {}"#, + SHOVEL_GUIDE_URL, + TLS_GUIDE_URL, + "https://www.rabbitmq.com/docs/shovel#tls-connections" + )); + + let enable_tls_peer_verification_source_cmd = Command::new("enable_tls_peer_verification_for_all_source_uris") + .about("Enables TLS peer verification for all shovel source URIs with provided [RabbitMQ node-local] certificate paths.") + .long_about("Enables TLS peer verification for all shovel source URIs by updating their 'verify' parameter and adding [RabbitMQ node-local] certificate and private key file paths.") + .arg( + Arg::new("node_local_ca_certificate_bundle_path") + .long("node-local-ca-certificate-bundle-path") + .help("Path to the CA certificate bundle file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .arg( + Arg::new("node_local_client_certificate_file_path") + .long("node-local-client-certificate-file-path") + .help("Path to the client certificate file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .arg( + Arg::new("node_local_client_private_key_file_path") + .long("node-local-client-private-key-file-path") + .help("Path to the client private key file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .after_help(color_print::cformat!( + r#"Doc guides: + + * {} + * {} + * {}"#, + SHOVEL_GUIDE_URL, + TLS_GUIDE_URL, + "https://www.rabbitmq.com/docs/shovel#tls-connections" + )); + + let enable_tls_peer_verification_dest_cmd = Command::new("enable_tls_peer_verification_for_all_destination_uris") + .about("Enables TLS peer verification for all shovel destination URIs with provided [RabbitMQ node-local] certificate paths.") + .long_about("Enables TLS peer verification for all shovel destination URIs by updating their 'verify' parameter and adding [RabbitMQ node-local] certificate and private key file paths.") + .arg( + Arg::new("node_local_ca_certificate_bundle_path") + .long("node-local-ca-certificate-bundle-path") + .help("Path to the CA certificate bundle file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .arg( + Arg::new("node_local_client_certificate_file_path") + .long("node-local-client-certificate-file-path") + .help("Path to the client certificate file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .arg( + Arg::new("node_local_client_private_key_file_path") + .long("node-local-client-private-key-file-path") + .help("Path to the client private key file on the target RabbitMQ node(s)") + .required(true) + .value_name("PATH") + ) + .after_help(color_print::cformat!( + r#"Doc guides: + * {} * {} * {}"#, @@ -3154,6 +3224,8 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 7 delete_cmd, disable_tls_peer_verification_cmd, disable_tls_peer_verification_dest_cmd, + enable_tls_peer_verification_source_cmd, + enable_tls_peer_verification_dest_cmd, ] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/commands.rs b/src/commands.rs index 456418a..2b0826a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -885,6 +885,118 @@ pub fn disable_tls_peer_verification_for_all_destination_uris( Ok(()) } +pub fn enable_tls_peer_verification_for_all_source_uris( + client: APIClient, + args: &ArgMatches, +) -> Result<(), CommandRunError> { + let ca_cert_path = args + .get_one::("node_local_ca_certificate_bundle_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_ca_certificate_bundle_path".to_string(), + })?; + let client_cert_path = args + .get_one::("node_local_client_certificate_file_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_client_certificate_file_path".to_string(), + })?; + let client_key_path = args + .get_one::("node_local_client_private_key_file_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_client_private_key_file_path".to_string(), + })?; + + let all_params = client.list_runtime_parameters()?; + let shovel_params: Vec<_> = all_params + .into_iter() + .filter(|p| p.component == "shovel") + .collect(); + + for param in shovel_params { + let owned_params = match OwnedShovelParams::try_from(param.clone()) { + Ok(params) => params, + Err(_) => continue, + }; + + let original_source_uri = &owned_params.source_uri; + if original_source_uri.is_empty() { + continue; + } + + let updated_source_uri = enable_tls_peer_verification( + original_source_uri, + ca_cert_path, + client_cert_path, + client_key_path, + )?; + + if original_source_uri != &updated_source_uri { + let mut updated_params = owned_params; + updated_params.source_uri = updated_source_uri; + + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + } + } + + Ok(()) +} + +pub fn enable_tls_peer_verification_for_all_destination_uris( + client: APIClient, + args: &ArgMatches, +) -> Result<(), CommandRunError> { + let ca_cert_path = args + .get_one::("node_local_ca_certificate_bundle_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_ca_certificate_bundle_path".to_string(), + })?; + let client_cert_path = args + .get_one::("node_local_client_certificate_file_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_client_certificate_file_path".to_string(), + })?; + let client_key_path = args + .get_one::("node_local_client_private_key_file_path") + .ok_or_else(|| CommandRunError::MissingArgumentValue { + property: "node_local_client_private_key_file_path".to_string(), + })?; + + let all_params = client.list_runtime_parameters()?; + let shovel_params: Vec<_> = all_params + .into_iter() + .filter(|p| p.component == "shovel") + .collect(); + + for param in shovel_params { + let owned_params = match OwnedShovelParams::try_from(param.clone()) { + Ok(params) => params, + Err(_) => continue, + }; + + let original_destination_uri = &owned_params.destination_uri; + if original_destination_uri.is_empty() { + continue; + } + + let updated_destination_uri = enable_tls_peer_verification( + original_destination_uri, + ca_cert_path, + client_cert_path, + client_key_path, + )?; + + if original_destination_uri != &updated_destination_uri { + let mut updated_params = owned_params; + updated_params.destination_uri = updated_destination_uri; + + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + } + } + + Ok(()) +} + // // Feature flags // diff --git a/src/main.rs b/src/main.rs index 3253cf9..1188fb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1086,6 +1086,20 @@ fn dispatch_common_subcommand( let result = commands::disable_tls_peer_verification_for_all_destination_uris(client); res_handler.no_output_on_success(result); } + ("shovels", "enable_tls_peer_verification_for_all_source_uris") => { + let result = commands::enable_tls_peer_verification_for_all_source_uris( + client, + second_level_args, + ); + res_handler.no_output_on_success(result); + } + ("shovels", "enable_tls_peer_verification_for_all_destination_uris") => { + let result = commands::enable_tls_peer_verification_for_all_destination_uris( + client, + second_level_args, + ); + res_handler.no_output_on_success(result); + } ("streams", "declare") => { let result = commands::declare_stream(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index b8d44cd..476993e 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -202,7 +202,7 @@ fn test_bindings_list() -> Result<(), Box> { #[test] fn test_bindings_delete_idempotently() -> Result<(), Box> { - let vh = "bindings.delete.idempotently.1"; + let vh = "rabbitmqadmin.bindings.test1"; let source_ex = "test_source_exchange"; let dest_queue = "test_dest_queue"; let routing_key = "test.routing.key"; diff --git a/tests/combined_integration_tests.rs b/tests/combined_integration_tests.rs index 934f956..fa445ef 100644 --- a/tests/combined_integration_tests.rs +++ b/tests/combined_integration_tests.rs @@ -20,7 +20,7 @@ use test_helpers::{run_fails, run_succeeds}; #[test] fn combined_integration_test1() -> Result<(), Box> { - let vh = "combined_integration_test1"; + let vh = "rabbitmqadmin.combined_integration.test1"; let config_path = path::absolute("./tests/fixtures/config_files/config_file1.conf") .expect("failed to compute an absolute version for a ./test/fixtures path"); @@ -45,7 +45,7 @@ fn combined_integration_test1() -> Result<(), Box> { #[test] fn combined_integration_test2() -> Result<(), Box> { - let vh = "combined_integration_test2"; + let vh = "rabbitmqadmin.combined_integration.test2"; // Uses a node alias that does not exist in the file let config_path = path::absolute("tests/fixtures/config_files/config_file1.conf") @@ -77,7 +77,7 @@ fn combined_integration_test2() -> Result<(), Box> { #[test] fn combined_integration_test3() -> Result<(), Box> { - let vh = "combined_integration_test3"; + let vh = "rabbitmqadmin.combined_integration.test3"; // Uses a node alias that does not exist in the file let config_path = path::absolute("tests/fixtures/config_files/non_exis7ent_c0nfig_f1le.conf") @@ -99,7 +99,7 @@ fn combined_integration_test3() -> Result<(), Box> { fn combined_integration_test4() -> Result<(), Box> { // This test uses administrative credentials to create a new user // and set up a topology using those new credentials - let vh = "combined_integration_test4"; + let vh = "rabbitmqadmin.combined_integration.test4"; let new_user = "user_from_combined_integration_test4"; let new_pass = "p4$$w0rd_from_combined_integration_test4"; let x = "fanout_combined_integration_test4"; diff --git a/tests/definitions_export_tests.rs b/tests/definitions_export_tests.rs index dbc9d0d..91443b7 100644 --- a/tests/definitions_export_tests.rs +++ b/tests/definitions_export_tests.rs @@ -27,7 +27,7 @@ fn test_export_cluster_wide_definitions() -> Result<(), Box Result<(), Box> { - let vh = "test_export_vhost_definitions.1"; + let vh = "rabbitmqadmin.definitions_export.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); @@ -49,7 +49,7 @@ fn test_export_vhost_definitions() -> Result<(), Box> { #[test] fn test_export_cluster_wide_definitions_with_transformations_case1() -> Result<(), Box> { - let vh = "test_export_cluster_definitions.transformations.1"; + let vh = "rabbitmqadmin.definitions_export.test2"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); @@ -98,7 +98,7 @@ fn test_export_cluster_wide_definitions_with_transformations_case1() #[test] fn test_export_vhost_definitions_with_transformations_case1() -> Result<(), Box> { - let vh = "test_export_vhost_definitions.transformations.1"; + let vh = "rabbitmqadmin.definitions_export.test3"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); diff --git a/tests/definitions_import_tests.rs b/tests/definitions_import_tests.rs index 3f5a4c2..03e4a79 100644 --- a/tests/definitions_import_tests.rs +++ b/tests/definitions_import_tests.rs @@ -35,7 +35,7 @@ fn test_import_cluster_definitions() -> Result<(), Box> { #[test] fn test_import_vhost_definitions() -> Result<(), Box> { - let vh = "test_import_vhost_definitions.1"; + let vh = "rabbitmqadmin.definitions_import.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); diff --git a/tests/exchange_federation_tests.rs b/tests/exchange_federation_tests.rs index 7f2db8b..60c540b 100644 --- a/tests/exchange_federation_tests.rs +++ b/tests/exchange_federation_tests.rs @@ -22,7 +22,7 @@ use test_helpers::{run_fails, run_succeeds}; #[test] fn test_federation_upstream_declaration_for_exchange_federation_case0() -> Result<(), Box> { - let vh = "rust.federation.0"; + let vh = "rabbitmqadmin.federation.exchange.test1"; let name = "up.for_exchange_federation.0"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -53,7 +53,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case0() #[test] fn test_federation_upstream_declaration_for_exchange_federation_case1a() -> Result<(), Box> { - let vh = "rust.federation.1a"; + let vh = "rabbitmqadmin.federation.exchange.test2"; let name = "up.for_exchange_federation.1a"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -90,7 +90,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case1a() #[test] fn test_federation_upstream_declaration_for_exchange_federation_case1b() -> Result<(), Box> { - let vh = "rust.federation.1b"; + let vh = "rabbitmqadmin.federation.exchange.test3"; let name = "up.for_exchange_federation.1b"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -130,7 +130,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case1b() #[test] fn test_federation_upstream_declaration_for_exchange_federation_case2() -> Result<(), Box> { - let vh = "rust.federation.2"; + let vh = "rabbitmqadmin.federation.exchange.test4"; let name = "up.for_exchange_federation.2"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -173,7 +173,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case2() #[test] fn test_federation_upstream_declaration_for_exchange_federation_case3() -> Result<(), Box> { - let vh = "rust.federation.3"; + let vh = "rabbitmqadmin.federation.exchange.test5"; let name = "up.for_exchange_federation.3"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -212,7 +212,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case3() #[test] fn test_federation_upstream_declaration_for_exchange_federation_case4() -> Result<(), Box> { - let vh = "rust.federation.4"; + let vh = "rabbitmqadmin.federation.exchange.test6"; let name = "up.for_exchange_federation.4"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -254,7 +254,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case4() #[test] fn test_federation_list_all_upstreams_with_exchange_federation() -> Result<(), Box> { - let vh = "rust.federation.5"; + let vh = "rabbitmqadmin.federation.exchange.test7"; let name = "up.for_exchange_federation.5"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -303,7 +303,7 @@ fn test_federation_list_all_upstreams_with_exchange_federation() #[test] fn test_federation_delete_an_upstream_with_exchange_federation_settings() -> Result<(), Box> { - let vh = "rust.federation.6"; + let vh = "rabbitmqadmin.federation.exchange.test8"; let name = "up.for_exchange_federation.6"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -363,7 +363,7 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() #[test] fn test_federation_delete_upstream_idempotently() -> Result<(), Box> { - let vh = "federation.delete.upstream.idempotently.1"; + let vh = "rabbitmqadmin.federation.exchange.test9"; let upstream_name = "test_upstream_delete_idempotently"; delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/exchanges_tests.rs b/tests/exchanges_tests.rs index 6dce59e..73922d6 100644 --- a/tests/exchanges_tests.rs +++ b/tests/exchanges_tests.rs @@ -129,7 +129,7 @@ fn exchanges_list() -> Result<(), Box> { #[test] fn delete_an_existing_exchange_using_original_command_group() -> Result<(), Box> { - let vh = "delete_exchange_vhost_1"; + let vh = "rabbitmqadmin.exchanges.test1"; let x = "exchange_1_to_delete"; // create a vhost @@ -156,7 +156,7 @@ fn delete_an_existing_exchange_using_original_command_group() #[test] fn delete_an_existing_exchange_using_exchanges_command_group() -> Result<(), Box> { - let vh = "delete_an_existing_exchange_using_exchanges_command_group_1"; + let vh = "rabbitmqadmin.exchanges.test2"; let x = "exchange_1_to_delete"; // create a vhost @@ -182,7 +182,7 @@ fn delete_an_existing_exchange_using_exchanges_command_group() #[test] fn delete_a_non_existing_exchange() -> Result<(), Box> { - let vh = "delete_a_non_existing_exchange_1"; + let vh = "rabbitmqadmin.exchanges.test3"; // declare a vhost create_vhost(vh)?; diff --git a/tests/federation_upstream_uri_modification_tests.rs b/tests/federation_upstream_uri_modification_tests.rs index e11794c..ce2cf38 100644 --- a/tests/federation_upstream_uri_modification_tests.rs +++ b/tests/federation_upstream_uri_modification_tests.rs @@ -15,18 +15,17 @@ mod test_helpers; use crate::test_helpers::*; -use predicates::prelude::*; #[test] fn test_disable_tls_peer_verification_for_all_upstreams_basic() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_upstreams_basic"; + let vh = "rabbitmqadmin.federation.modifications.test1"; let upstream_name = "test_basic_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); run_succeeds([ "-V", @@ -36,24 +35,29 @@ fn test_disable_tls_peer_verification_for_all_upstreams_basic() "--name", upstream_name, "--uri", - &amqps_endpoint, + &amqp_endpoint, "--exchange-name", "x.fanout", "--queue-type", "classic", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)); - run_succeeds([ "federation", "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)) - .stdout(predicate::str::contains("verify=verify_none")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let upstream_param = params + .iter() + .find(|p| p.name == upstream_name && p.component == "federation-upstream") + .expect("Federation upstream parameter should exist"); + + let uri = upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + assert!(uri.contains("verify=verify_none")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -63,16 +67,16 @@ fn test_disable_tls_peer_verification_for_all_upstreams_basic() #[test] fn test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_param() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_param"; + let vh = "rabbitmqadmin.federation.modifications.test2"; let upstream_name = "test_existing_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); let source_uri = format!( "{}?key1=abc&verify=verify_peer&cacertfile=/path/to/ca_bundle.pem&key2=def&certfile=/path/to/client.pem&keyfile=/path/to/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", - amqps_endpoint + amqp_endpoint ); run_succeeds([ @@ -96,22 +100,28 @@ fn test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_par "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("key1=abc")) - .stdout(predicate::str::contains("key2=def")) - .stdout(predicate::str::contains( - "cacertfile=/path/to/ca_bundle.pem", - )) - .stdout(predicate::str::contains("certfile=/path/to/client.pem")) - .stdout(predicate::str::contains("keyfile=/path/to/client.key")) - .stdout(predicate::str::contains( - "server_name_indication=example.com", - )) - .stdout(predicate::str::contains("custom_param=value123")) - .stdout(predicate::str::contains("another_param=xyz")) - .stdout(predicate::str::contains("heartbeat=60")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let upstream_param = params + .iter() + .find(|p| p.name == upstream_name && p.component == "federation-upstream") + .expect("Federation upstream parameter should exist"); + + let uri = upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + + assert!(uri.contains("verify=verify_none")); + assert!(!uri.contains("verify=verify_peer")); + assert!(uri.contains("key1=abc")); + assert!(uri.contains("key2=def")); + assert!(uri.contains("cacertfile=/path/to/ca_bundle.pem")); + assert!(uri.contains("certfile=/path/to/client.pem")); + assert!(uri.contains("keyfile=/path/to/client.key")); + assert!(uri.contains("server_name_indication=example.com")); + assert!(uri.contains("custom_param=value123")); + assert!(uri.contains("another_param=xyz")); + assert!(uri.contains("heartbeat=60")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -121,13 +131,13 @@ fn test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_par #[test] fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic"; + let vh = "rabbitmqadmin.federation.modifications.test3"; let upstream_name = "test_queue_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); run_succeeds([ "-V", @@ -137,24 +147,29 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic() "--name", upstream_name, "--uri", - &amqps_endpoint, + &amqp_endpoint, "--queue-name", "test.queue", "--consumer-tag", "test-consumer", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)); - run_succeeds([ "federation", "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)) - .stdout(predicate::str::contains("verify=verify_none")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let upstream_param = params + .iter() + .find(|p| p.name == upstream_name && p.component == "federation-upstream") + .expect("Federation upstream parameter should exist"); + + let uri = upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + assert!(uri.contains("verify=verify_none")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -164,16 +179,16 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic() #[test] fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_params() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_params"; + let vh = "rabbitmqadmin.federation.modifications.test4"; let upstream_name = "test_queue_upstream_with_params"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); let source_uri = format!( "{}?queue_param=test123&verify=verify_peer&cacertfile=/etc/ssl/certs/ca.pem&consumer_tag_param=custom&prefetch=100&ack_mode=on-confirm", - amqps_endpoint + amqp_endpoint ); run_succeeds([ @@ -197,14 +212,23 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_pa "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("queue_param=test123")) - .stdout(predicate::str::contains("cacertfile=/etc/ssl/certs/ca.pem")) - .stdout(predicate::str::contains("consumer_tag_param=custom")) - .stdout(predicate::str::contains("prefetch=100")) - .stdout(predicate::str::contains("ack_mode=on-confirm")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let upstream_param = params + .iter() + .find(|p| p.name == upstream_name && p.component == "federation-upstream") + .expect("Federation upstream parameter should exist"); + + let uri = upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + + assert!(uri.contains("verify=verify_none")); + assert!(uri.contains("queue_param=test123")); + assert!(uri.contains("cacertfile=/etc/ssl/certs/ca.pem")); + assert!(uri.contains("consumer_tag_param=custom")); + assert!(uri.contains("prefetch=100")); + assert!(uri.contains("ack_mode=on-confirm")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -214,21 +238,21 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_pa #[test] fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_upstreams_mixed_federation"; + let vh = "rabbitmqadmin.federation.modifications.test5"; let exchange_upstream_name = "exchange_upstream"; let queue_upstream_name = "queue_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); let exchange_uri = format!( "{}?exchange_param=value1&verify=verify_peer&certfile=/path/to/client.pem", - amqps_endpoint + amqp_endpoint ); let queue_uri = format!( "{}?queue_param=value2&verify=verify_peer&keyfile=/path/to/client.key", - amqps_endpoint + amqp_endpoint ); run_succeeds([ @@ -267,14 +291,32 @@ fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() "disable_tls_peer_verification_for_all_upstreams", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(exchange_upstream_name)) - .stdout(predicate::str::contains(queue_upstream_name)) - .stdout(predicate::str::contains("exchange_param=value1")) - .stdout(predicate::str::contains("queue_param=value2")) - .stdout(predicate::str::contains("certfile=/path/to/client.pem")) - .stdout(predicate::str::contains("keyfile=/path/to/client.key")) - .stdout(predicate::str::contains("verify=verify_none")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + + let exchange_upstream_param = params + .iter() + .find(|p| p.name == exchange_upstream_name && p.component == "federation-upstream") + .expect("Exchange upstream parameter should exist"); + let queue_upstream_param = params + .iter() + .find(|p| p.name == queue_upstream_name && p.component == "federation-upstream") + .expect("Queue upstream parameter should exist"); + + let exchange_uri = exchange_upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + let queue_uri = queue_upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + + assert!(exchange_uri.contains("verify=verify_none")); + assert!(exchange_uri.contains("exchange_param=value1")); + assert!(exchange_uri.contains("certfile=/path/to/client.pem")); + + assert!(queue_uri.contains("verify=verify_none")); + assert!(queue_uri.contains("queue_param=value2")); + assert!(queue_uri.contains("keyfile=/path/to/client.key")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -284,13 +326,13 @@ fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() #[test] fn test_enable_tls_peer_verification_for_all_upstreams_basic() -> Result<(), Box> { - let vh = "test_enable_tls_peer_verification_for_all_upstreams_basic"; + let vh = "rabbitmqadmin.federation.modifications.test6"; let upstream_name = "test_enable_basic_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); run_succeeds([ "-V", @@ -300,16 +342,13 @@ fn test_enable_tls_peer_verification_for_all_upstreams_basic() "--name", upstream_name, "--uri", - &amqps_endpoint, + &amqp_endpoint, "--exchange-name", "x.fanout", "--queue-type", "classic", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)); - run_succeeds([ "federation", "enable_tls_peer_verification_for_all_upstreams", @@ -321,18 +360,21 @@ fn test_enable_tls_peer_verification_for_all_upstreams_basic() "/etc/ssl/private/client.key", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)) - .stdout(predicate::str::contains("verify=verify_peer")) - .stdout(predicate::str::contains( - "cacertfile=/etc/ssl/certs/ca_bundle.pem", - )) - .stdout(predicate::str::contains( - "certfile=/etc/ssl/certs/client.pem", - )) - .stdout(predicate::str::contains( - "keyfile=/etc/ssl/private/client.key", - )); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let upstream_param = params + .iter() + .find(|p| p.name == upstream_name && p.component == "federation-upstream") + .expect("Federation upstream parameter should exist"); + + let uri = upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + + assert!(uri.contains("verify=verify_peer")); + assert!(uri.contains("cacertfile=/etc/ssl/certs/ca_bundle.pem")); + assert!(uri.contains("certfile=/etc/ssl/certs/client.pem")); + assert!(uri.contains("keyfile=/etc/ssl/private/client.key")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -342,16 +384,16 @@ fn test_enable_tls_peer_verification_for_all_upstreams_basic() #[test] fn test_enable_tls_peer_verification_for_all_upstreams_with_existing_params() -> Result<(), Box> { - let vh = "test_enable_tls_peer_verification_for_all_upstreams_with_existing_params"; + let vh = "rabbitmqadmin.federation.modifications.test7"; let upstream_name = "test_enable_existing_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); let source_uri = format!( "{}?key1=abc&verify=verify_none&cacertfile=/old/path/ca.pem&key2=def&certfile=/old/path/client.pem&keyfile=/old/path/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", - amqps_endpoint + amqp_endpoint ); run_succeeds([ @@ -381,22 +423,27 @@ fn test_enable_tls_peer_verification_for_all_upstreams_with_existing_params() "/new/path/client.key", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)) - .stdout(predicate::str::contains("verify=verify_peer")) - .stdout(predicate::str::contains("key1=abc")) - .stdout(predicate::str::contains("key2=def")) - .stdout(predicate::str::contains( - "cacertfile=/new/path/ca_bundle.pem", - )) - .stdout(predicate::str::contains("certfile=/new/path/client.pem")) - .stdout(predicate::str::contains("keyfile=/new/path/client.key")) - .stdout(predicate::str::contains( - "server_name_indication=example.com", - )) - .stdout(predicate::str::contains("custom_param=value123")) - .stdout(predicate::str::contains("another_param=xyz")) - .stdout(predicate::str::contains("heartbeat=60")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let upstream_param = params + .iter() + .find(|p| p.name == upstream_name && p.component == "federation-upstream") + .expect("Federation upstream parameter should exist"); + + let uri = upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + + assert!(uri.contains("verify=verify_peer")); + assert!(uri.contains("key1=abc")); + assert!(uri.contains("key2=def")); + assert!(uri.contains("cacertfile=/new/path/ca_bundle.pem")); + assert!(uri.contains("certfile=/new/path/client.pem")); + assert!(uri.contains("keyfile=/new/path/client.key")); + assert!(uri.contains("server_name_indication=example.com")); + assert!(uri.contains("custom_param=value123")); + assert!(uri.contains("another_param=xyz")); + assert!(uri.contains("heartbeat=60")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -406,13 +453,13 @@ fn test_enable_tls_peer_verification_for_all_upstreams_with_existing_params() #[test] fn test_enable_tls_peer_verification_for_all_upstreams_queue_federation() -> Result<(), Box> { - let vh = "test_enable_tls_peer_verification_for_all_upstreams_queue_federation"; + let vh = "rabbitmqadmin.federation.modifications.test8"; let upstream_name = "test_enable_queue_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); run_succeeds([ "-V", @@ -422,16 +469,13 @@ fn test_enable_tls_peer_verification_for_all_upstreams_queue_federation() "--name", upstream_name, "--uri", - &amqps_endpoint, + &amqp_endpoint, "--queue-name", "test.queue", "--consumer-tag", "test-consumer", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)); - run_succeeds([ "federation", "enable_tls_peer_verification_for_all_upstreams", @@ -443,12 +487,21 @@ fn test_enable_tls_peer_verification_for_all_upstreams_queue_federation() "/etc/ssl/client.key", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(upstream_name)) - .stdout(predicate::str::contains("verify=verify_peer")) - .stdout(predicate::str::contains("cacertfile=/etc/ssl/ca.pem")) - .stdout(predicate::str::contains("certfile=/etc/ssl/client.pem")) - .stdout(predicate::str::contains("keyfile=/etc/ssl/client.key")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let upstream_param = params + .iter() + .find(|p| p.name == upstream_name && p.component == "federation-upstream") + .expect("Federation upstream parameter should exist"); + + let uri = upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + + assert!(uri.contains("verify=verify_peer")); + assert!(uri.contains("cacertfile=/etc/ssl/ca.pem")); + assert!(uri.contains("certfile=/etc/ssl/client.pem")); + assert!(uri.contains("keyfile=/etc/ssl/client.key")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -458,21 +511,21 @@ fn test_enable_tls_peer_verification_for_all_upstreams_queue_federation() #[test] fn test_enable_tls_peer_verification_for_all_upstreams_mixed_federation() -> Result<(), Box> { - let vh = "test_enable_tls_peer_verification_for_all_upstreams_mixed_federation"; + let vh = "rabbitmqadmin.federation.modifications.test9"; let exchange_upstream_name = "enable_exchange_upstream"; let queue_upstream_name = "enable_queue_upstream"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_endpoint = format!("amqps://localhost:5671/{}", vh); + let amqp_endpoint = format!("amqp://localhost:5672/{}", vh); let exchange_uri = format!( "{}?exchange_param=value1&verify=verify_none&old_cert=/old/path.pem", - amqps_endpoint + amqp_endpoint ); let queue_uri = format!( "{}?queue_param=value2&verify=verify_none&old_key=/old/key.pem", - amqps_endpoint + amqp_endpoint ); run_succeeds([ @@ -517,15 +570,36 @@ fn test_enable_tls_peer_verification_for_all_upstreams_mixed_federation() "/path/to/client.key", ]); - run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(exchange_upstream_name)) - .stdout(predicate::str::contains(queue_upstream_name)) - .stdout(predicate::str::contains("exchange_param=value1")) - .stdout(predicate::str::contains("queue_param=value2")) - .stdout(predicate::str::contains("cacertfile=/path/to/ca.pem")) - .stdout(predicate::str::contains("certfile=/path/to/client.pem")) - .stdout(predicate::str::contains("keyfile=/path/to/client.key")) - .stdout(predicate::str::contains("verify=verify_peer")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + + let exchange_upstream_param = params + .iter() + .find(|p| p.name == exchange_upstream_name && p.component == "federation-upstream") + .expect("Exchange upstream parameter should exist"); + let queue_upstream_param = params + .iter() + .find(|p| p.name == queue_upstream_name && p.component == "federation-upstream") + .expect("Queue upstream parameter should exist"); + + let exchange_uri = exchange_upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + let queue_uri = queue_upstream_param.value["uri"] + .as_str() + .expect("uri should be a string"); + + assert!(exchange_uri.contains("verify=verify_peer")); + assert!(exchange_uri.contains("exchange_param=value1")); + assert!(exchange_uri.contains("cacertfile=/path/to/ca.pem")); + assert!(exchange_uri.contains("certfile=/path/to/client.pem")); + assert!(exchange_uri.contains("keyfile=/path/to/client.key")); + + assert!(queue_uri.contains("verify=verify_peer")); + assert!(queue_uri.contains("queue_param=value2")); + assert!(queue_uri.contains("cacertfile=/path/to/ca.pem")); + assert!(queue_uri.contains("certfile=/path/to/client.pem")); + assert!(queue_uri.contains("keyfile=/path/to/client.key")); delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/operator_policies_tests.rs b/tests/operator_policies_tests.rs index cdb3a39..28a412d 100644 --- a/tests/operator_policies_tests.rs +++ b/tests/operator_policies_tests.rs @@ -150,7 +150,7 @@ fn test_operator_policies_in() -> Result<(), Box> { #[test] fn test_operator_policies_in_with_entity_type() -> Result<(), Box> { - let vh = "rabbitmqadmin.vh.operator_policies.2"; + let vh = "rabbitmqadmin.operator_policies.test1"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); @@ -226,7 +226,7 @@ fn test_operator_policies_in_with_entity_type() -> Result<(), Box Result<(), Box> { - let vh = "rabbitmqadmin.vh.operator_policies.11"; + let vh = "rabbitmqadmin.operator_policies.test2"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); @@ -494,7 +494,7 @@ fn test_operator_policies_bulk_policy_keys_manipulation() -> Result<(), Box Result<(), Box> { - let vh = "rabbitmqadmin.test_operator_policies_patch_definition.1"; + let vh = "rabbitmqadmin.operator_policies.test3"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index abf2e85..a6c9cc5 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -143,7 +143,7 @@ fn test_policies_in() -> Result<(), Box> { #[test] fn test_policies_in_with_entity_type() -> Result<(), Box> { - let vh = "rabbitmqadmin.vh.policies.2"; + let vh = "rabbitmqadmin.policies.test1"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); diff --git a/tests/queue_federation_tests.rs b/tests/queue_federation_tests.rs index a0eb3f7..4e6755f 100644 --- a/tests/queue_federation_tests.rs +++ b/tests/queue_federation_tests.rs @@ -21,7 +21,7 @@ use test_helpers::{run_fails, run_succeeds}; #[test] fn test_federation_upstream_declaration_for_queue_federation_case0() -> Result<(), Box> { - let vh = "rust.federation.0"; + let vh = "rabbitmqadmin.federation.queue.test1"; let name = "up.for_queue_federation"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -58,7 +58,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case0() #[test] fn test_federation_upstream_declaration_for_queue_federation_case1a() -> Result<(), Box> { - let vh = "rust.federation.1a"; + let vh = "rabbitmqadmin.federation.queue.test2"; let name = "up.for_queue_federation.a"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -97,7 +97,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case1a() #[test] fn test_federation_upstream_declaration_for_queue_federation_case1b() -> Result<(), Box> { - let vh = "rust.federation.1b"; + let vh = "rabbitmqadmin.federation.queue.test3"; let name = "up.for_queue_federation.b"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -139,7 +139,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case1b() #[test] fn test_federation_upstream_declaration_for_queue_federation_case2() -> Result<(), Box> { - let vh = "rust.federation.2"; + let vh = "rabbitmqadmin.federation.queue.test4"; let name = "up.for_queue_federation"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -173,7 +173,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case2() #[test] fn test_federation_upstream_declaration_for_queue_federation_case3() -> Result<(), Box> { - let vh = "rust.federation.3"; + let vh = "rabbitmqadmin.federation.queue.test5"; let name = "up.for_queue_federation"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -207,7 +207,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case3() #[test] fn test_federation_upstream_declaration_for_queue_federation_case4() -> Result<(), Box> { - let vh = "rust.federation.4"; + let vh = "rabbitmqadmin.federation.queue.test6"; let name = "up.for_queue_federation"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -243,7 +243,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case4() #[test] fn test_federation_list_all_upstreams_with_queue_federation() -> Result<(), Box> { - let vh = "rust.federation.5"; + let vh = "rabbitmqadmin.federation.queue.test7"; let name = "up.for_queue_federation/5"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -286,7 +286,7 @@ fn test_federation_list_all_upstreams_with_queue_federation() #[test] fn test_federation_delete_an_upstream_with_queue_federation_settings() -> Result<(), Box> { - let vh = "rust.federation.6"; + let vh = "rabbitmqadmin.federation.queue.test8"; let name = "up.for_queue_federation.6"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); diff --git a/tests/queues_tests.rs b/tests/queues_tests.rs index a4990d0..789ab03 100644 --- a/tests/queues_tests.rs +++ b/tests/queues_tests.rs @@ -113,7 +113,7 @@ fn queues_lists() -> Result<(), Box> { #[test] fn test_queues_delete_idempotently() -> Result<(), Box> { - let vh = "queues.delete.idempotently.1"; + let vh = "rabbitmqadmin.queues.test1"; let q = "test_queue_delete_idempotently"; delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index c033aa5..cb72640 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -19,7 +19,7 @@ use crate::test_helpers::*; #[test] fn test_runtime_parameters_across_groups() -> Result<(), Box> { - let vh = "test_runtime_parameters_across_groups"; + let vh = "rabbitmqadmin.runtime_parameters.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); @@ -75,7 +75,7 @@ fn test_runtime_parameters_across_groups() -> Result<(), Box Result<(), Box> { - let vh = "test_runtime_parameters_cmd_group"; + let vh = "rabbitmqadmin.runtime_parameters.test2"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["vhosts", "declare", "--name", vh]); @@ -165,7 +165,7 @@ fn test_global_runtime_parameters_cmd_group() -> Result<(), Box Result<(), Box> { - let vh = "parameters.clear.idempotently.1"; + let vh = "rabbitmqadmin.runtime_parameters.test3"; let param_name = "test_param_delete_idempotently"; let component = "federation-upstream"; diff --git a/tests/shovel_destination_uri_modification_tests.rs b/tests/shovel_destination_uri_modification_tests.rs index 899b370..0facbe4 100644 --- a/tests/shovel_destination_uri_modification_tests.rs +++ b/tests/shovel_destination_uri_modification_tests.rs @@ -15,20 +15,18 @@ mod test_helpers; use crate::test_helpers::*; -use predicates::prelude::*; -use std::str; #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_basic() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_destination_uris_basic"; + let vh = "rabbitmqadmin.shovel.modifications.test8"; let shovel_name = "test_basic_dest_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_source = format!("amqps://localhost:5671/{}", vh); - let amqps_destination = format!("amqps://localhost:5671/{}", vh); + let amqp_source = format!("amqp://localhost:5672/{}", vh); + let amqp_destination = format!("amqp://localhost:5672/{}", vh); run_succeeds([ "-V", @@ -38,9 +36,9 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_basic() "--name", shovel_name, "--source-uri", - &amqps_source, + &amqp_source, "--destination-uri", - &amqps_destination, + &amqp_destination, "--source-queue", "source.queue", "--destination-queue", @@ -49,16 +47,22 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_basic() "on-confirm", ]); - run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); - run_succeeds([ "shovels", "disable_tls_peer_verification_for_all_destination_uris", ]); - run_succeeds(["parameters", "list_all"]) - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let dest_uri = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); + assert!(dest_uri.contains("verify=verify_none")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -68,18 +72,17 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_basic() #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param() -> Result<(), Box> { - let vh = - "test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param"; + let vh = "rabbitmqadmin.shovel.modifications.test9"; let shovel_name = "test_existing_dest_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_base = format!("amqps://localhost:5671/{}", vh); - let source_uri = format!("{}?source_key=abc&heartbeat=60", amqps_base); + let amqp_base = format!("amqp://localhost:5672/{}", vh); + let source_uri = format!("{}?source_key=abc&heartbeat=60", amqp_base); let dest_uri = format!( "{}?dest_key1=xyz&verify=verify_peer&cacertfile=/path/to/dest_ca.pem&dest_key2=abc&certfile=/path/to/dest_client.pem&keyfile=/path/to/dest_client.key&server_name_indication=dest.example.com&dest_param=value456&another_dest_param=def&heartbeat=30", - amqps_base + amqp_base ); run_succeeds([ @@ -107,69 +110,36 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_ver "disable_tls_peer_verification_for_all_destination_uris", ]); - let output = run_succeeds(["parameters", "list_all"]); - let stdout = output - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("dest_key1=xyz")) - .stdout(predicate::str::contains("dest_key2=abc")) - .stdout(predicate::str::contains("cacertfile=/path/to/dest_ca.pem")) - .stdout(predicate::str::contains( - "certfile=/path/to/dest_client.pem", - )) - .stdout(predicate::str::contains("keyfile=/path/to/dest_client.key")) - .stdout(predicate::str::contains( - "server_name_indication=dest.example.com", - )) - .stdout(predicate::str::contains("dest_param=value456")) - .stdout(predicate::str::contains("another_dest_param=def")) - .stdout(predicate::str::contains("heartbeat=30")); - - let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); - let lines: Vec<&str> = output_str.lines().collect(); - let mut shovel_section = String::new(); - let mut in_our_shovel = false; - - for line in lines { - if line.contains(&shovel_name) { - in_our_shovel = true; - } - if in_our_shovel { - shovel_section.push_str(line); - shovel_section.push('\n'); - if line.contains("└─") - || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) - { - break; - } - } - } - - let dest_uri_lines: Vec<&str> = shovel_section - .lines() - .filter(|line| line.contains("dest-uri")) - .collect(); - assert!( - !dest_uri_lines.is_empty(), - "Could not find dest-uri in shovel section" - ); - - let dest_uri_content = dest_uri_lines[0]; - let dest_verify_count = dest_uri_content.matches("verify=").count(); - assert_eq!( - dest_verify_count, 1, - "Expected exactly 1 verify parameter in destination URI, found {}", - dest_verify_count - ); - - assert!( - dest_uri_content.contains("verify=verify_none"), - "Destination URI should contain verify=verify_none" - ); - assert!( - !dest_uri_content.contains("verify=verify_peer"), - "Destination URI should not contain verify=verify_peer" - ); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let source_uri_after = shovel_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + let dest_uri_after = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); + + // Check that destination URI has verify=verify_none and preserves other parameters + assert!(dest_uri_after.contains("verify=verify_none")); + assert!(!dest_uri_after.contains("verify=verify_peer")); + assert!(dest_uri_after.contains("dest_key1=xyz")); + assert!(dest_uri_after.contains("dest_key2=abc")); + assert!(dest_uri_after.contains("cacertfile=/path/to/dest_ca.pem")); + assert!(dest_uri_after.contains("certfile=/path/to/dest_client.pem")); + assert!(dest_uri_after.contains("keyfile=/path/to/dest_client.key")); + assert!(dest_uri_after.contains("server_name_indication=dest.example.com")); + assert!(dest_uri_after.contains("dest_param=value456")); + assert!(dest_uri_after.contains("another_dest_param=def")); + assert!(dest_uri_after.contains("heartbeat=30")); + + // Check that source URI is unchanged + assert!(source_uri_after.contains("source_key=abc")); + assert!(source_uri_after.contains("heartbeat=60")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -179,15 +149,15 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_ver #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_destination_uris_amqp10"; + let vh = "rabbitmqadmin.shovel.modifications.test10"; let shovel_name = "test_amqp10_dest_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_source = format!("amqps://localhost:5671/{}", vh); - let amqps_destination = format!( - "amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", + let amqp_source = format!("amqp://localhost:5672/{}", vh); + let amqp_destination = format!( + "amqp://localhost:5672/{}?verify=verify_peer&certfile=/path/to/client.pem", vh ); @@ -199,9 +169,9 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() "--name", shovel_name, "--source-uri", - &amqps_source, + &amqp_source, "--destination-uri", - &amqps_destination, + &amqp_destination, "--source-address", "source.address", "--destination-address", @@ -210,17 +180,24 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() "on-confirm", ]); - run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); - run_succeeds([ "shovels", "disable_tls_peer_verification_for_all_destination_uris", ]); - run_succeeds(["parameters", "list_all"]) - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("certfile=/path/to/client.pem")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let dest_uri = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); + + assert!(dest_uri.contains("verify=verify_none")); + assert!(dest_uri.contains("certfile=/path/to/client.pem")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -230,17 +207,17 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_params() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_params"; + let vh = "rabbitmqadmin.shovel.modifications.test11"; let shovel_name = "test_dummy_dest_params_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_base = format!("amqps://localhost:5671/{}", vh); - let source_uri = format!("{}?source_abc=123&source_heartbeat=5", amqps_base); + let amqp_base = format!("amqp://localhost:5672/{}", vh); + let source_uri = format!("{}?source_abc=123&source_heartbeat=5", amqp_base); let dest_uri = format!( "{}?dest_xyz=456&dest_heartbeat=10&channel_max=100&another_dummy=example", - amqps_base + amqp_base ); run_succeeds([ @@ -268,60 +245,22 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_ "disable_tls_peer_verification_for_all_destination_uris", ]); - let output = run_succeeds(["parameters", "list_all"]); - let stdout = output - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("dest_xyz=456")) - .stdout(predicate::str::contains("dest_heartbeat=10")) - .stdout(predicate::str::contains("channel_max=100")) - .stdout(predicate::str::contains("another_dummy=example")); - - let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); - let lines: Vec<&str> = output_str.lines().collect(); - let mut shovel_section = String::new(); - let mut in_our_shovel = false; - - for line in lines { - if line.contains(&shovel_name) { - in_our_shovel = true; - } - if in_our_shovel { - shovel_section.push_str(line); - shovel_section.push('\n'); - if line.contains("└─") - || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) - { - break; - } - } - } - - let dest_uri_lines: Vec<&str> = shovel_section - .lines() - .filter(|line| line.contains("dest-uri")) - .collect(); - assert!( - !dest_uri_lines.is_empty(), - "Could not find dest-uri in shovel section" - ); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); - let dest_uri_content = dest_uri_lines[0]; - let dest_verify_count = dest_uri_content.matches("verify=").count(); - assert_eq!( - dest_verify_count, 1, - "Expected exactly 1 verify parameter in destination URI, found {}", - dest_verify_count - ); + let dest_uri_after = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); - assert!( - dest_uri_content.contains("verify=verify_none"), - "Destination URI should contain verify=verify_none" - ); - assert!( - !dest_uri_content.contains("verify=verify_peer"), - "Destination URI should not contain verify=verify_peer" - ); + assert!(dest_uri_after.contains("verify=verify_none")); + assert!(dest_uri_after.contains("dest_xyz=456")); + assert!(dest_uri_after.contains("dest_heartbeat=10")); + assert!(dest_uri_after.contains("channel_max=100")); + assert!(dest_uri_after.contains("another_dummy=example")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -331,7 +270,7 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_ #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_no_shovels() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_destination_uris_no_shovels"; + let vh = "rabbitmqadmin.shovel.modifications.test12"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); @@ -345,3 +284,143 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_no_shovels() Ok(()) } + +#[test] +fn test_enable_tls_peer_verification_for_all_destination_uris_basic() +-> Result<(), Box> { + let vh = "rabbitmqadmin.shovel.modifications.test13"; + let shovel_name = "test_enable_basic_dest_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqp_source = format!("amqp://localhost:5672/{}", vh); + let amqp_destination = format!("amqp://localhost:5672/{}", vh); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &amqp_source, + "--destination-uri", + &amqp_destination, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + + run_succeeds([ + "shovels", + "enable_tls_peer_verification_for_all_destination_uris", + "--node-local-ca-certificate-bundle-path", + "/etc/ssl/certs/ca_bundle.pem", + "--node-local-client-certificate-file-path", + "/etc/ssl/certs/client.pem", + "--node-local-client-private-key-file-path", + "/etc/ssl/private/client.key", + ]); + + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let dest_uri = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); + + assert!(dest_uri.contains("verify=verify_peer")); + assert!(dest_uri.contains("cacertfile=/etc/ssl/certs/ca_bundle.pem")); + assert!(dest_uri.contains("certfile=/etc/ssl/certs/client.pem")); + assert!(dest_uri.contains("keyfile=/etc/ssl/private/client.key")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_enable_tls_peer_verification_for_all_destination_uris_with_existing_params() +-> Result<(), Box> { + let vh = "rabbitmqadmin.shovel.modifications.test14"; + let shovel_name = "test_enable_existing_dest_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqp_base = format!("amqp://localhost:5672/{}", vh); + let source_uri = format!("amqp://localhost:5672/{}", vh); + let destination_uri = format!( + "{}?key1=abc&verify=verify_none&cacertfile=/old/path/ca.pem&key2=def&certfile=/old/path/client.pem&keyfile=/old/path/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", + amqp_base + ); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &source_uri, + "--destination-uri", + &destination_uri, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + await_metric_emission(500); + + run_succeeds([ + "shovels", + "enable_tls_peer_verification_for_all_destination_uris", + "--node-local-ca-certificate-bundle-path", + "/etc/ssl/certs/ca_bundle.pem", + "--node-local-client-certificate-file-path", + "/etc/ssl/certs/client.pem", + "--node-local-client-private-key-file-path", + "/etc/ssl/private/client.key", + ]); + + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let dest_uri2 = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); + + assert!(dest_uri2.contains("verify=verify_peer")); + assert!(dest_uri2.contains("cacertfile=/etc/ssl/certs/ca_bundle.pem")); + assert!(dest_uri2.contains("certfile=/etc/ssl/certs/client.pem")); + assert!(dest_uri2.contains("keyfile=/etc/ssl/private/client.key")); + assert!(!dest_uri2.contains("cacertfile=/old/path/ca.pem")); + assert!(!dest_uri2.contains("certfile=/old/path/client.pem")); + assert!(!dest_uri2.contains("keyfile=/old/path/client.key")); + assert!(dest_uri2.contains("key1=abc")); + assert!(dest_uri2.contains("key2=def")); + assert!(dest_uri2.contains("server_name_indication=example.com")); + assert!(dest_uri2.contains("custom_param=value123")); + assert!(dest_uri2.contains("another_param=xyz")); + assert!(dest_uri2.contains("heartbeat=60")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} diff --git a/tests/shovel_source_uri_modification_tests.rs b/tests/shovel_source_uri_modification_tests.rs index 0a728c0..5cfe9e1 100644 --- a/tests/shovel_source_uri_modification_tests.rs +++ b/tests/shovel_source_uri_modification_tests.rs @@ -15,20 +15,18 @@ mod test_helpers; use crate::test_helpers::*; -use predicates::prelude::*; -use std::str; #[test] fn test_disable_tls_peer_verification_for_all_shovels_basic() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_shovels_basic"; + let vh = "rabbitmqadmin.shovel.modifications.test1"; let shovel_name = "test_basic_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_source = format!("amqps://localhost:5671/{}", vh); - let amqps_destination = format!("amqps://localhost:5671/{}", vh); + let amqp_source = format!("amqp://localhost:5672/{}", vh); + let amqp_destination = format!("amqp://localhost:5672/{}", vh); run_succeeds([ "-V", @@ -38,9 +36,9 @@ fn test_disable_tls_peer_verification_for_all_shovels_basic() "--name", shovel_name, "--source-uri", - &amqps_source, + &amqp_source, "--destination-uri", - &amqps_destination, + &amqp_destination, "--source-queue", "source.queue", "--destination-queue", @@ -49,16 +47,22 @@ fn test_disable_tls_peer_verification_for_all_shovels_basic() "on-confirm", ]); - run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); - run_succeeds([ "shovels", "disable_tls_peer_verification_for_all_source_uris", ]); - run_succeeds(["parameters", "list_all"]) - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let source_uri = shovel_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + assert!(source_uri.contains("verify=verify_none")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -68,20 +72,20 @@ fn test_disable_tls_peer_verification_for_all_shovels_basic() #[test] fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param"; + let vh = "rabbitmqadmin.shovel.modifications.test2"; let shovel_name = "test_existing_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_base = format!("amqps://localhost:5671/{}", vh); + let amqp_base = format!("amqp://localhost:5672/{}", vh); let source_uri = format!( "{}?key1=abc&verify=verify_peer&cacertfile=/path/to/ca_bundle.pem&key2=def&certfile=/path/to/client.pem&keyfile=/path/to/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", - amqps_base + amqp_base ); let dest_uri = format!( "{}?dest_key1=xyz&verify=verify_peer&cacertfile=/path/to/dest_ca.pem&dest_key2=abc&certfile=/path/to/dest_client.pem&keyfile=/path/to/dest_client.key&server_name_indication=dest.example.com&dest_param=value456&another_dest_param=def&heartbeat=30", - amqps_base + amqp_base ); run_succeeds([ @@ -109,82 +113,36 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param "disable_tls_peer_verification_for_all_source_uris", ]); - let output = run_succeeds(["parameters", "list_all"]); - let stdout = output - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("key1=abc")) - .stdout(predicate::str::contains("key2=def")) - .stdout(predicate::str::contains( - "cacertfile=/path/to/ca_bundle.pem", - )) - .stdout(predicate::str::contains("certfile=/path/to/client.pem")) - .stdout(predicate::str::contains("keyfile=/path/to/client.key")) - .stdout(predicate::str::contains( - "server_name_indication=example.com", - )) - .stdout(predicate::str::contains("custom_param=value123")) - .stdout(predicate::str::contains("another_param=xyz")) - .stdout(predicate::str::contains("heartbeat=60")) - .stdout(predicate::str::contains("dest_key1=xyz")) - .stdout(predicate::str::contains("dest_key2=abc")) - .stdout(predicate::str::contains("cacertfile=/path/to/dest_ca.pem")) - .stdout(predicate::str::contains( - "certfile=/path/to/dest_client.pem", - )) - .stdout(predicate::str::contains("keyfile=/path/to/dest_client.key")) - .stdout(predicate::str::contains( - "server_name_indication=dest.example.com", - )) - .stdout(predicate::str::contains("dest_param=value456")) - .stdout(predicate::str::contains("another_dest_param=def")) - .stdout(predicate::str::contains("heartbeat=30")); - - let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); - let lines: Vec<&str> = output_str.lines().collect(); - let mut shovel_section = String::new(); - let mut in_our_shovel = false; - - for line in lines { - if line.contains(&shovel_name) { - in_our_shovel = true; - } - if in_our_shovel { - shovel_section.push_str(line); - shovel_section.push('\n'); - if line.contains("└─") - || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) - { - break; - } - } - } - - let src_uri_lines: Vec<&str> = shovel_section - .lines() - .filter(|line| line.contains("src-uri")) - .collect(); - assert!( - !src_uri_lines.is_empty(), - "Could not find src-uri in shovel section" - ); - - let src_uri_content = src_uri_lines[0]; - let src_verify_count = src_uri_content.matches("verify=").count(); - assert_eq!( - src_verify_count, 1, - "Expected exactly 1 verify parameter in source URI, found {}", - src_verify_count - ); - - assert!( - src_uri_content.contains("verify=verify_none"), - "Source URI should contain verify=verify_none" - ); - assert!( - !src_uri_content.contains("verify=verify_peer"), - "Source URI should not contain verify=verify_peer" - ); + let client = api_client(); + let params_after = client.list_runtime_parameters()?; + let shovel_param = params_after + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let source_uri_after = shovel_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + let dest_uri_after = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); + + // Check that source URI has verify=verify_none and preserves other parameters + assert!(source_uri_after.contains("verify=verify_none")); + assert!(!source_uri_after.contains("verify=verify_peer")); + assert!(source_uri_after.contains("key1=abc")); + assert!(source_uri_after.contains("key2=def")); + assert!(source_uri_after.contains("cacertfile=/path/to/ca_bundle.pem")); + assert!(source_uri_after.contains("certfile=/path/to/client.pem")); + assert!(source_uri_after.contains("keyfile=/path/to/client.key")); + assert!(source_uri_after.contains("server_name_indication=example.com")); + assert!(source_uri_after.contains("custom_param=value123")); + assert!(source_uri_after.contains("another_param=xyz")); + assert!(source_uri_after.contains("heartbeat=60")); + + // Check that destination URI is unchanged + assert!(dest_uri_after.contains("verify=verify_peer")); + assert!(dest_uri_after.contains("dest_key1=xyz")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -194,18 +152,18 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param #[test] fn test_disable_tls_peer_verification_for_all_shovels_amqp10() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_shovels_amqp10"; + let vh = "rabbitmqadmin.shovel.modifications.test3"; let shovel_name = "test_amqp10_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_source = format!( - "amqps://localhost:5671/{}?verify=verify_peer&cacertfile=/path/to/ca.pem", + let amqp_source = format!( + "amqp://localhost:5672/{}?verify=verify_peer&cacertfile=/path/to/ca.pem", vh ); - let amqps_destination = format!( - "amqps://localhost:5671/{}?verify=verify_peer&certfile=/path/to/client.pem", + let amqp_destination = format!( + "amqp://localhost:5672/{}?verify=verify_peer&certfile=/path/to/client.pem", vh ); @@ -217,9 +175,9 @@ fn test_disable_tls_peer_verification_for_all_shovels_amqp10() "--name", shovel_name, "--source-uri", - &amqps_source, + &amqp_source, "--destination-uri", - &amqps_destination, + &amqp_destination, "--source-address", "source.address", "--destination-address", @@ -228,18 +186,28 @@ fn test_disable_tls_peer_verification_for_all_shovels_amqp10() "on-confirm", ]); - run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains(shovel_name)); - run_succeeds([ "shovels", "disable_tls_peer_verification_for_all_source_uris", ]); - run_succeeds(["parameters", "list_all"]) - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("cacertfile=/path/to/ca.pem")) - .stdout(predicate::str::contains("certfile=/path/to/client.pem")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let source_uri = shovel_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + let dest_uri = shovel_param.value["dest-uri"] + .as_str() + .expect("dest-uri should be a string"); + + assert!(source_uri.contains("verify=verify_none")); + assert!(source_uri.contains("cacertfile=/path/to/ca.pem")); + assert!(dest_uri.contains("certfile=/path/to/client.pem")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -249,29 +217,29 @@ fn test_disable_tls_peer_verification_for_all_shovels_amqp10() #[test] fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_shovels_mixed_protocols"; + let vh = "rabbitmqadmin.shovel.modifications.test4"; let shovel_091_name = "test_091_shovel"; let shovel_10_name = "test_10_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_base = format!("amqps://localhost:5671/{}", vh); + let amqp_base = format!("amqp://localhost:5672/{}", vh); let uri_091_source = format!( "{}?protocol_param=091&verify=verify_peer&certfile=/path/to/091.pem", - amqps_base + amqp_base ); let uri_091_dest = format!( "{}?protocol_param=091_dest&verify=verify_peer&keyfile=/path/to/091.key", - amqps_base + amqp_base ); let uri_10_source = format!( "{}?protocol_param=10&verify=verify_peer&cacertfile=/path/to/10.pem", - amqps_base + amqp_base ); let uri_10_dest = format!( "{}?protocol_param=10_dest&verify=verify_peer&server_name_indication=amqp10.example.com", - amqps_base + amqp_base ); run_succeeds([ @@ -318,20 +286,32 @@ fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() "disable_tls_peer_verification_for_all_source_uris", ]); - run_succeeds(["parameters", "list_all"]) - .stdout(predicate::str::contains(shovel_091_name)) - .stdout(predicate::str::contains(shovel_10_name)) - .stdout(predicate::str::contains("protocol_param=091")) - .stdout(predicate::str::contains("protocol_param=091_dest")) - .stdout(predicate::str::contains("protocol_param=10")) - .stdout(predicate::str::contains("protocol_param=10_dest")) - .stdout(predicate::str::contains("certfile=/path/to/091.pem")) - .stdout(predicate::str::contains("keyfile=/path/to/091.key")) - .stdout(predicate::str::contains("cacertfile=/path/to/10.pem")) - .stdout(predicate::str::contains( - "server_name_indication=amqp10.example.com", - )) - .stdout(predicate::str::contains("verify=verify_none")); + let client = api_client(); + let params = client.list_runtime_parameters()?; + + let shovel_091_param = params + .iter() + .find(|p| p.name == shovel_091_name && p.component == "shovel") + .expect("091 shovel parameter should exist"); + let shovel_10_param = params + .iter() + .find(|p| p.name == shovel_10_name && p.component == "shovel") + .expect("10 shovel parameter should exist"); + + let uri_091_src = shovel_091_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + let uri_10_src = shovel_10_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + + assert!(uri_091_src.contains("verify=verify_none")); + assert!(uri_091_src.contains("protocol_param=091")); + assert!(uri_091_src.contains("certfile=/path/to/091.pem")); + + assert!(uri_10_src.contains("verify=verify_none")); + assert!(uri_10_src.contains("protocol_param=10")); + assert!(uri_10_src.contains("cacertfile=/path/to/10.pem")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -341,7 +321,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() #[test] fn test_disable_tls_peer_verification_for_all_shovels_no_shovels() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_shovels_no_shovels"; + let vh = "rabbitmqadmin.shovel.modifications.test5"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); @@ -359,20 +339,21 @@ fn test_disable_tls_peer_verification_for_all_shovels_no_shovels() #[test] fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() -> Result<(), Box> { - let vh = "test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params"; + let vh = + "rabbitmqadmin.test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params"; let shovel_name = "test_dummy_params_shovel"; delete_vhost(vh).ok(); run_succeeds(["declare", "vhost", "--name", vh]); - let amqps_base = format!("amqps://localhost:5671/{}", vh); + let amqp_base = format!("amqp://localhost:5672/{}", vh); let source_uri = format!( "{}?abc=123&heartbeat=5&connection_timeout=30&dummy_param=test_value", - amqps_base + amqp_base ); let dest_uri = format!( "{}?xyz=456&heartbeat=10&channel_max=100&another_dummy=example", - amqps_base + amqp_base ); run_succeeds([ @@ -400,64 +381,162 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() "disable_tls_peer_verification_for_all_source_uris", ]); - let output = run_succeeds(["parameters", "list_all"]); - let stdout = output - .stdout(predicate::str::contains(shovel_name)) - .stdout(predicate::str::contains("verify=verify_none")) - .stdout(predicate::str::contains("abc=123")) - .stdout(predicate::str::contains("heartbeat=5")) - .stdout(predicate::str::contains("connection_timeout=30")) - .stdout(predicate::str::contains("dummy_param=test_value")) - .stdout(predicate::str::contains("xyz=456")) - .stdout(predicate::str::contains("heartbeat=10")) - .stdout(predicate::str::contains("channel_max=100")) - .stdout(predicate::str::contains("another_dummy=example")); - - let output_str = str::from_utf8(&stdout.get_output().stdout).unwrap(); - let lines: Vec<&str> = output_str.lines().collect(); - let mut shovel_section = String::new(); - let mut in_our_shovel = false; - - for line in lines { - if line.contains(&shovel_name) { - in_our_shovel = true; - } - if in_our_shovel { - shovel_section.push_str(line); - shovel_section.push('\n'); - if line.contains("└─") - || (in_our_shovel && line.contains("├─") && !line.contains(&shovel_name)) - { - break; - } - } - } - - let src_uri_lines: Vec<&str> = shovel_section - .lines() - .filter(|line| line.contains("src-uri")) - .collect(); - assert!( - !src_uri_lines.is_empty(), - "Could not find src-uri in shovel section" - ); + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); - let src_uri_content = src_uri_lines[0]; - let src_verify_count = src_uri_content.matches("verify=").count(); - assert_eq!( - src_verify_count, 1, - "Expected exactly 1 verify parameter in source URI, found {}", - src_verify_count - ); + let source_uri_after = shovel_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); - assert!( - src_uri_content.contains("verify=verify_none"), - "Source URI should contain verify=verify_none" - ); - assert!( - !src_uri_content.contains("verify=verify_peer"), - "Source URI should not contain verify=verify_peer" + assert!(source_uri_after.contains("verify=verify_none")); + assert!(source_uri_after.contains("abc=123")); + assert!(source_uri_after.contains("heartbeat=5")); + assert!(source_uri_after.contains("connection_timeout=30")); + assert!(source_uri_after.contains("dummy_param=test_value")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_enable_tls_peer_verification_for_all_source_uris_basic() +-> Result<(), Box> { + let vh = "rabbitmqadmin.shovel.modifications.test6"; + let shovel_name = "test_enable_basic_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqp_source = format!("amqp://localhost:5672/{}", vh); + let amqp_destination = format!("amqp://localhost:5672/{}", vh); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &amqp_source, + "--destination-uri", + &amqp_destination, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + + run_succeeds([ + "shovels", + "enable_tls_peer_verification_for_all_source_uris", + "--node-local-ca-certificate-bundle-path", + "/etc/ssl/certs/ca_bundle.pem", + "--node-local-client-certificate-file-path", + "/etc/ssl/certs/client.pem", + "--node-local-client-private-key-file-path", + "/etc/ssl/private/client.key", + ]); + + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let source_uri = shovel_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + + assert!(source_uri.contains("verify=verify_peer")); + assert!(source_uri.contains("cacertfile=/etc/ssl/certs/ca_bundle.pem")); + assert!(source_uri.contains("certfile=/etc/ssl/certs/client.pem")); + assert!(source_uri.contains("keyfile=/etc/ssl/private/client.key")); + + delete_vhost(vh).expect("failed to delete a virtual host"); + + Ok(()) +} + +#[test] +fn test_enable_tls_peer_verification_for_all_source_uris_with_existing_params() +-> Result<(), Box> { + let vh = "rabbitmqadmin.shovel.modifications.test7"; + let shovel_name = "test_enable_existing_shovel"; + + delete_vhost(vh).ok(); + run_succeeds(["declare", "vhost", "--name", vh]); + + let amqp_base = format!("amqp://localhost:5672/{}", vh); + let source_uri = format!( + "{}?key1=abc&verify=verify_none&cacertfile=/old/path/ca.pem&key2=def&certfile=/old/path/client.pem&keyfile=/old/path/client.key&server_name_indication=example.com&custom_param=value123&another_param=xyz&heartbeat=60", + amqp_base ); + let destination_uri = format!("amqp://localhost:5672/{}", vh); + + run_succeeds([ + "-V", + vh, + "shovels", + "declare_amqp091", + "--name", + shovel_name, + "--source-uri", + &source_uri, + "--destination-uri", + &destination_uri, + "--source-queue", + "source.queue", + "--destination-queue", + "dest.queue", + "--ack-mode", + "on-confirm", + ]); + await_metric_emission(500); + + run_succeeds([ + "shovels", + "enable_tls_peer_verification_for_all_source_uris", + "--node-local-ca-certificate-bundle-path", + "/etc/ssl/certs/ca_bundle.pem", + "--node-local-client-certificate-file-path", + "/etc/ssl/certs/client.pem", + "--node-local-client-private-key-file-path", + "/etc/ssl/private/client.key", + ]); + + let client = api_client(); + let params = client.list_runtime_parameters()?; + let shovel_param = params + .iter() + .find(|p| p.name == shovel_name && p.component == "shovel") + .expect("Shovel parameter should exist"); + + let source_uri_after = shovel_param.value["src-uri"] + .as_str() + .expect("src-uri should be a string"); + + assert!(source_uri_after.contains("verify=verify_peer")); + assert!(source_uri_after.contains("cacertfile=/etc/ssl/certs/ca_bundle.pem")); + assert!(source_uri_after.contains("certfile=/etc/ssl/certs/client.pem")); + assert!(source_uri_after.contains("keyfile=/etc/ssl/private/client.key")); + assert!(!source_uri_after.contains("cacertfile=/old/path/ca.pem")); + assert!(!source_uri_after.contains("certfile=/old/path/client.pem")); + assert!(!source_uri_after.contains("keyfile=/old/path/client.key")); + assert!(source_uri_after.contains("key1=abc")); + assert!(source_uri_after.contains("key2=def")); + assert!(source_uri_after.contains("server_name_indication=example.com")); + assert!(source_uri_after.contains("custom_param=value123")); + assert!(source_uri_after.contains("another_param=xyz")); + assert!(source_uri_after.contains("heartbeat=60")); delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index ae7f842..a7920ea 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -20,7 +20,7 @@ use predicates::prelude::predicate; #[test] fn test_shovel_declaration_without_source_uri() -> Result<(), Box> { - let vh = "rust.shovels.0"; + let vh = "rabbitmqadmin.shovels.test20"; let name = "shovels.test_shovel_declaration_without_source_uri"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -53,7 +53,7 @@ fn test_shovel_declaration_without_source_uri() -> Result<(), Box Result<(), Box> { - let vh = "rust.shovels.0"; + let vh = "rabbitmqadmin.shovels.test25"; let name = "shovels.test_shovel_declaration_without_destination_uri"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -87,7 +87,7 @@ fn test_shovel_declaration_without_destination_uri() -> Result<(), Box Result<(), Box> { - let vh = "rust.shovels.1"; + let vh = "rabbitmqadmin.shovels.test21"; let name = "shovels.test_shovel_declaration_with_overlapping_destination_types"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -142,7 +142,7 @@ fn test_shovel_declaration_with_overlapping_destination_types() #[test] fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box> { - let vh = "rust.shovels.2"; + let vh = "rabbitmqadmin.shovels.test22"; delete_vhost(vh).expect("failed to delete a virtual host"); let name = "shovels.test_amqp091_shovel_declaration_and_deletion"; @@ -205,7 +205,7 @@ fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box Result<(), Box> { - let vh = "rust.shovels.3"; + let vh = "rabbitmqadmin.shovels.test23"; let name = "shovels.test_amqp10_shovel_declaration_and_deletion"; let amqp_endpoint = amqp_endpoint_with_vhost(vh); @@ -255,7 +255,7 @@ fn test_amqp10_shovel_declaration_and_deletion() -> Result<(), Box Result<(), Box> { - let vh = "shovels.delete.idempotently.1"; + let vh = "rabbitmqadmin.shovels.test24"; let shovel_name = "test_shovel_delete_idempotently"; delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/streams_tests.rs b/tests/streams_tests.rs index f207d58..29a9037 100644 --- a/tests/streams_tests.rs +++ b/tests/streams_tests.rs @@ -135,7 +135,7 @@ fn streams_list() -> Result<(), Box> { #[test] fn test_streams_delete_idempotently() -> Result<(), Box> { - let vh = "streams.delete.idempotently.1"; + let vh = "rabbitmqadmin.streams.test1"; let s = "test_stream_delete_idempotently"; delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/test_helpers.rs b/tests/test_helpers.rs index a94a199..6572518 100644 --- a/tests/test_helpers.rs +++ b/tests/test_helpers.rs @@ -100,3 +100,22 @@ pub fn delete_user(username: &str) -> CommandRunResult { cmd.assert().success(); Ok(()) } + +pub fn delete_all_test_vhosts() -> CommandRunResult { + let client = api_client(); + match client.list_vhosts() { + Ok(vhosts) => { + for vhost in vhosts { + if vhost.name.starts_with("rabbitmqadmin.") { + let mut cmd = Command::cargo_bin("rabbitmqadmin")?; + cmd.args(["vhosts", "delete", "--name", &vhost.name, "--idempotently"]); + let _ = cmd.assert().success(); + } + } + } + Err(_) => { + // If we can't list vhosts, continue anyway + } + } + Ok(()) +} diff --git a/tests/vhosts_tests.rs b/tests/vhosts_tests.rs index 1d7912b..78efe42 100644 --- a/tests/vhosts_tests.rs +++ b/tests/vhosts_tests.rs @@ -18,7 +18,7 @@ use crate::test_helpers::*; #[test] fn test_list_vhosts() -> Result<(), Box> { - let vh = "list_vhosts.1"; + let vh = "rabbitmqadmin.vhosts.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); @@ -34,7 +34,7 @@ fn test_list_vhosts() -> Result<(), Box> { #[test] fn test_vhosts_list() -> Result<(), Box> { - let vh = "list_vhosts.2"; + let vh = "rabbitmqadmin.vhosts.test2"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["vhosts", "declare", "--name", vh]); @@ -50,7 +50,7 @@ fn test_vhosts_list() -> Result<(), Box> { #[test] fn test_vhosts_create() -> Result<(), Box> { - let vh = "vhosts.create.1"; + let vh = "rabbitmqadmin.vhosts.test3"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds([ @@ -72,7 +72,7 @@ fn test_vhosts_create() -> Result<(), Box> { #[test] fn test_vhosts_delete() -> Result<(), Box> { - let vh = "vhosts.delete.1"; + let vh = "rabbitmqadmin.vhosts.test4"; run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); run_succeeds(["vhosts", "declare", "--name", vh]); From fa808677a09aaacac5fd3a97c54aa32027013ad6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 25 Sep 2025 17:24:22 -0400 Subject: [PATCH 263/320] Update change log --- CHANGELOG.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5914290..067d269 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,40 @@ ## v2.13.0 (in development) -No changes yet. +### Enhancements + +* `shovel enable_tls_peer_verification_for_all_source_uris` is a new command that enables TLS peer verification + for all shovel source URIs: + + ```shell + # The certificate and private key paths below refer + # to the files deployed to the target RabbitMQ node(s), not to the + # local files. + # + # As such, these arguments are command-specific and should not be confused + # with the global `--tls-ca-cert-file`, `--tls-cert-file`, and `--tls-key-file` + # arguments that are used by `rabbitmqadmin` itself to connect to the target node + # over the HTTP API. + rabbitmqadmin shovels enable_tls_peer_verification_for_all_source_uris \ + --node-local-ca-certificate-bundle-path /path/to/node/local/ca_bundle.pem \ + --node-local-client-certificate-file-path /path/to/node/local/client_certificate.pem \ + --node-local-client-private-key-file-path /path/to/node/local/client_private_key.pem + ``` + + See [TLS guide](https://www.rabbitmq.com/docs/ssl#peer-verification) and [Shovel guide](https://www.rabbitmq.com/docs/shovel#tls) to learn more. + +* `shovel enable_tls_peer_verification_for_all_destination_uris` is a new command that enables TLS peer verification + for all shovel destination URIs: + + ```shell + # Ditto, the certificate and private key paths below refer + # to the files deployed to the target RabbitMQ node(s), not to the + # local files. + rabbitmqadmin shovels enable_tls_peer_verification_for_all_destination_uris \ + --node-local-ca-certificate-bundle-path /path/to/node/local/ca_bundle.pem \ + --node-local-client-certificate-file-path /path/to/node/local/client_certificate.pem \ + --node-local-client-private-key-file-path /path/to/node/local/client_private_key.pem + ``` ## v2.12.0 (Sep 23, 2025) From c70caf61ebee3f451363c2304dc26d7b55fa3323 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 25 Sep 2025 18:19:41 -0400 Subject: [PATCH 264/320] Test module refactoring --- tests/bindings_tests.rs | 25 ++-- tests/combined_integration_tests.rs | 11 +- tests/definitions_export_tests.rs | 18 ++- tests/deprecated_feature_tests.rs | 6 +- tests/exchange_federation_tests.rs | 24 ++-- tests/exchanges_tests.rs | 67 +++++------ tests/feature_flag_management_tests.rs | 6 +- tests/feature_flag_tests.rs | 5 +- tests/health_check_tests.rs | 17 ++- tests/help_tests.rs | 10 +- tests/nodes_tests.rs | 21 ++-- tests/operator_policies_tests.rs | 87 +++++++------- tests/permissions_tests.rs | 9 +- tests/policies_tests.rs | 112 +++++++++--------- tests/queue_federation_tests.rs | 50 ++++---- tests/queues_tests.rs | 8 +- tests/runtime_parameters_tests.rs | 17 ++- tests/shovel_tests.rs | 33 +++--- tests/streams_tests.rs | 8 +- ...test_commands_recommended_against_tests.rs | 3 +- tests/test_helpers.rs | 5 + tests/user_limits_tests.rs | 5 +- tests/users_tests.rs | 19 ++- tests/vhost_limits_tests.rs | 5 +- tests/vhosts_tests.rs | 13 +- 25 files changed, 283 insertions(+), 301 deletions(-) diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index 476993e..ca6d023 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use predicates::prelude::*; mod test_helpers; @@ -78,9 +79,9 @@ fn test_list_bindings() -> Result<(), Box> { // list bindings in vhost 1 run_succeeds(["-V", "bindings_vhost_1", "list", "bindings"]).stdout( - predicate::str::contains("new_queue_1") - .and(predicate::str::contains("routing_key_queue")) - .and(predicate::str::contains("routing_key_exchange")), + output_includes("new_queue_1") + .and(output_includes("routing_key_queue")) + .and(output_includes("routing_key_exchange")), ); // delete the queue from vhost 1 @@ -88,11 +89,11 @@ fn test_list_bindings() -> Result<(), Box> { // these bindings were deleted with the queue run_succeeds(["-V", "bindings_vhost_1", "list", "bindings"]).stdout( - predicate::str::contains("new_queue_1") + output_includes("new_queue_1") .not() - .and(predicate::str::contains("routing_key_queue")) + .and(output_includes("routing_key_queue")) .not() - .and(predicate::str::contains("routing_key_exchange")), + .and(output_includes("routing_key_exchange")), ); delete_vhost(vh1).expect("failed to delete a virtual host"); @@ -163,9 +164,9 @@ fn test_bindings_list() -> Result<(), Box> { // list bindings in vhost 1 run_succeeds(["-V", vh1, "list", "bindings"]).stdout( - predicate::str::contains("new_queue_1") - .and(predicate::str::contains("routing_key_queue")) - .and(predicate::str::contains("routing_key_exchange")), + output_includes("new_queue_1") + .and(output_includes("routing_key_queue")) + .and(output_includes("routing_key_exchange")), ); // delete a binding @@ -187,11 +188,11 @@ fn test_bindings_list() -> Result<(), Box> { // ensure that the deleted binding is no longer listed run_succeeds(["-V", vh1, "list", "bindings"]).stdout( - predicate::str::contains("new_queue_1") + output_includes("new_queue_1") .not() - .and(predicate::str::contains("routing_key_queue")) + .and(output_includes("routing_key_queue")) .not() - .and(predicate::str::contains("routing_key_exchange")), + .and(output_includes("routing_key_exchange")), ); delete_vhost(vh1).expect("failed to delete a virtual host"); diff --git a/tests/combined_integration_tests.rs b/tests/combined_integration_tests.rs index fa445ef..b5987c1 100644 --- a/tests/combined_integration_tests.rs +++ b/tests/combined_integration_tests.rs @@ -11,11 +11,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use predicates::prelude::*; use std::path; use std::path::PathBuf; +use predicates::prelude::*; + mod test_helpers; +use crate::test_helpers::output_includes; use test_helpers::{run_fails, run_succeeds}; #[test] @@ -63,9 +65,8 @@ fn combined_integration_test2() -> Result<(), Box> { vh, ]) .stderr( - predicate::str::contains("specified configuration section (--node)").and( - predicate::str::contains("was not found in the configuration file"), - ), + output_includes("specified configuration section (--node)") + .and(output_includes("was not found in the configuration file")), ); test_helpers::delete_vhost(vh) @@ -90,7 +91,7 @@ fn combined_integration_test3() -> Result<(), Box> { "--name", vh, ]) - .stderr(predicate::str::contains("does not exist")); + .stderr(output_includes("does not exist")); test_helpers::delete_vhost(vh) } diff --git a/tests/definitions_export_tests.rs b/tests/definitions_export_tests.rs index 91443b7..e269364 100644 --- a/tests/definitions_export_tests.rs +++ b/tests/definitions_export_tests.rs @@ -15,12 +15,12 @@ use predicates::prelude::*; mod test_helpers; -use crate::test_helpers::delete_vhost; +use crate::test_helpers::{delete_vhost, output_includes}; use test_helpers::run_succeeds; #[test] fn test_export_cluster_wide_definitions() -> Result<(), Box> { - run_succeeds(["definitions", "export"]).stdout(predicate::str::contains("guest")); + run_succeeds(["definitions", "export"]).stdout(output_includes("guest")); Ok(()) } @@ -36,10 +36,9 @@ fn test_export_vhost_definitions() -> Result<(), Box> { "-V", vh, "declare", "queue", "--name", q, "--type", "quorum", ]); - run_succeeds(["--vhost", vh, "definitions", "export_from_vhost"]) - .stdout(predicate::str::contains(q)); + run_succeeds(["--vhost", vh, "definitions", "export_from_vhost"]).stdout(output_includes(q)); run_succeeds(["--vhost", "/", "definitions", "export_from_vhost"]) - .stdout(predicate::str::contains(q).not()); + .stdout(output_includes(q).not()); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -76,7 +75,7 @@ fn test_export_cluster_wide_definitions_with_transformations_case1() "-V", vh, "declare", "queue", "--name", q, "--type", "quorum", ]); - run_succeeds(["--vhost", vh, "definitions", "export"]).stdout(predicate::str::contains(p1)); + run_succeeds(["--vhost", vh, "definitions", "export"]).stdout(output_includes(p1)); // These two cannot be tested on 4.x: empty definitions will be rejected // by validation, and CMQ keys are no longer recognized as known/valid. // But at least we can test the code path this way. @@ -88,7 +87,7 @@ fn test_export_cluster_wide_definitions_with_transformations_case1() "--transformations", "prepare_for_quorum_queue_migration,strip_cmq_keys_from_policies,drop_empty_policies", ]) - .stdout(predicate::str::contains(p1)); + .stdout(output_includes(p1)); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -125,8 +124,7 @@ fn test_export_vhost_definitions_with_transformations_case1() "-V", vh, "declare", "queue", "--name", q, "--type", "quorum", ]); - run_succeeds(["--vhost", vh, "definitions", "export_from_vhost"]) - .stdout(predicate::str::contains(p1)); + run_succeeds(["--vhost", vh, "definitions", "export_from_vhost"]).stdout(output_includes(p1)); run_succeeds([ "--vhost", vh, @@ -135,7 +133,7 @@ fn test_export_vhost_definitions_with_transformations_case1() "--transformations", "prepare_for_quorum_queue_migration,strip_cmq_keys_from_policies,drop_empty_policies", ]) - .stdout(predicate::str::contains(p1)); + .stdout(output_includes(p1)); delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/deprecated_feature_tests.rs b/tests/deprecated_feature_tests.rs index 6f7a3ed..db8289f 100644 --- a/tests/deprecated_feature_tests.rs +++ b/tests/deprecated_feature_tests.rs @@ -12,21 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use predicates::prelude::*; - mod test_helpers; use crate::test_helpers::*; #[test] fn test_list_all_deprecated_features() -> Result<(), Box> { - run_succeeds(["deprecated_features", "list"]).stdout(predicate::str::contains("ram_node_type")); + run_succeeds(["deprecated_features", "list"]).stdout(output_includes("ram_node_type")); Ok(()) } #[test] fn test_list_all_deprecated_features_via_alias() -> Result<(), Box> { - run_succeeds(["list", "deprecated_features"]).stdout(predicate::str::contains("ram_node_type")); + run_succeeds(["list", "deprecated_features"]).stdout(output_includes("ram_node_type")); Ok(()) } diff --git a/tests/exchange_federation_tests.rs b/tests/exchange_federation_tests.rs index 60c540b..febb698 100644 --- a/tests/exchange_federation_tests.rs +++ b/tests/exchange_federation_tests.rs @@ -16,7 +16,7 @@ use rabbitmq_http_client::commons::QueueType; use rabbitmq_http_client::requests::{ExchangeFederationParams, FederationUpstreamParams}; mod test_helpers; -use crate::test_helpers::{amqp_endpoint_with_vhost, delete_vhost}; +use crate::test_helpers::{amqp_endpoint_with_vhost, delete_vhost, output_includes}; use test_helpers::{run_fails, run_succeeds}; #[test] @@ -200,9 +200,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case3() "--queue-type", &xfp.queue_type.to_string(), ]) - .stderr(predicate::str::contains( - "required arguments were not provided", - )); + .stderr(output_includes("required arguments were not provided")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -290,10 +288,10 @@ fn test_federation_list_all_upstreams_with_exchange_federation() ]); run_succeeds(["-V", vh, "federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(name)) - .stdout(predicate::str::contains(endpoint1.clone())) - .stdout(predicate::str::contains(x)) - .stdout(predicate::str::contains(queue_type.to_string())); + .stdout(output_includes(name)) + .stdout(output_includes(&endpoint1)) + .stdout(output_includes(x)) + .stdout(output_includes(&queue_type.to_string())); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -339,10 +337,10 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() ]); run_succeeds(["-V", vh, "federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(name)) - .stdout(predicate::str::contains(endpoint1.clone())) - .stdout(predicate::str::contains(x)) - .stdout(predicate::str::contains(queue_type.to_string())); + .stdout(output_includes(name)) + .stdout(output_includes(&endpoint1)) + .stdout(output_includes(x)) + .stdout(output_includes(&queue_type.to_string())); run_succeeds([ "-V", @@ -354,7 +352,7 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() ]); run_succeeds(["-V", vh, "federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(name).not()); + .stdout(output_includes(name).not()); delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/exchanges_tests.rs b/tests/exchanges_tests.rs index 73922d6..07a7ab9 100644 --- a/tests/exchanges_tests.rs +++ b/tests/exchanges_tests.rs @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use predicates::prelude::*; mod test_helpers; @@ -41,10 +42,10 @@ fn list_exchanges() -> Result<(), Box> { // list exchanges in vhost 1 run_succeeds(["-V", vh1, "list", "exchanges"]).stdout( - predicate::str::contains("amq.direct") - .and(predicate::str::contains("amq.fanout")) - .and(predicate::str::contains(x1)) - .and(predicate::str::contains(x2).not()), + output_includes("amq.direct") + .and(output_includes("amq.fanout")) + .and(output_includes(x1)) + .and(output_includes(x2).not()), ); // delete the exchanges from vhost 1 @@ -52,17 +53,17 @@ fn list_exchanges() -> Result<(), Box> { // list exchange in vhost 1 run_succeeds(["-V", vh1, "list", "exchanges"]).stdout( - predicate::str::contains("amq.direct") - .and(predicate::str::contains("amq.topic")) - .and(predicate::str::contains(x1).not()), + output_includes("amq.direct") + .and(output_includes("amq.topic")) + .and(output_includes(x1).not()), ); // list exchange in vhost 2 run_succeeds(["-V", vh2, "list", "exchanges"]).stdout( - predicate::str::contains("amq.direct") - .and(predicate::str::contains("amq.headers")) - .and(predicate::str::contains(x2)) - .and(predicate::str::contains(x1).not()), + output_includes("amq.direct") + .and(output_includes("amq.headers")) + .and(output_includes(x2)) + .and(output_includes(x1).not()), ); delete_vhost(vh1).expect("failed to delete a virtual host"); @@ -96,10 +97,10 @@ fn exchanges_list() -> Result<(), Box> { // list exchanges in vhost 1 run_succeeds(["-V", vh1, "exchanges", "list"]).stdout( - predicate::str::contains("amq.direct") - .and(predicate::str::contains("amq.fanout")) - .and(predicate::str::contains(x1)) - .and(predicate::str::contains(x2).not()), + output_includes("amq.direct") + .and(output_includes("amq.fanout")) + .and(output_includes(x1)) + .and(output_includes(x2).not()), ); // delete the exchanges from vhost 1 @@ -107,17 +108,17 @@ fn exchanges_list() -> Result<(), Box> { // list exchange in vhost 1 run_succeeds(["-V", vh1, "exchanges", "list"]).stdout( - predicate::str::contains("amq.direct") - .and(predicate::str::contains("amq.topic")) - .and(predicate::str::contains(x1).not()), + output_includes("amq.direct") + .and(output_includes("amq.topic")) + .and(output_includes(x1).not()), ); // list exchange in vhost 2 run_succeeds(["-V", vh2, "exchanges", "list"]).stdout( - predicate::str::contains("amq.direct") - .and(predicate::str::contains("amq.headers")) - .and(predicate::str::contains(x2)) - .and(predicate::str::contains(x1).not()), + output_includes("amq.direct") + .and(output_includes("amq.headers")) + .and(output_includes(x2)) + .and(output_includes(x1).not()), ); delete_vhost(vh1).expect("failed to delete a virtual host"); @@ -139,13 +140,13 @@ fn delete_an_existing_exchange_using_original_command_group() run_succeeds(["-V", vh, "declare", "exchange", "--name", x]); // list exchanges in vhost 1 - run_succeeds(["-V", vh, "list", "exchanges"]).stdout(predicate::str::contains(x)); + run_succeeds(["-V", vh, "list", "exchanges"]).stdout(output_includes(x)); // delete the exchange run_succeeds(["-V", vh, "delete", "exchange", "--name", x]); // list exchange in vhost 1 - run_succeeds(["-V", vh, "list", "exchanges"]).stdout(predicate::str::contains(x).not()); + run_succeeds(["-V", vh, "list", "exchanges"]).stdout(output_includes(x).not()); // delete the vhost delete_vhost(vh)?; @@ -166,13 +167,13 @@ fn delete_an_existing_exchange_using_exchanges_command_group() run_succeeds(["-V", vh, "exchanges", "declare", "--name", x]); // list exchanges in vhost 1 - run_succeeds(["-V", vh, "exchanges", "list"]).stdout(predicate::str::contains(x)); + run_succeeds(["-V", vh, "exchanges", "list"]).stdout(output_includes(x)); // delete the exchange run_succeeds(["-V", vh, "exchanges", "delete", "--name", x]); // list exchange in vhost 1 - run_succeeds(["-V", vh, "exchanges", "list"]).stdout(predicate::str::contains(x).not()); + run_succeeds(["-V", vh, "exchanges", "list"]).stdout(output_includes(x).not()); // delete the vhost delete_vhost(vh)?; @@ -207,7 +208,7 @@ fn delete_a_non_existing_exchange() -> Result<(), Box> { "--name", "7s98df7s79df-non-existent", ]) - .stderr(predicate::str::contains("Not Found")); + .stderr(output_includes("Not Found")); // delete the vhost delete_vhost(vh)?; @@ -277,9 +278,9 @@ fn test_exchanges_bind_and_unbind() -> Result<(), Box> { // list bindings in vhost 1 run_succeeds(["-V", vh2, "list", "bindings"]).stdout( - predicate::str::contains("new_queue_1") - .and(predicate::str::contains("routing_key_queue")) - .and(predicate::str::contains("routing_key_exchange")), + output_includes("new_queue_1") + .and(output_includes("routing_key_queue")) + .and(output_includes("routing_key_exchange")), ); // unbind @@ -299,11 +300,11 @@ fn test_exchanges_bind_and_unbind() -> Result<(), Box> { ]); run_succeeds(["-V", vh1, "list", "bindings"]).stdout( - predicate::str::contains("new_queue_1") + output_includes("new_queue_1") .not() - .and(predicate::str::contains("routing_key_queue")) + .and(output_includes("routing_key_queue")) .not() - .and(predicate::str::contains("routing_key_exchange")), + .and(output_includes("routing_key_exchange")), ); delete_vhost(vh1).expect("failed to delete a virtual host"); diff --git a/tests/feature_flag_management_tests.rs b/tests/feature_flag_management_tests.rs index bf9f678..ba6909d 100644 --- a/tests/feature_flag_management_tests.rs +++ b/tests/feature_flag_management_tests.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use predicates::prelude::*; - mod test_helpers; use crate::test_helpers::*; @@ -22,7 +20,7 @@ fn test_enable_a_feature_flag() -> Result<(), Box> { let ff_name = "detailed_queues_endpoint"; run_succeeds(["feature_flags", "enable", "--name", ff_name]); - run_succeeds(["feature_flags", "list"]).stdout(predicate::str::contains(ff_name)); + run_succeeds(["feature_flags", "list"]).stdout(output_includes(ff_name)); Ok(()) } @@ -32,7 +30,7 @@ fn test_enable_all_stable_feature_flags() -> Result<(), Box Result<(), Box> { - run_succeeds(["list", "feature_flags"]).stdout( - predicate::str::contains("rabbitmq_4.0.0").and(predicate::str::contains("khepri_db")), - ); + run_succeeds(["list", "feature_flags"]) + .stdout(output_includes("rabbitmq_4.0.0").and(output_includes("khepri_db"))); Ok(()) } diff --git a/tests/health_check_tests.rs b/tests/health_check_tests.rs index 1eca68b..08eb373 100644 --- a/tests/health_check_tests.rs +++ b/tests/health_check_tests.rs @@ -11,22 +11,21 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use predicates::prelude::*; mod test_helpers; +use crate::test_helpers::output_includes; use test_helpers::{run_fails, run_succeeds}; #[test] fn test_health_check_local_alarms() -> Result<(), Box> { - run_succeeds(["health_check", "local_alarms"]).stdout(predicate::str::contains("passed")); + run_succeeds(["health_check", "local_alarms"]).stdout(output_includes("passed")); Ok(()) } #[test] fn test_health_check_cluster_wide_alarms() -> Result<(), Box> { - run_succeeds(["health_check", "cluster_wide_alarms"]) - .stdout(predicate::str::contains("passed")); + run_succeeds(["health_check", "cluster_wide_alarms"]).stdout(output_includes("passed")); Ok(()) } @@ -34,7 +33,7 @@ fn test_health_check_cluster_wide_alarms() -> Result<(), Box Result<(), Box> { run_succeeds(["health_check", "port_listener", "--port", "15672"]) - .stdout(predicate::str::contains("passed")); + .stdout(output_includes("passed")); Ok(()) } @@ -42,7 +41,7 @@ fn test_health_check_port_listener_succeeds() -> Result<(), Box Result<(), Box> { run_fails(["health_check", "port_listener", "--port", "15679"]) - .stdout(predicate::str::contains("failed")); + .stdout(output_includes("failed")); Ok(()) } @@ -50,7 +49,7 @@ fn test_health_check_port_listener_fails() -> Result<(), Box Result<(), Box> { run_succeeds(["health_check", "protocol_listener", "--protocol", "amqp"]) - .stdout(predicate::str::contains("passed")); + .stdout(output_includes("passed")); Ok(()) } @@ -63,14 +62,14 @@ fn test_health_check_protocol_listener_fails() -> Result<(), Box Result<(), Box> { let args: [&str; 0] = []; - run_fails(args).stderr(predicate::str::contains( + run_fails(args).stderr(output_includes( "requires a subcommand but one was not provided", )); @@ -29,7 +29,7 @@ fn show_help_with_no_arguments() -> Result<(), Box> { #[test] fn show_subcommands_with_no_arguments() -> Result<(), Box> { let args: [&str; 0] = []; - run_fails(args).stderr(predicate::str::contains("subcommands:")); + run_fails(args).stderr(output_includes("subcommands:")); Ok(()) } @@ -37,7 +37,7 @@ fn show_subcommands_with_no_arguments() -> Result<(), Box #[test] fn show_subcommands_with_category_name_and_help() -> Result<(), Box> { let args = ["declare", "--help"]; - run_succeeds(args).stdout(predicate::str::contains("Commands:")); + run_succeeds(args).stdout(output_includes("Commands:")); Ok(()) } @@ -45,7 +45,7 @@ fn show_subcommands_with_category_name_and_help() -> Result<(), Box Result<(), Box> { let args = ["declare", "queue", "--help"]; - run_succeeds(args).stdout(predicate::str::contains("Usage:")); + run_succeeds(args).stdout(output_includes("Usage:")); Ok(()) } diff --git a/tests/nodes_tests.rs b/tests/nodes_tests.rs index c95fd51..ac9fe41 100644 --- a/tests/nodes_tests.rs +++ b/tests/nodes_tests.rs @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use predicates::prelude::*; mod test_helpers; @@ -18,9 +19,9 @@ use crate::test_helpers::*; #[test] fn test_list_nodes() -> Result<(), Box> { - run_succeeds(["list", "nodes"]).stdout(predicate::str::contains("rabbit@")); + run_succeeds(["list", "nodes"]).stdout(output_includes("rabbit@")); - run_succeeds(["nodes", "list"]).stdout(predicate::str::contains("rabbit@")); + run_succeeds(["nodes", "list"]).stdout(output_includes("rabbit@")); Ok(()) } @@ -38,10 +39,10 @@ fn test_nodes_memory_breakdown_in_bytes_succeeds() -> Result<(), Box Result<(), Box Result<(), Box> { ]); run_succeeds(["operator_policies", "list"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("12345"))); + .stdout(output_includes(policy_name).and(output_includes("12345"))); run_succeeds(["delete", "operator_policy", "--name", policy_name]); - run_succeeds(["operator_policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["operator_policies", "list"]).stdout(output_includes(policy_name).not()); Ok(()) } @@ -62,12 +63,10 @@ fn test_operator_policies() -> Result<(), Box> { "{\"max-length\": 12345}", ]); - run_succeeds(["list", "operator_policies"]).stdout( - predicate::str::contains(operator_policy_name).and(predicate::str::contains("op-foo")), - ); - run_succeeds(["delete", "operator_policy", "--name", operator_policy_name]); run_succeeds(["list", "operator_policies"]) - .stdout(predicate::str::contains(operator_policy_name).not()); + .stdout(output_includes(operator_policy_name).and(output_includes("op-foo"))); + run_succeeds(["delete", "operator_policy", "--name", operator_policy_name]); + run_succeeds(["list", "operator_policies"]).stdout(output_includes(operator_policy_name).not()); Ok(()) } @@ -92,9 +91,9 @@ fn test_operator_policies_declare_list_and_delete() -> Result<(), Box Result<(), Box> { ]); run_succeeds(["--vhost", vh1, "operator_policies", "list_in"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("98"))); + .stdout(output_includes(policy_name).and(output_includes("98"))); run_succeeds(["--vhost", vh2, "operator_policies", "list_in"]) - .stdout(predicate::str::contains(policy_name).not()); + .stdout(output_includes(policy_name).not()); run_succeeds([ "--vhost", vh1, @@ -140,7 +139,7 @@ fn test_operator_policies_in() -> Result<(), Box> { policy_name, ]); run_succeeds(["--vhost", vh1, "operator_policies", "list_in"]) - .stdout(predicate::str::contains(policy_name).not()); + .stdout(output_includes(policy_name).not()); run_succeeds(["delete", "vhost", "--name", vh1]); run_succeeds(["delete", "vhost", "--name", vh2]); @@ -180,7 +179,7 @@ fn test_operator_policies_in_with_entity_type() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { ]); run_succeeds(["list", "permissions"]).stdout( - predicate::str::contains("foo") - .and(predicate::str::contains("bar")) - .and(predicate::str::contains("baz")), + output_includes("foo") + .and(output_includes("bar")) + .and(output_includes("baz")), ); run_succeeds(["delete", "permissions", "--user", username]); - run_succeeds(["list", "permissions"]).stdout(predicate::str::contains(username).not()); + run_succeeds(["list", "permissions"]).stdout(output_includes(username).not()); run_succeeds(["delete", "user", "--name", username]); Ok(()) diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index a6c9cc5..58d80b5 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use predicates::prelude::*; mod test_helpers; @@ -36,9 +37,9 @@ fn test_list_policies() -> Result<(), Box> { ]); run_succeeds(["list", "policies"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("12345"))); + .stdout(output_includes(policy_name).and(output_includes("12345"))); run_succeeds(["delete", "policy", "--name", policy_name]); - run_succeeds(["list", "policies"]).stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["list", "policies"]).stdout(output_includes(policy_name).not()); Ok(()) } @@ -62,12 +63,10 @@ fn test_operator_policies() -> Result<(), Box> { "{\"max-length\": 12345}", ]); - run_succeeds(["list", "operator_policies"]).stdout( - predicate::str::contains(operator_policy_name).and(predicate::str::contains("op-foo")), - ); - run_succeeds(["delete", "operator_policy", "--name", operator_policy_name]); run_succeeds(["list", "operator_policies"]) - .stdout(predicate::str::contains(operator_policy_name).not()); + .stdout(output_includes(operator_policy_name).and(output_includes("op-foo"))); + run_succeeds(["delete", "operator_policy", "--name", operator_policy_name]); + run_succeeds(["list", "operator_policies"]).stdout(output_includes(operator_policy_name).not()); Ok(()) } @@ -92,9 +91,9 @@ fn test_policies_declare_list_and_delete() -> Result<(), Box Result<(), Box> { ]); run_succeeds(["--vhost", vh1, "policies", "list_in"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("98"))); + .stdout(output_includes(policy_name).and(output_includes("98"))); run_succeeds(["--vhost", vh2, "policies", "list_in"]) - .stdout(predicate::str::contains(policy_name).not()); + .stdout(output_includes(policy_name).not()); run_succeeds(["--vhost", vh1, "policies", "delete", "--name", policy_name]); run_succeeds(["--vhost", vh1, "policies", "list_in"]) - .stdout(predicate::str::contains(policy_name).not()); + .stdout(output_includes(policy_name).not()); run_succeeds(["delete", "vhost", "--name", vh1]); run_succeeds(["delete", "vhost", "--name", vh2]); @@ -166,7 +165,7 @@ fn test_policies_in_with_entity_type() -> Result<(), Box> ]); run_succeeds(["--vhost", vh, "policies", "list_in", "--apply-to", "queues"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("98"))); + .stdout(output_includes(policy_name).and(output_includes("98"))); run_succeeds([ "--vhost", vh, @@ -175,7 +174,7 @@ fn test_policies_in_with_entity_type() -> Result<(), Box> "--apply-to", "exchanges", ]) - .stdout(predicate::str::contains(policy_name).not()); + .stdout(output_includes(policy_name).not()); run_succeeds([ "--vhost", vh, @@ -184,7 +183,7 @@ fn test_policies_in_with_entity_type() -> Result<(), Box> "--apply-to", "streams", ]) - .stdout(predicate::str::contains(policy_name).not()); + .stdout(output_includes(policy_name).not()); run_succeeds([ "--vhost", "/", @@ -193,10 +192,9 @@ fn test_policies_in_with_entity_type() -> Result<(), Box> "--apply-to", "queues", ]) - .stdout(predicate::str::contains(policy_name).not()); + .stdout(output_includes(policy_name).not()); run_succeeds(["--vhost", vh, "policies", "delete", "--name", policy_name]); - run_succeeds(["--vhost", vh, "policies", "list_in"]) - .stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["--vhost", vh, "policies", "list_in"]).stdout(output_includes(policy_name).not()); run_succeeds(["delete", "vhost", "--name", vh]); @@ -280,7 +278,7 @@ fn test_policies_matching_objects() -> Result<(), Box> { "--type", "queues", ]) - .stdout(predicate::str::contains(policy1).and(predicate::str::contains("20"))); + .stdout(output_includes(policy1).and(output_includes("20"))); run_succeeds([ "--vhost", vh1, @@ -291,7 +289,7 @@ fn test_policies_matching_objects() -> Result<(), Box> { "--type", "exchanges", ]) - .stdout(predicate::str::contains(policy1).not()); + .stdout(output_includes(policy1).not()); run_succeeds([ "--vhost", @@ -303,7 +301,7 @@ fn test_policies_matching_objects() -> Result<(), Box> { "--type", "exchanges", ]) - .stdout(predicate::str::contains(policy2)); + .stdout(output_includes(policy2)); run_succeeds([ "--vhost", vh2, @@ -314,7 +312,7 @@ fn test_policies_matching_objects() -> Result<(), Box> { "--type", "streams", ]) - .stdout(predicate::str::contains(policy2).not()); + .stdout(output_includes(policy2).not()); run_succeeds([ "--vhost", @@ -326,7 +324,7 @@ fn test_policies_matching_objects() -> Result<(), Box> { "--type", "streams", ]) - .stdout(predicate::str::contains(policy3).and(predicate::str::contains("1D"))); + .stdout(output_includes(policy3).and(output_includes("1D"))); run_succeeds([ "--vhost", vh3, @@ -337,7 +335,7 @@ fn test_policies_matching_objects() -> Result<(), Box> { "--type", "exchanges", ]) - .stdout(predicate::str::contains(policy3).not()); + .stdout(output_includes(policy3).not()); run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["delete", "vhost", "--name", vh2, "--idempotently"]); @@ -366,7 +364,7 @@ fn test_policies_declare_list_update_definition_and_delete() "{\"max-length\": 20}", ]); run_succeeds(["policies", "list"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + .stdout(output_includes(policy_name).and(output_includes("20"))); run_succeeds([ "policies", @@ -380,10 +378,10 @@ fn test_policies_declare_list_update_definition_and_delete() ]); run_succeeds(["policies", "list"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("131"))); + .stdout(output_includes(policy_name).and(output_includes("131"))); run_succeeds(["policies", "delete", "--name", policy_name]); - run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["policies", "list"]).stdout(output_includes(policy_name).not()); Ok(()) } @@ -407,7 +405,7 @@ fn test_policies_individual_policy_key_manipulation() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { "{\"max-length\": 20, \"max-length-bytes\": 4823748374}", ]); run_succeeds(["--vhost", vh1, "policies", "list"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("20"))); + .stdout(output_includes(policy_name).and(output_includes("20"))); run_succeeds([ "--vhost", @@ -583,15 +581,15 @@ fn test_policies_patch_definition() -> Result<(), Box> { ]); run_succeeds(["policies", "list"]).stdout( - predicate::str::contains(policy_name) - .and(predicate::str::contains("8888888888")) - .and(predicate::str::contains("29")), + output_includes(policy_name) + .and(output_includes("8888888888")) + .and(output_includes("29")), ); - run_succeeds(["policies", "list"]).stdout(predicate::str::contains("4823748374").not()); + run_succeeds(["policies", "list"]).stdout(output_includes("4823748374").not()); run_succeeds(["--vhost", vh1, "policies", "delete", "--name", policy_name]); - run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["policies", "list"]).stdout(output_includes(policy_name).not()); run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); @@ -630,19 +628,19 @@ fn test_policies_declare_override() -> Result<(), Box> { ]); run_succeeds(["policies", "list"]) - .stdout(predicate::str::contains(policy_name).and(predicate::str::contains("12345"))); + .stdout(output_includes(policy_name).and(output_includes("12345"))); run_succeeds(["policies", "list"]).stdout( - predicate::str::contains(override_name) - .and(predicate::str::contains("23456")) - .and(predicate::str::contains("112")) - .and(predicate::str::contains("99999999")), + output_includes(override_name) + .and(output_includes("23456")) + .and(output_includes("112")) + .and(output_includes("99999999")), ); run_succeeds(["delete", "policy", "--name", policy_name]); - run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["policies", "list"]).stdout(output_includes(policy_name).not()); run_succeeds(["policies", "delete", "--name", override_name]); - run_succeeds(["policies", "list"]).stdout(predicate::str::contains(override_name).not()); + run_succeeds(["policies", "list"]).stdout(output_includes(override_name).not()); Ok(()) } @@ -663,9 +661,9 @@ fn test_policies_declare_blanket() -> Result<(), Box> { ]); run_succeeds(["policies", "list"]).stdout( - predicate::str::contains(policy_name) + output_includes(policy_name) // default blanket policy priority - .and(predicate::str::contains("787876")), + .and(output_includes("787876")), ); let client = api_client(); @@ -675,7 +673,7 @@ fn test_policies_declare_blanket() -> Result<(), Box> { assert!(pol.priority < 0); run_succeeds(["delete", "policy", "--name", policy_name]); - run_succeeds(["policies", "list"]).stdout(predicate::str::contains(policy_name).not()); + run_succeeds(["policies", "list"]).stdout(output_includes(policy_name).not()); Ok(()) } diff --git a/tests/queue_federation_tests.rs b/tests/queue_federation_tests.rs index 4e6755f..f978a11 100644 --- a/tests/queue_federation_tests.rs +++ b/tests/queue_federation_tests.rs @@ -15,7 +15,7 @@ use predicates::prelude::*; use rabbitmq_http_client::requests::{FederationUpstreamParams, QueueFederationParams}; mod test_helpers; -use crate::test_helpers::{amqp_endpoint_with_vhost, await_ms, delete_vhost}; +use crate::test_helpers::{amqp_endpoint_with_vhost, await_ms, delete_vhost, output_includes}; use test_helpers::{run_fails, run_succeeds}; #[test] @@ -30,7 +30,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case0() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); let qfp = upstream.queue_federation.unwrap(); @@ -67,7 +67,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case1a() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); let qfp = upstream.queue_federation.unwrap(); @@ -106,7 +106,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case1b() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); let qfp = upstream.queue_federation.unwrap(); @@ -148,7 +148,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case2() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); @@ -182,7 +182,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case3() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); @@ -195,9 +195,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case3() "--name", upstream.name, ]) - .stderr(predicate::str::contains( - "required arguments were not provided", - )); + .stderr(output_includes("required arguments were not provided")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -216,7 +214,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case4() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); @@ -231,9 +229,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case4() "--ack-mode", "on-publish", ]) - .stderr(predicate::str::contains( - "required arguments were not provided", - )); + .stderr(output_includes("required arguments were not provided")); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -252,7 +248,7 @@ fn test_federation_list_all_upstreams_with_queue_federation() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); let qfp = upstream.queue_federation.unwrap(); @@ -273,10 +269,10 @@ fn test_federation_list_all_upstreams_with_queue_federation() ]); run_succeeds(["-V", vh, "federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(name)) - .stdout(predicate::str::contains(endpoint1.clone())) - .stdout(predicate::str::contains(q)) - .stdout(predicate::str::contains(ctag)); + .stdout(output_includes(name)) + .stdout(output_includes(endpoint1.as_str())) + .stdout(output_includes(q)) + .stdout(output_includes(ctag)); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -295,7 +291,7 @@ fn test_federation_delete_an_upstream_with_queue_federation_settings() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh]); let qfp = upstream.queue_federation.unwrap(); @@ -316,8 +312,8 @@ fn test_federation_delete_an_upstream_with_queue_federation_settings() ]); run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(name)) - .stdout(predicate::str::contains(endpoint1.clone())); + .stdout(output_includes(name)) + .stdout(output_includes(endpoint1.as_str())); run_succeeds([ "-V", @@ -329,8 +325,8 @@ fn test_federation_delete_an_upstream_with_queue_federation_settings() ]); run_succeeds(["federation", "list_all_upstreams"]) - .stdout(predicate::str::contains(name).not()) - .stdout(predicate::str::contains(endpoint1.clone()).not()); + .stdout(output_includes(name).not()) + .stdout(output_includes(endpoint1.as_str()).not()); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -350,7 +346,7 @@ fn test_federation_list_all_links_with_queue_federation_settings() let qfp = QueueFederationParams::new_with_consumer_tag(q, ctag); let endpoint1 = amqp_endpoint.clone(); let upstream = - FederationUpstreamParams::new_queue_federation_upstream(vh1, name, &endpoint1, qfp); + FederationUpstreamParams::new_queue_federation_upstream(vh1, name, endpoint1.as_str(), qfp); run_succeeds(["declare", "vhost", "--name", vh1]); run_succeeds(["declare", "vhost", "--name", vh2]); @@ -397,9 +393,9 @@ fn test_federation_list_all_links_with_queue_federation_settings() await_ms(1000); run_succeeds(["federation", "list_all_links"]) - .stdout(predicate::str::contains(name)) - .stdout(predicate::str::contains(vh1)) - .stdout(predicate::str::contains(ctag)); + .stdout(output_includes(name)) + .stdout(output_includes(vh1)) + .stdout(output_includes(ctag)); delete_vhost(vh1).expect("failed to delete a virtual host"); delete_vhost(vh2).expect("failed to delete a virtual host"); diff --git a/tests/queues_tests.rs b/tests/queues_tests.rs index 789ab03..9ee1163 100644 --- a/tests/queues_tests.rs +++ b/tests/queues_tests.rs @@ -47,7 +47,7 @@ fn list_queues() -> Result<(), Box> { // list queues in vhost 1 run_succeeds(["-V", vh1, "list", "queues"]) - .stdout(predicate::str::contains(q1).and(predicate::str::contains("new_queue2").not())); + .stdout(output_includes(q1).and(output_includes("new_queue2").not())); // purge a queue in vhost 1 run_succeeds(["-V", vh1, "purge", "queue", "--name", q1]); @@ -56,7 +56,7 @@ fn list_queues() -> Result<(), Box> { run_succeeds(["-V", vh1, "delete", "queue", "--name", q1]); // list queues in vhost 1 - run_succeeds(["-V", vh1, "list", "queues"]).stdout(predicate::str::contains(q1).not()); + run_succeeds(["-V", vh1, "list", "queues"]).stdout(output_includes(q1).not()); delete_vhost(vh1).expect("failed to delete a virtual host"); delete_vhost(vh2).expect("failed to delete a virtual host"); @@ -94,7 +94,7 @@ fn queues_lists() -> Result<(), Box> { // list queues in vhost 1 run_succeeds(["-V", vh1, "queues", "list"]) - .stdout(predicate::str::contains(q1).and(predicate::str::contains("new_queue2").not())); + .stdout(output_includes(q1).and(output_includes("new_queue2").not())); // purge a queue in vhost 1 run_succeeds(["-V", vh1, "queues", "purge", "--name", q1]); @@ -103,7 +103,7 @@ fn queues_lists() -> Result<(), Box> { run_succeeds(["-V", vh1, "queues", "delete", "--name", q1]); // list queues in vhost 1 - run_succeeds(["-V", vh1, "queues", "list"]).stdout(predicate::str::contains(q1).not()); + run_succeeds(["-V", vh1, "queues", "list"]).stdout(output_includes(q1).not()); delete_vhost(vh1).expect("failed to delete a virtual host"); delete_vhost(vh2).expect("failed to delete a virtual host"); diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index cb72640..8bd38ff 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -45,7 +45,7 @@ fn test_runtime_parameters_across_groups() -> Result<(), Box Result<(), Box Result<(), Box> ]); await_metric_emission(200); - run_succeeds(["parameters", "list_all"]).stdout(predicate::str::contains("my-upstream")); + run_succeeds(["parameters", "list_all"]).stdout(output_includes("my-upstream")); run_succeeds([ "-V", @@ -103,7 +103,7 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "--component", "federation-upstream", ]) - .stdout(predicate::str::contains("my-upstream")); + .stdout(output_includes("my-upstream")); run_succeeds([ "-V", @@ -113,7 +113,7 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "--component", "federation-upstream", ]) - .stdout(predicate::str::contains("my-upstream")); + .stdout(output_includes("my-upstream")); run_succeeds([ "-V", @@ -134,7 +134,7 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> "--component", "federation-upstream", ]) - .stdout(predicate::str::contains("my-upstream").not()); + .stdout(output_includes("my-upstream").not()); delete_vhost(vh).expect("failed to delete a virtual host"); @@ -153,12 +153,11 @@ fn test_global_runtime_parameters_cmd_group() -> Result<(), Box Result<(), Box> { @@ -42,9 +41,7 @@ fn test_shovel_declaration_without_source_uri() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { // list streams in vhost 1 run_succeeds(["-V", vh1, "list", "queues"]) - .stdout(predicate::str::contains(s1).and(predicate::str::contains("random_stream").not())); + .stdout(output_includes(s1).and(output_includes("random_stream").not())); // delete the stream in vhost 1 run_succeeds(["-V", vh1, "delete", "stream", "--name", s1]); // list streams in vhost 1 - run_succeeds(["-V", vh1, "list", "queues"]).stdout(predicate::str::contains(s1).not()); + run_succeeds(["-V", vh1, "list", "queues"]).stdout(output_includes(s1).not()); delete_vhost(vh1).expect("failed to delete a virtual host"); delete_vhost(vh2).expect("failed to delete a virtual host"); @@ -119,13 +119,13 @@ fn streams_list() -> Result<(), Box> { // list streams in vhost 1 run_succeeds(["-V", vh1, "streams", "list"]) - .stdout(predicate::str::contains(s1).and(predicate::str::contains("random_stream").not())); + .stdout(output_includes(s1).and(output_includes("random_stream").not())); // delete the stream in vhost 1 run_succeeds(["-V", vh1, "streams", "delete", "--name", s1]); // list streams in vhost 1 - run_succeeds(["-V", vh1, "streams", "list"]).stdout(predicate::str::contains(s1).not()); + run_succeeds(["-V", vh1, "streams", "list"]).stdout(output_includes(s1).not()); delete_vhost(vh1).expect("failed to delete a virtual host"); delete_vhost(vh2).expect("failed to delete a virtual host"); diff --git a/tests/test_commands_recommended_against_tests.rs b/tests/test_commands_recommended_against_tests.rs index eba5d46..06a38b1 100644 --- a/tests/test_commands_recommended_against_tests.rs +++ b/tests/test_commands_recommended_against_tests.rs @@ -11,7 +11,6 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use predicates::prelude::*; mod test_helpers; use crate::test_helpers::*; @@ -36,7 +35,7 @@ fn test_messages() -> Result<(), Box> { ]); // consume a message - run_succeeds(["get", "messages", "--queue", q]).stdout(predicate::str::contains(payload)); + run_succeeds(["get", "messages", "--queue", q]).stdout(output_includes(payload)); // delete the test queue run_succeeds(["delete", "queue", "--name", q]); diff --git a/tests/test_helpers.rs b/tests/test_helpers.rs index 6572518..d784a03 100644 --- a/tests/test_helpers.rs +++ b/tests/test_helpers.rs @@ -19,6 +19,7 @@ use std::time::Duration; use assert_cmd::assert::Assert; use assert_cmd::prelude::*; +use predicates::prelude::predicate; use std::process::Command; use rabbitmq_http_client::blocking_api::Client as GenericAPIClient; @@ -119,3 +120,7 @@ pub fn delete_all_test_vhosts() -> CommandRunResult { } Ok(()) } + +pub fn output_includes(content: &str) -> predicates::str::ContainsPredicate { + predicate::str::contains(content) +} diff --git a/tests/user_limits_tests.rs b/tests/user_limits_tests.rs index 234b043..3671523 100644 --- a/tests/user_limits_tests.rs +++ b/tests/user_limits_tests.rs @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use predicates::prelude::*; mod test_helpers; @@ -32,7 +33,7 @@ fn test_user_limits() -> Result<(), Box> { ]); run_succeeds(["list", "user_limits"]) - .stdout(predicate::str::contains(limit_name).and(predicate::str::contains("1234"))); + .stdout(output_includes(limit_name).and(output_includes("1234"))); run_succeeds([ "delete", @@ -43,7 +44,7 @@ fn test_user_limits() -> Result<(), Box> { limit_name, ]); - run_succeeds(["list", "user_limits"]).stdout(predicate::str::contains(limit_name).not()); + run_succeeds(["list", "user_limits"]).stdout(output_includes(limit_name).not()); run_succeeds([ "delete", diff --git a/tests/users_tests.rs b/tests/users_tests.rs index a204469..b129ec0 100644 --- a/tests/users_tests.rs +++ b/tests/users_tests.rs @@ -30,11 +30,11 @@ fn test_list_users() -> Result<(), Box> { password, ]); - run_succeeds(["list", "users"]).stdout(predicate::str::contains(username)); + run_succeeds(["list", "users"]).stdout(output_includes(username)); run_succeeds(["delete", "user", "--name", username]); run_succeeds(["delete", "user", "--name", username, "--idempotently"]); - run_succeeds(["list", "users"]).stdout(predicate::str::contains(username).not()); + run_succeeds(["list", "users"]).stdout(output_includes(username).not()); Ok(()) } @@ -52,11 +52,11 @@ fn test_users_list() -> Result<(), Box> { password, ]); - run_succeeds(["users", "list"]).stdout(predicate::str::contains(username)); + run_succeeds(["users", "list"]).stdout(output_includes(username)); run_succeeds(["users", "delete", "--name", username]); run_succeeds(["users", "delete", "--name", username, "--idempotently"]); - run_succeeds(["users", "list"]).stdout(predicate::str::contains(username).not()); + run_succeeds(["users", "list"]).stdout(output_includes(username).not()); Ok(()) } @@ -74,13 +74,12 @@ fn test_list_users_with_table_styles() -> Result<(), Box> password, ]); - run_succeeds(["--table-style", "markdown", "list", "users"]) - .stdout(predicate::str::contains(username)); + run_succeeds(["--table-style", "markdown", "list", "users"]).stdout(output_includes(username)); run_succeeds(["delete", "user", "--name", username]); run_succeeds(["delete", "user", "--name", username, "--idempotently"]); run_succeeds(["--table-style", "borderless", "list", "users"]) - .stdout(predicate::str::contains(username).not()); + .stdout(output_includes(username).not()); Ok(()) } @@ -113,10 +112,10 @@ fn test_create_user_using_sha256_for_hashing() -> Result<(), Box Result<(), Box Result<(), Box> { ]); run_succeeds(["list", "vhost_limits"]) - .stdout(predicate::str::contains(limit_name).and(predicate::str::contains("1234"))); + .stdout(output_includes(limit_name).and(output_includes("1234"))); run_succeeds(["delete", "vhost_limit", "--name", limit_name]); - run_succeeds(["list", "vhost_limits"]).stdout(predicate::str::contains(limit_name).not()); + run_succeeds(["list", "vhost_limits"]).stdout(output_includes(limit_name).not()); Ok(()) } diff --git a/tests/vhosts_tests.rs b/tests/vhosts_tests.rs index 78efe42..b344ded 100644 --- a/tests/vhosts_tests.rs +++ b/tests/vhosts_tests.rs @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use predicates::prelude::*; mod test_helpers; @@ -22,12 +23,10 @@ fn test_list_vhosts() -> Result<(), Box> { delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); - run_succeeds(["list", "vhosts"]) - .stdout(predicate::str::contains("/").and(predicate::str::contains(vh))); + run_succeeds(["list", "vhosts"]).stdout(output_includes("/").and(output_includes(vh))); delete_vhost(vh).expect("failed to delete a virtual host"); - run_succeeds(["list", "vhosts"]) - .stdout(predicate::str::contains("/").and(predicate::str::contains(vh).not())); + run_succeeds(["list", "vhosts"]).stdout(output_includes("/").and(output_includes(vh).not())); Ok(()) } @@ -38,12 +37,10 @@ fn test_vhosts_list() -> Result<(), Box> { delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["vhosts", "declare", "--name", vh]); - run_succeeds(["vhosts", "list"]) - .stdout(predicate::str::contains("/").and(predicate::str::contains(vh))); + run_succeeds(["vhosts", "list"]).stdout(output_includes("/").and(output_includes(vh))); delete_vhost(vh).expect("failed to delete a virtual host"); - run_succeeds(["vhosts", "list"]) - .stdout(predicate::str::contains("/").and(predicate::str::contains(vh).not())); + run_succeeds(["vhosts", "list"]).stdout(output_includes("/").and(output_includes(vh).not())); Ok(()) } From d6a5025083d2ef94f329ca22319e2eb6aef41052 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 26 Sep 2025 00:28:51 -0400 Subject: [PATCH 265/320] Group non-interfering tests for parallel runs --- .config/nextest.toml | 52 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 004d650..67e2895 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -9,7 +9,15 @@ final-status-level = "pass" failure-output = "immediate" [test-groups] -parallel_safe = { max-threads = 8 } +# Read-only tests that can run with high parallelism +read_only = { max-threads = 16 } +# Tests that create isolated virtual hosts +isolated_vhosts = { max-threads = 8 } +# Tests that modify users/permissions (global state) +user_management = { max-threads = 1 } +# Tests that modify runtime parameters +runtime_params = { max-threads = 1 } +# Tests requiring complete isolation sequential = { max-threads = 1 } [[profile.default.overrides]] @@ -66,3 +74,45 @@ test-group = 'sequential' filter = 'binary(shovel_destination_uri_modification_tests)' priority = 23 test-group = 'sequential' + +# Read-only tests that can run with high parallelism +[[profile.default.overrides]] +filter = 'binary(help_tests) or binary(health_check_tests) or binary(nodes_tests) or binary(feature_flag_tests)' +priority = 80 +test-group = 'read_only' + +# Tests that create isolated virtual hosts +[[profile.default.overrides]] +filter = 'binary(queues_tests) or binary(exchanges_tests) or binary(bindings_tests) or binary(streams_tests) or binary(vhost_limits_tests) or binary(channels_tests) or binary(connections_tests)' +priority = 70 +test-group = 'isolated_vhosts' + +# User management tests (global state) +[[profile.default.overrides]] +filter = 'binary(users_tests) or binary(permissions_tests) or binary(user_limits_tests)' +priority = 65 +test-group = 'user_management' + +# Runtime parameter tests (excluding already sequential ones) +[[profile.default.overrides]] +filter = 'binary(runtime_parameters_tests)' +priority = 26 +test-group = 'runtime_params' + +# Virtual host management (global vhost operations) +[[profile.default.overrides]] +filter = 'binary(vhosts_tests)' +priority = 62 +test-group = 'user_management' + +# Combined integration tests (creates global users) +[[profile.default.overrides]] +filter = 'binary(combined_integration_tests)' +priority = 61 +test-group = 'user_management' + +# Tests that can run with moderate parallelism (no major conflicts) +[[profile.default.overrides]] +filter = 'binary(deprecated_feature_tests) or binary(test_commands_recommended_against_tests) or binary(feature_flag_management_tests)' +priority = 75 +test-group = 'isolated_vhosts' From 7f7a4e3af7f54db7dec34f74376063ad6f9b65a2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 26 Sep 2025 02:00:35 -0400 Subject: [PATCH 266/320] Update to RabbitMQ HTTP API 0.60.0 --- CHANGELOG.md | 8 ++++++++ Cargo.lock | 34 ++++++++++++++++----------------- Cargo.toml | 2 +- src/commands.rs | 2 +- src/output.rs | 24 +++++++++++++++++++---- src/tables.rs | 14 ++++++++++++++ tests/memory_breakdown_tests.rs | 2 +- tests/nodes_tests.rs | 2 +- 8 files changed, 63 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 067d269..3eb1cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Enhancements +* Memory breakdown commands (`show memory_breakdown_in_bytes` and `show memory_breakdown_in_percent`) now gracefully handle + cases where memory breakdown stats are not yet available on the target node + * `shovel enable_tls_peer_verification_for_all_source_uris` is a new command that enables TLS peer verification for all shovel source URIs: @@ -37,6 +40,11 @@ --node-local-client-private-key-file-path /path/to/node/local/client_private_key.pem ``` +### Upgrades + +* RabbitMQ HTTP API client was upgraded to [`0.59.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.59.0) + + ## v2.12.0 (Sep 23, 2025) ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index fe8742f..a1be7d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -149,7 +149,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link 0.2.0", ] [[package]] @@ -609,9 +609,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" @@ -1147,9 +1147,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1427,9 +1427,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.58.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2562d855c6713a7570ec103931221cad5f984ad8b149fd01f41ae71fe6c2285" +checksum = "b1b62fe158998317a6a35c65eb1265b202f2925a79eccc562c833dd459b2049c" dependencies = [ "backtrace", "percent-encoding", @@ -1765,9 +1765,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.226" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" dependencies = [ "serde_core", "serde_derive", @@ -1797,18 +1797,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 96f509b..eb1e8ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.58.0", features = [ +rabbitmq_http_client = { version = "0.59.0", features = [ "blocking", "tabled", ] } diff --git a/src/commands.rs b/src/commands.rs index 2b0826a..775ec5a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -53,7 +53,7 @@ pub fn show_overview(client: APIClient) -> ClientResult { pub fn show_memory_breakdown( client: APIClient, command_args: &ArgMatches, -) -> ClientResult { +) -> ClientResult> { let node = command_args.get_one::("node").unwrap(); client .get_node_memory_footprint(node) diff --git a/src/output.rs b/src/output.rs index dcada28..07bda23 100644 --- a/src/output.rs +++ b/src/output.rs @@ -245,9 +245,9 @@ impl<'a> ResultHandler<'a> { } } - pub fn memory_breakdown_in_bytes_result(&mut self, result: ClientResult) { + pub fn memory_breakdown_in_bytes_result(&mut self, result: ClientResult>) { match result { - Ok(output) => { + Ok(Some(output)) => { self.exit_code = Some(ExitCode::Ok); let mut table = tables::memory_breakdown_in_bytes(output); @@ -255,16 +255,24 @@ impl<'a> ResultHandler<'a> { println!("{}", table); } + Ok(None) => { + self.exit_code = Some(ExitCode::Ok); + + let mut table = tables::memory_breakdown_not_available(); + self.table_styler.apply(&mut table); + + println!("{}", table); + } Err(error) => self.report_command_run_error(&error), } } pub fn memory_breakdown_in_percent_result( &mut self, - result: ClientResult, + result: ClientResult>, ) { match result { - Ok(output) => { + Ok(Some(output)) => { self.exit_code = Some(ExitCode::Ok); let mut table = tables::memory_breakdown_in_percent(output); @@ -272,6 +280,14 @@ impl<'a> ResultHandler<'a> { println!("{}", table); } + Ok(None) => { + self.exit_code = Some(ExitCode::Ok); + + let mut table = tables::memory_breakdown_not_available(); + self.table_styler.apply(&mut table); + + println!("{}", table); + } Err(error) => self.report_command_run_error(&error), } } diff --git a/src/tables.rs b/src/tables.rs index abc6637..5a54bd4 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -943,3 +943,17 @@ pub(crate) fn memory_breakdown_in_percent(mut breakdown: NodeMemoryBreakdown) -> data.sort_by(|a, b| b.comparable.total_cmp(&a.comparable)); build_simple_table(data) } + +pub(crate) fn memory_breakdown_not_available() -> Table { + let data = vec![ + RowOfTwo { + key: "result", + value: "not available", + }, + RowOfTwo { + key: "reason", + value: "memory breakdown is not available (yet) on target node", + }, + ]; + build_table_with_header(data, "Memory Breakdown") +} diff --git a/tests/memory_breakdown_tests.rs b/tests/memory_breakdown_tests.rs index a28cb34..9d9a2cd 100644 --- a/tests/memory_breakdown_tests.rs +++ b/tests/memory_breakdown_tests.rs @@ -46,7 +46,7 @@ fn test_show_memory_breakdown_in_percent_succeeds() -> Result<(), Box Result<(), Box Date: Fri, 26 Sep 2025 02:00:58 -0400 Subject: [PATCH 267/320] cargo fmt --- src/output.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/output.rs b/src/output.rs index 07bda23..6f55535 100644 --- a/src/output.rs +++ b/src/output.rs @@ -245,7 +245,10 @@ impl<'a> ResultHandler<'a> { } } - pub fn memory_breakdown_in_bytes_result(&mut self, result: ClientResult>) { + pub fn memory_breakdown_in_bytes_result( + &mut self, + result: ClientResult>, + ) { match result { Ok(Some(output)) => { self.exit_code = Some(ExitCode::Ok); From 341afb81a8d9b5aabfb51a46e35efc60a5e3b5b6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 26 Sep 2025 02:13:49 -0400 Subject: [PATCH 268/320] Bump dev version --- CHANGELOG.md | 7 ++++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eb1cbb..34551eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.13.0 (in development) +## v2.14.0 (in development) + +No changes yet. + + +## v2.13.0 (Sep 26, 2025) ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index a1be7d5..2e80f5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1451,7 +1451,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.13.0" +version = "2.14.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index eb1e8ca..1cc46b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.13.0" +version = "2.14.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From e9167f57faf13473ebced44de42611abe2b4a086 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 27 Sep 2025 02:06:35 -0400 Subject: [PATCH 269/320] cargo update --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e80f5c..2f60337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" +checksum = "1ba2e2516bdf37af57fc6ff047855f54abad0066e5c4fdaaeb76dabb2e05bcf5" dependencies = [ "bindgen", "cc", @@ -215,9 +215,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.38" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "jobserver", @@ -2119,9 +2119,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", From 07294221440fcf6ab7cc7d148dbd23e38a7f403e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 27 Sep 2025 20:20:58 -0400 Subject: [PATCH 270/320] cargo update --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f60337..6243e1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1765,9 +1765,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1797,18 +1797,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", From f0671c24f057aeb2e915bdfcfec707ae16660456 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 27 Sep 2025 20:29:50 -0400 Subject: [PATCH 271/320] Minimalistic progress report bars for several commands that potentially operate on a lot of entities, e.g. a lot of shovels. --- src/commands.rs | 213 +++++++++++++++++------------- src/lib.rs | 16 +++ src/main.rs | 27 +++- src/output.rs | 164 +++++++++++++++++++++++ src/pre_flight.rs | 28 +++- tests/interactivity_mode_tests.rs | 53 ++++++++ tests/test_helpers.rs | 31 +++++ 7 files changed, 434 insertions(+), 98 deletions(-) create mode 100644 src/lib.rs create mode 100644 tests/interactivity_mode_tests.rs diff --git a/src/commands.rs b/src/commands.rs index 775ec5a..6b37116 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -15,6 +15,7 @@ use crate::constants::DEFAULT_BLANKET_POLICY_PRIORITY; use crate::errors::CommandRunError; +use crate::output::ProgressReporter; use clap::ArgMatches; use rabbitmq_http_client::blocking_api::Client; use rabbitmq_http_client::blocking_api::Result as ClientResult; @@ -691,59 +692,67 @@ pub fn delete_federation_upstream( pub fn disable_tls_peer_verification_for_all_federation_upstreams( client: APIClient, + prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let upstreams = client.list_federation_upstreams()?; + let total = upstreams.len(); + prog_rep.start_operation(total, "Updating federation upstream URIs"); + + for (index, upstream) in upstreams.into_iter().enumerate() { + let upstream_name = &upstream.name; + prog_rep.report_progress(index + 1, total, upstream_name); - for upstream in upstreams { let original_uri = &upstream.uri; let updated_uri = disable_tls_peer_verification(original_uri)?; - if original_uri != &updated_uri { - let upstream_params = FederationUpstreamParams { - name: &upstream.name, - vhost: &upstream.vhost, - uri: &updated_uri, - prefetch_count: upstream - .prefetch_count - .unwrap_or(DEFAULT_FEDERATION_PREFETCH), - reconnect_delay: upstream.reconnect_delay.unwrap_or(5), - ack_mode: upstream.ack_mode, - trust_user_id: upstream.trust_user_id.unwrap_or_default(), - bind_using_nowait: upstream.bind_using_nowait, - channel_use_mode: upstream.channel_use_mode, - queue_federation: if upstream.queue.is_some() { - Some(QueueFederationParams { - queue: upstream.queue.as_deref(), - consumer_tag: upstream.consumer_tag.as_deref(), - }) - } else { - None - }, - exchange_federation: if upstream.exchange.is_some() { - Some(ExchangeFederationParams { - exchange: upstream.exchange.as_deref(), - max_hops: upstream.max_hops, - queue_type: upstream.queue_type.unwrap_or(QueueType::Classic), - ttl: upstream.expires, - message_ttl: upstream.message_ttl, - resource_cleanup_mode: upstream.resource_cleanup_mode, - }) - } else { - None - }, - }; + let upstream_params = FederationUpstreamParams { + name: &upstream.name, + vhost: &upstream.vhost, + uri: &updated_uri, + prefetch_count: upstream + .prefetch_count + .unwrap_or(DEFAULT_FEDERATION_PREFETCH), + reconnect_delay: upstream.reconnect_delay.unwrap_or(5), + ack_mode: upstream.ack_mode, + trust_user_id: upstream.trust_user_id.unwrap_or_default(), + bind_using_nowait: upstream.bind_using_nowait, + channel_use_mode: upstream.channel_use_mode, + queue_federation: if upstream.queue.is_some() { + Some(QueueFederationParams { + queue: upstream.queue.as_deref(), + consumer_tag: upstream.consumer_tag.as_deref(), + }) + } else { + None + }, + exchange_federation: if upstream.exchange.is_some() { + Some(ExchangeFederationParams { + exchange: upstream.exchange.as_deref(), + max_hops: upstream.max_hops, + queue_type: upstream.queue_type.unwrap_or(QueueType::Classic), + ttl: upstream.expires, + message_ttl: upstream.message_ttl, + resource_cleanup_mode: upstream.resource_cleanup_mode, + }) + } else { + None + }, + }; - let param = RuntimeParameterDefinition::from(upstream_params); - client.upsert_runtime_parameter(¶m)?; - } + let param = RuntimeParameterDefinition::from(upstream_params); + client.upsert_runtime_parameter(¶m)?; + prog_rep.report_success(upstream_name); } + prog_rep.finish_operation(total, total); + Ok(()) } pub fn enable_tls_peer_verification_for_all_federation_upstreams( client: APIClient, args: &ArgMatches, + prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let ca_cert_path = args .get_one::("node_local_ca_certificate_bundle_path") @@ -762,8 +771,13 @@ pub fn enable_tls_peer_verification_for_all_federation_upstreams( })?; let upstreams = client.list_federation_upstreams()?; + let total = upstreams.len(); + prog_rep.start_operation(total, "Updating federation upstream URIs"); + + for (index, upstream) in upstreams.into_iter().enumerate() { + let upstream_name = &upstream.name; + prog_rep.report_progress(index + 1, total, upstream_name); - for upstream in upstreams { let original_uri = &upstream.uri; let updated_uri = enable_tls_peer_verification( original_uri, @@ -772,51 +786,52 @@ pub fn enable_tls_peer_verification_for_all_federation_upstreams( client_key_path, )?; - if original_uri != &updated_uri { - let upstream_params = FederationUpstreamParams { - name: &upstream.name, - vhost: &upstream.vhost, - uri: &updated_uri, - prefetch_count: upstream - .prefetch_count - .unwrap_or(DEFAULT_FEDERATION_PREFETCH), - reconnect_delay: upstream.reconnect_delay.unwrap_or(5), - ack_mode: upstream.ack_mode, - trust_user_id: upstream.trust_user_id.unwrap_or_default(), - bind_using_nowait: upstream.bind_using_nowait, - channel_use_mode: upstream.channel_use_mode, - queue_federation: if upstream.queue.is_some() { - Some(QueueFederationParams { - queue: upstream.queue.as_deref(), - consumer_tag: upstream.consumer_tag.as_deref(), - }) - } else { - None - }, - exchange_federation: if upstream.exchange.is_some() { - Some(ExchangeFederationParams { - exchange: upstream.exchange.as_deref(), - max_hops: upstream.max_hops, - queue_type: upstream.queue_type.unwrap_or(QueueType::Classic), - ttl: upstream.expires, - message_ttl: upstream.message_ttl, - resource_cleanup_mode: upstream.resource_cleanup_mode, - }) - } else { - None - }, - }; + let upstream_params = FederationUpstreamParams { + name: &upstream.name, + vhost: &upstream.vhost, + uri: &updated_uri, + prefetch_count: upstream + .prefetch_count + .unwrap_or(DEFAULT_FEDERATION_PREFETCH), + reconnect_delay: upstream.reconnect_delay.unwrap_or(5), + ack_mode: upstream.ack_mode, + trust_user_id: upstream.trust_user_id.unwrap_or_default(), + bind_using_nowait: upstream.bind_using_nowait, + channel_use_mode: upstream.channel_use_mode, + queue_federation: if upstream.queue.is_some() { + Some(QueueFederationParams { + queue: upstream.queue.as_deref(), + consumer_tag: upstream.consumer_tag.as_deref(), + }) + } else { + None + }, + exchange_federation: if upstream.exchange.is_some() { + Some(ExchangeFederationParams { + exchange: upstream.exchange.as_deref(), + max_hops: upstream.max_hops, + queue_type: upstream.queue_type.unwrap_or(QueueType::Classic), + ttl: upstream.expires, + message_ttl: upstream.message_ttl, + resource_cleanup_mode: upstream.resource_cleanup_mode, + }) + } else { + None + }, + }; - let param = RuntimeParameterDefinition::from(upstream_params); - client.upsert_runtime_parameter(¶m)?; - } + let param = RuntimeParameterDefinition::from(upstream_params); + client.upsert_runtime_parameter(¶m)?; + prog_rep.report_success(upstream_name); } + prog_rep.finish_operation(total, total); Ok(()) } pub fn disable_tls_peer_verification_for_all_source_uris( client: APIClient, + prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let all_params = client.list_runtime_parameters()?; let shovel_params: Vec<_> = all_params @@ -824,34 +839,46 @@ pub fn disable_tls_peer_verification_for_all_source_uris( .filter(|p| p.component == "shovel") .collect(); - for param in shovel_params { + let total = shovel_params.len(); + prog_rep.start_operation(total, "Updating shovel source URIs"); + + for (index, param) in shovel_params.into_iter().enumerate() { + let param_name = ¶m.name; + prog_rep.report_progress(index + 1, total, param_name); + let owned_params = match OwnedShovelParams::try_from(param.clone()) { Ok(params) => params, - Err(_) => continue, + Err(_) => { + prog_rep.report_skip(param_name, "invalid shovel parameters"); + continue; + } }; let original_source_uri = &owned_params.source_uri; if original_source_uri.is_empty() { + prog_rep.report_skip(param_name, "empty source URI"); continue; } let updated_source_uri = disable_tls_peer_verification(original_source_uri)?; - if original_source_uri != &updated_source_uri { - let mut updated_params = owned_params; - updated_params.source_uri = updated_source_uri; + let mut updated_params = owned_params; + updated_params.source_uri = updated_source_uri; - let param = RuntimeParameterDefinition::from(&updated_params); - client.upsert_runtime_parameter(¶m)?; - } + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + prog_rep.report_success(param_name); } + prog_rep.finish_operation(total, total); + Ok(()) } pub fn disable_tls_peer_verification_for_all_destination_uris( client: APIClient, + _prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let all_params = client.list_runtime_parameters()?; let shovel_params: Vec<_> = all_params @@ -888,6 +915,7 @@ pub fn disable_tls_peer_verification_for_all_destination_uris( pub fn enable_tls_peer_verification_for_all_source_uris( client: APIClient, args: &ArgMatches, + _prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let ca_cert_path = args .get_one::("node_local_ca_certificate_bundle_path") @@ -944,6 +972,7 @@ pub fn enable_tls_peer_verification_for_all_source_uris( pub fn enable_tls_peer_verification_for_all_destination_uris( client: APIClient, args: &ArgMatches, + _prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let ca_cert_path = args .get_one::("node_local_ca_certificate_bundle_path") @@ -1486,7 +1515,7 @@ pub fn update_policy_definition( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value)?; + let parsed_value = parse_json_from_arg::(&value)?; update_policy_definition_with(&client, vhost, &name, &key, &parsed_value).map_err(Into::into) } @@ -1505,7 +1534,7 @@ pub fn update_operator_policy_definition( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value)?; + let parsed_value = parse_json_from_arg::(&value)?; update_operator_policy_definition_with(&client, vhost, &name, &key, &parsed_value) .map_err(Into::into) @@ -1521,7 +1550,7 @@ pub fn patch_policy_definition( .get_one::("definition") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value)?; + let parsed_value = parse_json_from_arg::(&value)?; let mut pol = client .get_policy(vhost, &name) @@ -1551,7 +1580,7 @@ pub fn update_all_policy_definitions_in( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value)?; + let parsed_value = parse_json_from_arg::(&value)?; for pol in pols { update_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? @@ -1570,7 +1599,7 @@ pub fn patch_operator_policy_definition( .get_one::("definition") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value)?; + let parsed_value = parse_json_from_arg::(&value)?; let mut pol = client .get_operator_policy(vhost, &name) @@ -1600,7 +1629,7 @@ pub fn update_all_operator_policy_definitions_in( .get_one::("definition_value") .cloned() .unwrap(); - let parsed_value = parse_json_from_arg::(&value)?; + let parsed_value = parse_json_from_arg::(&value)?; for pol in pols { update_operator_policy_definition_with(&client, vhost, &pol.name, &key, &parsed_value)? @@ -2192,7 +2221,7 @@ fn parse_json_from_arg(input: &str) -> Result Result { +pub fn disable_tls_peer_verification(uri: &str) -> Result { use rabbitmq_http_client::uris::UriBuilder; let ub = UriBuilder::new(uri) @@ -2207,7 +2236,7 @@ fn disable_tls_peer_verification(uri: &str) -> Result { }) } -fn enable_tls_peer_verification( +pub fn enable_tls_peer_verification( uri: &str, ca_cert_path: &str, client_cert_path: &str, diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5ed0288 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Re-export modules for testing +pub mod pre_flight; diff --git a/src/main.rs b/src/main.rs index 1188fb9..8a8b63b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ mod config; mod constants; mod errors; mod output; -mod pre_flight; +pub mod pre_flight; mod static_urls; mod tables; mod tanzu_cli; @@ -669,14 +669,19 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("federation", "disable_tls_peer_verification_for_all_upstreams") => { - let result = - commands::disable_tls_peer_verification_for_all_federation_upstreams(client); + let mut prog_rep = res_handler.instantiate_progress_reporter(); + let result = commands::disable_tls_peer_verification_for_all_federation_upstreams( + client, + prog_rep.as_mut(), + ); res_handler.no_output_on_success(result); } ("federation", "enable_tls_peer_verification_for_all_upstreams") => { + let mut prog_rep = res_handler.instantiate_progress_reporter(); let result = commands::enable_tls_peer_verification_for_all_federation_upstreams( client, second_level_args, + prog_rep.as_mut(), ); res_handler.no_output_on_success(result); } @@ -1079,24 +1084,36 @@ fn dispatch_common_subcommand( res_handler.tabular_result(result) } ("shovels", "disable_tls_peer_verification_for_all_source_uris") => { - let result = commands::disable_tls_peer_verification_for_all_source_uris(client); + let mut prog_rep = res_handler.instantiate_progress_reporter(); + let result = commands::disable_tls_peer_verification_for_all_source_uris( + client, + prog_rep.as_mut(), + ); res_handler.no_output_on_success(result); } ("shovels", "disable_tls_peer_verification_for_all_destination_uris") => { - let result = commands::disable_tls_peer_verification_for_all_destination_uris(client); + let mut prog_rep = res_handler.instantiate_progress_reporter(); + let result = commands::disable_tls_peer_verification_for_all_destination_uris( + client, + prog_rep.as_mut(), + ); res_handler.no_output_on_success(result); } ("shovels", "enable_tls_peer_verification_for_all_source_uris") => { + let mut prog_rep = res_handler.instantiate_progress_reporter(); let result = commands::enable_tls_peer_verification_for_all_source_uris( client, second_level_args, + prog_rep.as_mut(), ); res_handler.no_output_on_success(result); } ("shovels", "enable_tls_peer_verification_for_all_destination_uris") => { + let mut prog_rep = res_handler.instantiate_progress_reporter(); let result = commands::enable_tls_peer_verification_for_all_destination_uris( client, second_level_args, + prog_rep.as_mut(), ); res_handler.no_output_on_success(result); } diff --git a/src/output.rs b/src/output.rs index 6f55535..c5193b0 100644 --- a/src/output.rs +++ b/src/output.rs @@ -27,6 +27,7 @@ use std::fmt; use sysexits::ExitCode; use tabled::settings::object::Rows; +use std::io::{self, Write}; use tabled::settings::{Panel, Remove, Style}; use tabled::{ Table, Tabled, @@ -173,6 +174,15 @@ impl<'a> ResultHandler<'a> { } } + #[allow(dead_code)] + pub fn instantiate_progress_reporter(&self) -> Box { + match (self.quiet, self.non_interactive) { + (true, _) => Box::new(QuietProgressReporter::new()), + (false, true) => Box::new(NonInteractiveProgressReporter::new()), + (false, false) => Box::new(InteractiveProgressReporter::new()), + } + } + pub fn show_overview(&mut self, result: ClientResult) { match result { Ok(ov) => { @@ -444,3 +454,157 @@ pub(crate) fn client_error_to_exit_code(error: &HttpClientError) -> ExitCode { ClientError::Other => ExitCode::Usage, } } + +#[allow(dead_code)] +pub trait ProgressReporter { + fn start_operation(&mut self, total: usize, operation_name: &str); + fn report_progress(&mut self, current: usize, total: usize, item_name: &str); + fn report_success(&mut self, item_name: &str); + fn report_skip(&mut self, item_name: &str, reason: &str); + fn finish_operation(&mut self, succeeded: usize, total: usize); +} + +#[allow(dead_code)] +pub struct InteractiveProgressReporter { + operation_name: String, +} + +#[allow(dead_code)] +impl InteractiveProgressReporter { + pub fn new() -> Self { + Self { + operation_name: String::new(), + } + } +} + +impl ProgressReporter for InteractiveProgressReporter { + fn start_operation(&mut self, _total: usize, operation_name: &str) { + self.operation_name = operation_name.to_string(); + println!("{}...", operation_name); + } + + fn report_progress(&mut self, current: usize, total: usize, _item_name: &str) { + let percentage = if total > 0 { + (current * 100) / total + } else { + 0 + }; + let bar_width = 50; + let filled = if total > 0 { + (current * bar_width) / total + } else { + 0 + }; + let bar = format!("{}{}", "#".repeat(filled), ".".repeat(bar_width - filled)); + print!("\rProgress: [{:3}%] [{}]", percentage, bar); + io::stdout().flush().unwrap(); + } + + fn report_success(&mut self, _item_name: &str) { + print!(" ✅"); + } + + fn report_skip(&mut self, _item_name: &str, _reason: &str) { + // No-op: progress bar already shows the advancement + } + + fn finish_operation(&mut self, succeeded: usize, total: usize) { + let skipped = total - succeeded; + if skipped > 0 { + println!( + "\n✓ Completed: {} updated, {} already configured", + succeeded, skipped + ); + } else { + println!("\n✓ Completed: {} items updated", succeeded); + } + } +} + +#[allow(dead_code)] +pub struct NonInteractiveProgressReporter { + operation_name: String, +} + +#[allow(dead_code)] +impl NonInteractiveProgressReporter { + pub fn new() -> Self { + Self { + operation_name: String::new(), + } + } +} + +impl ProgressReporter for NonInteractiveProgressReporter { + fn start_operation(&mut self, _total: usize, operation_name: &str) { + self.operation_name = operation_name.to_string(); + print!("{}: ", operation_name); + io::stdout().flush().unwrap(); + } + + fn report_progress(&mut self, _current: usize, _total: usize, _item_name: &str) { + print!("."); + io::stdout().flush().unwrap(); + } + + fn report_success(&mut self, _item_name: &str) { + // Dot already printed in report_progress + } + + fn report_skip(&mut self, _item_name: &str, _reason: &str) { + // Dot already printed in report_progress + } + + fn finish_operation(&mut self, succeeded: usize, total: usize) { + let skipped = total - succeeded; + if skipped > 0 { + println!( + "\nCompleted: {} updated, {} already configured", + succeeded, skipped + ); + } else { + println!("\nCompleted: {} items updated", succeeded); + } + } +} + +#[allow(dead_code)] +pub struct QuietProgressReporter; + +#[allow(dead_code)] +impl QuietProgressReporter { + pub fn new() -> Self { + Self + } +} + +impl ProgressReporter for QuietProgressReporter { + fn start_operation(&mut self, _total: usize, _operation_name: &str) { + // Silent + } + + fn report_progress(&mut self, _current: usize, _total: usize, _item_name: &str) { + // Silent + } + + fn report_success(&mut self, _item_name: &str) { + // Silent + } + + fn report_skip(&mut self, _item_name: &str, _reason: &str) { + // Silent + } + + fn finish_operation(&mut self, succeeded: usize, total: usize) { + let skipped = total - succeeded; + if skipped > 0 { + println!( + "Completed: {} updated, {} already configured", + succeeded, skipped + ); + } else { + println!("Completed: {} items updated", succeeded); + } + } +} diff --git a/src/pre_flight.rs b/src/pre_flight.rs index 8b79336..c65374d 100644 --- a/src/pre_flight.rs +++ b/src/pre_flight.rs @@ -12,8 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InteractivityMode { + Interactive, + NonInteractive, +} + +impl Default for InteractivityMode { + fn default() -> Self { + Self::Interactive + } +} + +impl InteractivityMode { + pub fn from_env() -> Self { + if is_enabled_in_env("RABBITMQADMIN_NON_INTERACTIVE_MODE") { + Self::NonInteractive + } else { + Self::Interactive + } + } + + pub fn is_non_interactive(&self) -> bool { + matches!(self, Self::NonInteractive) + } +} + pub fn is_non_interactive() -> bool { - is_enabled_in_env("RABBITMQADMIN_NON_INTERACTIVE_MODE") + InteractivityMode::from_env().is_non_interactive() } pub fn should_infer_subcommands() -> bool { diff --git a/tests/interactivity_mode_tests.rs b/tests/interactivity_mode_tests.rs new file mode 100644 index 0000000..11cdabe --- /dev/null +++ b/tests/interactivity_mode_tests.rs @@ -0,0 +1,53 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use rabbitmqadmin::pre_flight::InteractivityMode; + +#[test] +fn test_interactivity_mode_default() { + let mode = InteractivityMode::default(); + assert_eq!(mode, InteractivityMode::Interactive); + assert!(!mode.is_non_interactive()); +} + +#[test] +fn test_interactivity_mode_non_interactive() { + let mode = InteractivityMode::NonInteractive; + assert!(mode.is_non_interactive()); +} + +#[test] +fn test_interactivity_mode_from_env_interactive() { + // Clear the environment variable to test default case + unsafe { + std::env::remove_var("RABBITMQADMIN_NON_INTERACTIVE_MODE"); + } + let mode = InteractivityMode::from_env(); + assert_eq!(mode, InteractivityMode::Interactive); +} + +#[test] +fn test_interactivity_mode_from_env_non_interactive() { + // Set the environment variable + unsafe { + std::env::set_var("RABBITMQADMIN_NON_INTERACTIVE_MODE", "true"); + } + let mode = InteractivityMode::from_env(); + assert_eq!(mode, InteractivityMode::NonInteractive); + + // Clean up + unsafe { + std::env::remove_var("RABBITMQADMIN_NON_INTERACTIVE_MODE"); + } +} diff --git a/tests/test_helpers.rs b/tests/test_helpers.rs index d784a03..5603043 100644 --- a/tests/test_helpers.rs +++ b/tests/test_helpers.rs @@ -23,6 +23,7 @@ use predicates::prelude::predicate; use std::process::Command; use rabbitmq_http_client::blocking_api::Client as GenericAPIClient; +use rabbitmqadmin::pre_flight::InteractivityMode; type APIClient<'a> = GenericAPIClient<&'a str, &'a str, &'a str>; @@ -81,6 +82,36 @@ where cmd.args(args).assert().failure() } +pub fn run_succeeds_with_interactivity_mode(args: I, mode: InteractivityMode) -> Assert +where + I: IntoIterator, + S: AsRef, +{ + match mode { + InteractivityMode::NonInteractive => { + let mut cmd = Command::cargo_bin("rabbitmqadmin").unwrap(); + cmd.env("RABBITMQADMIN_NON_INTERACTIVE_MODE", "true"); + cmd.args(args).assert().success() + } + InteractivityMode::Interactive => run_succeeds(args), + } +} + +pub fn run_fails_with_interactivity_mode(args: I, mode: InteractivityMode) -> Assert +where + I: IntoIterator, + S: AsRef, +{ + match mode { + InteractivityMode::NonInteractive => { + let mut cmd = Command::cargo_bin("rabbitmqadmin").unwrap(); + cmd.env("RABBITMQADMIN_NON_INTERACTIVE_MODE", "true"); + cmd.args(args).assert().failure() + } + InteractivityMode::Interactive => run_fails(args), + } +} + pub fn create_vhost(vhost: &str) -> CommandRunResult { let mut cmd = Command::cargo_bin("rabbitmqadmin")?; cmd.args(["vhosts", "declare", "--name", vhost]); From 0e81559375ca6099f969ac6a4f20a340abc074a3 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 27 Sep 2025 20:47:57 -0400 Subject: [PATCH 272/320] Implement progress reporting for more 'shovels' commands --- CHANGELOG.md | 3 +- src/commands.rs | 95 +++++++++++++++++++++---------- src/output.rs | 40 +++---------- tests/interactivity_mode_tests.rs | 2 +- 4 files changed, 75 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34551eb..371ed9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## v2.14.0 (in development) -No changes yet. +### Enhancements +* Several commands now have minimalistic progress indicators: `federation disable_tls_peer_verification_for_all_upstreams`, `federation enable_tls_peer_verification_for_all_upstreams`, `shovels disable_tls_peer_verification_for_all_source_uris`, `shovels disable_tls_peer_verification_for_all_destination_uris`, `shovels enable_tls_peer_verification_for_all_source_uris`, and `shovels enable_tls_peer_verification_for_all_destination_uris` ## v2.13.0 (Sep 26, 2025) diff --git a/src/commands.rs b/src/commands.rs index 6b37116..74eda8a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -744,7 +744,7 @@ pub fn disable_tls_peer_verification_for_all_federation_upstreams( prog_rep.report_success(upstream_name); } - prog_rep.finish_operation(total, total); + prog_rep.finish_operation(total); Ok(()) } @@ -825,7 +825,7 @@ pub fn enable_tls_peer_verification_for_all_federation_upstreams( prog_rep.report_success(upstream_name); } - prog_rep.finish_operation(total, total); + prog_rep.finish_operation(total); Ok(()) } @@ -849,7 +849,7 @@ pub fn disable_tls_peer_verification_for_all_source_uris( let owned_params = match OwnedShovelParams::try_from(param.clone()) { Ok(params) => params, Err(_) => { - prog_rep.report_skip(param_name, "invalid shovel parameters"); + prog_rep.report_skip(param_name, "shovel parameters fail validation"); continue; } }; @@ -871,14 +871,14 @@ pub fn disable_tls_peer_verification_for_all_source_uris( prog_rep.report_success(param_name); } - prog_rep.finish_operation(total, total); + prog_rep.finish_operation(total); Ok(()) } pub fn disable_tls_peer_verification_for_all_destination_uris( client: APIClient, - _prog_rep: &mut dyn ProgressReporter, + prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let all_params = client.list_runtime_parameters()?; let shovel_params: Vec<_> = all_params @@ -886,36 +886,47 @@ pub fn disable_tls_peer_verification_for_all_destination_uris( .filter(|p| p.component == "shovel") .collect(); - for param in shovel_params { + let total = shovel_params.len(); + prog_rep.start_operation(total, "Updating shovel destination URIs"); + + for (index, param) in shovel_params.into_iter().enumerate() { + let param_name = ¶m.name; + prog_rep.report_progress(index + 1, total, param_name); + let owned_params = match OwnedShovelParams::try_from(param.clone()) { Ok(params) => params, - Err(_) => continue, + Err(_) => { + prog_rep.report_skip(param_name, "shovel parameters fail validation"); + continue; + } }; let original_destination_uri = &owned_params.destination_uri; if original_destination_uri.is_empty() { + prog_rep.report_skip(param_name, "empty destination URI"); continue; } let updated_destination_uri = disable_tls_peer_verification(original_destination_uri)?; - if original_destination_uri != &updated_destination_uri { - let mut updated_params = owned_params; - updated_params.destination_uri = updated_destination_uri; + let mut updated_params = owned_params; + updated_params.destination_uri = updated_destination_uri; - let param = RuntimeParameterDefinition::from(&updated_params); - client.upsert_runtime_parameter(¶m)?; - } + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + prog_rep.report_success(param_name); } + prog_rep.finish_operation(total); + Ok(()) } pub fn enable_tls_peer_verification_for_all_source_uris( client: APIClient, args: &ArgMatches, - _prog_rep: &mut dyn ProgressReporter, + prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let ca_cert_path = args .get_one::("node_local_ca_certificate_bundle_path") @@ -939,14 +950,24 @@ pub fn enable_tls_peer_verification_for_all_source_uris( .filter(|p| p.component == "shovel") .collect(); - for param in shovel_params { + let total = shovel_params.len(); + prog_rep.start_operation(total, "Updating shovel source URIs"); + + for (index, param) in shovel_params.into_iter().enumerate() { + let param_name = ¶m.name; + prog_rep.report_progress(index + 1, total, param_name); + let owned_params = match OwnedShovelParams::try_from(param.clone()) { Ok(params) => params, - Err(_) => continue, + Err(_) => { + prog_rep.report_skip(param_name, "shovel parameters fail validation"); + continue; + } }; let original_source_uri = &owned_params.source_uri; if original_source_uri.is_empty() { + prog_rep.report_skip(param_name, "empty source URI"); continue; } @@ -957,22 +978,23 @@ pub fn enable_tls_peer_verification_for_all_source_uris( client_key_path, )?; - if original_source_uri != &updated_source_uri { - let mut updated_params = owned_params; - updated_params.source_uri = updated_source_uri; + let mut updated_params = owned_params; + updated_params.source_uri = updated_source_uri; - let param = RuntimeParameterDefinition::from(&updated_params); - client.upsert_runtime_parameter(¶m)?; - } + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + prog_rep.report_success(param_name); } + prog_rep.finish_operation(total); + Ok(()) } pub fn enable_tls_peer_verification_for_all_destination_uris( client: APIClient, args: &ArgMatches, - _prog_rep: &mut dyn ProgressReporter, + prog_rep: &mut dyn ProgressReporter, ) -> Result<(), CommandRunError> { let ca_cert_path = args .get_one::("node_local_ca_certificate_bundle_path") @@ -996,14 +1018,24 @@ pub fn enable_tls_peer_verification_for_all_destination_uris( .filter(|p| p.component == "shovel") .collect(); - for param in shovel_params { + let total = shovel_params.len(); + prog_rep.start_operation(total, "Updating shovel destination URIs"); + + for (index, param) in shovel_params.into_iter().enumerate() { + let param_name = ¶m.name; + prog_rep.report_progress(index + 1, total, param_name); + let owned_params = match OwnedShovelParams::try_from(param.clone()) { Ok(params) => params, - Err(_) => continue, + Err(_) => { + prog_rep.report_skip(param_name, "shovel parameters fail validation"); + continue; + } }; let original_destination_uri = &owned_params.destination_uri; if original_destination_uri.is_empty() { + prog_rep.report_skip(param_name, "empty destination URI"); continue; } @@ -1014,15 +1046,16 @@ pub fn enable_tls_peer_verification_for_all_destination_uris( client_key_path, )?; - if original_destination_uri != &updated_destination_uri { - let mut updated_params = owned_params; - updated_params.destination_uri = updated_destination_uri; + let mut updated_params = owned_params; + updated_params.destination_uri = updated_destination_uri; - let param = RuntimeParameterDefinition::from(&updated_params); - client.upsert_runtime_parameter(¶m)?; - } + let param = RuntimeParameterDefinition::from(&updated_params); + client.upsert_runtime_parameter(¶m)?; + prog_rep.report_success(param_name); } + prog_rep.finish_operation(total); + Ok(()) } diff --git a/src/output.rs b/src/output.rs index c5193b0..c15ec0c 100644 --- a/src/output.rs +++ b/src/output.rs @@ -461,7 +461,7 @@ pub trait ProgressReporter { fn report_progress(&mut self, current: usize, total: usize, item_name: &str); fn report_success(&mut self, item_name: &str); fn report_skip(&mut self, item_name: &str, reason: &str); - fn finish_operation(&mut self, succeeded: usize, total: usize); + fn finish_operation(&mut self, total: usize); } #[allow(dead_code)] @@ -502,23 +502,15 @@ impl ProgressReporter for InteractiveProgressReporter { } fn report_success(&mut self, _item_name: &str) { - print!(" ✅"); + // No-op: progress bar already shows the advancement } fn report_skip(&mut self, _item_name: &str, _reason: &str) { // No-op: progress bar already shows the advancement } - fn finish_operation(&mut self, succeeded: usize, total: usize) { - let skipped = total - succeeded; - if skipped > 0 { - println!( - "\n✓ Completed: {} updated, {} already configured", - succeeded, skipped - ); - } else { - println!("\n✓ Completed: {} items updated", succeeded); - } + fn finish_operation(&mut self, total: usize) { + println!("\n✅ Completed: {} items processed", total); } } @@ -556,16 +548,8 @@ impl ProgressReporter for NonInteractiveProgressReporter { // Dot already printed in report_progress } - fn finish_operation(&mut self, succeeded: usize, total: usize) { - let skipped = total - succeeded; - if skipped > 0 { - println!( - "\nCompleted: {} updated, {} already configured", - succeeded, skipped - ); - } else { - println!("\nCompleted: {} items updated", succeeded); - } + fn finish_operation(&mut self, total: usize) { + println!("\nCompleted: {} items processed", total); } } @@ -596,15 +580,7 @@ impl ProgressReporter for QuietProgressReporter { // Silent } - fn finish_operation(&mut self, succeeded: usize, total: usize) { - let skipped = total - succeeded; - if skipped > 0 { - println!( - "Completed: {} updated, {} already configured", - succeeded, skipped - ); - } else { - println!("Completed: {} items updated", succeeded); - } + fn finish_operation(&mut self, total: usize) { + println!("Completed: {} items processed", total); } } diff --git a/tests/interactivity_mode_tests.rs b/tests/interactivity_mode_tests.rs index 11cdabe..a69c418 100644 --- a/tests/interactivity_mode_tests.rs +++ b/tests/interactivity_mode_tests.rs @@ -29,7 +29,7 @@ fn test_interactivity_mode_non_interactive() { #[test] fn test_interactivity_mode_from_env_interactive() { - // Clear the environment variable to test default case + // Clear the environment variable to test the default case unsafe { std::env::remove_var("RABBITMQADMIN_NON_INTERACTIVE_MODE"); } From e04aa58e2c10384856d1ca890eb43da02ab78a91 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 27 Sep 2025 20:50:20 -0400 Subject: [PATCH 273/320] Docs --- src/pre_flight.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pre_flight.rs b/src/pre_flight.rs index c65374d..f1d8f1d 100644 --- a/src/pre_flight.rs +++ b/src/pre_flight.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// Represents the two modes of operation for the `rabbitmqadmin` CLI: +/// interactive (driven by a human) and non-interactive (driven by automation tools). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InteractivityMode { Interactive, From 993e30666266aa8a00bfd497e86d6d3cfdbe2e72 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 27 Sep 2025 22:24:34 -0400 Subject: [PATCH 274/320] Introduce 'vhosts delete_multiple' --- .config/nextest.toml | 10 +- CHANGELOG.md | 18 ++ Cargo.lock | 1 + Cargo.toml | 1 + src/cli.rs | 29 ++- src/commands.rs | 86 ++++++- src/main.rs | 16 ++ src/output.rs | 76 ++++-- tests/test_helpers.rs | 19 ++ tests/vhosts_delete_multiple_tests.rs | 319 ++++++++++++++++++++++++++ 10 files changed, 556 insertions(+), 19 deletions(-) create mode 100644 tests/vhosts_delete_multiple_tests.rs diff --git a/.config/nextest.toml b/.config/nextest.toml index 67e2895..a747759 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -15,6 +15,8 @@ read_only = { max-threads = 16 } isolated_vhosts = { max-threads = 8 } # Tests that modify users/permissions (global state) user_management = { max-threads = 1 } +# Tests that modify virtual hosts (global state) +vhost_management = { max-threads = 1 } # Tests that modify runtime parameters runtime_params = { max-threads = 1 } # Tests requiring complete isolation @@ -103,7 +105,13 @@ test-group = 'runtime_params' [[profile.default.overrides]] filter = 'binary(vhosts_tests)' priority = 62 -test-group = 'user_management' +test-group = 'vhost_management' + +# Virtual host delete_multiple tests (global vhost operations, can be flaky due to race conditions) +[[profile.default.overrides]] +filter = 'binary(vhosts_delete_multiple_tests)' +priority = 61 +test-group = 'vhost_management' # Combined integration tests (creates global users) [[profile.default.overrides]] diff --git a/CHANGELOG.md b/CHANGELOG.md index 371ed9f..95a5617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ * Several commands now have minimalistic progress indicators: `federation disable_tls_peer_verification_for_all_upstreams`, `federation enable_tls_peer_verification_for_all_upstreams`, `shovels disable_tls_peer_verification_for_all_source_uris`, `shovels disable_tls_peer_verification_for_all_destination_uris`, `shovels enable_tls_peer_verification_for_all_source_uris`, and `shovels enable_tls_peer_verification_for_all_destination_uris` +* `vhosts delete_multiple` is a new command that deletes multiple virtual hosts matching a regular expression pattern: + + ```shell + # Delete all virtual hosts matching a pattern (requires explicit approval) + rabbitmqadmin vhosts delete_multiple --name-pattern "test-.*" --approve + + # Dry-run to see what would be deleted without actually deleting + rabbitmqadmin vhosts delete_multiple --name-pattern "staging-.*" --dry-run + + # Non-interactive mode (no --approve flag needed) + rabbitmqadmin --non-interactive vhosts delete_multiple --name-pattern "temp-.*" + ``` + + One virtual host — named `/`, that is, the default one — is always skipped to preserve + at least one functional virtual host at all times. + + **Important**: this command is **very destructive** and should be used with caution. Always test with `--dry-run` first. + ## v2.13.0 (Sep 26, 2025) ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index 6243e1e..1d0d0d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,6 +1459,7 @@ dependencies = [ "log", "predicates", "rabbitmq_http_client", + "regex", "reqwest", "rustls", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1cc46b1..603217f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ toml = "0.9" color-print = "0.3" thiserror = "2" shellexpand = "3.1" +regex = "1" log = "0.4" rustls = { version = "0.23", features = ["aws_lc_rs"] } diff --git a/src/cli.rs b/src/cli.rs index 0a18e3b..0c1c7b0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2711,7 +2711,7 @@ pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { let list_cmd = Command::new("list") .long_about("Lists virtual hosts") .after_help(color_print::cformat!( @@ -2764,7 +2764,32 @@ pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3 ) .arg(idempotently_arg.clone()); - [list_cmd, declare_cmd, delete_cmd] + let bulk_delete_cmd = Command::new("delete_multiple") + .about(color_print::cstr!("DANGER ZONE. Deletes multiple virtual hosts at once using a name matching pattern")) + .after_help(color_print::cformat!("Doc guide:: {}", VIRTUAL_HOST_GUIDE_URL)) + .arg( + Arg::new("name_pattern") + .long("name-pattern") + .help("a regular expression that will be used to match virtual host names") + .required(true), + ) + .arg( + Arg::new("approve") + .long("approve") + .action(ArgAction::SetTrue) + .help("this operation is very destructive and requires an explicit approval") + .required(false), + ) + .arg( + Arg::new("dry_run") + .long("dry-run") + .action(ArgAction::SetTrue) + .help("show what would be deleted without performing the actual deletion") + .required(false), + ) + .arg(idempotently_arg.clone()); + + [list_cmd, declare_cmd, delete_cmd, bulk_delete_cmd] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } diff --git a/src/commands.rs b/src/commands.rs index 74eda8a..441cf8b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,9 +13,10 @@ // limitations under the License. #![allow(clippy::result_large_err)] -use crate::constants::DEFAULT_BLANKET_POLICY_PRIORITY; +use crate::constants::{DEFAULT_BLANKET_POLICY_PRIORITY, DEFAULT_VHOST}; use crate::errors::CommandRunError; use crate::output::ProgressReporter; +use crate::pre_flight; use clap::ArgMatches; use rabbitmq_http_client::blocking_api::Client; use rabbitmq_http_client::blocking_api::Result as ClientResult; @@ -39,6 +40,7 @@ use rabbitmq_http_client::requests::{ use rabbitmq_http_client::transformers::{TransformationChain, VirtualHostTransformationChain}; use rabbitmq_http_client::{password_hashing, requests, responses}; +use regex::Regex; use serde::de::DeserializeOwned; use serde_json::Value; use std::fs; @@ -1265,6 +1267,88 @@ pub fn delete_vhost(client: APIClient, command_args: &ArgMatches) -> ClientResul client.delete_vhost(name, idempotently) } +pub fn delete_multiple_vhosts( + client: APIClient, + command_args: &ArgMatches, + prog_rep: &mut dyn ProgressReporter, +) -> Result>, CommandRunError> { + let name_pattern = command_args.get_one::("name_pattern").unwrap(); + let approve = command_args + .get_one::("approve") + .cloned() + .unwrap_or(false); + let dry_run = command_args + .get_one::("dry_run") + .cloned() + .unwrap_or(false); + let idempotently = command_args + .get_one::("idempotently") + .cloned() + .unwrap_or(false); + let non_interactive_cli = command_args + .get_one::("non_interactive") + .cloned() + .unwrap_or_else(|| pre_flight::InteractivityMode::from_env().is_non_interactive()); + + let regex = + Regex::new(name_pattern).map_err(|_| CommandRunError::UnsupportedArgumentValue { + property: "name_pattern".to_string(), + })?; + + let vhosts = client.list_vhosts()?; + + let matching_vhosts: Vec = vhosts + .into_iter() + .filter(|vhost| regex.is_match(&vhost.name)) + .filter(|vhost| vhost.name != DEFAULT_VHOST) + .collect(); + + if dry_run { + return Ok(Some(matching_vhosts)); + } + + if !approve && !pre_flight::is_non_interactive() && !non_interactive_cli { + return Err(CommandRunError::FailureDuringExecution { + message: "This operation is destructive and requires the --approve flag".to_string(), + }); + } + + let total = matching_vhosts.len(); + + if total == 0 { + return Ok(None); + } + + prog_rep.start_operation(total, "Deleting virtual hosts"); + + let mut successes = 0; + let mut failures = 0; + + for (index, vhost) in matching_vhosts.iter().enumerate() { + let vhost_name = &vhost.name; + match client.delete_vhost(vhost_name, idempotently) { + Ok(_) => { + prog_rep.report_progress(index + 1, total, vhost_name); + successes += 1; + } + Err(error) => { + prog_rep.report_failure(vhost_name, &error.to_string()); + failures += 1; + } + } + } + + prog_rep.finish_operation(total); + + if failures > 0 && successes == 0 { + return Err(CommandRunError::FailureDuringExecution { + message: format!("Failed to delete all {} virtual hosts", failures), + }); + } + + Ok(None) +} + pub fn delete_user(client: APIClient, command_args: &ArgMatches) -> ClientResult<()> { // the flag is required let name = command_args.get_one::("name").unwrap(); diff --git a/src/main.rs b/src/main.rs index 8a8b63b..dd6ed7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1161,6 +1161,22 @@ fn dispatch_common_subcommand( let result = commands::delete_vhost(client, second_level_args); res_handler.delete_operation_result(result); } + ("vhosts", "delete_multiple") => { + let mut prog_rep = res_handler.instantiate_progress_reporter(); + let result = + commands::delete_multiple_vhosts(client, second_level_args, &mut *prog_rep); + match result { + Ok(Some(vhosts)) => { + res_handler.tabular_result(Ok(vhosts)); + } + Ok(None) => { + res_handler.no_output_on_success(Ok(())); + } + Err(e) => { + res_handler.no_output_on_success::<()>(Err(e)); + } + } + } ("vhosts", "list") => { let result = commands::list_vhosts(client); res_handler.tabular_result(result) diff --git a/src/output.rs b/src/output.rs index c15ec0c..940d4af 100644 --- a/src/output.rs +++ b/src/output.rs @@ -461,12 +461,17 @@ pub trait ProgressReporter { fn report_progress(&mut self, current: usize, total: usize, item_name: &str); fn report_success(&mut self, item_name: &str); fn report_skip(&mut self, item_name: &str, reason: &str); + fn report_failure(&mut self, item_name: &str, error: &str); fn finish_operation(&mut self, total: usize); } #[allow(dead_code)] pub struct InteractiveProgressReporter { operation_name: String, + failures: usize, + current_position: usize, + total: usize, + results: Vec, } #[allow(dead_code)] @@ -474,29 +479,35 @@ impl InteractiveProgressReporter { pub fn new() -> Self { Self { operation_name: String::new(), + failures: 0, + current_position: 0, + total: 0, + results: Vec::new(), } } } impl ProgressReporter for InteractiveProgressReporter { - fn start_operation(&mut self, _total: usize, operation_name: &str) { + fn start_operation(&mut self, total: usize, operation_name: &str) { self.operation_name = operation_name.to_string(); + self.failures = 0; + self.current_position = 0; + self.total = total; + self.results = vec!['.'; total]; println!("{}...", operation_name); } - fn report_progress(&mut self, current: usize, total: usize, _item_name: &str) { - let percentage = if total > 0 { - (current * 100) / total - } else { - 0 - }; - let bar_width = 50; - let filled = if total > 0 { - (current * bar_width) / total + fn report_progress(&mut self, _current: usize, _total: usize, _item_name: &str) { + if self.current_position < self.results.len() { + self.results[self.current_position] = '#'; + } + self.current_position += 1; + let percentage = if self.total > 0 { + (self.current_position * 100) / self.total } else { 0 }; - let bar = format!("{}{}", "#".repeat(filled), ".".repeat(bar_width - filled)); + let bar: String = self.results.iter().collect(); print!("\rProgress: [{:3}%] [{}]", percentage, bar); io::stdout().flush().unwrap(); } @@ -509,8 +520,34 @@ impl ProgressReporter for InteractiveProgressReporter { // No-op: progress bar already shows the advancement } + fn report_failure(&mut self, _item_name: &str, _error: &str) { + self.failures += 1; + if self.current_position < self.results.len() { + self.results[self.current_position] = 'X'; + } + self.current_position += 1; + let percentage = if self.total > 0 { + (self.current_position * 100) / self.total + } else { + 0 + }; + let bar: String = self.results.iter().collect(); + print!("\rProgress: [{:3}%] [{}]", percentage, bar); + io::stdout().flush().unwrap(); + } + fn finish_operation(&mut self, total: usize) { - println!("\n✅ Completed: {} items processed", total); + let successes = total - self.failures; + if self.failures == 0 { + println!("\n✅ Completed: {} items processed successfully", total); + } else if successes == 0 { + println!("\n❌ Failed: All {} items failed to process", total); + } else { + println!( + "\n⚠️ Completed with failures: {} succeeded, {} failed out of {} total", + successes, self.failures, total + ); + } } } @@ -536,16 +573,21 @@ impl ProgressReporter for NonInteractiveProgressReporter { } fn report_progress(&mut self, _current: usize, _total: usize, _item_name: &str) { - print!("."); + print!("#"); io::stdout().flush().unwrap(); } fn report_success(&mut self, _item_name: &str) { - // Dot already printed in report_progress + // Hash already printed in report_progress } fn report_skip(&mut self, _item_name: &str, _reason: &str) { - // Dot already printed in report_progress + // Hash already printed in report_progress + } + + fn report_failure(&mut self, _item_name: &str, _error: &str) { + print!("X"); + io::stdout().flush().unwrap(); } fn finish_operation(&mut self, total: usize) { @@ -580,6 +622,10 @@ impl ProgressReporter for QuietProgressReporter { // Silent } + fn report_failure(&mut self, _item_name: &str, _error: &str) { + // Silent + } + fn finish_operation(&mut self, total: usize) { println!("Completed: {} items processed", total); } diff --git a/tests/test_helpers.rs b/tests/test_helpers.rs index 5603043..2f3997e 100644 --- a/tests/test_helpers.rs +++ b/tests/test_helpers.rs @@ -152,6 +152,25 @@ pub fn delete_all_test_vhosts() -> CommandRunResult { Ok(()) } +pub fn delete_vhosts_with_prefix(prefix: &str) -> CommandRunResult { + let client = api_client(); + match client.list_vhosts() { + Ok(vhosts) => { + for vhost in vhosts { + if vhost.name.starts_with(prefix) { + let mut cmd = Command::cargo_bin("rabbitmqadmin")?; + cmd.args(["vhosts", "delete", "--name", &vhost.name, "--idempotently"]); + let _ = cmd.assert().success(); + } + } + } + Err(_) => { + // If we can't list vhosts, continue anyway + } + } + Ok(()) +} + pub fn output_includes(content: &str) -> predicates::str::ContainsPredicate { predicate::str::contains(content) } diff --git a/tests/vhosts_delete_multiple_tests.rs b/tests/vhosts_delete_multiple_tests.rs new file mode 100644 index 0000000..f5f0550 --- /dev/null +++ b/tests/vhosts_delete_multiple_tests.rs @@ -0,0 +1,319 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_helpers; + +use crate::test_helpers::*; + +#[test] +fn test_vhosts_delete_multiple_basic() -> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-basic"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create 5 test virtual hosts + for i in 1..=5 { + let vh_name = format!("{}-{}", prefix, i); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + } + + // Verify they exist + let client = api_client(); + let vhosts_before = client.list_vhosts()?; + let test_vhosts_before: Vec<_> = vhosts_before + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_before.len(), 5); + + // Delete them using the new command with idempotently flag + run_succeeds([ + "vhosts", + "delete_multiple", + "--name-pattern", + &format!("{}.*", prefix), + "--approve", + "--idempotently", + ]); + + // Verify they're gone + let vhosts_after = client.list_vhosts()?; + let test_vhosts_after: Vec<_> = vhosts_after + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_after.len(), 0); + + Ok(()) +} + +#[test] +fn test_vhosts_delete_multiple_dry_run() -> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-dry-run"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create 3 test virtual hosts + for i in 1..=3 { + let vh_name = format!("{}-{}", prefix, i); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + } + + // Verify they exist + let client = api_client(); + let vhosts_before = client.list_vhosts()?; + let test_vhosts_before: Vec<_> = vhosts_before + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_before.len(), 3); + + // Run dry-run (should not delete anything) + run_succeeds([ + "vhosts", + "delete_multiple", + "--name-pattern", + &format!("{}.*", prefix), + "--dry-run", + ]); + + // Verify they still exist + let vhosts_after = client.list_vhosts()?; + let test_vhosts_after: Vec<_> = vhosts_after + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_after.len(), 3); + + // Clean up + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + Ok(()) +} + +#[test] +fn test_vhosts_delete_multiple_non_interactive() -> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-non-interactive"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create 2 test virtual hosts + for i in 1..=2 { + let vh_name = format!("{}-{}", prefix, i); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + } + + // Verify they exist + let client = api_client(); + let vhosts_before = client.list_vhosts()?; + let test_vhosts_before: Vec<_> = vhosts_before + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_before.len(), 2); + + // Delete using non-interactive mode (no --approve needed) + run_succeeds([ + "--non-interactive", + "vhosts", + "delete_multiple", + "--name-pattern", + &format!("{}.*", prefix), + "--idempotently", + ]); + + // Verify they're gone + let vhosts_after = client.list_vhosts()?; + let test_vhosts_after: Vec<_> = vhosts_after + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_after.len(), 0); + + Ok(()) +} + +#[test] +fn test_vhosts_delete_multiple_protects_default_vhost() -> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-protects-default"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create test virtual hosts + for i in 1..=2 { + let vh_name = format!("{}-{}", prefix, i); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + } + + // Verify they exist + let client = api_client(); + let vhosts_before = client.list_vhosts()?; + let test_vhosts_before: Vec<_> = vhosts_before + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_before.len(), 2); + + // Verify default vhost exists + let default_vhost_before = vhosts_before.iter().find(|vh| vh.name == "/"); + assert!(default_vhost_before.is_some()); + + // Try to delete everything including default vhost + run_succeeds([ + "vhosts", + "delete_multiple", + "--name-pattern", + ".*", // This would match everything including "/" + "--approve", + "--idempotently", + ]); + + // Verify test vhosts are gone but default vhost still exists + let vhosts_after = client.list_vhosts()?; + let test_vhosts_after: Vec<_> = vhosts_after + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_after.len(), 0); + + let default_vhost_after = vhosts_after.iter().find(|vh| vh.name == "/"); + assert!(default_vhost_after.is_some()); + + Ok(()) +} + +#[test] +fn test_vhosts_delete_multiple_with_invalid_regex() -> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-invalid-regex"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create a test virtual host + let vh_name = format!("{}-1", prefix); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + + // Try to delete with invalid regex pattern + run_fails([ + "vhosts", + "delete_multiple", + "--name-pattern", + "[invalid", // Invalid regex + "--approve", + ]); + + // Verify the vhost still exists + let client = api_client(); + let vhosts = client.list_vhosts()?; + let test_vhost = vhosts.iter().find(|vh| vh.name == vh_name); + assert!(test_vhost.is_some()); + + // Clean up + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + Ok(()) +} + +#[test] +fn test_vhosts_delete_multiple_requires_approve_in_interactive_mode() +-> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-requires-approve"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create a test virtual host + let vh_name = format!("{}-1", prefix); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + + // Try to delete without --approve flag (should fail) + run_fails([ + "vhosts", + "delete_multiple", + "--name-pattern", + &format!("{}.*", prefix), + ]); + + // Verify the vhost still exists + let client = api_client(); + let vhosts = client.list_vhosts()?; + let test_vhost = vhosts.iter().find(|vh| vh.name == vh_name); + assert!(test_vhost.is_some()); + + // Clean up + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + Ok(()) +} +// This test verifies that the delete_multiple command continues processing +// even when individual vhost deletions fail and shows appropriate progress indicators. +#[test] +fn test_vhosts_delete_multiple_continues_on_individual_failures() +-> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-continues"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create test virtual hosts + for i in 1..=3 { + let vh_name = format!("{}-{}", prefix, i); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + } + + // Verify they exist + let client = api_client(); + let vhosts_before = client.list_vhosts()?; + let test_vhosts_before: Vec<_> = vhosts_before + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_before.len(), 3); + + // Manually delete one vhost to simulate a failure scenario + // (This would cause a 404 when the command tries to delete it) + let vh_name_to_predelete = format!("{}-2", prefix); + delete_vhost(&vh_name_to_predelete).ok(); + + // Run delete_multiple - it should: + // 1. Continue processing even when deleting vh-2 fails (404) + // 2. Show progress with 'X' for failed deletions + // 3. Successfully delete vh-1 and vh-3 + run_succeeds([ + "vhosts", + "delete_multiple", + "--name-pattern", + &format!("{}.*", prefix), + "--approve", + "--idempotently", + ]); + + // Verify that only the successfully deleted vhosts are gone + // (vh-1 and vh-3 should be deleted, vh-2 was already gone) + let vhosts_after = client.list_vhosts()?; + let test_vhosts_after: Vec<_> = vhosts_after + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_after.len(), 0); + + // Clean up any remaining test vhosts to ensure test isolation + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + Ok(()) +} From dce5165f9a24f4eeaa468be70adc650a7ec965bd Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 02:26:17 -0400 Subject: [PATCH 275/320] Commands for enabling and disabling virtual host deletion protection [1] 1. https://www.rabbitmq.com/docs/vhosts#deletion-protection --- CHANGELOG.md | 15 +++++++ src/cli.rs | 37 +++++++++++++-- src/commands.rs | 16 +++++++ src/constants.rs | 3 ++ src/main.rs | 10 +++++ tests/vhosts_delete_multiple_tests.rs | 65 +++++++++++++++++++++++++++ tests/vhosts_tests.rs | 46 +++++++++++++++++++ 7 files changed, 189 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95a5617..423f16a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,21 @@ **Important**: this command is **very destructive** and should be used with caution. Always test with `--dry-run` first. +* `vhosts enable_deletion_protection` and `vhosts disable_deletion_protection` are two new commands + for managing [virtual host deletion protection](https://www.rabbitmq.com/docs/vhosts#deletion-protection): + + ```shell + # Enable deletion protection for a virtual host + rabbitmqadmin vhosts enable_deletion_protection --name "production-vhost" + + # Disable deletion protection for a virtual host + rabbitmqadmin vhosts disable_deletion_protection --name "production-vhost" + ``` + + Protected virtual hosts cannot be deleted, either individually using `vhosts delete` or + as part of bulk operations using `vhosts delete_multiple`. To delete a protected + virtual host, its protection must be lifted first. + ## v2.13.0 (Sep 26, 2025) ### Enhancements diff --git a/src/cli.rs b/src/cli.rs index 0c1c7b0..f87e2f7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2711,7 +2711,7 @@ pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } -pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { +pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { let list_cmd = Command::new("list") .long_about("Lists virtual hosts") .after_help(color_print::cformat!( @@ -2788,9 +2788,40 @@ pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4 .required(false), ) .arg(idempotently_arg.clone()); + let enable_deletion_protection_cmd = Command::new("enable_deletion_protection") + .about("Enables deletion protection for a virtual host") + .after_help(color_print::cformat!( + "Doc guide:: {}", + VHOST_DELETION_PROTECTION_GUIDE_URL + )) + .arg( + Arg::new("name") + .long("name") + .help("virtual host name") + .required(true), + ); + let disable_deletion_protection_cmd = Command::new("disable_deletion_protection") + .about("Disables deletion protection for a virtual host") + .after_help(color_print::cformat!( + "Doc guide:: {}", + VHOST_DELETION_PROTECTION_GUIDE_URL + )) + .arg( + Arg::new("name") + .long("name") + .help("virtual host name") + .required(true), + ); - [list_cmd, declare_cmd, delete_cmd, bulk_delete_cmd] - .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + [ + list_cmd, + declare_cmd, + delete_cmd, + bulk_delete_cmd, + enable_deletion_protection_cmd, + disable_deletion_protection_cmd, + ] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { diff --git a/src/commands.rs b/src/commands.rs index 441cf8b..88f7fd7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1267,6 +1267,22 @@ pub fn delete_vhost(client: APIClient, command_args: &ArgMatches) -> ClientResul client.delete_vhost(name, idempotently) } +pub fn enable_vhost_deletion_protection( + client: APIClient, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").unwrap(); + client.enable_vhost_deletion_protection(name) +} + +pub fn disable_vhost_deletion_protection( + client: APIClient, + command_args: &ArgMatches, +) -> ClientResult<()> { + let name = command_args.get_one::("name").unwrap(); + client.disable_vhost_deletion_protection(name) +} + pub fn delete_multiple_vhosts( client: APIClient, command_args: &ArgMatches, diff --git a/src/constants.rs b/src/constants.rs index 592a270..cb5e909 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -35,3 +35,6 @@ pub const DEFAULT_CONFIG_SECTION_NAME: &str = "default"; pub const TANZU_COMMAND_PREFIX: &str = "tanzu"; pub const DEFAULT_BLANKET_POLICY_PRIORITY: i16 = -20; + +pub const VHOST_DELETION_PROTECTION_GUIDE_URL: &str = + "https://www.rabbitmq.com/docs/vhosts#deletion-protection"; diff --git a/src/main.rs b/src/main.rs index dd6ed7d..79a4838 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1181,6 +1181,16 @@ fn dispatch_common_subcommand( let result = commands::list_vhosts(client); res_handler.tabular_result(result) } + ("vhosts", "enable_deletion_protection") => { + let result = commands::enable_vhost_deletion_protection(client, second_level_args) + .map_err(Into::into); + res_handler.no_output_on_success(result); + } + ("vhosts", "disable_deletion_protection") => { + let result = commands::disable_vhost_deletion_protection(client, second_level_args) + .map_err(Into::into); + res_handler.no_output_on_success(result); + } _ => { let error = CommandRunError::UnknownCommandTarget { command: pair.0.into(), diff --git a/tests/vhosts_delete_multiple_tests.rs b/tests/vhosts_delete_multiple_tests.rs index f5f0550..3d3cb89 100644 --- a/tests/vhosts_delete_multiple_tests.rs +++ b/tests/vhosts_delete_multiple_tests.rs @@ -317,3 +317,68 @@ fn test_vhosts_delete_multiple_continues_on_individual_failures() Ok(()) } + +#[test] +fn test_vhosts_delete_multiple_protects_deletion_protected_vhosts() +-> Result<(), Box> { + let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-protects-protected"; + + // Clean up any existing test vhosts first (only our specific ones) + delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok(); + + // Create test virtual hosts + for i in 1..=3 { + let vh_name = format!("{}-{}", prefix, i); + run_succeeds(["vhosts", "declare", "--name", &vh_name]); + } + + // Enable deletion protection for the second vhost only + let protected_vh = format!("{}-2", prefix); + run_succeeds([ + "vhosts", + "enable_deletion_protection", + "--name", + &protected_vh, + ]); + + // We begin with this many virtual hosts + let client = api_client(); + let vhosts_before = client.list_vhosts()?; + let test_vhosts_before: Vec<_> = vhosts_before + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + assert_eq!(test_vhosts_before.len(), 3); + + // Try to delete all using the 'vhosts delete_multiple' command + run_succeeds([ + "vhosts", + "delete_multiple", + "--name-pattern", + &format!("{}.*", prefix), + "--approve", + "--idempotently", + ]); + + // Verify that the protected vhost still exists, but several others were deleted + let vhosts_after = client.list_vhosts()?; + let test_vhosts_after: Vec<_> = vhosts_after + .iter() + .filter(|vh| vh.name.starts_with(prefix)) + .collect(); + + // Only the protected vhost should remain + assert_eq!(test_vhosts_after.len(), 1); + assert_eq!(test_vhosts_after[0].name, protected_vh); + + // Clean up + run_succeeds([ + "vhosts", + "disable_deletion_protection", + "--name", + &protected_vh, + ]); + run_succeeds(["vhosts", "delete", "--name", &protected_vh]); + + Ok(()) +} diff --git a/tests/vhosts_tests.rs b/tests/vhosts_tests.rs index b344ded..a46a74b 100644 --- a/tests/vhosts_tests.rs +++ b/tests/vhosts_tests.rs @@ -80,3 +80,49 @@ fn test_vhosts_delete() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_vhosts_enable_deletion_protection() -> Result<(), Box> { + let vh = "rabbitmqadmin.vhosts.test-deletion-protection-enable"; + run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); + + run_succeeds(["vhosts", "declare", "--name", vh]); + + run_succeeds(["vhosts", "enable_deletion_protection", "--name", vh]); + + run_succeeds(["vhosts", "disable_deletion_protection", "--name", vh]); + run_succeeds(["vhosts", "delete", "--name", vh]); + + Ok(()) +} + +#[test] +fn test_vhosts_disable_deletion_protection() -> Result<(), Box> { + let vh = "rabbitmqadmin.vhosts.test-deletion-protection-disable"; + run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); + + run_succeeds(["vhosts", "declare", "--name", vh]); + + run_succeeds(["vhosts", "enable_deletion_protection", "--name", vh]); + run_succeeds(["vhosts", "disable_deletion_protection", "--name", vh]); + + run_succeeds(["vhosts", "delete", "--name", vh]); + + Ok(()) +} + +#[test] +fn test_vhosts_protected_vhost_cannot_be_deleted() -> Result<(), Box> { + let vh = "rabbitmqadmin.vhosts.test-protected-cannot-delete"; + run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); + + run_succeeds(["vhosts", "declare", "--name", vh]); + run_succeeds(["vhosts", "enable_deletion_protection", "--name", vh]); + + run_fails(["vhosts", "delete", "--name", vh]); + + run_succeeds(["vhosts", "disable_deletion_protection", "--name", vh]); + run_succeeds(["vhosts", "delete", "--name", vh]); + + Ok(()) +} From 2dd4acd951fcd474fe351c67efae524fba6597ed Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 02:59:38 -0400 Subject: [PATCH 276/320] Standardize naming of test virtual hosts --- tests/bindings_tests.rs | 8 ++++---- tests/exchanges_tests.rs | 12 ++++++------ tests/queue_federation_tests.rs | 4 ++-- tests/queues_tests.rs | 8 ++++---- tests/streams_tests.rs | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index ca6d023..757a875 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -19,8 +19,8 @@ use crate::test_helpers::*; #[test] fn test_list_bindings() -> Result<(), Box> { - let vh1 = "test_list_bindings_1"; - let vh2 = "test_list_bindings_2"; + let vh1 = "rabbitmqadmin.test_list_bindings_1"; + let vh2 = "rabbitmqadmin.test_list_bindings_2"; let q1 = "new_queue_1"; let q2 = "new_queue_2"; @@ -104,8 +104,8 @@ fn test_list_bindings() -> Result<(), Box> { #[test] fn test_bindings_list() -> Result<(), Box> { - let vh1 = "test_bindings_list_1"; - let vh2 = "test_bindings_list_2"; + let vh1 = "rabbitmqadmin.test_bindings_list_1"; + let vh2 = "rabbitmqadmin.test_bindings_list_2"; let q1 = "new_queue_1"; let q2 = "new_queue_2"; diff --git a/tests/exchanges_tests.rs b/tests/exchanges_tests.rs index 07a7ab9..a8f9db9 100644 --- a/tests/exchanges_tests.rs +++ b/tests/exchanges_tests.rs @@ -19,8 +19,8 @@ use crate::test_helpers::*; #[test] fn list_exchanges() -> Result<(), Box> { - let vh1 = "exchange_vhost_1"; - let vh2 = "exchange_vhost_2"; + let vh1 = "rabbitmqadmin.exchange_vhost_1"; + let vh2 = "rabbitmqadmin.exchange_vhost_2"; let x1 = "new_exchange_1"; let x2 = "new_exchange_2"; @@ -74,8 +74,8 @@ fn list_exchanges() -> Result<(), Box> { #[test] fn exchanges_list() -> Result<(), Box> { - let vh1 = "exchange_vhost_3"; - let vh2 = "exchange_vhost_4"; + let vh1 = "rabbitmqadmin.exchange_vhost_3"; + let vh2 = "rabbitmqadmin.exchange_vhost_4"; let x1 = "new_exchange_1"; let x2 = "new_exchange_2"; @@ -218,8 +218,8 @@ fn delete_a_non_existing_exchange() -> Result<(), Box> { #[test] fn test_exchanges_bind_and_unbind() -> Result<(), Box> { - let vh1 = "exchanges_bind_vhost_3"; - let vh2 = "exchanges_bind_vhost_4"; + let vh1 = "rabbitmqadmin.exchanges_bind_vhost_3"; + let vh2 = "rabbitmqadmin.exchanges_bind_vhost_4"; let q1 = "new_queue_1"; let q2 = "new_queue_2"; diff --git a/tests/queue_federation_tests.rs b/tests/queue_federation_tests.rs index f978a11..8730c4e 100644 --- a/tests/queue_federation_tests.rs +++ b/tests/queue_federation_tests.rs @@ -336,8 +336,8 @@ fn test_federation_delete_an_upstream_with_queue_federation_settings() #[test] fn test_federation_list_all_links_with_queue_federation_settings() -> Result<(), Box> { - let vh1 = "rust.federation.links.a"; - let vh2 = "rust.federation.links.b"; + let vh1 = "rabbitmqadmin.federation.links.a"; + let vh2 = "rabbitmqadmin.federation.links.b"; let name = "up.for_queue_federation.links.a"; let amqp_endpoint = amqp_endpoint_with_vhost(vh2); diff --git a/tests/queues_tests.rs b/tests/queues_tests.rs index 9ee1163..5fcf05f 100644 --- a/tests/queues_tests.rs +++ b/tests/queues_tests.rs @@ -19,8 +19,8 @@ use crate::test_helpers::*; #[test] fn list_queues() -> Result<(), Box> { - let vh1 = "queue_vhost_1"; - let vh2 = "queue_vhost_2"; + let vh1 = "rabbitmqadmin.queue_vhost_1"; + let vh2 = "rabbitmqadmin.queue_vhost_2"; let q1 = "new_queue1"; let q2 = "new_queue2"; @@ -66,8 +66,8 @@ fn list_queues() -> Result<(), Box> { #[test] fn queues_lists() -> Result<(), Box> { - let vh1 = "queue_vhost_3"; - let vh2 = "queue_vhost_4"; + let vh1 = "rabbitmqadmin.queue_vhost_3"; + let vh2 = "rabbitmqadmin.queue_vhost_4"; let q1 = "new_queue1"; let q2 = "new_queue2"; diff --git a/tests/streams_tests.rs b/tests/streams_tests.rs index ac1c20d..3e14a04 100644 --- a/tests/streams_tests.rs +++ b/tests/streams_tests.rs @@ -19,8 +19,8 @@ use crate::test_helpers::*; #[test] fn list_streams() -> Result<(), Box> { - let vh1 = "stream_vhost_1"; - let vh2 = "stream_vhost_2"; + let vh1 = "rabbitmqadmin.stream_vhost_1"; + let vh2 = "rabbitmqadmin.stream_vhost_2"; let s1 = "new_stream1"; let s2 = "new_stream2"; @@ -77,8 +77,8 @@ fn list_streams() -> Result<(), Box> { #[test] fn streams_list() -> Result<(), Box> { - let vh1 = "stream_vhost_3"; - let vh2 = "stream_vhost_4"; + let vh1 = "rabbitmqadmin.stream_vhost_3"; + let vh2 = "rabbitmqadmin.stream_vhost_4"; let s1 = "new_stream1"; let s2 = "new_stream2"; From 734cb5bc27f1ce221c6c7dbb63bf293cd74b8bcd Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 03:04:03 -0400 Subject: [PATCH 277/320] Tweak what the non-interactive progress bar looks like --- src/output.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/output.rs b/src/output.rs index 940d4af..7cef773 100644 --- a/src/output.rs +++ b/src/output.rs @@ -573,7 +573,7 @@ impl ProgressReporter for NonInteractiveProgressReporter { } fn report_progress(&mut self, _current: usize, _total: usize, _item_name: &str) { - print!("#"); + print!("."); io::stdout().flush().unwrap(); } @@ -586,7 +586,7 @@ impl ProgressReporter for NonInteractiveProgressReporter { } fn report_failure(&mut self, _item_name: &str, _error: &str) { - print!("X"); + print!("x"); io::stdout().flush().unwrap(); } From 84e28296b3e32c9d83da2d362ce47a3f0171357c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 03:05:30 -0400 Subject: [PATCH 278/320] bin/ci/before_build.sh: delete virtual hosts prefixed with 'rabbitmqadmin.' --- bin/ci/before_build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/ci/before_build.sh b/bin/ci/before_build.sh index 9ceabcd..e217ee8 100755 --- a/bin/ci/before_build.sh +++ b/bin/ci/before_build.sh @@ -40,4 +40,6 @@ $PLUGINS enable rabbitmq_federation_management $PLUGINS enable rabbitmq_stream $PLUGINS enable rabbitmq_stream_management +cargo run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" + true From 22ce45e131d715f422c28ec48f808f020b14ef22 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 03:30:10 -0400 Subject: [PATCH 279/320] Delete virtual hosts used by tests before [re-]creating them --- bin/ci/before_build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/ci/before_build.sh b/bin/ci/before_build.sh index e217ee8..5a5064d 100755 --- a/bin/ci/before_build.sh +++ b/bin/ci/before_build.sh @@ -22,6 +22,8 @@ $CTL add_vhost / $CTL add_user guest guest $CTL set_permissions -p / guest ".*" ".*" ".*" +cargo run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" + $CTL add_vhost "rust/rabbitmqadmin" $CTL set_permissions -p "rust/rabbitmqadmin" guest ".*" ".*" ".*" @@ -40,6 +42,4 @@ $PLUGINS enable rabbitmq_federation_management $PLUGINS enable rabbitmq_stream $PLUGINS enable rabbitmq_stream_management -cargo run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" - true From 2a8c6bce434529cb060c6bb11fea8f5c0a695de6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 03:46:28 -0400 Subject: [PATCH 280/320] Switch to 'indicatif' for progress reporting --- Cargo.lock | 45 +++++++++++++++++++ Cargo.toml | 1 + src/output.rs | 118 +++++++++++++++++++++++--------------------------- 3 files changed, 101 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d0d0d8..ef0f4d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,6 +331,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -448,6 +461,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -912,6 +931,19 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -1145,6 +1177,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.37.3" @@ -1260,6 +1298,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "potential_utf" version = "0.1.3" @@ -1456,6 +1500,7 @@ dependencies = [ "assert_cmd", "clap", "color-print", + "indicatif", "log", "predicates", "rabbitmq_http_client", diff --git a/Cargo.toml b/Cargo.toml index 603217f..d8a9e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ color-print = "0.3" thiserror = "2" shellexpand = "3.1" regex = "1" +indicatif = "0.17" log = "0.4" rustls = { version = "0.23", features = ["aws_lc_rs"] } diff --git a/src/output.rs b/src/output.rs index 7cef773..f0c88e1 100644 --- a/src/output.rs +++ b/src/output.rs @@ -27,7 +27,7 @@ use std::fmt; use sysexits::ExitCode; use tabled::settings::object::Rows; -use std::io::{self, Write}; +use indicatif::{ProgressBar, ProgressStyle}; use tabled::settings::{Panel, Remove, Style}; use tabled::{ Table, Tabled, @@ -467,131 +467,123 @@ pub trait ProgressReporter { #[allow(dead_code)] pub struct InteractiveProgressReporter { - operation_name: String, + bar: Option, failures: usize, - current_position: usize, - total: usize, - results: Vec, } #[allow(dead_code)] impl InteractiveProgressReporter { pub fn new() -> Self { Self { - operation_name: String::new(), + bar: None, failures: 0, - current_position: 0, - total: 0, - results: Vec::new(), } } } impl ProgressReporter for InteractiveProgressReporter { fn start_operation(&mut self, total: usize, operation_name: &str) { - self.operation_name = operation_name.to_string(); + let bar = ProgressBar::new(total as u64); + bar.set_style( + ProgressStyle::with_template( + "{msg} [{bar:40.cyan/blue}] {pos}/{len} ({percent}%) {elapsed_precise}", + ) + .unwrap(), + ); + bar.set_message(operation_name.to_string()); + self.bar = Some(bar); self.failures = 0; - self.current_position = 0; - self.total = total; - self.results = vec!['.'; total]; - println!("{}...", operation_name); } fn report_progress(&mut self, _current: usize, _total: usize, _item_name: &str) { - if self.current_position < self.results.len() { - self.results[self.current_position] = '#'; + if let Some(bar) = &self.bar { + bar.inc(1); } - self.current_position += 1; - let percentage = if self.total > 0 { - (self.current_position * 100) / self.total - } else { - 0 - }; - let bar: String = self.results.iter().collect(); - print!("\rProgress: [{:3}%] [{}]", percentage, bar); - io::stdout().flush().unwrap(); } fn report_success(&mut self, _item_name: &str) { - // No-op: progress bar already shows the advancement + // No-op: progress already incremented in report_progress } fn report_skip(&mut self, _item_name: &str, _reason: &str) { - // No-op: progress bar already shows the advancement + // No-op: progress already incremented in report_progress } fn report_failure(&mut self, _item_name: &str, _error: &str) { self.failures += 1; - if self.current_position < self.results.len() { - self.results[self.current_position] = 'X'; + if let Some(bar) = &self.bar { + bar.inc(1); } - self.current_position += 1; - let percentage = if self.total > 0 { - (self.current_position * 100) / self.total - } else { - 0 - }; - let bar: String = self.results.iter().collect(); - print!("\rProgress: [{:3}%] [{}]", percentage, bar); - io::stdout().flush().unwrap(); } fn finish_operation(&mut self, total: usize) { - let successes = total - self.failures; - if self.failures == 0 { - println!("\n✅ Completed: {} items processed successfully", total); - } else if successes == 0 { - println!("\n❌ Failed: All {} items failed to process", total); - } else { - println!( - "\n⚠️ Completed with failures: {} succeeded, {} failed out of {} total", - successes, self.failures, total - ); + if let Some(bar) = &self.bar { + bar.finish(); + + let successes = total - self.failures; + if self.failures == 0 { + println!("✅ Completed: {} items processed successfully", total); + } else if successes == 0 { + println!("❌ Failed: All {} items failed to process", total); + } else { + println!( + "⚠️ Completed with failures: {} succeeded, {} failed out of {} total", + successes, self.failures, total + ); + } } + self.bar = None; } } #[allow(dead_code)] pub struct NonInteractiveProgressReporter { - operation_name: String, + bar: Option, } #[allow(dead_code)] impl NonInteractiveProgressReporter { pub fn new() -> Self { - Self { - operation_name: String::new(), - } + Self { bar: None } } } impl ProgressReporter for NonInteractiveProgressReporter { - fn start_operation(&mut self, _total: usize, operation_name: &str) { - self.operation_name = operation_name.to_string(); - print!("{}: ", operation_name); - io::stdout().flush().unwrap(); + fn start_operation(&mut self, total: usize, operation_name: &str) { + let bar = ProgressBar::new(total as u64); + bar.set_style( + ProgressStyle::with_template("{msg}: {pos}/{len} [{elapsed_precise}]").unwrap(), + ); + bar.set_message(operation_name.to_string()); + self.bar = Some(bar); } fn report_progress(&mut self, _current: usize, _total: usize, _item_name: &str) { - print!("."); - io::stdout().flush().unwrap(); + if let Some(bar) = &self.bar { + bar.inc(1); + } } fn report_success(&mut self, _item_name: &str) { - // Hash already printed in report_progress + // No-op: progress already incremented in report_progress } fn report_skip(&mut self, _item_name: &str, _reason: &str) { - // Hash already printed in report_progress + // No-op: progress already incremented in report_progress } fn report_failure(&mut self, _item_name: &str, _error: &str) { - print!("x"); - io::stdout().flush().unwrap(); + if let Some(bar) = &self.bar { + bar.inc(1); + } } fn finish_operation(&mut self, total: usize) { - println!("\nCompleted: {} items processed", total); + if let Some(bar) = &self.bar { + bar.finish(); + println!("Completed: {} items processed", total); + } + self.bar = None; } } From 3519260b1cb5ba9f0f1a8d754700777ba88b470d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 04:00:59 -0400 Subject: [PATCH 281/320] Change progress bar coloring to be closer to orange --- src/output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output.rs b/src/output.rs index f0c88e1..bcb9db2 100644 --- a/src/output.rs +++ b/src/output.rs @@ -486,7 +486,7 @@ impl ProgressReporter for InteractiveProgressReporter { let bar = ProgressBar::new(total as u64); bar.set_style( ProgressStyle::with_template( - "{msg} [{bar:40.cyan/blue}] {pos}/{len} ({percent}%) {elapsed_precise}", + "{msg} [{bar:40.yellow/red}] {pos}/{len} ({percent}%) {elapsed_precise}", ) .unwrap(), ); From 1e190f28fe0c925cf739b9f93adf6a8b4f4e70ba Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 16:04:53 -0400 Subject: [PATCH 282/320] rabbitmq_http_client 0.60.0 --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef0f4d1..9a41b21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1471,9 +1471,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.59.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b62fe158998317a6a35c65eb1265b202f2925a79eccc562c833dd459b2049c" +checksum = "f1d5051af8cbb77f8c6103cc4d429f8be84503515f42af90bef2da3b8c36bd68" dependencies = [ "backtrace", "percent-encoding", @@ -1727,7 +1727,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.0", + "security-framework 3.5.1", ] [[package]] @@ -1788,9 +1788,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags", "core-foundation 0.10.1", diff --git a/Cargo.toml b/Cargo.toml index d8a9e71..a2f4356 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.59.0", features = [ +rabbitmq_http_client = { version = "0.60.0", features = [ "blocking", "tabled", ] } From 7b5c80f33ceec71c36682d8f4c77fc95b00d941f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 16:09:06 -0400 Subject: [PATCH 283/320] bin/ci/before_build.sh: announce what virtual hosts will be deleted first --- bin/ci/before_build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/ci/before_build.sh b/bin/ci/before_build.sh index 5a5064d..f06c869 100755 --- a/bin/ci/before_build.sh +++ b/bin/ci/before_build.sh @@ -22,7 +22,8 @@ $CTL add_vhost / $CTL add_user guest guest $CTL set_permissions -p / guest ".*" ".*" ".*" -cargo run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" +cargo -q run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" --dry-run +cargo -q run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" $CTL add_vhost "rust/rabbitmqadmin" $CTL set_permissions -p "rust/rabbitmqadmin" guest ".*" ".*" ".*" From c4a8c287d34eb04ec8e3069d51d522d128c5dace Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 16:13:21 -0400 Subject: [PATCH 284/320] Cosmetics --- bin/ci/before_build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/ci/before_build.sh b/bin/ci/before_build.sh index f06c869..3ab4af5 100755 --- a/bin/ci/before_build.sh +++ b/bin/ci/before_build.sh @@ -22,7 +22,7 @@ $CTL add_vhost / $CTL add_user guest guest $CTL set_permissions -p / guest ".*" ".*" ".*" -cargo -q run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" --dry-run +cargo -q run '--' vhosts delete_multiple --name-pattern "^rabbitmqadmin" --dry-run --table-style modern cargo -q run '--' --non-interactive vhosts delete_multiple --name-pattern "^rabbitmqadmin" $CTL add_vhost "rust/rabbitmqadmin" From 51d5d2f61290c536ee865b739b3bd33ee42e2f1c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sun, 28 Sep 2025 16:25:54 -0400 Subject: [PATCH 285/320] Refactor, reformat imports --- src/cli.rs | 8 ++--- src/config.rs | 6 ++-- src/errors.rs | 7 +++-- src/main.rs | 12 +++---- src/output.rs | 2 +- src/pre_flight.rs | 4 ++- src/tables.rs | 4 +-- tests/bindings_tests.rs | 7 +++-- tests/channels_tests.rs | 6 ++-- tests/combined_integration_tests.rs | 14 +++++---- tests/connections_tests.rs | 14 ++++----- tests/definitions_export_tests.rs | 12 +++---- tests/definitions_import_tests.rs | 6 ++-- tests/deprecated_feature_tests.rs | 6 ++-- tests/exchange_federation_tests.rs | 28 ++++++++--------- tests/exchanges_tests.rs | 15 +++++---- tests/feature_flag_management_tests.rs | 6 ++-- tests/feature_flag_tests.rs | 4 +-- ...eration_upstream_uri_modification_tests.rs | 22 ++++++------- tests/health_check_tests.rs | 14 ++++----- tests/help_tests.rs | 10 +++--- tests/interactivity_mode_tests.rs | 7 +++-- tests/memory_breakdown_tests.rs | 6 ++-- tests/nodes_tests.rs | 8 ++--- tests/operator_policies_tests.rs | 26 +++++++--------- tests/permissions_tests.rs | 4 +-- tests/policies_tests.rs | 27 ++++++++-------- tests/queue_federation_tests.rs | 31 ++++++++----------- tests/queues_tests.rs | 7 +++-- tests/runtime_parameters_tests.rs | 12 +++---- ...ovel_destination_uri_modification_tests.rs | 22 ++++++------- tests/shovel_source_uri_modification_tests.rs | 24 ++++++-------- tests/shovel_tests.rs | 15 +++++---- tests/streams_tests.rs | 7 +++-- ...test_commands_recommended_against_tests.rs | 4 +-- tests/test_helpers.rs | 10 +++--- tests/user_limits_tests.rs | 3 +- tests/users_tests.rs | 11 ++++--- tests/vhost_limits_tests.rs | 3 +- tests/vhosts_delete_multiple_tests.rs | 21 ++++++------- tests/vhosts_tests.rs | 15 ++++----- 41 files changed, 233 insertions(+), 237 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index f87e2f7..dc42357 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -18,7 +18,7 @@ use super::static_urls::*; use super::tanzu_cli::tanzu_subcommands; use crate::config::PreFlightSettings; use crate::output::TableStyle; -use clap::{Arg, ArgAction, ArgGroup, Command, value_parser}; +use clap::{Arg, ArgAction, ArgGroup, Command, crate_version, value_parser}; use rabbitmq_http_client::commons::{ BindingDestinationType, ChannelUseMode, ExchangeType, MessageTransferAcknowledgementMode, PolicyTarget, QueueType, SupportedProtocol, @@ -346,12 +346,12 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { ]; Command::new("rabbitmqadmin") - .version(clap::crate_version!()) + .version(crate_version!()) .author("The RabbitMQ Core Team") - .about(format!("rabbitmqadmin gen 2, version: {}", clap::crate_version!())) + .about(format!("rabbitmqadmin gen 2, version: {}", crate_version!())) .long_about(format!( "RabbitMQ CLI that uses the HTTP API. Version: {}", - clap::crate_version!() + crate_version!() )) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) diff --git a/src/config.rs b/src/config.rs index b71fe0f..1eaad44 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,8 +19,8 @@ use crate::constants::{ use crate::output::TableStyle; use clap::ArgMatches; use serde::Deserialize; -use std::collections::HashMap; use std::path::{Path, PathBuf}; +use std::{collections::HashMap, fs, io}; use thiserror::Error; use url::Url; @@ -62,7 +62,7 @@ pub enum ConfigFileError { )] MissingConfigSection(String), #[error(transparent)] - IoError(#[from] std::io::Error), + IoError(#[from] io::Error), #[error("failed to deserialize the config file. Make sure it is valid TOML. Details: {0}")] DeserializationError(#[from] toml::de::Error), } @@ -532,7 +532,7 @@ fn from_local_path(path: &Path) -> Result, ConfigFileError> } fn read_from_local_path(path: &PathBuf) -> Result, ConfigFileError> { - let contents = std::fs::read_to_string(path)?; + let contents = fs::read_to_string(path)?; toml::from_str(&contents).map_err(ConfigFileError::from) } diff --git a/src/errors.rs b/src/errors.rs index dd71740..70b4ec1 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -18,6 +18,7 @@ use reqwest::{ StatusCode, header::{HeaderMap, InvalidHeaderValue}, }; +use std::io; use url::Url; #[derive(thiserror::Error, Debug)] @@ -39,7 +40,7 @@ pub enum CommandRunError { cause: rustls::pki_types::pem::Error, }, #[error("Run into an I/O error when loading a file: {0}")] - IoError(std::io::Error), + IoError(io::Error), #[error( "Local TLS certificate file at {local_path} does not exist, cannot be read or passed as a PEM file: {cause}" )] @@ -103,8 +104,8 @@ pub enum CommandRunError { Other, } -impl From for CommandRunError { - fn from(value: std::io::Error) -> Self { +impl From for CommandRunError { + fn from(value: io::Error) -> Self { CommandRunError::IoError(value) } } diff --git a/src/main.rs b/src/main.rs index 79a4838..e2de763 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,10 +15,10 @@ #![allow(clippy::unnecessary_unwrap)] #![allow(clippy::collapsible_if)] -use clap::ArgMatches; +use clap::{ArgMatches, crate_version}; use errors::CommandRunError; -use reqwest::Identity; -use std::path::PathBuf; +use reqwest::{Identity, tls::Version as TlsVersion}; +use std::path::{Path, PathBuf}; use std::{fs, process}; use sysexits::ExitCode; @@ -185,7 +185,7 @@ fn build_http_client( cli: &ArgMatches, common_settings: &SharedSettings, ) -> Result { - let user_agent = format!("rabbitmqadmin-ng {}", clap::crate_version!()); + let user_agent = format!("rabbitmqadmin-ng {}", crate_version!()); if should_use_tls(common_settings) { let _ = CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider()); @@ -202,7 +202,7 @@ fn build_http_client( .use_rustls_tls() .tls_info(true) .tls_sni(true) - .min_tls_version(reqwest::tls::Version::TLS_1_2) + .min_tls_version(TlsVersion::TLS_1_2) .tls_built_in_native_certs(true) .tls_built_in_root_certs(true) .danger_accept_invalid_certs(disable_peer_verification) @@ -315,7 +315,7 @@ fn read_pem_file(buf: &PathBuf, file_path: &str) -> Result, CommandRunEr } fn validate_certificate_file(path: &str) -> Result<(), CommandRunError> { - let path_buf = std::path::Path::new(path); + let path_buf = Path::new(path); if !path_buf.exists() { return Err(CommandRunError::CertificateFileNotFound { diff --git a/src/output.rs b/src/output.rs index bcb9db2..204c3ea 100644 --- a/src/output.rs +++ b/src/output.rs @@ -244,7 +244,7 @@ impl<'a> ResultHandler<'a> { pub fn single_value_output_with_result( &mut self, - result: Result, + result: Result, ) { match result { Ok(output) => { diff --git a/src/pre_flight.rs b/src/pre_flight.rs index f1d8f1d..bafdae4 100644 --- a/src/pre_flight.rs +++ b/src/pre_flight.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; + /// Represents the two modes of operation for the `rabbitmqadmin` CLI: /// interactive (driven by a human) and non-interactive (driven by automation tools). #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -53,7 +55,7 @@ pub fn should_infer_long_options() -> bool { } fn is_enabled_in_env(key: &str) -> bool { - match std::env::var(key) { + match env::var(key) { Ok(val) => val.to_lowercase().trim() == "true", Err(_) => false, } diff --git a/src/tables.rs b/src/tables.rs index 5a54bd4..7baa57e 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -19,7 +19,7 @@ use rabbitmq_http_client::responses::{ QuorumCriticalityCheckDetails, SchemaDefinitionSyncStatus, }; use reqwest::StatusCode; -use std::error::Error; +use std::{error::Error, fmt}; use tabled::settings::Panel; use tabled::{Table, Tabled}; use url::Url; @@ -57,7 +57,7 @@ struct OverviewRow<'a> { #[derive(Debug, Tabled)] struct RowOfTwo<'a, T> where - T: ?Sized + std::fmt::Display, + T: ?Sized + fmt::Display, { key: &'a str, value: &'a T, diff --git a/tests/bindings_tests.rs b/tests/bindings_tests.rs index 757a875..dd9e991 100644 --- a/tests/bindings_tests.rs +++ b/tests/bindings_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_bindings() -> Result<(), Box> { +fn test_list_bindings() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_list_bindings_1"; let vh2 = "rabbitmqadmin.test_list_bindings_2"; let q1 = "new_queue_1"; @@ -103,7 +104,7 @@ fn test_list_bindings() -> Result<(), Box> { } #[test] -fn test_bindings_list() -> Result<(), Box> { +fn test_bindings_list() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_bindings_list_1"; let vh2 = "rabbitmqadmin.test_bindings_list_2"; let q1 = "new_queue_1"; @@ -202,7 +203,7 @@ fn test_bindings_list() -> Result<(), Box> { } #[test] -fn test_bindings_delete_idempotently() -> Result<(), Box> { +fn test_bindings_delete_idempotently() -> Result<(), Box> { let vh = "rabbitmqadmin.bindings.test1"; let source_ex = "test_source_exchange"; let dest_queue = "test_dest_queue"; diff --git a/tests/channels_tests.rs b/tests/channels_tests.rs index 88c41cc..ab4c25d 100644 --- a/tests/channels_tests.rs +++ b/tests/channels_tests.rs @@ -14,16 +14,16 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_list_channels1() -> Result<(), Box> { +fn test_list_channels1() -> Result<(), Box> { run_succeeds(["channels", "list"]); Ok(()) } #[test] -fn test_list_channels2() -> Result<(), Box> { +fn test_list_channels2() -> Result<(), Box> { run_succeeds(["list", "channels"]); Ok(()) diff --git a/tests/combined_integration_tests.rs b/tests/combined_integration_tests.rs index b5987c1..8928885 100644 --- a/tests/combined_integration_tests.rs +++ b/tests/combined_integration_tests.rs @@ -11,6 +11,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use std::env; +use std::error::Error; use std::path; use std::path::PathBuf; @@ -21,7 +23,7 @@ use crate::test_helpers::output_includes; use test_helpers::{run_fails, run_succeeds}; #[test] -fn combined_integration_test1() -> Result<(), Box> { +fn combined_integration_test1() -> Result<(), Box> { let vh = "rabbitmqadmin.combined_integration.test1"; let config_path = path::absolute("./tests/fixtures/config_files/config_file1.conf") .expect("failed to compute an absolute version for a ./test/fixtures path"); @@ -46,7 +48,7 @@ fn combined_integration_test1() -> Result<(), Box> { } #[test] -fn combined_integration_test2() -> Result<(), Box> { +fn combined_integration_test2() -> Result<(), Box> { let vh = "rabbitmqadmin.combined_integration.test2"; // Uses a node alias that does not exist in the file @@ -77,7 +79,7 @@ fn combined_integration_test2() -> Result<(), Box> { } #[test] -fn combined_integration_test3() -> Result<(), Box> { +fn combined_integration_test3() -> Result<(), Box> { let vh = "rabbitmqadmin.combined_integration.test3"; // Uses a node alias that does not exist in the file @@ -97,7 +99,7 @@ fn combined_integration_test3() -> Result<(), Box> { } #[test] -fn combined_integration_test4() -> Result<(), Box> { +fn combined_integration_test4() -> Result<(), Box> { // This test uses administrative credentials to create a new user // and set up a topology using those new credentials let vh = "rabbitmqadmin.combined_integration.test4"; @@ -235,11 +237,11 @@ fn combined_integration_test4() -> Result<(), Box> { // Implementation // -fn report_a_missing_config_file(config_path: PathBuf) -> Result<(), Box> { +fn report_a_missing_config_file(config_path: PathBuf) -> Result<(), Box> { println!( "{} doesn't exist. Current working directory: {}", config_path.to_string_lossy(), - std::env::current_dir()?.display() + env::current_dir()?.display() ); Ok(()) } diff --git a/tests/connections_tests.rs b/tests/connections_tests.rs index f3de04c..0bfd4a7 100644 --- a/tests/connections_tests.rs +++ b/tests/connections_tests.rs @@ -14,30 +14,30 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_list_connections1() -> Result<(), Box> { +fn test_list_connections1() -> Result<(), Box> { run_succeeds(["connections", "list"]); Ok(()) } #[test] -fn test_list_connections2() -> Result<(), Box> { +fn test_list_connections2() -> Result<(), Box> { run_succeeds(["list", "connections"]); Ok(()) } #[test] -fn test_list_connections_table_styles() -> Result<(), Box> { +fn test_list_connections_table_styles() -> Result<(), Box> { run_succeeds(["--table-style", "markdown", "list", "connections"]); Ok(()) } #[test] -fn test_list_user_connections1() -> Result<(), Box> { +fn test_list_user_connections1() -> Result<(), Box> { run_succeeds([ "--table-style", "markdown", @@ -51,7 +51,7 @@ fn test_list_user_connections1() -> Result<(), Box> { } #[test] -fn test_list_user_connections2() -> Result<(), Box> { +fn test_list_user_connections2() -> Result<(), Box> { run_succeeds([ "--table-style", "markdown", @@ -65,7 +65,7 @@ fn test_list_user_connections2() -> Result<(), Box> { } #[test] -fn test_connections_close_idempotently() -> Result<(), Box> { +fn test_connections_close_idempotently() -> Result<(), Box> { run_succeeds([ "connections", "close", diff --git a/tests/definitions_export_tests.rs b/tests/definitions_export_tests.rs index e269364..f00556f 100644 --- a/tests/definitions_export_tests.rs +++ b/tests/definitions_export_tests.rs @@ -13,20 +13,20 @@ // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::{delete_vhost, output_includes}; use test_helpers::run_succeeds; #[test] -fn test_export_cluster_wide_definitions() -> Result<(), Box> { +fn test_export_cluster_wide_definitions() -> Result<(), Box> { run_succeeds(["definitions", "export"]).stdout(output_includes("guest")); Ok(()) } #[test] -fn test_export_vhost_definitions() -> Result<(), Box> { +fn test_export_vhost_definitions() -> Result<(), Box> { let vh = "rabbitmqadmin.definitions_export.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); @@ -46,8 +46,7 @@ fn test_export_vhost_definitions() -> Result<(), Box> { } #[test] -fn test_export_cluster_wide_definitions_with_transformations_case1() --> Result<(), Box> { +fn test_export_cluster_wide_definitions_with_transformations_case1() -> Result<(), Box> { let vh = "rabbitmqadmin.definitions_export.test2"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); @@ -95,8 +94,7 @@ fn test_export_cluster_wide_definitions_with_transformations_case1() } #[test] -fn test_export_vhost_definitions_with_transformations_case1() --> Result<(), Box> { +fn test_export_vhost_definitions_with_transformations_case1() -> Result<(), Box> { let vh = "rabbitmqadmin.definitions_export.test3"; delete_vhost(vh).expect("failed to delete a virtual host"); run_succeeds(["declare", "vhost", "--name", vh]); diff --git a/tests/definitions_import_tests.rs b/tests/definitions_import_tests.rs index 03e4a79..8eef4c5 100644 --- a/tests/definitions_import_tests.rs +++ b/tests/definitions_import_tests.rs @@ -14,10 +14,10 @@ mod test_helpers; use crate::test_helpers::delete_vhost; +use std::error::Error; use test_helpers::run_succeeds; - #[test] -fn test_import_cluster_definitions() -> Result<(), Box> { +fn test_import_cluster_definitions() -> Result<(), Box> { let q = "queue_from_definitions"; run_succeeds(["delete", "queue", "--name", q, "--idempotently"]); @@ -34,7 +34,7 @@ fn test_import_cluster_definitions() -> Result<(), Box> { } #[test] -fn test_import_vhost_definitions() -> Result<(), Box> { +fn test_import_vhost_definitions() -> Result<(), Box> { let vh = "rabbitmqadmin.definitions_import.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); diff --git a/tests/deprecated_feature_tests.rs b/tests/deprecated_feature_tests.rs index db8289f..89aeb34 100644 --- a/tests/deprecated_feature_tests.rs +++ b/tests/deprecated_feature_tests.rs @@ -14,16 +14,16 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_list_all_deprecated_features() -> Result<(), Box> { +fn test_list_all_deprecated_features() -> Result<(), Box> { run_succeeds(["deprecated_features", "list"]).stdout(output_includes("ram_node_type")); Ok(()) } #[test] -fn test_list_all_deprecated_features_via_alias() -> Result<(), Box> { +fn test_list_all_deprecated_features_via_alias() -> Result<(), Box> { run_succeeds(["list", "deprecated_features"]).stdout(output_includes("ram_node_type")); Ok(()) diff --git a/tests/exchange_federation_tests.rs b/tests/exchange_federation_tests.rs index febb698..cf3604f 100644 --- a/tests/exchange_federation_tests.rs +++ b/tests/exchange_federation_tests.rs @@ -14,14 +14,15 @@ use predicates::prelude::*; use rabbitmq_http_client::commons::QueueType; use rabbitmq_http_client::requests::{ExchangeFederationParams, FederationUpstreamParams}; +use std::error::Error; mod test_helpers; use crate::test_helpers::{amqp_endpoint_with_vhost, delete_vhost, output_includes}; use test_helpers::{run_fails, run_succeeds}; #[test] -fn test_federation_upstream_declaration_for_exchange_federation_case0() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_exchange_federation_case0() -> Result<(), Box> +{ let vh = "rabbitmqadmin.federation.exchange.test1"; let name = "up.for_exchange_federation.0"; @@ -52,7 +53,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case0() #[test] fn test_federation_upstream_declaration_for_exchange_federation_case1a() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.exchange.test2"; let name = "up.for_exchange_federation.1a"; @@ -89,7 +90,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case1a() #[test] fn test_federation_upstream_declaration_for_exchange_federation_case1b() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.exchange.test3"; let name = "up.for_exchange_federation.1b"; @@ -128,8 +129,8 @@ fn test_federation_upstream_declaration_for_exchange_federation_case1b() } #[test] -fn test_federation_upstream_declaration_for_exchange_federation_case2() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_exchange_federation_case2() -> Result<(), Box> +{ let vh = "rabbitmqadmin.federation.exchange.test4"; let name = "up.for_exchange_federation.2"; @@ -171,8 +172,8 @@ fn test_federation_upstream_declaration_for_exchange_federation_case2() } #[test] -fn test_federation_upstream_declaration_for_exchange_federation_case3() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_exchange_federation_case3() -> Result<(), Box> +{ let vh = "rabbitmqadmin.federation.exchange.test5"; let name = "up.for_exchange_federation.3"; @@ -208,8 +209,8 @@ fn test_federation_upstream_declaration_for_exchange_federation_case3() } #[test] -fn test_federation_upstream_declaration_for_exchange_federation_case4() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_exchange_federation_case4() -> Result<(), Box> +{ let vh = "rabbitmqadmin.federation.exchange.test6"; let name = "up.for_exchange_federation.4"; @@ -250,8 +251,7 @@ fn test_federation_upstream_declaration_for_exchange_federation_case4() } #[test] -fn test_federation_list_all_upstreams_with_exchange_federation() --> Result<(), Box> { +fn test_federation_list_all_upstreams_with_exchange_federation() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.exchange.test7"; let name = "up.for_exchange_federation.5"; @@ -300,7 +300,7 @@ fn test_federation_list_all_upstreams_with_exchange_federation() #[test] fn test_federation_delete_an_upstream_with_exchange_federation_settings() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.exchange.test8"; let name = "up.for_exchange_federation.6"; @@ -360,7 +360,7 @@ fn test_federation_delete_an_upstream_with_exchange_federation_settings() } #[test] -fn test_federation_delete_upstream_idempotently() -> Result<(), Box> { +fn test_federation_delete_upstream_idempotently() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.exchange.test9"; let upstream_name = "test_upstream_delete_idempotently"; diff --git a/tests/exchanges_tests.rs b/tests/exchanges_tests.rs index a8f9db9..5485229 100644 --- a/tests/exchanges_tests.rs +++ b/tests/exchanges_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn list_exchanges() -> Result<(), Box> { +fn list_exchanges() -> Result<(), Box> { let vh1 = "rabbitmqadmin.exchange_vhost_1"; let vh2 = "rabbitmqadmin.exchange_vhost_2"; @@ -73,7 +74,7 @@ fn list_exchanges() -> Result<(), Box> { } #[test] -fn exchanges_list() -> Result<(), Box> { +fn exchanges_list() -> Result<(), Box> { let vh1 = "rabbitmqadmin.exchange_vhost_3"; let vh2 = "rabbitmqadmin.exchange_vhost_4"; @@ -128,8 +129,7 @@ fn exchanges_list() -> Result<(), Box> { } #[test] -fn delete_an_existing_exchange_using_original_command_group() --> Result<(), Box> { +fn delete_an_existing_exchange_using_original_command_group() -> Result<(), Box> { let vh = "rabbitmqadmin.exchanges.test1"; let x = "exchange_1_to_delete"; @@ -155,8 +155,7 @@ fn delete_an_existing_exchange_using_original_command_group() } #[test] -fn delete_an_existing_exchange_using_exchanges_command_group() --> Result<(), Box> { +fn delete_an_existing_exchange_using_exchanges_command_group() -> Result<(), Box> { let vh = "rabbitmqadmin.exchanges.test2"; let x = "exchange_1_to_delete"; @@ -182,7 +181,7 @@ fn delete_an_existing_exchange_using_exchanges_command_group() } #[test] -fn delete_a_non_existing_exchange() -> Result<(), Box> { +fn delete_a_non_existing_exchange() -> Result<(), Box> { let vh = "rabbitmqadmin.exchanges.test3"; // declare a vhost @@ -217,7 +216,7 @@ fn delete_a_non_existing_exchange() -> Result<(), Box> { } #[test] -fn test_exchanges_bind_and_unbind() -> Result<(), Box> { +fn test_exchanges_bind_and_unbind() -> Result<(), Box> { let vh1 = "rabbitmqadmin.exchanges_bind_vhost_3"; let vh2 = "rabbitmqadmin.exchanges_bind_vhost_4"; let q1 = "new_queue_1"; diff --git a/tests/feature_flag_management_tests.rs b/tests/feature_flag_management_tests.rs index ba6909d..fc3b19d 100644 --- a/tests/feature_flag_management_tests.rs +++ b/tests/feature_flag_management_tests.rs @@ -14,9 +14,9 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_enable_a_feature_flag() -> Result<(), Box> { +fn test_enable_a_feature_flag() -> Result<(), Box> { let ff_name = "detailed_queues_endpoint"; run_succeeds(["feature_flags", "enable", "--name", ff_name]); @@ -26,7 +26,7 @@ fn test_enable_a_feature_flag() -> Result<(), Box> { } #[test] -fn test_enable_all_stable_feature_flags() -> Result<(), Box> { +fn test_enable_all_stable_feature_flags() -> Result<(), Box> { let ff_name = "rabbitmq_4.0.0"; run_succeeds(["feature_flags", "enable_all"]); diff --git a/tests/feature_flag_tests.rs b/tests/feature_flag_tests.rs index b61d8ee..6b49e26 100644 --- a/tests/feature_flag_tests.rs +++ b/tests/feature_flag_tests.rs @@ -13,12 +13,12 @@ // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_feature_flags() -> Result<(), Box> { +fn test_list_feature_flags() -> Result<(), Box> { run_succeeds(["list", "feature_flags"]) .stdout(output_includes("rabbitmq_4.0.0").and(output_includes("khepri_db"))); diff --git a/tests/federation_upstream_uri_modification_tests.rs b/tests/federation_upstream_uri_modification_tests.rs index ce2cf38..9dc7e4a 100644 --- a/tests/federation_upstream_uri_modification_tests.rs +++ b/tests/federation_upstream_uri_modification_tests.rs @@ -15,10 +15,9 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_disable_tls_peer_verification_for_all_upstreams_basic() --> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_upstreams_basic() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test1"; let upstream_name = "test_basic_upstream"; @@ -66,7 +65,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_basic() #[test] fn test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_param() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test2"; let upstream_name = "test_existing_upstream"; @@ -130,7 +129,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_with_existing_verify_par #[test] fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test3"; let upstream_name = "test_queue_upstream"; @@ -178,7 +177,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_basic() #[test] fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_params() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test4"; let upstream_name = "test_queue_upstream_with_params"; @@ -237,7 +236,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_queue_federation_with_pa #[test] fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test5"; let exchange_upstream_name = "exchange_upstream"; let queue_upstream_name = "queue_upstream"; @@ -324,8 +323,7 @@ fn test_disable_tls_peer_verification_for_all_upstreams_mixed_federation() } #[test] -fn test_enable_tls_peer_verification_for_all_upstreams_basic() --> Result<(), Box> { +fn test_enable_tls_peer_verification_for_all_upstreams_basic() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test6"; let upstream_name = "test_enable_basic_upstream"; @@ -383,7 +381,7 @@ fn test_enable_tls_peer_verification_for_all_upstreams_basic() #[test] fn test_enable_tls_peer_verification_for_all_upstreams_with_existing_params() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test7"; let upstream_name = "test_enable_existing_upstream"; @@ -452,7 +450,7 @@ fn test_enable_tls_peer_verification_for_all_upstreams_with_existing_params() #[test] fn test_enable_tls_peer_verification_for_all_upstreams_queue_federation() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test8"; let upstream_name = "test_enable_queue_upstream"; @@ -510,7 +508,7 @@ fn test_enable_tls_peer_verification_for_all_upstreams_queue_federation() #[test] fn test_enable_tls_peer_verification_for_all_upstreams_mixed_federation() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.federation.modifications.test9"; let exchange_upstream_name = "enable_exchange_upstream"; let queue_upstream_name = "enable_queue_upstream"; diff --git a/tests/health_check_tests.rs b/tests/health_check_tests.rs index 08eb373..b9b0812 100644 --- a/tests/health_check_tests.rs +++ b/tests/health_check_tests.rs @@ -14,24 +14,24 @@ mod test_helpers; use crate::test_helpers::output_includes; +use std::error::Error; use test_helpers::{run_fails, run_succeeds}; - #[test] -fn test_health_check_local_alarms() -> Result<(), Box> { +fn test_health_check_local_alarms() -> Result<(), Box> { run_succeeds(["health_check", "local_alarms"]).stdout(output_includes("passed")); Ok(()) } #[test] -fn test_health_check_cluster_wide_alarms() -> Result<(), Box> { +fn test_health_check_cluster_wide_alarms() -> Result<(), Box> { run_succeeds(["health_check", "cluster_wide_alarms"]).stdout(output_includes("passed")); Ok(()) } #[test] -fn test_health_check_port_listener_succeeds() -> Result<(), Box> { +fn test_health_check_port_listener_succeeds() -> Result<(), Box> { run_succeeds(["health_check", "port_listener", "--port", "15672"]) .stdout(output_includes("passed")); @@ -39,7 +39,7 @@ fn test_health_check_port_listener_succeeds() -> Result<(), Box Result<(), Box> { +fn test_health_check_port_listener_fails() -> Result<(), Box> { run_fails(["health_check", "port_listener", "--port", "15679"]) .stdout(output_includes("failed")); @@ -47,7 +47,7 @@ fn test_health_check_port_listener_fails() -> Result<(), Box Result<(), Box> { +fn test_health_check_protocol_listener_succeeds() -> Result<(), Box> { run_succeeds(["health_check", "protocol_listener", "--protocol", "amqp"]) .stdout(output_includes("passed")); @@ -55,7 +55,7 @@ fn test_health_check_protocol_listener_succeeds() -> Result<(), Box Result<(), Box> { +fn test_health_check_protocol_listener_fails() -> Result<(), Box> { run_fails([ "health_check", "protocol_listener", diff --git a/tests/help_tests.rs b/tests/help_tests.rs index feb9d30..87c5282 100644 --- a/tests/help_tests.rs +++ b/tests/help_tests.rs @@ -14,10 +14,10 @@ mod test_helpers; use crate::test_helpers::output_includes; +use std::error::Error; use test_helpers::{run_fails, run_succeeds}; - #[test] -fn show_help_with_no_arguments() -> Result<(), Box> { +fn show_help_with_no_arguments() -> Result<(), Box> { let args: [&str; 0] = []; run_fails(args).stderr(output_includes( "requires a subcommand but one was not provided", @@ -27,7 +27,7 @@ fn show_help_with_no_arguments() -> Result<(), Box> { } #[test] -fn show_subcommands_with_no_arguments() -> Result<(), Box> { +fn show_subcommands_with_no_arguments() -> Result<(), Box> { let args: [&str; 0] = []; run_fails(args).stderr(output_includes("subcommands:")); @@ -35,7 +35,7 @@ fn show_subcommands_with_no_arguments() -> Result<(), Box } #[test] -fn show_subcommands_with_category_name_and_help() -> Result<(), Box> { +fn show_subcommands_with_category_name_and_help() -> Result<(), Box> { let args = ["declare", "--help"]; run_succeeds(args).stdout(output_includes("Commands:")); @@ -43,7 +43,7 @@ fn show_subcommands_with_category_name_and_help() -> Result<(), Box Result<(), Box> { +fn shows_subcommand_specific_info_with_help() -> Result<(), Box> { let args = ["declare", "queue", "--help"]; run_succeeds(args).stdout(output_includes("Usage:")); diff --git a/tests/interactivity_mode_tests.rs b/tests/interactivity_mode_tests.rs index a69c418..718861d 100644 --- a/tests/interactivity_mode_tests.rs +++ b/tests/interactivity_mode_tests.rs @@ -13,6 +13,7 @@ // limitations under the License. use rabbitmqadmin::pre_flight::InteractivityMode; +use std::env; #[test] fn test_interactivity_mode_default() { @@ -31,7 +32,7 @@ fn test_interactivity_mode_non_interactive() { fn test_interactivity_mode_from_env_interactive() { // Clear the environment variable to test the default case unsafe { - std::env::remove_var("RABBITMQADMIN_NON_INTERACTIVE_MODE"); + env::remove_var("RABBITMQADMIN_NON_INTERACTIVE_MODE"); } let mode = InteractivityMode::from_env(); assert_eq!(mode, InteractivityMode::Interactive); @@ -41,13 +42,13 @@ fn test_interactivity_mode_from_env_interactive() { fn test_interactivity_mode_from_env_non_interactive() { // Set the environment variable unsafe { - std::env::set_var("RABBITMQADMIN_NON_INTERACTIVE_MODE", "true"); + env::set_var("RABBITMQADMIN_NON_INTERACTIVE_MODE", "true"); } let mode = InteractivityMode::from_env(); assert_eq!(mode, InteractivityMode::NonInteractive); // Clean up unsafe { - std::env::remove_var("RABBITMQADMIN_NON_INTERACTIVE_MODE"); + env::remove_var("RABBITMQADMIN_NON_INTERACTIVE_MODE"); } } diff --git a/tests/memory_breakdown_tests.rs b/tests/memory_breakdown_tests.rs index 9d9a2cd..89020bc 100644 --- a/tests/memory_breakdown_tests.rs +++ b/tests/memory_breakdown_tests.rs @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_show_memory_breakdown_in_bytes_succeeds() -> Result<(), Box> { +fn test_show_memory_breakdown_in_bytes_succeeds() -> Result<(), Box> { let rc = api_client(); let nodes = rc.list_nodes()?; let first = nodes.first().unwrap(); @@ -39,7 +39,7 @@ fn test_show_memory_breakdown_in_bytes_succeeds() -> Result<(), Box Result<(), Box> { +fn test_show_memory_breakdown_in_percent_succeeds() -> Result<(), Box> { let rc = api_client(); let nodes = rc.list_nodes()?; let first = nodes.first().unwrap(); diff --git a/tests/nodes_tests.rs b/tests/nodes_tests.rs index ee3fdcf..2aba43a 100644 --- a/tests/nodes_tests.rs +++ b/tests/nodes_tests.rs @@ -13,12 +13,12 @@ // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_nodes() -> Result<(), Box> { +fn test_list_nodes() -> Result<(), Box> { run_succeeds(["list", "nodes"]).stdout(output_includes("rabbit@")); run_succeeds(["nodes", "list"]).stdout(output_includes("rabbit@")); @@ -27,7 +27,7 @@ fn test_list_nodes() -> Result<(), Box> { } #[test] -fn test_nodes_memory_breakdown_in_bytes_succeeds() -> Result<(), Box> { +fn test_nodes_memory_breakdown_in_bytes_succeeds() -> Result<(), Box> { let rc = api_client(); let nodes = rc.list_nodes()?; let first = nodes.first().unwrap(); @@ -49,7 +49,7 @@ fn test_nodes_memory_breakdown_in_bytes_succeeds() -> Result<(), Box Result<(), Box> { +fn test_nodes_memory_breakdown_in_percent_succeeds() -> Result<(), Box> { let rc = api_client(); let nodes = rc.list_nodes()?; let first = nodes.first().unwrap(); diff --git a/tests/operator_policies_tests.rs b/tests/operator_policies_tests.rs index 9e08e5a..557931d 100644 --- a/tests/operator_policies_tests.rs +++ b/tests/operator_policies_tests.rs @@ -13,12 +13,12 @@ // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_operator_policies() -> Result<(), Box> { +fn test_list_operator_policies() -> Result<(), Box> { let policy_name = "test_list_operator_policies"; run_succeeds([ @@ -45,7 +45,7 @@ fn test_list_operator_policies() -> Result<(), Box> { } #[test] -fn test_operator_policies() -> Result<(), Box> { +fn test_operator_policies() -> Result<(), Box> { let operator_policy_name = "test_operator_policies.1"; run_succeeds([ @@ -72,7 +72,7 @@ fn test_operator_policies() -> Result<(), Box> { } #[test] -fn test_operator_policies_declare_list_and_delete() -> Result<(), Box> { +fn test_operator_policies_declare_list_and_delete() -> Result<(), Box> { let policy_name = "test_operator_policies_declare_list_and_delete"; run_succeeds([ @@ -99,7 +99,7 @@ fn test_operator_policies_declare_list_and_delete() -> Result<(), Box Result<(), Box> { +fn test_operator_policies_in() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_operator_policies_in.1"; run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh1]); @@ -148,7 +148,7 @@ fn test_operator_policies_in() -> Result<(), Box> { } #[test] -fn test_operator_policies_in_with_entity_type() -> Result<(), Box> { +fn test_operator_policies_in_with_entity_type() -> Result<(), Box> { let vh = "rabbitmqadmin.operator_policies.test1"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); @@ -224,7 +224,7 @@ fn test_operator_policies_in_with_entity_type() -> Result<(), Box Result<(), Box> { +fn test_operator_policies_matching_objects() -> Result<(), Box> { let vh = "rabbitmqadmin.operator_policies.test2"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); @@ -277,8 +277,8 @@ fn test_operator_policies_matching_objects() -> Result<(), Box Result<(), Box> { +fn test_operator_policies_declare_list_update_definition_and_delete() -> Result<(), Box> +{ let policy_name = "test_operator_policies_declare_list_update_definition_and_delete"; run_succeeds([ @@ -319,8 +319,7 @@ fn test_operator_policies_declare_list_update_definition_and_delete() } #[test] -fn test_operator_policies_individual_policy_key_manipulation() --> Result<(), Box> { +fn test_operator_policies_individual_policy_key_manipulation() -> Result<(), Box> { let policy_name = "test_operator_policies_individual_policy_key_manipulation"; run_succeeds([ @@ -375,8 +374,7 @@ fn test_operator_policies_individual_policy_key_manipulation() } #[test] -fn test_operator_policies_bulk_policy_keys_manipulation() -> Result<(), Box> -{ +fn test_operator_policies_bulk_policy_keys_manipulation() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_operator_policies_bulk_policy_keys_manipulation.1"; let vh2 = "rabbitmqadmin.test_operator_policies_bulk_policy_keys_manipulation.2"; @@ -491,7 +489,7 @@ fn test_operator_policies_bulk_policy_keys_manipulation() -> Result<(), Box Result<(), Box> { +fn test_operator_policies_patch_definition() -> Result<(), Box> { let vh = "rabbitmqadmin.operator_policies.test3"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); diff --git a/tests/permissions_tests.rs b/tests/permissions_tests.rs index a864e3e..57223d1 100644 --- a/tests/permissions_tests.rs +++ b/tests/permissions_tests.rs @@ -13,12 +13,12 @@ // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_permissions() -> Result<(), Box> { +fn test_list_permissions() -> Result<(), Box> { let username = "user_with_permissions"; let password = "pa$$w0rd"; run_succeeds([ diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index 58d80b5..6fbfec1 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -13,12 +13,12 @@ // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_policies() -> Result<(), Box> { +fn test_list_policies() -> Result<(), Box> { let policy_name = "test_policy"; run_succeeds([ @@ -45,7 +45,7 @@ fn test_list_policies() -> Result<(), Box> { } #[test] -fn test_operator_policies() -> Result<(), Box> { +fn test_operator_policies() -> Result<(), Box> { let operator_policy_name = "test_operator_policy"; run_succeeds([ @@ -72,7 +72,7 @@ fn test_operator_policies() -> Result<(), Box> { } #[test] -fn test_policies_declare_list_and_delete() -> Result<(), Box> { +fn test_policies_declare_list_and_delete() -> Result<(), Box> { let policy_name = "test_policies_declare_list_and_delete"; run_succeeds([ @@ -99,7 +99,7 @@ fn test_policies_declare_list_and_delete() -> Result<(), Box Result<(), Box> { +fn test_policies_in() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_policies_in.1"; run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh1]); @@ -141,7 +141,7 @@ fn test_policies_in() -> Result<(), Box> { } #[test] -fn test_policies_in_with_entity_type() -> Result<(), Box> { +fn test_policies_in_with_entity_type() -> Result<(), Box> { let vh = "rabbitmqadmin.policies.test1"; run_succeeds(["delete", "vhost", "--name", vh, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh]); @@ -202,7 +202,7 @@ fn test_policies_in_with_entity_type() -> Result<(), Box> } #[test] -fn test_policies_matching_objects() -> Result<(), Box> { +fn test_policies_matching_objects() -> Result<(), Box> { let vh1 = "rabbitmqadmin.vh.policies.11"; let vh2 = "rabbitmqadmin.vh.policies.12"; let vh3 = "rabbitmqadmin.vh.policies.13"; @@ -345,8 +345,7 @@ fn test_policies_matching_objects() -> Result<(), Box> { } #[test] -fn test_policies_declare_list_update_definition_and_delete() --> Result<(), Box> { +fn test_policies_declare_list_update_definition_and_delete() -> Result<(), Box> { let policy_name = "test_policies_declare_list_update_definition_and_delete"; run_succeeds([ @@ -387,7 +386,7 @@ fn test_policies_declare_list_update_definition_and_delete() } #[test] -fn test_policies_individual_policy_key_manipulation() -> Result<(), Box> { +fn test_policies_individual_policy_key_manipulation() -> Result<(), Box> { let policy_name = "test_policies_individual_policy_key_manipulation"; run_succeeds([ @@ -442,7 +441,7 @@ fn test_policies_individual_policy_key_manipulation() -> Result<(), Box Result<(), Box> { +fn test_policies_bulk_policy_key_manipulation() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.1"; let vh2 = "rabbitmqadmin.test_policies_bulk_policy_key_manipulation.2"; @@ -543,7 +542,7 @@ fn test_policies_bulk_policy_key_manipulation() -> Result<(), Box Result<(), Box> { +fn test_policies_patch_definition() -> Result<(), Box> { let vh1 = "rabbitmqadmin.test_policies_patch_definition.1"; run_succeeds(["delete", "vhost", "--name", vh1, "--idempotently"]); run_succeeds(["declare", "vhost", "--name", vh1]); @@ -597,7 +596,7 @@ fn test_policies_patch_definition() -> Result<(), Box> { } #[test] -fn test_policies_declare_override() -> Result<(), Box> { +fn test_policies_declare_override() -> Result<(), Box> { let policy_name = "test_list_policies_override.1"; let override_name = "overrides.test_list_policies_override.a"; @@ -646,7 +645,7 @@ fn test_policies_declare_override() -> Result<(), Box> { } #[test] -fn test_policies_declare_blanket() -> Result<(), Box> { +fn test_policies_declare_blanket() -> Result<(), Box> { let policy_name = "test_policies_declare_blanket.1"; run_succeeds([ diff --git a/tests/queue_federation_tests.rs b/tests/queue_federation_tests.rs index 8730c4e..598d7b0 100644 --- a/tests/queue_federation_tests.rs +++ b/tests/queue_federation_tests.rs @@ -13,14 +13,14 @@ // limitations under the License. use predicates::prelude::*; use rabbitmq_http_client::requests::{FederationUpstreamParams, QueueFederationParams}; +use std::error::Error; mod test_helpers; use crate::test_helpers::{amqp_endpoint_with_vhost, await_ms, delete_vhost, output_includes}; use test_helpers::{run_fails, run_succeeds}; #[test] -fn test_federation_upstream_declaration_for_queue_federation_case0() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_queue_federation_case0() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.queue.test1"; let name = "up.for_queue_federation"; @@ -56,8 +56,8 @@ fn test_federation_upstream_declaration_for_queue_federation_case0() } #[test] -fn test_federation_upstream_declaration_for_queue_federation_case1a() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_queue_federation_case1a() -> Result<(), Box> +{ let vh = "rabbitmqadmin.federation.queue.test2"; let name = "up.for_queue_federation.a"; @@ -95,8 +95,8 @@ fn test_federation_upstream_declaration_for_queue_federation_case1a() } #[test] -fn test_federation_upstream_declaration_for_queue_federation_case1b() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_queue_federation_case1b() -> Result<(), Box> +{ let vh = "rabbitmqadmin.federation.queue.test3"; let name = "up.for_queue_federation.b"; @@ -137,8 +137,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case1b() } #[test] -fn test_federation_upstream_declaration_for_queue_federation_case2() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_queue_federation_case2() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.queue.test4"; let name = "up.for_queue_federation"; @@ -171,8 +170,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case2() } #[test] -fn test_federation_upstream_declaration_for_queue_federation_case3() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_queue_federation_case3() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.queue.test5"; let name = "up.for_queue_federation"; @@ -203,8 +201,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case3() } #[test] -fn test_federation_upstream_declaration_for_queue_federation_case4() --> Result<(), Box> { +fn test_federation_upstream_declaration_for_queue_federation_case4() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.queue.test6"; let name = "up.for_queue_federation"; @@ -237,8 +234,7 @@ fn test_federation_upstream_declaration_for_queue_federation_case4() } #[test] -fn test_federation_list_all_upstreams_with_queue_federation() --> Result<(), Box> { +fn test_federation_list_all_upstreams_with_queue_federation() -> Result<(), Box> { let vh = "rabbitmqadmin.federation.queue.test7"; let name = "up.for_queue_federation/5"; @@ -280,8 +276,8 @@ fn test_federation_list_all_upstreams_with_queue_federation() } #[test] -fn test_federation_delete_an_upstream_with_queue_federation_settings() --> Result<(), Box> { +fn test_federation_delete_an_upstream_with_queue_federation_settings() -> Result<(), Box> +{ let vh = "rabbitmqadmin.federation.queue.test8"; let name = "up.for_queue_federation.6"; @@ -334,8 +330,7 @@ fn test_federation_delete_an_upstream_with_queue_federation_settings() } #[test] -fn test_federation_list_all_links_with_queue_federation_settings() --> Result<(), Box> { +fn test_federation_list_all_links_with_queue_federation_settings() -> Result<(), Box> { let vh1 = "rabbitmqadmin.federation.links.a"; let vh2 = "rabbitmqadmin.federation.links.b"; let name = "up.for_queue_federation.links.a"; diff --git a/tests/queues_tests.rs b/tests/queues_tests.rs index 5fcf05f..e976fa4 100644 --- a/tests/queues_tests.rs +++ b/tests/queues_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn list_queues() -> Result<(), Box> { +fn list_queues() -> Result<(), Box> { let vh1 = "rabbitmqadmin.queue_vhost_1"; let vh2 = "rabbitmqadmin.queue_vhost_2"; let q1 = "new_queue1"; @@ -65,7 +66,7 @@ fn list_queues() -> Result<(), Box> { } #[test] -fn queues_lists() -> Result<(), Box> { +fn queues_lists() -> Result<(), Box> { let vh1 = "rabbitmqadmin.queue_vhost_3"; let vh2 = "rabbitmqadmin.queue_vhost_4"; let q1 = "new_queue1"; @@ -112,7 +113,7 @@ fn queues_lists() -> Result<(), Box> { } #[test] -fn test_queues_delete_idempotently() -> Result<(), Box> { +fn test_queues_delete_idempotently() -> Result<(), Box> { let vh = "rabbitmqadmin.queues.test1"; let q = "test_queue_delete_idempotently"; diff --git a/tests/runtime_parameters_tests.rs b/tests/runtime_parameters_tests.rs index 8bd38ff..0ea5710 100644 --- a/tests/runtime_parameters_tests.rs +++ b/tests/runtime_parameters_tests.rs @@ -13,12 +13,12 @@ // limitations under the License. use predicates::prelude::*; - +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_runtime_parameters_across_groups() -> Result<(), Box> { +fn test_runtime_parameters_across_groups() -> Result<(), Box> { let vh = "rabbitmqadmin.runtime_parameters.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -74,7 +74,7 @@ fn test_runtime_parameters_across_groups() -> Result<(), Box Result<(), Box> { +fn test_runtime_parameters_cmd_group() -> Result<(), Box> { let vh = "rabbitmqadmin.runtime_parameters.test2"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -142,7 +142,7 @@ fn test_runtime_parameters_cmd_group() -> Result<(), Box> } #[test] -fn test_global_runtime_parameters_cmd_group() -> Result<(), Box> { +fn test_global_runtime_parameters_cmd_group() -> Result<(), Box> { run_succeeds([ "global_parameters", "set", @@ -163,7 +163,7 @@ fn test_global_runtime_parameters_cmd_group() -> Result<(), Box Result<(), Box> { +fn test_parameters_clear_idempotently() -> Result<(), Box> { let vh = "rabbitmqadmin.runtime_parameters.test3"; let param_name = "test_param_delete_idempotently"; let component = "federation-upstream"; @@ -230,7 +230,7 @@ fn test_parameters_clear_idempotently() -> Result<(), Box } #[test] -fn test_global_parameters_clear_idempotently() -> Result<(), Box> { +fn test_global_parameters_clear_idempotently() -> Result<(), Box> { let param_name = "test_global_param_delete_idempotently"; // Set the global parameter first diff --git a/tests/shovel_destination_uri_modification_tests.rs b/tests/shovel_destination_uri_modification_tests.rs index 0facbe4..305c85a 100644 --- a/tests/shovel_destination_uri_modification_tests.rs +++ b/tests/shovel_destination_uri_modification_tests.rs @@ -15,10 +15,10 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_disable_tls_peer_verification_for_all_destination_uris_basic() --> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_destination_uris_basic() -> Result<(), Box> +{ let vh = "rabbitmqadmin.shovel.modifications.test8"; let shovel_name = "test_basic_dest_shovel"; @@ -71,7 +71,7 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_basic() #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_verify_param() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test9"; let shovel_name = "test_existing_dest_shovel"; @@ -147,8 +147,8 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_existing_ver } #[test] -fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() --> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() -> Result<(), Box> +{ let vh = "rabbitmqadmin.shovel.modifications.test10"; let shovel_name = "test_amqp10_dest_shovel"; @@ -206,7 +206,7 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_amqp10() #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_params() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test11"; let shovel_name = "test_dummy_dest_params_shovel"; @@ -269,7 +269,7 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_with_dummy_query_ #[test] fn test_disable_tls_peer_verification_for_all_destination_uris_no_shovels() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test12"; delete_vhost(vh).ok(); @@ -286,8 +286,8 @@ fn test_disable_tls_peer_verification_for_all_destination_uris_no_shovels() } #[test] -fn test_enable_tls_peer_verification_for_all_destination_uris_basic() --> Result<(), Box> { +fn test_enable_tls_peer_verification_for_all_destination_uris_basic() -> Result<(), Box> +{ let vh = "rabbitmqadmin.shovel.modifications.test13"; let shovel_name = "test_enable_basic_dest_shovel"; @@ -350,7 +350,7 @@ fn test_enable_tls_peer_verification_for_all_destination_uris_basic() #[test] fn test_enable_tls_peer_verification_for_all_destination_uris_with_existing_params() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test14"; let shovel_name = "test_enable_existing_dest_shovel"; diff --git a/tests/shovel_source_uri_modification_tests.rs b/tests/shovel_source_uri_modification_tests.rs index 5cfe9e1..fb0de9d 100644 --- a/tests/shovel_source_uri_modification_tests.rs +++ b/tests/shovel_source_uri_modification_tests.rs @@ -15,10 +15,9 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_disable_tls_peer_verification_for_all_shovels_basic() --> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_shovels_basic() -> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test1"; let shovel_name = "test_basic_shovel"; @@ -71,7 +70,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_basic() #[test] fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test2"; let shovel_name = "test_existing_shovel"; @@ -150,8 +149,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_existing_verify_param } #[test] -fn test_disable_tls_peer_verification_for_all_shovels_amqp10() --> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_shovels_amqp10() -> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test3"; let shovel_name = "test_amqp10_shovel"; @@ -215,8 +213,8 @@ fn test_disable_tls_peer_verification_for_all_shovels_amqp10() } #[test] -fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() --> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() -> Result<(), Box> +{ let vh = "rabbitmqadmin.shovel.modifications.test4"; let shovel_091_name = "test_091_shovel"; let shovel_10_name = "test_10_shovel"; @@ -319,8 +317,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_mixed_protocols() } #[test] -fn test_disable_tls_peer_verification_for_all_shovels_no_shovels() --> Result<(), Box> { +fn test_disable_tls_peer_verification_for_all_shovels_no_shovels() -> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test5"; delete_vhost(vh).ok(); @@ -338,7 +335,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_no_shovels() #[test] fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params"; let shovel_name = "test_dummy_params_shovel"; @@ -404,8 +401,7 @@ fn test_disable_tls_peer_verification_for_all_shovels_with_dummy_query_params() } #[test] -fn test_enable_tls_peer_verification_for_all_source_uris_basic() --> Result<(), Box> { +fn test_enable_tls_peer_verification_for_all_source_uris_basic() -> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test6"; let shovel_name = "test_enable_basic_shovel"; @@ -468,7 +464,7 @@ fn test_enable_tls_peer_verification_for_all_source_uris_basic() #[test] fn test_enable_tls_peer_verification_for_all_source_uris_with_existing_params() --> Result<(), Box> { +-> Result<(), Box> { let vh = "rabbitmqadmin.shovel.modifications.test7"; let shovel_name = "test_enable_existing_shovel"; diff --git a/tests/shovel_tests.rs b/tests/shovel_tests.rs index 257e5f8..aad00d8 100644 --- a/tests/shovel_tests.rs +++ b/tests/shovel_tests.rs @@ -16,9 +16,9 @@ mod test_helpers; use crate::test_helpers::*; use predicates::boolean::PredicateBooleanExt; - +use std::error::Error; #[test] -fn test_shovel_declaration_without_source_uri() -> Result<(), Box> { +fn test_shovel_declaration_without_source_uri() -> Result<(), Box> { let vh = "rabbitmqadmin.shovels.test20"; let name = "shovels.test_shovel_declaration_without_source_uri"; @@ -49,7 +49,7 @@ fn test_shovel_declaration_without_source_uri() -> Result<(), Box Result<(), Box> { +fn test_shovel_declaration_without_destination_uri() -> Result<(), Box> { let vh = "rabbitmqadmin.shovels.test25"; let name = "shovels.test_shovel_declaration_without_destination_uri"; @@ -80,8 +80,7 @@ fn test_shovel_declaration_without_destination_uri() -> Result<(), Box Result<(), Box> { +fn test_shovel_declaration_with_overlapping_destination_types() -> Result<(), Box> { let vh = "rabbitmqadmin.shovels.test21"; let name = "shovels.test_shovel_declaration_with_overlapping_destination_types"; @@ -136,7 +135,7 @@ fn test_shovel_declaration_with_overlapping_destination_types() } #[test] -fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box> { +fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box> { let vh = "rabbitmqadmin.shovels.test22"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -199,7 +198,7 @@ fn test_amqp091_shovel_declaration_and_deletion() -> Result<(), Box Result<(), Box> { +fn test_amqp10_shovel_declaration_and_deletion() -> Result<(), Box> { let vh = "rabbitmqadmin.shovels.test23"; let name = "shovels.test_amqp10_shovel_declaration_and_deletion"; @@ -249,7 +248,7 @@ fn test_amqp10_shovel_declaration_and_deletion() -> Result<(), Box Result<(), Box> { +fn test_shovels_delete_idempotently() -> Result<(), Box> { let vh = "rabbitmqadmin.shovels.test24"; let shovel_name = "test_shovel_delete_idempotently"; diff --git a/tests/streams_tests.rs b/tests/streams_tests.rs index 3e14a04..725fb2b 100644 --- a/tests/streams_tests.rs +++ b/tests/streams_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn list_streams() -> Result<(), Box> { +fn list_streams() -> Result<(), Box> { let vh1 = "rabbitmqadmin.stream_vhost_1"; let vh2 = "rabbitmqadmin.stream_vhost_2"; let s1 = "new_stream1"; @@ -76,7 +77,7 @@ fn list_streams() -> Result<(), Box> { } #[test] -fn streams_list() -> Result<(), Box> { +fn streams_list() -> Result<(), Box> { let vh1 = "rabbitmqadmin.stream_vhost_3"; let vh2 = "rabbitmqadmin.stream_vhost_4"; let s1 = "new_stream1"; @@ -134,7 +135,7 @@ fn streams_list() -> Result<(), Box> { } #[test] -fn test_streams_delete_idempotently() -> Result<(), Box> { +fn test_streams_delete_idempotently() -> Result<(), Box> { let vh = "rabbitmqadmin.streams.test1"; let s = "test_stream_delete_idempotently"; diff --git a/tests/test_commands_recommended_against_tests.rs b/tests/test_commands_recommended_against_tests.rs index 06a38b1..3844e8c 100644 --- a/tests/test_commands_recommended_against_tests.rs +++ b/tests/test_commands_recommended_against_tests.rs @@ -14,9 +14,9 @@ mod test_helpers; use crate::test_helpers::*; - +use std::error::Error; #[test] -fn test_messages() -> Result<(), Box> { +fn test_messages() -> Result<(), Box> { // declare a new queue let q = "publish_consume"; run_succeeds(["declare", "queue", "--name", q, "--type", "classic"]); diff --git a/tests/test_helpers.rs b/tests/test_helpers.rs index 2f3997e..f915933 100644 --- a/tests/test_helpers.rs +++ b/tests/test_helpers.rs @@ -14,20 +14,22 @@ #![allow(dead_code)] use std::env; +use std::error::Error; use std::ffi::OsStr; +use std::process::Command; +use std::thread; use std::time::Duration; use assert_cmd::assert::Assert; use assert_cmd::prelude::*; use predicates::prelude::predicate; -use std::process::Command; use rabbitmq_http_client::blocking_api::Client as GenericAPIClient; use rabbitmqadmin::pre_flight::InteractivityMode; type APIClient<'a> = GenericAPIClient<&'a str, &'a str, &'a str>; -type CommandRunResult = Result<(), Box>; +type CommandRunResult = Result<(), Box>; pub const ENDPOINT: &str = "http://localhost:15672/api"; pub const USERNAME: &str = "guest"; @@ -52,11 +54,11 @@ pub fn amqp_endpoint_with_vhost(name: &str) -> String { } pub fn await_ms(ms: u64) { - std::thread::sleep(Duration::from_millis(ms)); + thread::sleep(Duration::from_millis(ms)); } pub fn await_metric_emission(ms: u64) { - std::thread::sleep(Duration::from_millis(ms)); + thread::sleep(Duration::from_millis(ms)); } pub fn await_queue_metric_emission() { diff --git a/tests/user_limits_tests.rs b/tests/user_limits_tests.rs index 3671523..e047fa6 100644 --- a/tests/user_limits_tests.rs +++ b/tests/user_limits_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_user_limits() -> Result<(), Box> { +fn test_user_limits() -> Result<(), Box> { let limit_name = "max-connections"; let username = "guest"; run_succeeds([ diff --git a/tests/users_tests.rs b/tests/users_tests.rs index b129ec0..587f872 100644 --- a/tests/users_tests.rs +++ b/tests/users_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_users() -> Result<(), Box> { +fn test_list_users() -> Result<(), Box> { let username = "test_list_users"; let password = "pa$$w0rd"; run_succeeds([ @@ -40,7 +41,7 @@ fn test_list_users() -> Result<(), Box> { } #[test] -fn test_users_list() -> Result<(), Box> { +fn test_users_list() -> Result<(), Box> { let username = "test_users_list.2"; let password = "pa$$w0rd"; run_succeeds([ @@ -62,7 +63,7 @@ fn test_users_list() -> Result<(), Box> { } #[test] -fn test_list_users_with_table_styles() -> Result<(), Box> { +fn test_list_users_with_table_styles() -> Result<(), Box> { let username = "test_list_users_with_table_styles"; let password = "pa$$w0rd"; run_succeeds([ @@ -85,7 +86,7 @@ fn test_list_users_with_table_styles() -> Result<(), Box> } #[test] -fn test_create_user_using_sha256_for_hashing() -> Result<(), Box> { +fn test_create_user_using_sha256_for_hashing() -> Result<(), Box> { let username = "test_create_user_using_sha256_for_hashing.1"; let password = "pa$$w0rd_9w798f__sd8f7"; @@ -121,7 +122,7 @@ fn test_create_user_using_sha256_for_hashing() -> Result<(), Box Result<(), Box> { +fn test_create_user_using_sha512_for_hashing() -> Result<(), Box> { let username = "test_create_user_using_sha512_for_hashing.1"; let password = "pa$$w0rd///8*9"; diff --git a/tests/vhost_limits_tests.rs b/tests/vhost_limits_tests.rs index 5cea4bd..a9513b9 100644 --- a/tests/vhost_limits_tests.rs +++ b/tests/vhost_limits_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_vhost_limits() -> Result<(), Box> { +fn test_vhost_limits() -> Result<(), Box> { let limit_name = "max-connections"; run_succeeds([ "declare", diff --git a/tests/vhosts_delete_multiple_tests.rs b/tests/vhosts_delete_multiple_tests.rs index 3d3cb89..23df713 100644 --- a/tests/vhosts_delete_multiple_tests.rs +++ b/tests/vhosts_delete_multiple_tests.rs @@ -15,9 +15,10 @@ mod test_helpers; use crate::test_helpers::*; +use std::error::Error; #[test] -fn test_vhosts_delete_multiple_basic() -> Result<(), Box> { +fn test_vhosts_delete_multiple_basic() -> Result<(), Box> { let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-basic"; // Clean up any existing test vhosts first (only our specific ones) @@ -60,7 +61,7 @@ fn test_vhosts_delete_multiple_basic() -> Result<(), Box> } #[test] -fn test_vhosts_delete_multiple_dry_run() -> Result<(), Box> { +fn test_vhosts_delete_multiple_dry_run() -> Result<(), Box> { let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-dry-run"; // Clean up any existing test vhosts first (only our specific ones) @@ -105,7 +106,7 @@ fn test_vhosts_delete_multiple_dry_run() -> Result<(), Box Result<(), Box> { +fn test_vhosts_delete_multiple_non_interactive() -> Result<(), Box> { let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-non-interactive"; // Clean up any existing test vhosts first (only our specific ones) @@ -148,7 +149,7 @@ fn test_vhosts_delete_multiple_non_interactive() -> Result<(), Box Result<(), Box> { +fn test_vhosts_delete_multiple_protects_default_vhost() -> Result<(), Box> { let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-protects-default"; // Clean up any existing test vhosts first (only our specific ones) @@ -198,7 +199,7 @@ fn test_vhosts_delete_multiple_protects_default_vhost() -> Result<(), Box Result<(), Box> { +fn test_vhosts_delete_multiple_with_invalid_regex() -> Result<(), Box> { let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-invalid-regex"; // Clean up any existing test vhosts first (only our specific ones) @@ -230,8 +231,8 @@ fn test_vhosts_delete_multiple_with_invalid_regex() -> Result<(), Box Result<(), Box> { +fn test_vhosts_delete_multiple_requires_approve_in_interactive_mode() -> Result<(), Box> +{ let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-requires-approve"; // Clean up any existing test vhosts first (only our specific ones) @@ -263,8 +264,7 @@ fn test_vhosts_delete_multiple_requires_approve_in_interactive_mode() // This test verifies that the delete_multiple command continues processing // even when individual vhost deletions fail and shows appropriate progress indicators. #[test] -fn test_vhosts_delete_multiple_continues_on_individual_failures() --> Result<(), Box> { +fn test_vhosts_delete_multiple_continues_on_individual_failures() -> Result<(), Box> { let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-continues"; // Clean up any existing test vhosts first (only our specific ones) @@ -319,8 +319,7 @@ fn test_vhosts_delete_multiple_continues_on_individual_failures() } #[test] -fn test_vhosts_delete_multiple_protects_deletion_protected_vhosts() --> Result<(), Box> { +fn test_vhosts_delete_multiple_protects_deletion_protected_vhosts() -> Result<(), Box> { let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-protects-protected"; // Clean up any existing test vhosts first (only our specific ones) diff --git a/tests/vhosts_tests.rs b/tests/vhosts_tests.rs index a46a74b..85375f8 100644 --- a/tests/vhosts_tests.rs +++ b/tests/vhosts_tests.rs @@ -13,12 +13,13 @@ // limitations under the License. use predicates::prelude::*; +use std::error::Error; mod test_helpers; use crate::test_helpers::*; #[test] -fn test_list_vhosts() -> Result<(), Box> { +fn test_list_vhosts() -> Result<(), Box> { let vh = "rabbitmqadmin.vhosts.test1"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -32,7 +33,7 @@ fn test_list_vhosts() -> Result<(), Box> { } #[test] -fn test_vhosts_list() -> Result<(), Box> { +fn test_vhosts_list() -> Result<(), Box> { let vh = "rabbitmqadmin.vhosts.test2"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -46,7 +47,7 @@ fn test_vhosts_list() -> Result<(), Box> { } #[test] -fn test_vhosts_create() -> Result<(), Box> { +fn test_vhosts_create() -> Result<(), Box> { let vh = "rabbitmqadmin.vhosts.test3"; delete_vhost(vh).expect("failed to delete a virtual host"); @@ -68,7 +69,7 @@ fn test_vhosts_create() -> Result<(), Box> { } #[test] -fn test_vhosts_delete() -> Result<(), Box> { +fn test_vhosts_delete() -> Result<(), Box> { let vh = "rabbitmqadmin.vhosts.test4"; run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); @@ -82,7 +83,7 @@ fn test_vhosts_delete() -> Result<(), Box> { } #[test] -fn test_vhosts_enable_deletion_protection() -> Result<(), Box> { +fn test_vhosts_enable_deletion_protection() -> Result<(), Box> { let vh = "rabbitmqadmin.vhosts.test-deletion-protection-enable"; run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); @@ -97,7 +98,7 @@ fn test_vhosts_enable_deletion_protection() -> Result<(), Box Result<(), Box> { +fn test_vhosts_disable_deletion_protection() -> Result<(), Box> { let vh = "rabbitmqadmin.vhosts.test-deletion-protection-disable"; run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); @@ -112,7 +113,7 @@ fn test_vhosts_disable_deletion_protection() -> Result<(), Box Result<(), Box> { +fn test_vhosts_protected_vhost_cannot_be_deleted() -> Result<(), Box> { let vh = "rabbitmqadmin.vhosts.test-protected-cannot-delete"; run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]); From d1f4e9b31eb8d5e8a585ae687e863f9fa3e0c8e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 00:47:03 +0000 Subject: [PATCH 286/320] Bump indicatif from 0.17.11 to 0.18.0 Bumps [indicatif](https://github.com/console-rs/indicatif) from 0.17.11 to 0.18.0. - [Release notes](https://github.com/console-rs/indicatif/releases) - [Commits](https://github.com/console-rs/indicatif/compare/0.17.11...0.18.0) --- updated-dependencies: - dependency-name: indicatif dependency-version: 0.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a41b21..66d752b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,15 +333,15 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "console" -version = "0.15.11" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" dependencies = [ "encode_unicode", "libc", "once_cell", "unicode-width", - "windows-sys 0.59.0", + "windows-sys 0.61.1", ] [[package]] @@ -933,14 +933,14 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.11" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ "console", - "number_prefix", "portable-atomic", "unicode-width", + "unit-prefix", "web-time", ] @@ -1177,12 +1177,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" version = "0.37.3" @@ -2313,6 +2307,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index a2f4356..f448483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ color-print = "0.3" thiserror = "2" shellexpand = "3.1" regex = "1" -indicatif = "0.17" +indicatif = "0.18" log = "0.4" rustls = { version = "0.23", features = ["aws_lc_rs"] } From b58ed72ff0881ff35a31eaa3950ae05a11d71a9a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 30 Sep 2025 14:09:37 -0400 Subject: [PATCH 287/320] cargo update --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66d752b..09793ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,9 +52,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -1450,9 +1450,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -2056,18 +2056,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2833,9 +2833,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" From 7d114e281452df1de9af39a869a2169090c33e45 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 30 Sep 2025 15:06:28 -0400 Subject: [PATCH 288/320] 2.14.0 --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 423f16a..af2d9ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.14.0 (in development) +## v2.15.0 (in development) + +No changes yet. + + +## v2.14.0 (Sep 30, 2025) ### Enhancements From 25863b6d7541fe6c8e59a55ae9223f3850eab75f Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 30 Sep 2025 15:54:55 -0400 Subject: [PATCH 289/320] Bridge the gap between the "verb" command groups and "noun" ones --- CHANGELOG.md | 36 ++++++- Cargo.lock | 6 +- Cargo.toml | 2 +- src/cli.rs | 189 ++++++++++++++++++++++++++++++++++++ src/main.rs | 41 ++++++++ tests/permissions_tests.rs | 8 +- tests/user_limits_tests.rs | 10 +- tests/vhost_limits_tests.rs | 8 +- 8 files changed, 282 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af2d9ef..9aec442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,41 @@ ## v2.15.0 (in development) -No changes yet. +### Enhancements + +* `permissions` is a new command group for operations on user permissions: + + ```shell + rabbitmqadmin permissions list + + rabbitmqadmin permissions declare --user "user1" --configure ".*" --read ".*" --write ".*" + + rabbitmqadmin permissions delete --user "user1" + ``` + +* `user_limits` is a new command group for operations on per-user limits: + + ```shell + rabbitmqadmin user_limits list + + rabbitmqadmin user_limits declare --user "user1" --name "max-connections" --value "100" + + rabbitmqadmin user_limits delete --user "user1" --name "max-connections" + ``` + +* `vhost_limits` is a new command group for operations on virtual host limits: + + ```shell + rabbitmqadmin vhost_limits list + + rabbitmqadmin vhost_limits declare --name "max-connections" --value "1000" + + rabbitmqadmin vhost_limits delete --name "max-connections" + ``` + +### Deprecations + +* "Verb" command groups (`list [object]`, `declare [object]`, `delete [object]`) are now deprecated in favor of the "noun" group commands (such as `users [operation]` or `permissions [operation]`). ## v2.14.0 (Sep 30, 2025) diff --git a/Cargo.lock b/Cargo.lock index 09793ec..1bb3ac1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba2e2516bdf37af57fc6ff047855f54abad0066e5c4fdaaeb76dabb2e05bcf5" +checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" dependencies = [ "bindgen", "cc", @@ -1489,7 +1489,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.14.0" +version = "2.15.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index f448483..8e91f7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.14.0" +version = "2.15.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" diff --git a/src/cli.rs b/src/cli.rs index dc42357..3e0df7c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -220,6 +220,16 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { PASSWORD_GUIDE_URL )) .subcommands(passwords_subcommands(pre_flight_settings.clone())); + let permissions_group = Command::new("permissions") + .about("Operations on user permissions") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + ACCESS_CONTROL_GUIDE_URL + )) + .subcommand_value_name("permission") + .subcommands(permissions_subcommands(pre_flight_settings.clone())); let policies_group = Command::new("policies") .about("Operations on policies") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -304,11 +314,31 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { )) .subcommand_value_name("subcommand") .subcommands(users_subcommands(pre_flight_settings.clone())); + let user_limits_group = Command::new("user_limits") + .about("Operations on per-user (resource) limits") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + USER_LIMIT_GUIDE_URL + )) + .subcommand_value_name("user_limit") + .subcommands(user_limits_subcommands(pre_flight_settings.clone())); let vhosts_group = Command::new("vhosts") .about("Virtual host operations") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommands(vhosts_subcommands(pre_flight_settings.clone())); + let vhost_limits_group = Command::new("vhost_limits") + .about("Operations on virtual host (resource) limits") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + VIRTUAL_HOST_LIMIT_GUIDE_URL + )) + .subcommand_value_name("vhost_limit") + .subcommands(vhost_limits_subcommands(pre_flight_settings.clone())); let command_groups = [ bindings_group, @@ -332,6 +362,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { operator_policies_group, parameters_group, passwords_group, + permissions_group, policies_group, publish_group, purge_group, @@ -342,7 +373,9 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { streams_group, tanzu_group, users_group, + user_limits_group, vhosts_group, + vhost_limits_group, ]; Command::new("rabbitmqadmin") @@ -2948,6 +2981,162 @@ pub fn passwords_subcommands(pre_flight_settings: PreFlightSettings) -> [Command [hash_password].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) } +pub fn permissions_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let idempotently_arg = Arg::new("idempotently") + .long("idempotently") + .value_parser(value_parser!(bool)) + .action(ArgAction::SetTrue) + .help("do not consider 404 Not Found API responses to be errors") + .required(false); + + let list_cmd = Command::new("list") + .long_about("Lists user permissions") + .after_help(color_print::cformat!( + "Doc guide: {}", + ACCESS_CONTROL_GUIDE_URL + )); + + let declare_cmd = Command::new("declare") + .about("grants permissions to a user") + .after_help(color_print::cformat!( + "Doc guide:: {}", + ACCESS_CONTROL_GUIDE_URL + )) + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg( + Arg::new("configure") + .long("configure") + .help("name pattern for configuration access") + .required(true), + ) + .arg( + Arg::new("read") + .long("read") + .help("name pattern for read access") + .required(true), + ) + .arg( + Arg::new("write") + .long("write") + .help("name pattern for write access") + .required(true), + ); + + let delete_cmd = Command::new("delete") + .about("Revokes user permissions to a given vhost") + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg(idempotently_arg.clone()); + + [list_cmd, declare_cmd, delete_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + +pub fn user_limits_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let list_cmd = Command::new("list") + .long_about("Lists per-user (resource) limits") + .after_help(color_print::cformat!( + "Doc guide: {}", + USER_LIMIT_GUIDE_URL + )) + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(false), + ); + + let declare_cmd = Command::new("declare") + .about("Set a user limit") + .after_help(color_print::cformat!( + "Doc guide:: {}", + USER_LIMIT_GUIDE_URL + )) + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ) + .arg( + Arg::new("value") + .long("value") + .help("limit value") + .required(true), + ); + + let delete_cmd = Command::new("delete") + .about("Clears a user limit") + .arg( + Arg::new("user") + .long("user") + .help("username") + .required(true), + ) + .arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ); + + [list_cmd, declare_cmd, delete_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + +pub fn vhost_limits_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { + let list_cmd = Command::new("list") + .long_about("Lists virtual host (resource) limits") + .after_help(color_print::cformat!( + "Doc guide: {}", + VIRTUAL_HOST_GUIDE_URL + )); + + let declare_cmd = Command::new("declare") + .about("Set a vhost limit") + .after_help(color_print::cformat!( + "Doc guide:: {}", + VIRTUAL_HOST_LIMIT_GUIDE_URL + )) + .arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ) + .arg( + Arg::new("value") + .long("value") + .help("limit value") + .required(true), + ); + + let delete_cmd = Command::new("delete").about("delete a vhost limit").arg( + Arg::new("name") + .long("name") + .help("limit name (eg. max-connections, max-queues)") + .required(true), + ); + + [list_cmd, declare_cmd, delete_cmd] + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) +} + pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { [Command::new("message") .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) diff --git a/src/main.rs b/src/main.rs index e2de763..b07b80a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -910,6 +910,20 @@ fn dispatch_common_subcommand( let result = commands::salt_and_hash_password(second_level_args); res_handler.show_salted_and_hashed_value(result) } + ("permissions", "list") => { + let result = commands::list_permissions(client); + res_handler.tabular_result(result) + } + ("permissions", "declare") => { + let result = commands::declare_permissions(client, &vhost, second_level_args) + .map_err(Into::into); + res_handler.no_output_on_success(result); + } + ("permissions", "delete") => { + let result = + commands::delete_permissions(client, &vhost, second_level_args).map_err(Into::into); + res_handler.no_output_on_success(result); + } ("policies", "declare") => { let result = commands::declare_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); @@ -1153,6 +1167,19 @@ fn dispatch_common_subcommand( let result = commands::list_permissions(client); res_handler.tabular_result(result) } + ("user_limits", "list") => { + let result = commands::list_user_limits(client, second_level_args); + res_handler.tabular_result(result) + } + ("user_limits", "declare") => { + let result = + commands::declare_user_limit(client, second_level_args).map_err(Into::into); + res_handler.no_output_on_success(result); + } + ("user_limits", "delete") => { + let result = commands::delete_user_limit(client, second_level_args).map_err(Into::into); + res_handler.no_output_on_success(result); + } ("vhosts", "declare") => { let result = commands::declare_vhost(client, second_level_args).map_err(Into::into); res_handler.no_output_on_success(result); @@ -1191,6 +1218,20 @@ fn dispatch_common_subcommand( .map_err(Into::into); res_handler.no_output_on_success(result); } + ("vhost_limits", "list") => { + let result = commands::list_vhost_limits(client, &vhost); + res_handler.tabular_result(result) + } + ("vhost_limits", "declare") => { + let result = commands::declare_vhost_limit(client, &vhost, second_level_args) + .map_err(Into::into); + res_handler.no_output_on_success(result); + } + ("vhost_limits", "delete") => { + let result = + commands::delete_vhost_limit(client, &vhost, second_level_args).map_err(Into::into); + res_handler.no_output_on_success(result); + } _ => { let error = CommandRunError::UnknownCommandTarget { command: pair.0.into(), diff --git a/tests/permissions_tests.rs b/tests/permissions_tests.rs index 57223d1..90dad3e 100644 --- a/tests/permissions_tests.rs +++ b/tests/permissions_tests.rs @@ -31,8 +31,8 @@ fn test_list_permissions() -> Result<(), Box> { ]); run_succeeds([ - "declare", "permissions", + "declare", "--user", username, "--configure", @@ -43,14 +43,14 @@ fn test_list_permissions() -> Result<(), Box> { "baz", ]); - run_succeeds(["list", "permissions"]).stdout( + run_succeeds(["permissions", "list"]).stdout( output_includes("foo") .and(output_includes("bar")) .and(output_includes("baz")), ); - run_succeeds(["delete", "permissions", "--user", username]); - run_succeeds(["list", "permissions"]).stdout(output_includes(username).not()); + run_succeeds(["permissions", "delete", "--user", username]); + run_succeeds(["permissions", "list"]).stdout(output_includes(username).not()); run_succeeds(["delete", "user", "--name", username]); Ok(()) diff --git a/tests/user_limits_tests.rs b/tests/user_limits_tests.rs index e047fa6..d5c0a3f 100644 --- a/tests/user_limits_tests.rs +++ b/tests/user_limits_tests.rs @@ -23,8 +23,8 @@ fn test_user_limits() -> Result<(), Box> { let limit_name = "max-connections"; let username = "guest"; run_succeeds([ + "user_limits", "declare", - "user_limit", "--user", username, "--name", @@ -33,23 +33,23 @@ fn test_user_limits() -> Result<(), Box> { "1234", ]); - run_succeeds(["list", "user_limits"]) + run_succeeds(["user_limits", "list"]) .stdout(output_includes(limit_name).and(output_includes("1234"))); run_succeeds([ + "user_limits", "delete", - "user_limit", "--user", username, "--name", limit_name, ]); - run_succeeds(["list", "user_limits"]).stdout(output_includes(limit_name).not()); + run_succeeds(["user_limits", "list"]).stdout(output_includes(limit_name).not()); run_succeeds([ + "user_limits", "delete", - "user_limit", "--user", username, "--name", diff --git a/tests/vhost_limits_tests.rs b/tests/vhost_limits_tests.rs index a9513b9..76126f4 100644 --- a/tests/vhost_limits_tests.rs +++ b/tests/vhost_limits_tests.rs @@ -22,18 +22,18 @@ use crate::test_helpers::*; fn test_vhost_limits() -> Result<(), Box> { let limit_name = "max-connections"; run_succeeds([ + "vhost_limits", "declare", - "vhost_limit", "--name", limit_name, "--value", "1234", ]); - run_succeeds(["list", "vhost_limits"]) + run_succeeds(["vhost_limits", "list"]) .stdout(output_includes(limit_name).and(output_includes("1234"))); - run_succeeds(["delete", "vhost_limit", "--name", limit_name]); - run_succeeds(["list", "vhost_limits"]).stdout(output_includes(limit_name).not()); + run_succeeds(["vhost_limits", "delete", "--name", limit_name]); + run_succeeds(["vhost_limits", "list"]).stdout(output_includes(limit_name).not()); Ok(()) } From 6803023ecacaea8f0d253b1deb3f75ee1d9cfa91 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 30 Sep 2025 20:59:18 -0400 Subject: [PATCH 290/320] 2.15.0 --- CHANGELOG.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aec442..e9bc15a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.15.0 (in development) +## v2.16.0 (in development) + +No changes yet. + + +## v2.15.0 (Sep 30, 2025) ### Enhancements @@ -185,7 +190,7 @@ ### Enhancements * `federation disable_tls_peer_verification_for_all_upstreams` is a new command that disables TLS peer verification - for all federation upstreams. + for all federation upstreams. **Important**: this command should **only** be used to correct federation upstream URI after a bad deployment, for example, if [peer verification](https://www.rabbitmq.com/docs/ssl#peer-verification) was enabled before certificates and keys were @@ -236,7 +241,7 @@ ### Enhancements - * `definitions export` is now compatible with RabbitMQ 3.10.0, a series that has + * `definitions export` is now compatible with RabbitMQ 3.10.0, a series that has reached end of life (EOL) in late 2023 ### Upgrades @@ -302,12 +307,12 @@ ### Enhancements - * `rabbitmqadmin.conf` now supports more TLS-related settings: `ca_certificate_bundle_path` (corresponds to `--tls-ca-cert-file` on the command line), - `client_certificate_file_path` (corresponds to `--tls-cert-file`), and `client_private_key_file_path` (corresponds to `--tls-key-file`). + * `rabbitmqadmin.conf` now supports more TLS-related settings: `ca_certificate_bundle_path` (corresponds to `--tls-ca-cert-file` on the command line), + `client_certificate_file_path` (corresponds to `--tls-cert-file`), and `client_private_key_file_path` (corresponds to `--tls-key-file`). As the names suggest, they are used to configure the CA certificate bundle file path, the client certificate file path, and the client private key file path, respectively: - + ```toml [production] hostname = "(redacted)" @@ -324,8 +329,8 @@ ### Bug Fixes - * Tool version was unintentionally missing from `-h` output (but present in its long counterpart, `--help`) - * The `tls` setting in `rabbitmqadmin.conf`, a `--use-tls` equivalent, was not respected when connecting to a node + * Tool version was unintentionally missing from `-h` output (but present in its long counterpart, `--help`) + * The `tls` setting in `rabbitmqadmin.conf`, a `--use-tls` equivalent, was not respected when connecting to a node in certain cases ## v2.6.0 (Jul 12, 2025) From 92d7c938e87ccb064ad27f23409188737fc05d6c Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 30 Sep 2025 21:14:01 -0400 Subject: [PATCH 291/320] 2.15.0 change log: link to relevant doc guides (cherry picked from commit 06538d4cb39f089cffa41fe35a47e1612c37b8ef) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9bc15a..4de5789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ No changes yet. rabbitmqadmin permissions delete --user "user1" ``` -* `user_limits` is a new command group for operations on per-user limits: +* `user_limits` is a new command group for operations on [per-user limits](https://www.rabbitmq.com/docs/user-limits): ```shell rabbitmqadmin user_limits list @@ -29,7 +29,7 @@ No changes yet. rabbitmqadmin user_limits delete --user "user1" --name "max-connections" ``` -* `vhost_limits` is a new command group for operations on virtual host limits: +* `vhost_limits` is a new command group for operations on [virtual host limits](https://www.rabbitmq.com/docs/vhosts#limits): ```shell rabbitmqadmin vhost_limits list From 0dd2c9f4c7eadf3cd4a79e4b15581df99ab0edf3 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 30 Sep 2025 21:45:23 -0400 Subject: [PATCH 292/320] Bump dev version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bb3ac1..df08d46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1489,7 +1489,7 @@ dependencies = [ [[package]] name = "rabbitmqadmin" -version = "2.15.0" +version = "2.16.0" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 8e91f7a..884b1f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rabbitmqadmin" -version = "2.15.0" +version = "2.16.0" edition = "2024" description = "rabbitmqadmin v2 is a modern CLI tool for the RabbitMQ HTTP API" From fde118d6da6433d56e18563e76ea1e3de3b9d486 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Thu, 2 Oct 2025 21:52:42 +0200 Subject: [PATCH 293/320] Show help when no args provided --- src/cli.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index 3e0df7c..e2fdde0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -51,26 +51,31 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("binding") + .arg_required_else_help(true) .subcommands(binding_subcommands(pre_flight_settings.clone())); let channels_group = Command::new("channels") .about("Operations on channels") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(channels_subcommands(pre_flight_settings.clone())); let close_group = Command::new("close") .about("Closes connections") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(close_subcommands(pre_flight_settings.clone())); let connections_group = Command::new("connections") .about("Operations on connections") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(connections_subcommands(pre_flight_settings.clone())); let declare_group = Command::new("declare") .about("Creates or declares objects") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(declare_subcommands(pre_flight_settings.clone())); let definitions_group = Command::new("definitions") .about("Operations on definitions (everything except for messages: virtual hosts, queues, streams, exchanges, bindings, users, etc)") @@ -81,11 +86,13 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { DEFINITION_GUIDE_URL )) .subcommand_value_name("export") + .arg_required_else_help(true) .subcommands(definitions_subcommands(pre_flight_settings.clone())); let delete_group = Command::new("delete") .about("Deletes objects") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(delete_subcommands(pre_flight_settings.clone())); let deprecated_features_group = Command::new("deprecated_features") .about("Operations on deprecated features") @@ -96,12 +103,14 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { DEPRECATED_FEATURE_GUIDE_URL )) .subcommand_value_name("deprecated feature") + .arg_required_else_help(true) .subcommands(deprecated_features_subcommands(pre_flight_settings.clone())); let exchanges_group = Command::new("exchanges") .about("Operations on exchanges") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("exchange") + .arg_required_else_help(true) .subcommands(exchanges_subcommands(pre_flight_settings.clone())); let export_group = Command::new("export") .about("See 'definitions export'") @@ -112,6 +121,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { DEFINITION_GUIDE_URL )) .subcommand_value_name("definitions") + .arg_required_else_help(true) .subcommands(export_subcommands(pre_flight_settings.clone())); let feature_flags_group = Command::new("feature_flags") .about("Operations on feature flags") @@ -122,6 +132,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { FEATURE_FLAG_GUIDE_URL )) .subcommand_value_name("feature flag") + .arg_required_else_help(true) .subcommands(feature_flags_subcommands(pre_flight_settings.clone())); let federation_group = Command::new("federation") .about("Operations on federation upstreams and links") @@ -139,6 +150,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { FEDERATED_QUEUES_GUIDE_URL, FEDERATION_REFERENCE_URL )) + .arg_required_else_help(true) .subcommands(federation_subcommands(pre_flight_settings.clone())); let get_group = Command::new("get") .about(color_print::cstr!("Fetches message(s) from a queue or stream via polling. Only suitable for development and test environments.")) @@ -146,6 +158,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) .subcommand_value_name("message") + .arg_required_else_help(true) .subcommands(get_subcommands(pre_flight_settings.clone())); let global_parameters_group = Command::new("global_parameters") .about("Operations on global runtime parameters") @@ -156,12 +169,14 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { RUNTIME_PARAMETER_GUIDE_URL )) .subcommand_value_name("runtime_parameter") + .arg_required_else_help(true) .subcommands(global_parameters_subcommands(pre_flight_settings.clone())); let health_check_group = Command::new("health_check") .about("Runs health checks") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("check") + .arg_required_else_help(true) .subcommands(health_check_subcommands(pre_flight_settings.clone())) .after_help(color_print::cformat!( r#"Doc guides: @@ -180,16 +195,19 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { DEFINITION_GUIDE_URL )) .subcommand_value_name("definitions") + .arg_required_else_help(true) .subcommands(import_subcommands(pre_flight_settings.clone())); let list_group = Command::new("list") .about("Lists objects") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(list_subcommands(pre_flight_settings.clone())); let nodes_group = Command::new("nodes") .about("Node operations") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(nodes_subcommands(pre_flight_settings.clone())); let operator_policies_group = Command::new("operator_policies") .about("Operations on operator policies") @@ -200,6 +218,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { POLICY_GUIDE_URL )) .subcommand_value_name("operator policy") + .arg_required_else_help(true) .subcommands(operator_policies_subcommands(pre_flight_settings.clone())); let parameters_group = Command::new("parameters") .about("Operations on runtime parameters") @@ -210,6 +229,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { RUNTIME_PARAMETER_GUIDE_URL )) .subcommand_value_name("runtime_parameter") + .arg_required_else_help(true) .subcommands(parameters_subcommands(pre_flight_settings.clone())); let passwords_group = Command::new("passwords") .about("Operations on passwords") @@ -219,6 +239,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { "Doc guide: {}", PASSWORD_GUIDE_URL )) + .arg_required_else_help(true) .subcommands(passwords_subcommands(pre_flight_settings.clone())); let permissions_group = Command::new("permissions") .about("Operations on user permissions") @@ -229,6 +250,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { ACCESS_CONTROL_GUIDE_URL )) .subcommand_value_name("permission") + .arg_required_else_help(true) .subcommands(permissions_subcommands(pre_flight_settings.clone())); let policies_group = Command::new("policies") .about("Operations on policies") @@ -239,6 +261,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { POLICY_GUIDE_URL )) .subcommand_value_name("policy") + .arg_required_else_help(true) .subcommands(policies_subcommands(pre_flight_settings.clone())); let publish_group = Command::new("publish") .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments.")) @@ -246,18 +269,21 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .infer_long_args(pre_flight_settings.infer_long_options) .after_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) .subcommand_value_name("message") + .arg_required_else_help(true) .subcommands(publish_subcommands(pre_flight_settings.clone())); let purge_group = Command::new("purge") .about("Purges queues") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("queue") + .arg_required_else_help(true) .subcommands(purge_subcommands(pre_flight_settings.clone())); let queues_group = Command::new("queues") .about("Operations on queues") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("queue") + .arg_required_else_help(true) .subcommands(queues_subcommands(pre_flight_settings.clone())); let rebalance_group = Command::new("rebalance") .about("Rebalancing of leader replicas") @@ -268,6 +294,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { QUORUM_QUEUE_GUIDE_URL )) .subcommand_value_name("queues") + .arg_required_else_help(true) .subcommands(rebalance_subcommands(pre_flight_settings.clone())); let show_group = Command::new("show") .about("Overview, memory footprint breakdown, and more") @@ -277,6 +304,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { )) .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(show_subcommands(pre_flight_settings.clone())); let shovels_group = Command::new("shovels") .about("Operations on shovels") @@ -287,12 +315,14 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { SHOVEL_GUIDE_URL )) .subcommand_value_name("shovels") + .arg_required_else_help(true) .subcommands(shovel_subcommands(pre_flight_settings.clone())); let streams_group = Command::new("streams") .about("Operations on streams") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) .subcommand_value_name("stream") + .arg_required_else_help(true) .subcommands(streams_subcommands(pre_flight_settings.clone())); let tanzu_group = Command::new("tanzu") .about("Tanzu RabbitMQ-specific commands") @@ -303,6 +333,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { COMMERCIAL_OFFERINGS_GUIDE_URL )) .subcommand_value_name("subcommand") + .arg_required_else_help(true) .subcommands(tanzu_subcommands()); let users_group = Command::new("users") .about("Operations on users") @@ -313,6 +344,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { ACCESS_CONTROL_GUIDE_URL )) .subcommand_value_name("subcommand") + .arg_required_else_help(true) .subcommands(users_subcommands(pre_flight_settings.clone())); let user_limits_group = Command::new("user_limits") .about("Operations on per-user (resource) limits") @@ -323,11 +355,13 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { USER_LIMIT_GUIDE_URL )) .subcommand_value_name("user_limit") + .arg_required_else_help(true) .subcommands(user_limits_subcommands(pre_flight_settings.clone())); let vhosts_group = Command::new("vhosts") .about("Virtual host operations") .infer_subcommands(pre_flight_settings.infer_subcommands) .infer_long_args(pre_flight_settings.infer_long_options) + .arg_required_else_help(true) .subcommands(vhosts_subcommands(pre_flight_settings.clone())); let vhost_limits_group = Command::new("vhost_limits") .about("Operations on virtual host (resource) limits") @@ -338,6 +372,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { VIRTUAL_HOST_LIMIT_GUIDE_URL )) .subcommand_value_name("vhost_limit") + .arg_required_else_help(true) .subcommands(vhost_limits_subcommands(pre_flight_settings.clone())); let command_groups = [ From d01fb108a4e704f8da3429298d2a0c151b3ad2d2 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Sat, 4 Oct 2025 19:08:47 -0700 Subject: [PATCH 294/320] cargo update --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df08d46..a27c549 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,9 +37,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -215,9 +215,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.39" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "jobserver", @@ -500,9 +500,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "float-cmp" @@ -1736,9 +1736,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "aws-lc-rs", "ring", From 244c10f5f1a50e359c2d96f0d2d3525b78c46ad6 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 6 Oct 2025 10:08:57 -0700 Subject: [PATCH 295/320] cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a27c549..5c19607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2303,9 +2303,9 @@ checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unit-prefix" From 36811f2f7713e8e279a32b39999c7aceab1aa026 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 6 Oct 2025 18:35:51 -0700 Subject: [PATCH 296/320] Adopt clap's crate_name macro --- src/cli.rs | 6 +++--- src/main.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index e2fdde0..57b9cd8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -18,7 +18,7 @@ use super::static_urls::*; use super::tanzu_cli::tanzu_subcommands; use crate::config::PreFlightSettings; use crate::output::TableStyle; -use clap::{Arg, ArgAction, ArgGroup, Command, crate_version, value_parser}; +use clap::{Arg, ArgAction, ArgGroup, Command, crate_name, crate_version, value_parser}; use rabbitmq_http_client::commons::{ BindingDestinationType, ChannelUseMode, ExchangeType, MessageTransferAcknowledgementMode, PolicyTarget, QueueType, SupportedProtocol, @@ -413,10 +413,10 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { vhost_limits_group, ]; - Command::new("rabbitmqadmin") + Command::new(crate_name!()) .version(crate_version!()) .author("The RabbitMQ Core Team") - .about(format!("rabbitmqadmin gen 2, version: {}", crate_version!())) + .about(format!("{} gen 2, version: {}", crate_name!(), crate_version!())) .long_about(format!( "RabbitMQ CLI that uses the HTTP API. Version: {}", crate_version!() diff --git a/src/main.rs b/src/main.rs index b07b80a..14a8ccf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ #![allow(clippy::unnecessary_unwrap)] #![allow(clippy::collapsible_if)] -use clap::{ArgMatches, crate_version}; +use clap::{ArgMatches, crate_name, crate_version}; use errors::CommandRunError; use reqwest::{Identity, tls::Version as TlsVersion}; use std::path::{Path, PathBuf}; @@ -185,7 +185,7 @@ fn build_http_client( cli: &ArgMatches, common_settings: &SharedSettings, ) -> Result { - let user_agent = format!("rabbitmqadmin-ng {}", crate_version!()); + let user_agent = format!("{} {}", crate_name!(), crate_version!()); if should_use_tls(common_settings) { let _ = CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider()); From 57991d0c1c7bc42b27cb4665962f2e968bcc6b3b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 8 Oct 2025 00:30:01 -0400 Subject: [PATCH 297/320] cargo update --- Cargo.lock | 114 ++++++++++++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c19607..78acc36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,7 +149,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -254,7 +254,7 @@ checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "num-traits", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -341,7 +341,7 @@ dependencies = [ "libc", "once_cell", "unicode-width", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -429,7 +429,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -489,7 +489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -1025,7 +1025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] @@ -1693,7 +1693,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -1764,7 +1764,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2036,7 +2036,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2491,22 +2491,22 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.62.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", - "windows-result 0.4.0", - "windows-strings 0.5.0", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] name = "windows-implement" -version = "0.60.1" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -2515,9 +2515,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.2" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -2532,9 +2532,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" @@ -2558,11 +2558,11 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -2576,11 +2576,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -2607,16 +2607,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.4", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -2637,19 +2637,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.0", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2660,9 +2660,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2672,9 +2672,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2684,9 +2684,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2696,9 +2696,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2708,9 +2708,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2720,9 +2720,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2732,9 +2732,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2744,9 +2744,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" From 7a1ea28c765b71387753e2e253e31bdc8f434b3b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 8 Oct 2025 00:30:27 -0400 Subject: [PATCH 298/320] Use Vec for subcommand lists --- src/cli.rs | 157 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 41 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 57b9cd8..25fcdaa 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -596,7 +596,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommands(command_groups) } -fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { +fn list_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let nodes_cmd = Command::new("nodes").long_about("Lists cluster members"); let vhosts_cmd = Command::new("vhosts") .long_about("Lists virtual hosts") @@ -731,10 +731,12 @@ fn list_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 19] { deprecated_features_cmd, deprecated_features_in_use_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] { +fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let user_cmd = Command::new("user") .about("Creates a user") .arg( @@ -1134,10 +1136,12 @@ fn declare_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] vhost_limit_cmd, user_limit_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { +fn show_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let overview_cmd = Command::new("overview") .about("Displays essential information about target node and its cluster"); let churn_cmd = Command::new("churn").about("Displays object churn metrics"); @@ -1176,10 +1180,12 @@ fn show_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { memory_breakdown_in_bytes_cmd, memory_breakdown_in_percent_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { +fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -1355,10 +1361,12 @@ fn delete_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 13] { user_limit_cmd, shovel_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let queue_cmd = Command::new("queue") .long_about("Purges (permanently removes unacknowledged messages from) a queue") .arg( @@ -1367,10 +1375,13 @@ fn purge_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { .help("name of the queue to purge") .required(true), ); - [queue_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + [queue_cmd] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn binding_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +fn binding_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -1451,10 +1462,12 @@ fn binding_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { let list_cmd = Command::new("list").long_about("Lists bindings"); [declare_cmd, delete_cmd, list_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn queues_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { +fn queues_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let declare_cmd = Command::new("declare") .about("Declares a queue or a stream") .after_help(color_print::cformat!( @@ -1523,10 +1536,12 @@ fn queues_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { ); let rebalance_cmd = Command::new("rebalance").about("Rebalances queue leaders"); [declare_cmd, delete_cmd, list_cmd, purge_cmd, rebalance_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn streams_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +fn streams_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let declare_cmd = Command::new("declare") .about("Declares a stream") .after_help(color_print::cformat!( @@ -1585,10 +1600,12 @@ fn streams_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { STREAM_GUIDE_URL )); [declare_cmd, delete_cmd, list_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { +fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -1671,10 +1688,12 @@ fn parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5 .arg(idempotently_arg.clone()); [clear_cmd, list_all_cmd, list_cmd, list_in_cmd, set_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -1721,10 +1740,12 @@ fn global_parameters_subcommands(pre_flight_settings: PreFlightSettings) -> [Com .arg(idempotently_arg.clone()); [clear_cmd, list_cmd, set_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 10] { +fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -1902,10 +1923,12 @@ fn operator_policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Com update_cmd, update_all_in_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] { +fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -2130,10 +2153,12 @@ fn policies_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 12] update_cmd, update_all_in_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { +fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let node_is_quorum_critical_after_help = color_print::cformat!( r#" Doc guides: @@ -2195,15 +2220,20 @@ fn health_check_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; port_listener, protocol_listener, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn rebalance_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +fn rebalance_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let queues_cmd = Command::new("queues").about("Rebalances queue leaders"); - [queues_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + [queues_cmd] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { +fn close_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -2231,10 +2261,12 @@ fn close_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { ) .arg(idempotently_arg.clone()); [close_connection, close_user_connections] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn channels_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +fn channels_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists all channels across all virtual hosts") .after_help(color_print::cformat!( @@ -2242,10 +2274,13 @@ fn channels_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] "https://www.rabbitmq.com/docs/channels" )); - [list_cmd].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + [list_cmd] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { +fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -2298,10 +2333,12 @@ fn connections_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; list_cmd, list_user_connections_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn definitions_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] { +fn definitions_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let export_cmd = Command::new("export") .about("Export cluster-wide definitions") .after_help(color_print::cformat!( @@ -2476,10 +2513,12 @@ Examples: import_cmd, import_into_vhost_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn exchanges_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] { +fn exchanges_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let bind_cmd = Command::new("bind") .about("Creates a binding between a source exchange and a destination (a queue or an exchange)") .arg( @@ -2604,9 +2643,11 @@ fn exchanges_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 5] ) .arg(idempotently_arg.clone()); [bind_cmd, declare_cmd, delete_cmd, list_cmd, unbind_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn export_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +fn export_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let definitions = Command::new("definitions") .about("Export cluster-wide definitions") .after_help(color_print::cformat!( @@ -2667,10 +2708,13 @@ Examples: .action(ArgAction::Append) .required(false), ); - [definitions].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + [definitions] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn import_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +fn import_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { [Command::new("definitions") .about("Prefer 'definitions import'") .after_help(color_print::cformat!( @@ -2683,10 +2727,12 @@ fn import_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { .help("JSON file with definitions") .required(true), )] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists feature flags and their cluster state") .after_help(color_print::cformat!( @@ -2715,10 +2761,12 @@ pub fn feature_flags_subcommands(pre_flight_settings: PreFlightSettings) -> [Com )); [list_cmd, enable_cmd, enable_all_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 2] { +pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists deprecated features") .after_help(color_print::cformat!( @@ -2734,10 +2782,12 @@ pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) - )); [list_cmd, list_in_use_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists cluster nodes") .after_help(color_print::cformat!( @@ -2776,10 +2826,12 @@ pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] memory_breakdown_in_percent_cmd, memory_breakdown_in_bytes_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { +pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists virtual hosts") .after_help(color_print::cformat!( @@ -2889,10 +2941,12 @@ pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6 enable_deletion_protection_cmd, disable_deletion_protection_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] { +pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let declare_cmd = Command::new("declare") .about("Creates a user") .arg( @@ -2994,10 +3048,12 @@ pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] list_cmd, permissions_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn passwords_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +pub fn passwords_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let hash_password = Command::new("salt_and_hash") .arg( Arg::new("password") @@ -3013,10 +3069,13 @@ pub fn passwords_subcommands(pre_flight_settings: PreFlightSettings) -> [Command .help("The hashing algorithm to use: SHA256 or SHA512"), ); - [hash_password].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + [hash_password] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn permissions_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +pub fn permissions_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -3073,10 +3132,12 @@ pub fn permissions_subcommands(pre_flight_settings: PreFlightSettings) -> [Comma .arg(idempotently_arg.clone()); [list_cmd, declare_cmd, delete_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn user_limits_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +pub fn user_limits_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists per-user (resource) limits") .after_help(color_print::cformat!( @@ -3131,10 +3192,12 @@ pub fn user_limits_subcommands(pre_flight_settings: PreFlightSettings) -> [Comma ); [list_cmd, declare_cmd, delete_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn vhost_limits_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3] { +pub fn vhost_limits_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists virtual host (resource) limits") .after_help(color_print::cformat!( @@ -3169,10 +3232,12 @@ pub fn vhost_limits_subcommands(pre_flight_settings: PreFlightSettings) -> [Comm ); [list_cmd, declare_cmd, delete_cmd] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { [Command::new("message") .about(color_print::cstr!("Publishes (inefficiently) message(s) to a queue or a stream. Only suitable for development and test environments. Prefer messaging or streaming protocol clients!")) .after_help(color_print::cformat!("Doc guide: {}", PUBLISHER_GUIDE_URL)) @@ -3207,10 +3272,13 @@ pub fn publish_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; .required(false) .default_value("{}") .help("Message properties"), - )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + )] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { +pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { [Command::new("messages") .about(color_print::cstr!("Fetches (via polling, very inefficiently) message(s) from a queue. Only suitable for development and test environments")) .after_help(color_print::cformat!("Doc guide: {}", POLLING_CONSUMER_GUIDE_URL)) @@ -3236,10 +3304,13 @@ pub fn get_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 1] { .required(false) .default_value("ack_requeue_false") .help("Accepted values are: ack_requeue_false, reject_requeue_false, ack_requeue_true, reject_requeue_true"), - )].map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + )] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 9] { +pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -3507,10 +3578,12 @@ pub fn shovel_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 9 enable_tls_peer_verification_source_cmd, enable_tls_peer_verification_dest_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } -fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 8] { +fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let idempotently_arg = Arg::new("idempotently") .long("idempotently") .value_parser(value_parser!(bool)) @@ -3914,5 +3987,7 @@ fn federation_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 8 disable_tls_peer_verification_cmd, enable_tls_peer_verification_cmd, ] + .into_iter() .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() } From 5f0bee4040fb629281b368a6725fe94ffda1d88b Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 8 Oct 2025 23:09:58 -0400 Subject: [PATCH 299/320] Support request --timeout in seconds --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- src/cli.rs | 10 +++++++++ src/main.rs | 9 ++++++++ tests/timeout_tests.rs | 47 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tests/timeout_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 78acc36..8db48a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1465,11 +1465,12 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.60.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d5051af8cbb77f8c6103cc4d429f8be84503515f42af90bef2da3b8c36bd68" +checksum = "49a2b85d40eab61e32d53637e760d067329d68743618f80518879d6321f24d2d" dependencies = [ "backtrace", + "log", "percent-encoding", "rand", "rbase64", diff --git a/Cargo.toml b/Cargo.toml index 884b1f9..8e83009 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.60.0", features = [ +rabbitmq_http_client = { version = "0.61.0", features = [ "blocking", "tabled", ] } diff --git a/src/cli.rs b/src/cli.rs index 25fcdaa..5eb77eb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -558,6 +558,16 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .help("Local path to a client private key file in the PEM format") .value_parser(value_parser!(PathBuf)), ) + // --timeout + .arg( + Arg::new("timeout") + .long("timeout") + .env("RABBITMQADMIN_TIMEOUT") + .help("HTTP API request timeout in seconds. Must be greater than 0") + .required(false) + .default_value("60") + .value_parser(value_parser!(u64).range(1..)), + ) // --quiet .arg( Arg::new("quiet") diff --git a/src/main.rs b/src/main.rs index 14a8ccf..5e93160 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ use clap::{ArgMatches, crate_name, crate_version}; use errors::CommandRunError; use reqwest::{Identity, tls::Version as TlsVersion}; use std::path::{Path, PathBuf}; +use std::time::Duration; use std::{fs, process}; use sysexits::ExitCode; @@ -124,11 +125,17 @@ fn configure_http_api_client<'a>( // Due to how SharedSettings are computed, these should safe to unwrap() let username = merged_settings.username.clone().unwrap(); let password = merged_settings.password.clone().unwrap(); + + // Extract timeout from CLI arguments (default is 60 seconds) + let timeout_secs = cli.get_one::("timeout").copied().unwrap_or(60); + let timeout = Duration::from_secs(timeout_secs); + let client = build_rabbitmq_http_api_client( httpc, endpoint.to_owned(), username.clone(), password.clone(), + timeout, ); Ok(client) } @@ -173,11 +180,13 @@ fn build_rabbitmq_http_api_client( endpoint: String, username: String, password: String, + timeout: Duration, ) -> APIClient { ClientBuilder::new() .with_endpoint(endpoint) .with_basic_auth_credentials(username, password) .with_client(httpc) + .with_request_timeout(timeout) .build() } diff --git a/tests/timeout_tests.rs b/tests/timeout_tests.rs new file mode 100644 index 0000000..e33d811 --- /dev/null +++ b/tests/timeout_tests.rs @@ -0,0 +1,47 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod test_helpers; +use std::error::Error; +use test_helpers::{run_fails, run_succeeds}; + +#[test] +fn timeout_flag_with_valid_value() -> Result<(), Box> { + run_succeeds(["--timeout", "30", "show", "overview"]); + + Ok(()) +} + +#[test] +fn timeout_flag_with_zero_value_should_fail() -> Result<(), Box> { + run_fails(["--timeout", "0", "show", "overview"]); + + Ok(()) +} + +#[test] +fn timeout_flag_with_negative_value_should_fail() -> Result<(), Box> { + run_fails(["--timeout", "-1", "show", "overview"]); + + Ok(()) +} + +#[test] +fn timeout_uses_default_when_not_specified() -> Result<(), Box> { + // Should use the default timeout of 60 seconds but we have no + // easy way of testing this. Welp. + run_succeeds(["show", "overview"]); + + Ok(()) +} From 0b0fda833747653ce053bbbde4174b901aae0192 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 8 Oct 2025 23:49:32 -0400 Subject: [PATCH 300/320] Introduce a bunch of property-based tests --- .gitignore | 1 + Cargo.lock | 75 +++++ Cargo.toml | 1 + src/lib.rs | 5 + src/output.rs | 18 ++ tests/config_proptests.rs | 639 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 739 insertions(+) create mode 100644 tests/config_proptests.rs diff --git a/.gitignore b/.gitignore index c00fe18..556b5fe 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .fleet/* profile.json .tool-versions +*.proptest-regressions diff --git a/Cargo.lock b/Cargo.lock index 8db48a8..c42044a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,6 +178,21 @@ dependencies = [ "syn", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.9.4" @@ -1012,6 +1027,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.176" @@ -1393,6 +1414,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quinn" version = "0.11.9" @@ -1498,6 +1545,7 @@ dependencies = [ "indicatif", "log", "predicates", + "proptest", "rabbitmq_http_client", "regex", "reqwest", @@ -1541,6 +1589,15 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.11.0" @@ -1753,6 +1810,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.20" @@ -2290,6 +2359,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.8.1" diff --git a/Cargo.toml b/Cargo.toml index 8e83009..8fc6ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ rustls = { version = "0.23", features = ["aws_lc_rs"] } [dev-dependencies] assert_cmd = "2.0" predicates = "3.1" +proptest = "1.5" [lints.clippy] uninlined_format_args = "allow" diff --git a/src/lib.rs b/src/lib.rs index 5ed0288..ebb1712 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,4 +13,9 @@ // limitations under the License. // Re-export modules for testing +pub mod config; +pub mod constants; +pub mod errors; +pub mod output; pub mod pre_flight; +pub mod tables; diff --git a/src/output.rs b/src/output.rs index 204c3ea..ec73883 100644 --- a/src/output.rs +++ b/src/output.rs @@ -471,6 +471,12 @@ pub struct InteractiveProgressReporter { failures: usize, } +impl Default for InteractiveProgressReporter { + fn default() -> Self { + Self::new() + } +} + #[allow(dead_code)] impl InteractiveProgressReporter { pub fn new() -> Self { @@ -541,6 +547,12 @@ pub struct NonInteractiveProgressReporter { bar: Option, } +impl Default for NonInteractiveProgressReporter { + fn default() -> Self { + Self::new() + } +} + #[allow(dead_code)] impl NonInteractiveProgressReporter { pub fn new() -> Self { @@ -590,6 +602,12 @@ impl ProgressReporter for NonInteractiveProgressReporter { #[allow(dead_code)] pub struct QuietProgressReporter; +impl Default for QuietProgressReporter { + fn default() -> Self { + Self::new() + } +} + #[allow(dead_code)] impl QuietProgressReporter { pub fn new() -> Self { diff --git a/tests/config_proptests.rs b/tests/config_proptests.rs new file mode 100644 index 0000000..6159859 --- /dev/null +++ b/tests/config_proptests.rs @@ -0,0 +1,639 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proptest::prelude::*; +use rabbitmqadmin::config::SharedSettings; +use std::path::PathBuf; +use url::Url; + +/// Normalizes a path prefix by ensuring it starts with a forward slash +fn normalize_path_prefix(prefix: &str) -> String { + if prefix.starts_with('/') { + prefix.to_string() + } else { + format!("/{}", prefix) + } +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(200))] + + /// Path prefix normalization always produces a string that starts with '/' + #[test] + fn path_prefix_always_starts_with_slash(prefix in "[a-zA-Z0-9/_-]{0,50}") { + let normalized = normalize_path_prefix(&prefix); + prop_assert!(normalized.starts_with('/'), + "Normalized prefix '{}' doesn't start with '/'", normalized); + } + + /// Normalizing a prefix that already starts with '/' is idempotent + #[test] + fn path_prefix_normalization_idempotent(prefix in "/[a-zA-Z0-9/_-]{0,50}") { + let first = normalize_path_prefix(&prefix); + let second = normalize_path_prefix(&first); + prop_assert_eq!(&first, &second, + "Normalization is not idempotent: '{}' != '{}'", first, second); + } + + /// Prefixes without a leading slash get exactly one slash prepended + #[test] + fn path_prefix_adds_single_slash(prefix in "[a-zA-Z0-9_-]{1,50}") { + prop_assume!(!prefix.starts_with('/')); + let normalized = normalize_path_prefix(&prefix); + let expected = format!("/{}", prefix); + prop_assert_eq!(&normalized, &expected, + "Expected '{}' but got '{}'", expected, normalized); + } + + /// Empty strings get normalized to '/' + #[test] + fn path_prefix_empty_becomes_slash(_unit in 0u8..1) { + let normalized = normalize_path_prefix(""); + prop_assert_eq!(normalized, "/", "Empty prefix should become '/'"); + } +} + +fn scheme_strategy() -> impl Strategy { + prop_oneof![Just("http".to_string()), Just("https".to_string()),] +} + +fn hostname_strategy() -> impl Strategy { + prop_oneof![ + 3 => Just("localhost".to_string()), + 3 => Just("127.0.0.1".to_string()), + 3 => Just("rabbitmq.example.com".to_string()), + 3 => Just("rabbit.local".to_string()), + 3 => Just("rmq.test".to_string()), + 2 => "[a-z]{3,10}\\.[a-z]{3,10}\\.[a-z]{2,3}", + ] +} + +fn port_strategy() -> impl Strategy { + prop_oneof![ + Just(15672u16), + Just(15671u16), + Just(80u16), + Just(443u16), + Just(8080u16), + 1024u16..65535u16, + ] +} + +fn path_prefix_strategy() -> impl Strategy { + prop_oneof![ + Just("/api".to_string()), + Just("api".to_string()), + Just("/".to_string()), + Just("".to_string()), + "/[a-z]{2,10}", + "[a-z]{2,10}", + "/[a-z]{2,10}/[a-z]{2,10}", + ] +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(200))] + + /// Property: Generated endpoint URL is always parseable + #[test] + fn endpoint_is_always_valid_url( + scheme in scheme_strategy(), + hostname in hostname_strategy(), + port in port_strategy(), + path_prefix in path_prefix_strategy(), + ) { + let settings = SharedSettings { + scheme: scheme.clone(), + hostname: Some(hostname.clone()), + port: Some(port), + path_prefix: path_prefix.clone(), + base_uri: None, + tls: scheme == "https", + non_interactive: false, + quiet: false, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let endpoint = settings.endpoint(); + + // The endpoint should be parseable as a URL + let parsed = Url::parse(&endpoint); + prop_assert!(parsed.is_ok(), + "Failed to parse endpoint '{}' as URL: {:?}", endpoint, parsed.err()); + } + + /// Property: Endpoint URL always has normalized path prefix (starts with a slash) + #[test] + fn endpoint_path_always_starts_with_slash( + scheme in scheme_strategy(), + hostname in hostname_strategy(), + port in port_strategy(), + path_prefix in path_prefix_strategy(), + ) { + let settings = SharedSettings { + scheme: scheme.clone(), + hostname: Some(hostname.clone()), + port: Some(port), + path_prefix: path_prefix.clone(), + base_uri: None, + tls: scheme == "https", + non_interactive: false, + quiet: false, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let endpoint = settings.endpoint(); + let parsed = Url::parse(&endpoint).unwrap(); + let path = parsed.path(); + + prop_assert!(path.starts_with('/'), + "Endpoint path '{}' doesn't start with '/' for prefix '{}'", + path, path_prefix); + } + + /// Property: Endpoint preserves scheme, hostname, and port correctly + #[test] + fn endpoint_preserves_components( + scheme in scheme_strategy(), + hostname in hostname_strategy(), + port in port_strategy(), + ) { + let settings = SharedSettings { + scheme: scheme.clone(), + hostname: Some(hostname.clone()), + port: Some(port), + path_prefix: "/api".to_string(), + base_uri: None, + tls: scheme == "https", + non_interactive: false, + quiet: false, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let endpoint = settings.endpoint(); + let parsed = Url::parse(&endpoint).unwrap(); + + prop_assert_eq!(parsed.scheme(), scheme.as_str(), + "Scheme mismatch in endpoint '{}'", endpoint); + prop_assert_eq!(parsed.host_str(), Some(hostname.as_str()), + "Hostname mismatch in endpoint '{}'", endpoint); + + // URL parser returns None for default ports (80 for http, 443 for https) + // but the endpoint string always includes a port + let expected_port = if (scheme == "http" && port == 80) || (scheme == "https" && port == 443) { + None + } else { + Some(port) + }; + prop_assert_eq!(parsed.port(), expected_port, + "Port mismatch in endpoint '{}' (scheme={}, expected_port={:?})", endpoint, scheme, expected_port); + } + + /// Property: Endpoint has no leading or trailing whitespace + #[test] + fn endpoint_has_no_whitespace( + scheme in scheme_strategy(), + hostname in hostname_strategy(), + port in port_strategy(), + path_prefix in path_prefix_strategy(), + ) { + let settings = SharedSettings { + scheme, + hostname: Some(hostname), + port: Some(port), + path_prefix, + base_uri: None, + tls: false, + non_interactive: false, + quiet: false, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let endpoint = settings.endpoint(); + + let trimmed = endpoint.trim(); + prop_assert_eq!(trimmed, endpoint.as_str(), + "Endpoint has whitespace: '{}'", endpoint); + } + + /// Property: Endpoint contains the scheme, hostname, and port in correct format + #[test] + fn endpoint_format_is_correct( + scheme in scheme_strategy(), + hostname in hostname_strategy(), + port in port_strategy(), + ) { + let settings = SharedSettings { + scheme: scheme.clone(), + hostname: Some(hostname.clone()), + port: Some(port), + path_prefix: "/api".to_string(), + base_uri: None, + tls: false, + non_interactive: false, + quiet: false, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let endpoint = settings.endpoint(); + + // Should contain scheme:// + prop_assert!(endpoint.starts_with(&format!("{}://", scheme)), + "Endpoint '{}' doesn't start with '{}://'", endpoint, scheme); + + // Should contain hostname + prop_assert!(endpoint.contains(&hostname), + "Endpoint '{}' doesn't contain hostname '{}'", endpoint, hostname); + + // Should contain :port + prop_assert!(endpoint.contains(&format!(":{}", port)), + "Endpoint '{}' doesn't contain port ':{}'", endpoint, port); + } +} + +// ============================================================================ +// Priority 4: Configuration Merging Logic +// ============================================================================ + +use clap::{Arg, ArgAction, Command}; + +/// Helper to create a mock CLI parser for testing +fn create_test_parser() -> Command { + Command::new("test") + .arg(Arg::new("host").long("host")) + .arg( + Arg::new("port") + .long("port") + .value_parser(clap::value_parser!(u16)), + ) + .arg(Arg::new("username").long("username")) + .arg(Arg::new("password").long("password")) + .arg(Arg::new("vhost").long("vhost")) + .arg(Arg::new("tls").long("tls").action(ArgAction::SetTrue)) + .arg( + Arg::new("non_interactive") + .long("non-interactive") + .action(ArgAction::SetTrue), + ) + .arg(Arg::new("quiet").long("quiet").action(ArgAction::SetTrue)) + .arg(Arg::new("path_prefix").long("path-prefix")) + .arg(Arg::new("base_uri").long("base-uri")) + .arg(Arg::new("table_style").long("table-style")) + .arg( + Arg::new("ca_certificate_bundle_path") + .long("ca-cert") + .value_parser(clap::value_parser!(PathBuf)), + ) + .arg( + Arg::new("client_certificate_file_path") + .long("client-cert") + .value_parser(clap::value_parser!(PathBuf)), + ) + .arg( + Arg::new("client_private_key_file_path") + .long("client-key") + .value_parser(clap::value_parser!(PathBuf)), + ) +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(50))] + + /// Property: CLI arguments always override config file defaults + #[test] + fn cli_args_override_config_defaults( + cli_hostname in "[a-z]{5,10}\\.[a-z]{3,5}", + config_hostname in "[a-z]{5,10}\\.[a-z]{3,5}", + cli_port in 1024u16..65535u16, + config_port in 1024u16..65535u16, + ) { + prop_assume!(cli_hostname != config_hostname); + prop_assume!(cli_port != config_port); + + let parser = create_test_parser(); + let matches = parser.try_get_matches_from(vec![ + "test", + "--host", &cli_hostname, + "--port", &cli_port.to_string(), + ]).unwrap(); + + let config_defaults = SharedSettings { + hostname: Some(config_hostname.clone()), + port: Some(config_port), + scheme: "http".to_string(), + path_prefix: "/api".to_string(), + tls: false, + non_interactive: false, + quiet: false, + base_uri: None, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let merged = SharedSettings::new_with_defaults(&matches, &config_defaults); + + prop_assert_eq!(merged.hostname, Some(cli_hostname.clone()), + "CLI hostname should override config default"); + prop_assert_eq!(merged.port, Some(cli_port), + "CLI port should override config default"); + } + + /// Property: When CLI doesn't provide values, config defaults are used + #[test] + fn config_defaults_used_when_no_cli_args( + config_hostname in "[a-z]{5,10}\\.[a-z]{3,5}", + config_port in 1024u16..65535u16, + config_username in "[a-z]{5,10}", + ) { + let parser = create_test_parser(); + let matches = parser.try_get_matches_from(vec!["test"]).unwrap(); + + let config_defaults = SharedSettings { + hostname: Some(config_hostname.clone()), + port: Some(config_port), + username: Some(config_username.clone()), + scheme: "http".to_string(), + path_prefix: "/api".to_string(), + tls: false, + non_interactive: false, + quiet: false, + base_uri: None, + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let merged = SharedSettings::new_with_defaults(&matches, &config_defaults); + + prop_assert_eq!(merged.hostname, Some(config_hostname), + "Config hostname should be used"); + prop_assert_eq!(merged.port, Some(config_port), + "Config port should be used"); + prop_assert_eq!(merged.username, Some(config_username), + "Config username should be used"); + } + + /// Property: TLS flag properly sets HTTPS scheme + #[test] + fn tls_flag_sets_https_scheme(use_tls in proptest::bool::ANY) { + let parser = create_test_parser(); + let args = if use_tls { + vec!["test", "--tls"] + } else { + vec!["test"] + }; + let matches = parser.try_get_matches_from(args).unwrap(); + + let config_defaults = SharedSettings { + scheme: "http".to_string(), + hostname: Some("localhost".to_string()), + port: Some(15672), + path_prefix: "/api".to_string(), + tls: false, + non_interactive: false, + quiet: false, + base_uri: None, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let merged = SharedSettings::new_with_defaults(&matches, &config_defaults); + + if use_tls { + prop_assert_eq!(merged.scheme, "https", + "TLS flag should set scheme to https"); + prop_assert!(merged.tls, "TLS flag should set tls to true"); + } + } + + /// Property: Port stays in valid u16 range after merging + #[test] + fn port_stays_in_valid_range( + config_port in 1u16..65535u16, + cli_port in 1u16..65535u16, + ) { + let parser = create_test_parser(); + let matches = parser.try_get_matches_from(vec![ + "test", + "--port", &cli_port.to_string(), + ]).unwrap(); + + let config_defaults = SharedSettings { + hostname: Some("localhost".to_string()), + port: Some(config_port), + scheme: "http".to_string(), + path_prefix: "/api".to_string(), + tls: false, + non_interactive: false, + quiet: false, + base_uri: None, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let merged = SharedSettings::new_with_defaults(&matches, &config_defaults); + + if let Some(port) = merged.port { + prop_assert!(port > 0, + "Port {} must be greater than 0", port); + } + } + + /// Property: Username and password are always set (use defaults if not provided) + #[test] + fn username_password_always_set( + provide_username in proptest::bool::ANY, + provide_password in proptest::bool::ANY, + username in "[a-z]{5,10}", + password in "[a-zA-Z0-9]{8,16}", + ) { + let parser = create_test_parser(); + let mut args = vec!["test"]; + if provide_username { + args.push("--username"); + args.push(&username); + } + if provide_password { + args.push("--password"); + args.push(&password); + } + let matches = parser.try_get_matches_from(args).unwrap(); + + let config_defaults = SharedSettings { + hostname: Some("localhost".to_string()), + port: Some(15672), + scheme: "http".to_string(), + path_prefix: "/api".to_string(), + tls: false, + non_interactive: false, + quiet: false, + base_uri: None, + username: Some("config_user".to_string()), + password: Some("config_pass".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let merged = SharedSettings::new_with_defaults(&matches, &config_defaults); + + prop_assert!(merged.username.is_some(), + "Username should always be set"); + prop_assert!(merged.password.is_some(), + "Password should always be set"); + + if provide_username { + prop_assert_eq!(merged.username, Some(username), + "CLI username should be used"); + } else { + prop_assert_eq!(merged.username, Some("config_user".to_string()), + "Config username should be used"); + } + } + + /// Property: Virtual host defaults to "/" when not provided + #[test] + fn vhost_defaults_to_slash(provide_vhost in proptest::bool::ANY, vhost in "[a-z]{3,10}") { + let parser = create_test_parser(); + let mut args = vec!["test"]; + if provide_vhost { + args.push("--vhost"); + args.push(&vhost); + } + let matches = parser.try_get_matches_from(args).unwrap(); + + let config_defaults = SharedSettings { + hostname: Some("localhost".to_string()), + port: Some(15672), + scheme: "http".to_string(), + path_prefix: "/api".to_string(), + tls: false, + non_interactive: false, + quiet: false, + base_uri: None, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: None, + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let merged = SharedSettings::new_with_defaults(&matches, &config_defaults); + + if provide_vhost { + prop_assert_eq!(merged.virtual_host, Some(vhost), + "CLI vhost should be used"); + } else { + prop_assert_eq!(merged.virtual_host, Some("/".to_string()), + "Vhost should default to /"); + } + } + + /// Property: Boolean flags (non_interactive, quiet) are merged using OR + #[test] + fn boolean_flags_combine_with_or( + cli_non_interactive in proptest::bool::ANY, + config_non_interactive in proptest::bool::ANY, + cli_quiet in proptest::bool::ANY, + config_quiet in proptest::bool::ANY, + ) { + let parser = create_test_parser(); + let mut args = vec!["test"]; + if cli_non_interactive { + args.push("--non-interactive"); + } + if cli_quiet { + args.push("--quiet"); + } + let matches = parser.try_get_matches_from(args).unwrap(); + + let config_defaults = SharedSettings { + hostname: Some("localhost".to_string()), + port: Some(15672), + scheme: "http".to_string(), + path_prefix: "/api".to_string(), + tls: false, + non_interactive: config_non_interactive, + quiet: config_quiet, + base_uri: None, + username: Some("guest".to_string()), + password: Some("guest".to_string()), + virtual_host: Some("/".to_string()), + table_style: None, + ca_certificate_bundle_path: None, + client_certificate_file_path: None, + client_private_key_file_path: None, + }; + + let merged = SharedSettings::new_with_defaults(&matches, &config_defaults); + + // Boolean flags should use OR logic + prop_assert_eq!(merged.non_interactive, cli_non_interactive || config_non_interactive, + "non_interactive should be true if either CLI or config is true"); + prop_assert_eq!(merged.quiet, cli_quiet || config_quiet, + "quiet should be true if either CLI or config is true"); + } +} From aef41b7b18295214f7a2e9abcba1b8f815c6143d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 9 Oct 2025 11:00:23 -0400 Subject: [PATCH 301/320] cargo update --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c42044a..ae9466b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1035,9 +1035,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" @@ -1998,9 +1998,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" From 2dcad26a99c746aa902e9ddb7847ce24f19c998a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 9 Oct 2025 15:37:01 -0400 Subject: [PATCH 302/320] Bump HTTP API client to 0.62.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae9466b..53c0277 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1512,9 +1512,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.61.0" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a2b85d40eab61e32d53637e760d067329d68743618f80518879d6321f24d2d" +checksum = "58d0c9b3123a0de512dc09690b4b14e1075528692c6d6591a70cd0c558c2a133" dependencies = [ "backtrace", "log", diff --git a/Cargo.toml b/Cargo.toml index 8fc6ba0..53925bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.61.0", features = [ +rabbitmq_http_client = { version = "0.62.0", features = [ "blocking", "tabled", ] } From 4810a9a8818f72fe576ddda05ec38241e55c7a2d Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 9 Oct 2025 15:37:57 -0400 Subject: [PATCH 303/320] New command group: plugins --- CHANGELOG.md | 16 +++++++++++++- src/cli.rs | 42 +++++++++++++++++++++++++++++++++++++ src/commands.rs | 47 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 8 +++++++ src/static_urls.rs | 1 + tests/plugins_tests.rs | 36 ++++++++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tests/plugins_tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4de5789..6869710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,21 @@ ## v2.16.0 (in development) -No changes yet. +### Enhancements + +* `plugins` is a new command group for listing enabled plugins: + + ```shell + # List plugins across all cluster nodes + rabbitmqadmin plugins list_all + + # List plugins on a specific node + rabbitmqadmin plugins list_on_node --node rabbit@hostname + ``` + +### Upgrades + +* RabbitMQ HTTP API client was upgraded to [`0.62.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.62.0) ## v2.15.0 (Sep 30, 2025) diff --git a/src/cli.rs b/src/cli.rs index 5eb77eb..243bd73 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -252,6 +252,17 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { .subcommand_value_name("permission") .arg_required_else_help(true) .subcommands(permissions_subcommands(pre_flight_settings.clone())); + let plugins_group = Command::new("plugins") + .about("List enabled plugins") + .infer_subcommands(pre_flight_settings.infer_subcommands) + .infer_long_args(pre_flight_settings.infer_long_options) + .after_help(color_print::cformat!( + "Doc guide: {}", + PLUGIN_GUIDE_URL + )) + .subcommand_value_name("plugin") + .arg_required_else_help(true) + .subcommands(plugins_subcommands(pre_flight_settings.clone())); let policies_group = Command::new("policies") .about("Operations on policies") .infer_subcommands(pre_flight_settings.infer_subcommands) @@ -398,6 +409,7 @@ pub fn parser(pre_flight_settings: PreFlightSettings) -> Command { parameters_group, passwords_group, permissions_group, + plugins_group, policies_group, publish_group, purge_group, @@ -2797,6 +2809,36 @@ pub fn deprecated_features_subcommands(pre_flight_settings: PreFlightSettings) - .collect() } +pub fn plugins_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { + let list_all_cmd = Command::new("list_all") + .about("Lists plugins across all cluster nodes") + .after_help(color_print::cformat!( + "Doc guide: {}", + PLUGIN_GUIDE_URL + )); + + let list_on_node_cmd = Command::new("list_on_node") + .about("Lists plugins enabled on a specific node") + .arg( + Arg::new("node") + .long("node") + .help("target node, must be a cluster member") + .required(true), + ) + .after_help(color_print::cformat!( + "Doc guide: {}", + PLUGIN_GUIDE_URL + )); + + [ + list_all_cmd, + list_on_node_cmd, + ] + .into_iter() + .map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options)) + .collect() +} + pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> Vec { let list_cmd = Command::new("list") .long_about("Lists cluster nodes") diff --git a/src/commands.rs b/src/commands.rs index 88f7fd7..2608688 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -46,6 +46,7 @@ use serde_json::Value; use std::fs; use std::io; use std::process; +use tabled::Tabled; type APIClient = Client; @@ -1090,6 +1091,52 @@ pub fn list_deprecated_features_in_use( client.list_deprecated_features_in_use() } +// +// Plugins +// + +#[derive(Debug, Clone, Tabled)] +pub struct PluginOnNode { + pub node: String, + pub name: String, + pub state: String, +} + +pub fn list_plugins_on_node( + client: APIClient, + command_args: &ArgMatches, +) -> ClientResult> { + let node = command_args.get_one::("node").cloned().unwrap(); + let plugins = client.list_node_plugins(&node)?; + + Ok(plugins + .into_iter() + .map(|plugin_name| PluginOnNode { + node: node.clone(), + name: plugin_name, + state: "Enabled".to_string(), + }) + .collect()) +} + +pub fn list_plugins_across_cluster(client: APIClient) -> ClientResult> { + let nodes = client.list_nodes()?; + let mut result = Vec::new(); + + for node in nodes { + let plugins = client.list_node_plugins(&node.name)?; + for plugin_name in plugins { + result.push(PluginOnNode { + node: node.name.clone(), + name: plugin_name, + state: "Enabled".to_string(), + }); + } + } + + Ok(result) +} + // // Declaration of core resources // diff --git a/src/main.rs b/src/main.rs index 5e93160..0f9bc4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -823,6 +823,14 @@ fn dispatch_common_subcommand( let result = commands::show_memory_breakdown(client, second_level_args); res_handler.memory_breakdown_in_percent_result(result) } + ("plugins", "list_all") => { + let result = commands::list_plugins_across_cluster(client); + res_handler.tabular_result(result) + } + ("plugins", "list_on_node") => { + let result = commands::list_plugins_on_node(client, second_level_args); + res_handler.tabular_result(result) + } ("operator_policies", "declare") => { let result = commands::declare_operator_policy(client, &vhost, second_level_args); res_handler.no_output_on_success(result); diff --git a/src/static_urls.rs b/src/static_urls.rs index 1564613..2890583 100644 --- a/src/static_urls.rs +++ b/src/static_urls.rs @@ -60,6 +60,7 @@ pub(crate) const OPERATOR_POLICY_GUIDE_URL: &str = "https://rabbitmq.com/docs/parameters#operator-policies"; pub(crate) const USER_LIMIT_GUIDE_URL: &str = "https://rabbitmq.com/docs/user-limits"; pub(crate) const PASSWORD_GUIDE_URL: &str = "https://rabbitmq.com/docs/passwords"; +pub(crate) const PLUGIN_GUIDE_URL: &str = "https://rabbitmq.com/docs/plugins"; pub(crate) const SHOVEL_GUIDE_URL: &str = "https://rabbitmq.com/docs/shovel"; pub(crate) const FEDERATION_GUIDE_URL: &str = "https://rabbitmq.com/docs/federation"; pub(crate) const FEDERATED_QUEUES_GUIDE_URL: &str = "https://rabbitmq.com/docs/federated-queues"; diff --git a/tests/plugins_tests.rs b/tests/plugins_tests.rs new file mode 100644 index 0000000..9aa16ee --- /dev/null +++ b/tests/plugins_tests.rs @@ -0,0 +1,36 @@ +// Copyright (C) 2023-2025 RabbitMQ Core Team (teamrabbitmq@gmail.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::error::Error; +mod test_helpers; +use crate::test_helpers::*; + +#[test] +fn test_plugins_list_all_succeeds() -> Result<(), Box> { + run_succeeds(["plugins", "list_all"]).stdout(output_includes("rabbitmq_management")); + + Ok(()) +} + +#[test] +fn test_plugins_list_on_node_succeeds() -> Result<(), Box> { + let rc = api_client(); + let nodes = rc.list_nodes()?; + let first = nodes.first().unwrap(); + + run_succeeds(["plugins", "list_on_node", "--node", first.name.as_str()]) + .stdout(output_includes("rabbitmq_management")); + + Ok(()) +} From a4d76165535300601dcb946b9a7737b4e84d9767 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 9 Oct 2025 15:38:25 -0400 Subject: [PATCH 304/320] cargo fmt --all --- src/cli.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 243bd73..a2e90ae 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2830,13 +2830,10 @@ pub fn plugins_subcommands(pre_flight_settings: PreFlightSettings) -> Vec Vec { From ba4c78a6aa2f45b82f7a68d18a73148effe6b136 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 9 Oct 2025 16:51:45 -0400 Subject: [PATCH 305/320] Bump HTTP API client version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53c0277..c744f50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1512,9 +1512,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.62.0" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58d0c9b3123a0de512dc09690b4b14e1075528692c6d6591a70cd0c558c2a133" +checksum = "83f7f168339a49876ed5df442eec9da515035100d769ad7a4ca67cc125f150e9" dependencies = [ "backtrace", "log", diff --git a/Cargo.toml b/Cargo.toml index 53925bd..8ba4bce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.62.0", features = [ +rabbitmq_http_client = { version = "0.64.0", features = [ "blocking", "tabled", ] } From a2617555323592203a72252c8256899f6700157a Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Thu, 9 Oct 2025 17:42:41 -0400 Subject: [PATCH 306/320] Change log updates --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6869710..a5da4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ rabbitmqadmin plugins list_on_node --node rabbit@hostname ``` +* `--timeout` is a new global option limits HTTP API request execution timeout. The value is in seconds and defaults + to 60s: + + ```shell + rabbitmqadmin --timeout 15 queues list + ``` + ### Upgrades * RabbitMQ HTTP API client was upgraded to [`0.62.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.62.0) From 25fbef2f7dcaf6cd7f7cf6d63315e719cdaa757e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 00:34:44 +0000 Subject: [PATCH 307/320] Bump regex from 1.11.3 to 1.12.1 Bumps [regex](https://github.com/rust-lang/regex) from 1.11.3 to 1.12.1. - [Release notes](https://github.com/rust-lang/regex/releases) - [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/regex/compare/1.11.3...1.12.1) --- updated-dependencies: - dependency-name: regex dependency-version: 1.12.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c744f50..1808906 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1641,9 +1641,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" dependencies = [ "aho-corasick", "memchr", @@ -1653,9 +1653,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" dependencies = [ "aho-corasick", "memchr", From 367c71805116bc7ca1670c51e2d82de1c4412c4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 00:35:58 +0000 Subject: [PATCH 308/320] Bump toml from 0.9.7 to 0.9.8 Bumps [toml](https://github.com/toml-rs/toml) from 0.9.7 to 0.9.8. - [Commits](https://github.com/toml-rs/toml/compare/toml-v0.9.7...toml-v0.9.8) --- updated-dependencies: - dependency-name: toml dependency-version: 0.9.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c744f50..a946f2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1940,9 +1940,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ "serde_core", ] @@ -2252,9 +2252,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", "serde_core", @@ -2267,27 +2267,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tower" From b8ea9e26e35f687f5b3a96027f67c22b4c3ce7c7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 13 Oct 2025 00:48:29 -0700 Subject: [PATCH 309/320] cargo update --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f488aca..1c12f5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,9 +230,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.40" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "jobserver", @@ -515,9 +515,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "float-cmp" @@ -1664,9 +1664,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" [[package]] name = "reqwest" From 7f9825d6651bb8d7a23f385fd2f275b8d76f18a0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 13 Oct 2025 14:36:08 -0700 Subject: [PATCH 310/320] cargo update --- Cargo.lock | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c12f5c..6362633 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,18 +285,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstream", "anstyle", @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cmake" @@ -1641,9 +1641,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1653,9 +1653,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1664,15 +1664,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", @@ -1988,12 +1988,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] From 6db0ed896b6d569312e80a185a8264404e81f007 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 14 Oct 2025 00:04:20 -0700 Subject: [PATCH 311/320] rabbitmq_http_client 0.65.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6362633..e3ba859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1512,9 +1512,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.64.0" +version = "0.65.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f7f168339a49876ed5df442eec9da515035100d769ad7a4ca67cc125f150e9" +checksum = "17f03705ab15935d1ca97605ecdfbb4fed6938df94e64dafcb08e741265308d1" dependencies = [ "backtrace", "log", diff --git a/Cargo.toml b/Cargo.toml index 8ba4bce..2af76b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.64.0", features = [ +rabbitmq_http_client = { version = "0.65.0", features = [ "blocking", "tabled", ] } From 9ae9d6495b6e35f5156436fddfa874dd4601804f Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Fri, 17 Oct 2025 09:33:00 +0200 Subject: [PATCH 312/320] Print descriptive errors on failures --- src/commands.rs | 8 +++---- src/errors.rs | 47 +++++++++++++++++++++++++++++++++++------ src/tables.rs | 18 +++++++++++++--- tests/policies_tests.rs | 29 +++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 2608688..8761f93 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1832,7 +1832,7 @@ pub fn delete_policy_definition_keys( let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); let pol = client.get_policy(vhost, &name)?; - let updated_pol = pol.without_keys(str_keys); + let updated_pol = pol.without_keys(&str_keys); let params = PolicyParams::from(&updated_pol); client.declare_policy(¶ms) @@ -1852,7 +1852,7 @@ pub fn delete_policy_definition_keys_in( let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); for pol in pols { - let updated_pol = pol.without_keys(str_keys.clone()); + let updated_pol = pol.without_keys(&str_keys); let params = PolicyParams::from(&updated_pol); client.declare_policy(¶ms)? @@ -1875,7 +1875,7 @@ pub fn delete_operator_policy_definition_keys( let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); let pol = client.get_operator_policy(vhost, &name)?; - let updated_pol = pol.without_keys(str_keys); + let updated_pol = pol.without_keys(&str_keys); let params = PolicyParams::from(&updated_pol); client.declare_operator_policy(¶ms) @@ -1895,7 +1895,7 @@ pub fn delete_operator_policy_definition_keys_in( let str_keys: Vec<&str> = keys.iter().map(AsRef::as_ref).collect::>(); for pol in pols { - let updated_pol = pol.without_keys(str_keys.clone()); + let updated_pol = pol.without_keys(&str_keys); let params = PolicyParams::from(&updated_pol); client.declare_operator_policy(¶ms)? diff --git a/src/errors.rs b/src/errors.rs index 70b4ec1..2680b76 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -60,18 +60,20 @@ pub enum CommandRunError { PrivateKeyFileUnsupported { local_path: String }, #[error("TLS certificate and private key files do not match")] CertificateKeyMismatch { cert_path: String, key_path: String }, - #[error("API responded with a client error: status code of {status_code}")] + #[error("{}", format_client_error(.status_code, .error_details))] ClientError { status_code: StatusCode, url: Option, body: Option, + error_details: Option, headers: Option, }, - #[error("API responded with a client error: status code of {status_code}")] + #[error("{}", format_server_error(.status_code, .error_details))] ServerError { status_code: StatusCode, url: Option, body: Option, + error_details: Option, headers: Option, }, #[error("Health check failed")] @@ -115,11 +117,11 @@ impl From for CommandRunError { use ApiClientError::*; match value { UnsupportedArgumentValue { property } => Self::UnsupportedArgumentValue { property }, - ClientErrorResponse { status_code, url, body, headers, .. } => { - Self::ClientError { status_code, url, body, headers } + ClientErrorResponse { status_code, url, body, error_details, headers, .. } => { + Self::ClientError { status_code, url, body, error_details, headers } } - ServerErrorResponse { status_code, url, body, headers, .. } => { - Self::ServerError { status_code, url, body, headers } + ServerErrorResponse { status_code, url, body, error_details, headers, .. } => { + Self::ServerError { status_code, url, body, error_details, headers } } HealthCheckFailed { path, details, status_code } => { Self::HealthCheckFailed { health_check_path: path, details, status_code } @@ -137,3 +139,36 @@ impl From for CommandRunError { } } } + +fn format_client_error( + status_code: &StatusCode, + error_details: &Option, +) -> String { + if let Some(details) = error_details { + if let Some(reason) = details.reason() { + return reason.to_string(); + } + } + format!( + "API responded with a client error: status code of {}", + status_code + ) +} + +fn format_server_error( + status_code: &StatusCode, + error_details: &Option, +) -> String { + if let Some(details) = error_details { + if let Some(reason) = details.reason() { + return format!( + "API responded with a server error: status code of {}\n\n{}", + status_code, reason + ); + } + } + format!( + "API responded with a server error: status code of {}", + status_code + ) +} diff --git a/src/tables.rs b/src/tables.rs index 7baa57e..87b75be 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -314,14 +314,16 @@ pub fn failure_details(error: &HttpClientError) -> Table { status_code, url, body, + error_details, .. - } => generic_failed_request_details(status_code, url, body), + } => generic_failed_request_details(status_code, url, body, error_details), HttpClientError::ServerErrorResponse { status_code, url, body, + error_details, .. - } => generic_failed_request_details(status_code, url, body), + } => generic_failed_request_details(status_code, url, body, error_details), HttpClientError::HealthCheckFailed { status_code, path, @@ -496,12 +498,13 @@ fn generic_failed_request_details( status_code: &StatusCode, url: &Option, body: &Option, + error_details: &Option, ) -> Table { let status_code_s = status_code.to_string(); let url_s = url.clone().unwrap().to_string(); let body_s = body.clone().unwrap_or("N/A".to_string()); - let data = vec![ + let mut data = vec![ RowOfTwo { key: "result", value: "request failed", @@ -520,6 +523,15 @@ fn generic_failed_request_details( }, ]; + if let Some(details) = error_details { + if let Some(reason) = details.reason() { + data.push(RowOfTwo { + key: "error", + value: reason, + }); + } + } + build_simple_table(data) } diff --git a/tests/policies_tests.rs b/tests/policies_tests.rs index 6fbfec1..75cbc24 100644 --- a/tests/policies_tests.rs +++ b/tests/policies_tests.rs @@ -676,3 +676,32 @@ fn test_policies_declare_blanket() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_policy_validation_error() -> Result<(), Box> { + let policy_name = "test_policy_validation_error"; + + // Attempt to declare a policy with invalid/unknown settings in the definition + // This should fail with a descriptive validation error message + run_fails([ + "declare", + "policy", + "--name", + policy_name, + "--pattern", + "^qq$", + "--apply-to", + "queues", + "--priority", + "1", + "--definition", + r#"{"foo": "bar", "invalid-setting": 12345}"#, + ]) + .stderr(output_includes("Validation failed")) + .stderr(output_includes("not recognised").or(output_includes("not recognized"))); + + // Verify the policy was not created + run_succeeds(["list", "policies"]).stdout(output_includes(policy_name).not()); + + Ok(()) +} From eba1dbdfe51fe9df0037854b34f803e2e9565e42 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Fri, 17 Oct 2025 10:04:45 +0200 Subject: [PATCH 313/320] Make clippy happy --- src/commands.rs | 16 ++++++++-------- src/errors.rs | 22 +++++++++++----------- src/tables.rs | 14 +++++++------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 8761f93..877d5f5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -129,7 +129,7 @@ pub fn list_policies_in_and_applying_to( let policies = client.list_policies_in(vhost)?; Ok(policies .into_iter() - .filter(|pol| apply_to.does_apply_to(pol.apply_to.clone())) + .filter(|pol| apply_to.does_apply_to(pol.apply_to)) .collect()) } @@ -139,10 +139,10 @@ pub fn list_matching_policies_in( name: &str, typ: PolicyTarget, ) -> ClientResult> { - let candidates = list_policies_in_and_applying_to(client, vhost, typ.clone())?; + let candidates = list_policies_in_and_applying_to(client, vhost, typ)?; Ok(candidates .into_iter() - .filter(|pol| pol.does_match_name(vhost, name, typ.clone())) + .filter(|pol| pol.does_match_name(vhost, name, typ)) .collect()) } @@ -165,7 +165,7 @@ pub fn list_operator_policies_in_and_applying_to( let policies = client.list_operator_policies_in(vhost)?; Ok(policies .into_iter() - .filter(|pol| apply_to.does_apply_to(pol.apply_to.clone())) + .filter(|pol| apply_to.does_apply_to(pol.apply_to)) .collect()) } @@ -175,10 +175,10 @@ pub fn list_matching_operator_policies_in( name: &str, typ: PolicyTarget, ) -> ClientResult> { - let candidates = list_operator_policies_in_and_applying_to(client, vhost, typ.clone())?; + let candidates = list_operator_policies_in_and_applying_to(client, vhost, typ)?; Ok(candidates .into_iter() - .filter(|pol| pol.does_match_name(vhost, name, typ.clone())) + .filter(|pol| pol.does_match_name(vhost, name, typ)) .collect()) } @@ -1578,7 +1578,7 @@ pub fn declare_policy( vhost, name, pattern, - apply_to: apply_to.clone(), + apply_to, priority: priority.parse::().unwrap(), definition: parsed_definition, }; @@ -1606,7 +1606,7 @@ pub fn declare_operator_policy( vhost, name: &name, pattern: &pattern, - apply_to: apply_to.clone(), + apply_to, priority: priority.parse::().unwrap(), definition: parsed_definition, }; diff --git a/src/errors.rs b/src/errors.rs index 2680b76..265cb6a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -144,10 +144,10 @@ fn format_client_error( status_code: &StatusCode, error_details: &Option, ) -> String { - if let Some(details) = error_details { - if let Some(reason) = details.reason() { - return reason.to_string(); - } + if let Some(details) = error_details + && let Some(reason) = details.reason() + { + return reason.to_string(); } format!( "API responded with a client error: status code of {}", @@ -159,13 +159,13 @@ fn format_server_error( status_code: &StatusCode, error_details: &Option, ) -> String { - if let Some(details) = error_details { - if let Some(reason) = details.reason() { - return format!( - "API responded with a server error: status code of {}\n\n{}", - status_code, reason - ); - } + if let Some(details) = error_details + && let Some(reason) = details.reason() + { + return format!( + "API responded with a server error: status code of {}\n\n{}", + status_code, reason + ); } format!( "API responded with a server error: status code of {}", diff --git a/src/tables.rs b/src/tables.rs index 87b75be..e6013d0 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -523,13 +523,13 @@ fn generic_failed_request_details( }, ]; - if let Some(details) = error_details { - if let Some(reason) = details.reason() { - data.push(RowOfTwo { - key: "error", - value: reason, - }); - } + if let Some(details) = error_details + && let Some(reason) = details.reason() + { + data.push(RowOfTwo { + key: "error", + value: reason, + }); } build_simple_table(data) From d61ddf8a27a6e7bb8c1872e8c57db72b8de05f10 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Fri, 17 Oct 2025 09:48:46 +0200 Subject: [PATCH 314/320] Use HTTP client branch --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3ba859..de27033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1512,9 +1512,8 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" -version = "0.65.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f03705ab15935d1ca97605ecdfbb4fed6938df94e64dafcb08e741265308d1" +version = "0.66.0" +source = "git+https://github.com/mkuratczyk/rabbitmq-http-api-rs?branch=parse-error-reason#3bff32654442012135f3406f1e0edb60a74fcab0" dependencies = [ "backtrace", "log", diff --git a/Cargo.toml b/Cargo.toml index 2af76b0..e565008 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { version = "0.65.0", features = [ +rabbitmq_http_client = { git = "https://github.com/mkuratczyk/rabbitmq-http-api-rs", branch = "parse-error-reason", features = [ "blocking", "tabled", ] } From 34a420d8ab8095bb762f30633d7b1ba85034336e Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 17 Oct 2025 15:24:47 -0400 Subject: [PATCH 315/320] Update dependencies --- Cargo.lock | 93 ++++++++++++++++++------------------------------------ 1 file changed, 30 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de27033..b8b2345 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,16 +125,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libloading", ] [[package]] @@ -251,9 +250,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -623,21 +622,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -959,17 +958,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -1013,7 +1001,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -1041,12 +1029,12 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.5", + "windows-link 0.2.1", ] [[package]] @@ -1141,13 +1129,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1221,9 +1209,9 @@ checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" dependencies = [ "bitflags", "cfg-if", @@ -1253,9 +1241,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" dependencies = [ "cc", "libc", @@ -1467,7 +1455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand", "ring", @@ -1585,7 +1573,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -1755,9 +1743,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" dependencies = [ "aws-lc-rs", "log", @@ -1771,9 +1759,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2102,7 +2090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2201,19 +2189,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "pin-project-lite", - "slab", "socket2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2454,15 +2439,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -2667,15 +2643,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" From 06a4392587d37b68f02c7b6d17fac7aeee2f81ec Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 17 Oct 2025 15:24:57 -0400 Subject: [PATCH 316/320] Refactoring * Optimize imports while avoiding naming conflicts * Minor simplification to a return value --- src/errors.rs | 47 +++++++++++++++++++---------------------------- src/tables.rs | 3 ++- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 265cb6a..f0ec75c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -12,12 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rabbitmq_http_client::error::{ConversionError, Error as ApiClientError}; +use rabbitmq_http_client::error::{ConversionError, Error as ApiClientError, ErrorDetails}; use rabbitmq_http_client::{blocking_api::HttpClientError, responses::HealthCheckFailureDetails}; -use reqwest::{ - StatusCode, - header::{HeaderMap, InvalidHeaderValue}, -}; +use reqwest::StatusCode; +use reqwest::header::{HeaderMap, InvalidHeaderValue}; use std::io; use url::Url; @@ -65,7 +63,7 @@ pub enum CommandRunError { status_code: StatusCode, url: Option, body: Option, - error_details: Option, + error_details: Option, headers: Option, }, #[error("{}", format_server_error(.status_code, .error_details))] @@ -73,7 +71,7 @@ pub enum CommandRunError { status_code: StatusCode, url: Option, body: Option, - error_details: Option, + error_details: Option, headers: Option, }, #[error("Health check failed")] @@ -114,36 +112,32 @@ impl From for CommandRunError { impl From for CommandRunError { fn from(value: HttpClientError) -> Self { - use ApiClientError::*; match value { - UnsupportedArgumentValue { property } => Self::UnsupportedArgumentValue { property }, - ClientErrorResponse { status_code, url, body, error_details, headers, .. } => { + ApiClientError::UnsupportedArgumentValue { property } => Self::UnsupportedArgumentValue { property }, + ApiClientError::ClientErrorResponse { status_code, url, body, error_details, headers, .. } => { Self::ClientError { status_code, url, body, error_details, headers } } - ServerErrorResponse { status_code, url, body, error_details, headers, .. } => { + ApiClientError::ServerErrorResponse { status_code, url, body, error_details, headers, .. } => { Self::ServerError { status_code, url, body, error_details, headers } } - HealthCheckFailed { path, details, status_code } => { + ApiClientError::HealthCheckFailed { path, details, status_code } => { Self::HealthCheckFailed { health_check_path: path, details, status_code } } - NotFound => Self::NotFound, - MultipleMatchingBindings => Self::ConflictingOptions { + ApiClientError::NotFound => Self::NotFound, + ApiClientError::MultipleMatchingBindings => Self::ConflictingOptions { message: "multiple bindings match, cannot determine which binding to delete without explicitly provided binding properties".to_owned() }, - InvalidHeaderValue { error } => Self::InvalidHeaderValue { error }, - RequestError { error, .. } => Self::RequestError { error }, - Other => Self::Other, - MissingProperty { argument } => Self::MissingArgumentValue { property: argument }, - IncompatibleBody { error, .. } => Self::IncompatibleBody { error }, - ParsingError { message } => Self::FailureDuringExecution { message }, + ApiClientError::InvalidHeaderValue { error } => Self::InvalidHeaderValue { error }, + ApiClientError::RequestError { error, .. } => Self::RequestError { error }, + ApiClientError::Other => Self::Other, + ApiClientError::MissingProperty { argument } => Self::MissingArgumentValue { property: argument }, + ApiClientError::IncompatibleBody { error, .. } => Self::IncompatibleBody { error }, + ApiClientError::ParsingError { message } => Self::FailureDuringExecution { message }, } } } -fn format_client_error( - status_code: &StatusCode, - error_details: &Option, -) -> String { +fn format_client_error(status_code: &StatusCode, error_details: &Option) -> String { if let Some(details) = error_details && let Some(reason) = details.reason() { @@ -155,10 +149,7 @@ fn format_client_error( ) } -fn format_server_error( - status_code: &StatusCode, - error_details: &Option, -) -> String { +fn format_server_error(status_code: &StatusCode, error_details: &Option) -> String { if let Some(details) = error_details && let Some(reason) = details.reason() { diff --git a/src/tables.rs b/src/tables.rs index e6013d0..4bb587f 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use rabbitmq_http_client::blocking_api::HttpClientError; +use rabbitmq_http_client::error::ErrorDetails; use rabbitmq_http_client::formatting::*; use rabbitmq_http_client::password_hashing::HashingError; use rabbitmq_http_client::responses::{ @@ -498,7 +499,7 @@ fn generic_failed_request_details( status_code: &StatusCode, url: &Option, body: &Option, - error_details: &Option, + error_details: &Option, ) -> Table { let status_code_s = status_code.to_string(); let url_s = url.clone().unwrap().to_string(); From 8dfbeca88ce4b46f73b5b272c22eac711d01a712 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 17 Oct 2025 19:45:52 -0400 Subject: [PATCH 317/320] rabbitmq_http_client 0.66.0 is out --- Cargo.lock | 7 ++++--- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8b2345..c61b761 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -937,9 +937,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", @@ -1501,7 +1501,8 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rabbitmq_http_client" version = "0.66.0" -source = "git+https://github.com/mkuratczyk/rabbitmq-http-api-rs?branch=parse-error-reason#3bff32654442012135f3406f1e0edb60a74fcab0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f429603d727775bf2761cfc7f2f75d9cfb1ad99ff059e4458f95040ce9ed7b7" dependencies = [ "backtrace", "log", diff --git a/Cargo.toml b/Cargo.toml index e565008..09d85da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12", features = [ "__rustls", "rustls-tls-native-roots", ] } -rabbitmq_http_client = { git = "https://github.com/mkuratczyk/rabbitmq-http-api-rs", branch = "parse-error-reason", features = [ +rabbitmq_http_client = { version = "0.66.0", features = [ "blocking", "tabled", ] } From b22c58662f14a48cd6a88abebca79339a2d77a12 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 17 Oct 2025 19:46:48 -0400 Subject: [PATCH 318/320] Update change log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5da4db..bf726b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ rabbitmqadmin plugins list_on_node --node rabbit@hostname ``` +* Errors now include the `error` or `reason` field from the API response (if they were present there) + * `--timeout` is a new global option limits HTTP API request execution timeout. The value is in seconds and defaults to 60s: From b1e786b98f98c9b49c42d6258ba7edf1135f42e0 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Fri, 17 Oct 2025 19:47:00 -0400 Subject: [PATCH 319/320] Update change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf726b4..a2579cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ ### Upgrades -* RabbitMQ HTTP API client was upgraded to [`0.62.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.62.0) +* RabbitMQ HTTP API client was upgraded to [`0.66.0`](https://github.com/michaelklishin/rabbitmq-http-api-rs/releases/tag/v0.66.0) ## v2.15.0 (Sep 30, 2025) From 6828752ec308a1233620897c23dc9a0fb3d5ff82 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Mon, 20 Oct 2025 22:41:30 -0700 Subject: [PATCH 320/320] Change log updates --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2579cc..45df4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # rabbitmqadmin-ng Change Log -## v2.16.0 (in development) +## v2.17.0 (in development) + +No changes yet. + + +## v2.16.0 (Oct 20, 2025) ### Enhancements