Skip to content

Commit dce5165

Browse files
Commands for enabling and disabling virtual host deletion protection [1]
1. https://www.rabbitmq.com/docs/vhosts#deletion-protection
1 parent 993e306 commit dce5165

File tree

7 files changed

+189
-3
lines changed

7 files changed

+189
-3
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@
2424

2525
**Important**: this command is **very destructive** and should be used with caution. Always test with `--dry-run` first.
2626

27+
* `vhosts enable_deletion_protection` and `vhosts disable_deletion_protection` are two new commands
28+
for managing [virtual host deletion protection](https://www.rabbitmq.com/docs/vhosts#deletion-protection):
29+
30+
```shell
31+
# Enable deletion protection for a virtual host
32+
rabbitmqadmin vhosts enable_deletion_protection --name "production-vhost"
33+
34+
# Disable deletion protection for a virtual host
35+
rabbitmqadmin vhosts disable_deletion_protection --name "production-vhost"
36+
```
37+
38+
Protected virtual hosts cannot be deleted, either individually using `vhosts delete` or
39+
as part of bulk operations using `vhosts delete_multiple`. To delete a protected
40+
virtual host, its protection must be lifted first.
41+
2742
## v2.13.0 (Sep 26, 2025)
2843

2944
### Enhancements

src/cli.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2711,7 +2711,7 @@ pub fn nodes_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 3]
27112711
.map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options))
27122712
}
27132713

2714-
pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4] {
2714+
pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] {
27152715
let list_cmd = Command::new("list")
27162716
.long_about("Lists virtual hosts")
27172717
.after_help(color_print::cformat!(
@@ -2788,9 +2788,40 @@ pub fn vhosts_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 4
27882788
.required(false),
27892789
)
27902790
.arg(idempotently_arg.clone());
2791+
let enable_deletion_protection_cmd = Command::new("enable_deletion_protection")
2792+
.about("Enables deletion protection for a virtual host")
2793+
.after_help(color_print::cformat!(
2794+
"<bold>Doc guide:</bold>: {}",
2795+
VHOST_DELETION_PROTECTION_GUIDE_URL
2796+
))
2797+
.arg(
2798+
Arg::new("name")
2799+
.long("name")
2800+
.help("virtual host name")
2801+
.required(true),
2802+
);
2803+
let disable_deletion_protection_cmd = Command::new("disable_deletion_protection")
2804+
.about("Disables deletion protection for a virtual host")
2805+
.after_help(color_print::cformat!(
2806+
"<bold>Doc guide:</bold>: {}",
2807+
VHOST_DELETION_PROTECTION_GUIDE_URL
2808+
))
2809+
.arg(
2810+
Arg::new("name")
2811+
.long("name")
2812+
.help("virtual host name")
2813+
.required(true),
2814+
);
27912815

2792-
[list_cmd, declare_cmd, delete_cmd, bulk_delete_cmd]
2793-
.map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options))
2816+
[
2817+
list_cmd,
2818+
declare_cmd,
2819+
delete_cmd,
2820+
bulk_delete_cmd,
2821+
enable_deletion_protection_cmd,
2822+
disable_deletion_protection_cmd,
2823+
]
2824+
.map(|cmd| cmd.infer_long_args(pre_flight_settings.infer_long_options))
27942825
}
27952826

27962827
pub fn users_subcommands(pre_flight_settings: PreFlightSettings) -> [Command; 6] {

src/commands.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,22 @@ pub fn delete_vhost(client: APIClient, command_args: &ArgMatches) -> ClientResul
12671267
client.delete_vhost(name, idempotently)
12681268
}
12691269

1270+
pub fn enable_vhost_deletion_protection(
1271+
client: APIClient,
1272+
command_args: &ArgMatches,
1273+
) -> ClientResult<()> {
1274+
let name = command_args.get_one::<String>("name").unwrap();
1275+
client.enable_vhost_deletion_protection(name)
1276+
}
1277+
1278+
pub fn disable_vhost_deletion_protection(
1279+
client: APIClient,
1280+
command_args: &ArgMatches,
1281+
) -> ClientResult<()> {
1282+
let name = command_args.get_one::<String>("name").unwrap();
1283+
client.disable_vhost_deletion_protection(name)
1284+
}
1285+
12701286
pub fn delete_multiple_vhosts(
12711287
client: APIClient,
12721288
command_args: &ArgMatches,

src/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ pub const DEFAULT_CONFIG_SECTION_NAME: &str = "default";
3535
pub const TANZU_COMMAND_PREFIX: &str = "tanzu";
3636

3737
pub const DEFAULT_BLANKET_POLICY_PRIORITY: i16 = -20;
38+
39+
pub const VHOST_DELETION_PROTECTION_GUIDE_URL: &str =
40+
"https://www.rabbitmq.com/docs/vhosts#deletion-protection";

src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,16 @@ fn dispatch_common_subcommand(
11811181
let result = commands::list_vhosts(client);
11821182
res_handler.tabular_result(result)
11831183
}
1184+
("vhosts", "enable_deletion_protection") => {
1185+
let result = commands::enable_vhost_deletion_protection(client, second_level_args)
1186+
.map_err(Into::into);
1187+
res_handler.no_output_on_success(result);
1188+
}
1189+
("vhosts", "disable_deletion_protection") => {
1190+
let result = commands::disable_vhost_deletion_protection(client, second_level_args)
1191+
.map_err(Into::into);
1192+
res_handler.no_output_on_success(result);
1193+
}
11841194
_ => {
11851195
let error = CommandRunError::UnknownCommandTarget {
11861196
command: pair.0.into(),

tests/vhosts_delete_multiple_tests.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,68 @@ fn test_vhosts_delete_multiple_continues_on_individual_failures()
317317

318318
Ok(())
319319
}
320+
321+
#[test]
322+
fn test_vhosts_delete_multiple_protects_deletion_protected_vhosts()
323+
-> Result<(), Box<dyn std::error::Error>> {
324+
let prefix = "rabbitmqadmin.test-vhosts-delete-multiple-protects-protected";
325+
326+
// Clean up any existing test vhosts first (only our specific ones)
327+
delete_vhosts_with_prefix("rabbitmqadmin.test-vhosts-delete-multiple").ok();
328+
329+
// Create test virtual hosts
330+
for i in 1..=3 {
331+
let vh_name = format!("{}-{}", prefix, i);
332+
run_succeeds(["vhosts", "declare", "--name", &vh_name]);
333+
}
334+
335+
// Enable deletion protection for the second vhost only
336+
let protected_vh = format!("{}-2", prefix);
337+
run_succeeds([
338+
"vhosts",
339+
"enable_deletion_protection",
340+
"--name",
341+
&protected_vh,
342+
]);
343+
344+
// We begin with this many virtual hosts
345+
let client = api_client();
346+
let vhosts_before = client.list_vhosts()?;
347+
let test_vhosts_before: Vec<_> = vhosts_before
348+
.iter()
349+
.filter(|vh| vh.name.starts_with(prefix))
350+
.collect();
351+
assert_eq!(test_vhosts_before.len(), 3);
352+
353+
// Try to delete all using the 'vhosts delete_multiple' command
354+
run_succeeds([
355+
"vhosts",
356+
"delete_multiple",
357+
"--name-pattern",
358+
&format!("{}.*", prefix),
359+
"--approve",
360+
"--idempotently",
361+
]);
362+
363+
// Verify that the protected vhost still exists, but several others were deleted
364+
let vhosts_after = client.list_vhosts()?;
365+
let test_vhosts_after: Vec<_> = vhosts_after
366+
.iter()
367+
.filter(|vh| vh.name.starts_with(prefix))
368+
.collect();
369+
370+
// Only the protected vhost should remain
371+
assert_eq!(test_vhosts_after.len(), 1);
372+
assert_eq!(test_vhosts_after[0].name, protected_vh);
373+
374+
// Clean up
375+
run_succeeds([
376+
"vhosts",
377+
"disable_deletion_protection",
378+
"--name",
379+
&protected_vh,
380+
]);
381+
run_succeeds(["vhosts", "delete", "--name", &protected_vh]);
382+
383+
Ok(())
384+
}

tests/vhosts_tests.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,49 @@ fn test_vhosts_delete() -> Result<(), Box<dyn std::error::Error>> {
8080

8181
Ok(())
8282
}
83+
84+
#[test]
85+
fn test_vhosts_enable_deletion_protection() -> Result<(), Box<dyn std::error::Error>> {
86+
let vh = "rabbitmqadmin.vhosts.test-deletion-protection-enable";
87+
run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]);
88+
89+
run_succeeds(["vhosts", "declare", "--name", vh]);
90+
91+
run_succeeds(["vhosts", "enable_deletion_protection", "--name", vh]);
92+
93+
run_succeeds(["vhosts", "disable_deletion_protection", "--name", vh]);
94+
run_succeeds(["vhosts", "delete", "--name", vh]);
95+
96+
Ok(())
97+
}
98+
99+
#[test]
100+
fn test_vhosts_disable_deletion_protection() -> Result<(), Box<dyn std::error::Error>> {
101+
let vh = "rabbitmqadmin.vhosts.test-deletion-protection-disable";
102+
run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]);
103+
104+
run_succeeds(["vhosts", "declare", "--name", vh]);
105+
106+
run_succeeds(["vhosts", "enable_deletion_protection", "--name", vh]);
107+
run_succeeds(["vhosts", "disable_deletion_protection", "--name", vh]);
108+
109+
run_succeeds(["vhosts", "delete", "--name", vh]);
110+
111+
Ok(())
112+
}
113+
114+
#[test]
115+
fn test_vhosts_protected_vhost_cannot_be_deleted() -> Result<(), Box<dyn std::error::Error>> {
116+
let vh = "rabbitmqadmin.vhosts.test-protected-cannot-delete";
117+
run_succeeds(["vhosts", "delete", "--name", vh, "--idempotently"]);
118+
119+
run_succeeds(["vhosts", "declare", "--name", vh]);
120+
run_succeeds(["vhosts", "enable_deletion_protection", "--name", vh]);
121+
122+
run_fails(["vhosts", "delete", "--name", vh]);
123+
124+
run_succeeds(["vhosts", "disable_deletion_protection", "--name", vh]);
125+
run_succeeds(["vhosts", "delete", "--name", vh]);
126+
127+
Ok(())
128+
}

0 commit comments

Comments
 (0)