Skip to content

Commit c4a8b70

Browse files
michaelklishinmergify[bot]
authored andcommitted
CLI commands for #12772
(cherry picked from commit 6281dcb)
1 parent 2541e6e commit c4a8b70

6 files changed

+278
-7
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## This Source Code Form is subject to the terms of the Mozilla Public
2+
## License, v. 2.0. If a copy of the MPL was not distributed with this
3+
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
##
5+
## Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
6+
7+
defmodule RabbitMQ.CLI.Ctl.Commands.DisableVhostDeletionProtectionCommand do
8+
alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
9+
10+
@behaviour RabbitMQ.CLI.CommandBehaviour
11+
12+
@metadata_key :protected_from_deletion
13+
14+
def switches(), do: []
15+
def aliases(), do: []
16+
17+
def merge_defaults(args, opts) do
18+
{args, opts}
19+
end
20+
21+
use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
22+
use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
23+
24+
def run([vhost], %{node: node_name}) do
25+
metadata_patch = %{
26+
@metadata_key => false
27+
}
28+
:rabbit_misc.rpc_call(node_name, :rabbit_vhost, :update_metadata, [
29+
vhost,
30+
metadata_patch,
31+
Helpers.cli_acting_user()
32+
])
33+
end
34+
35+
use RabbitMQ.CLI.DefaultOutput
36+
37+
def usage,
38+
do:
39+
"disable_vhost_deletion_protection <vhost>"
40+
41+
def usage_additional() do
42+
[
43+
["<vhost>", "Virtual host name"]
44+
]
45+
end
46+
47+
def usage_doc_guides() do
48+
[
49+
DocGuide.virtual_hosts()
50+
]
51+
end
52+
53+
def help_section(), do: :virtual_hosts
54+
55+
def description(), do: "Removes deletion protection from a virtual host (so that it can be deleted)"
56+
57+
def banner([vhost], _), do: "Removing deletion protection from virtual host \"#{vhost}\" by updating its metadata..."
58+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## This Source Code Form is subject to the terms of the Mozilla Public
2+
## License, v. 2.0. If a copy of the MPL was not distributed with this
3+
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
##
5+
## Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
6+
7+
defmodule RabbitMQ.CLI.Ctl.Commands.EnableVhostDeletionProtectionCommand do
8+
alias RabbitMQ.CLI.Core.{DocGuide, Helpers}
9+
10+
@behaviour RabbitMQ.CLI.CommandBehaviour
11+
12+
@metadata_key :protected_from_deletion
13+
14+
def switches(), do: []
15+
def aliases(), do: []
16+
17+
def merge_defaults(args, opts) do
18+
{args, opts}
19+
end
20+
21+
use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
22+
use RabbitMQ.CLI.Core.AcceptsOnePositionalArgument
23+
24+
def run([vhost], %{node: node_name}) do
25+
metadata_patch = %{
26+
@metadata_key => true
27+
}
28+
:rabbit_misc.rpc_call(node_name, :rabbit_vhost, :update_metadata, [
29+
vhost,
30+
metadata_patch,
31+
Helpers.cli_acting_user()
32+
])
33+
end
34+
35+
use RabbitMQ.CLI.DefaultOutput
36+
37+
def usage,
38+
do:
39+
"enable_vhost_deletion_protection <vhost>"
40+
41+
def usage_additional() do
42+
[
43+
["<vhost>", "Virtual host name"]
44+
]
45+
end
46+
47+
def usage_doc_guides() do
48+
[
49+
DocGuide.virtual_hosts()
50+
]
51+
end
52+
53+
def help_section(), do: :virtual_hosts
54+
55+
def description(), do: "Protects a virtual host from deletion (until the protection is removed)"
56+
57+
def banner([vhost], _), do: "Protecting virtual host \"#{vhost}\" from removal by updating its metadata..."
58+
end

deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/update_vhost_metadata_command.ex

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ defmodule RabbitMQ.CLI.Ctl.Commands.UpdateVhostMetadataCommand do
99

1010
@behaviour RabbitMQ.CLI.CommandBehaviour
1111

12-
@metadata_keys [:description, :tags, :default_queue_type]
12+
@metadata_keys [:description, :tags, :default_queue_type, :protected_from_deletion]
1313

14-
def switches(), do: [description: :string, tags: :string, default_queue_type: :string]
14+
def switches(), do: [description: :string, tags: :string, default_queue_type: :string, protected_from_deletion: :boolean]
1515
def aliases(), do: [d: :description]
1616

1717
def merge_defaults(args, opts) do
@@ -86,7 +86,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.UpdateVhostMetadataCommand do
8686

8787
def usage,
8888
do:
89-
"update_vhost_metadata <vhost> [--description <description>] [--tags \"<tag1>,<tag2>,<...>\"] [--default-queue-type <quorum|classic|stream>]"
89+
"update_vhost_metadata <vhost> [--description=<description>] [--tags=\"<tag1>,<tag2>,<...>\"] [--default-queue-type=<quorum|classic|stream>] [--protected-from-deletion=<true|false>]"
9090

9191
def usage_additional() do
9292
[
@@ -96,7 +96,8 @@ defmodule RabbitMQ.CLI.Ctl.Commands.UpdateVhostMetadataCommand do
9696
[
9797
"--default-queue-type <quorum|classic|stream>",
9898
"Queue type to use if no type is explicitly provided by the client"
99-
]
99+
],
100+
["--protected-from-deletion", "When set to true, will make it impossible to delete a virtual host until the protection is removed"]
100101
]
101102
end
102103

@@ -108,7 +109,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.UpdateVhostMetadataCommand do
108109

109110
def help_section(), do: :virtual_hosts
110111

111-
def description(), do: "Updates metadata (tags, description, default queue type) a virtual host"
112+
def description(), do: "Updates metadata (tags, description, default queue type, protection from deletion) a virtual host"
112113

113114
def banner([vhost], _), do: "Updating metadata of vhost \"#{vhost}\" ..."
114115
end
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
## This Source Code Form is subject to the terms of the Mozilla Public
2+
## License, v. 2.0. If a copy of the MPL was not distributed with this
3+
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
##
5+
## Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
6+
7+
defmodule DisableVhostDeletionProtectionCommandTest do
8+
use ExUnit.Case, async: false
9+
import TestHelper
10+
11+
@command RabbitMQ.CLI.Ctl.Commands.DisableVhostDeletionProtectionCommand
12+
@inverse_command RabbitMQ.CLI.Ctl.Commands.EnableVhostDeletionProtectionCommand
13+
@vhost "disable-vhost-deletion-protection"
14+
15+
setup_all do
16+
RabbitMQ.CLI.Core.Distribution.start()
17+
{:ok, opts: %{node: get_rabbit_hostname()}}
18+
end
19+
20+
setup context do
21+
on_exit(context, fn -> delete_vhost(context[:vhost]) end)
22+
:ok
23+
end
24+
25+
test "validate: no arguments fails validation" do
26+
assert @command.validate([], %{}) == {:validation_failure, :not_enough_args}
27+
end
28+
29+
test "validate: too many arguments fails validation" do
30+
assert @command.validate(["test", "extra"], %{}) == {:validation_failure, :too_many_args}
31+
end
32+
33+
test "validate: virtual host name without options fails validation" do
34+
assert @command.validate(["a-vhost"], %{}) == :ok
35+
end
36+
37+
test "run: enabling deletion protection succeeds", context do
38+
_ = @command.run([@vhost], context[:opts])
39+
delete_vhost(@vhost)
40+
add_vhost(@vhost)
41+
42+
assert @inverse_command.run([@vhost], context[:opts]) == :ok
43+
vh = find_vhost(@vhost)
44+
assert vh[:protected_from_deletion]
45+
46+
assert @command.run([@vhost], context[:opts]) == :ok
47+
vh = find_vhost(@vhost)
48+
assert !vh[:protected_from_deletion]
49+
50+
delete_vhost(@vhost)
51+
end
52+
53+
test "run: attempt to use a non-existent virtual host fails", context do
54+
vh = "a-non-existent-3882-vhost"
55+
56+
assert match?(
57+
{:error, {:no_such_vhost, _}},
58+
@command.run([vh], Map.merge(context[:opts], %{}))
59+
)
60+
end
61+
62+
test "run: attempt to use an unreachable node returns a nodedown" do
63+
opts = %{node: :jake@thedog, timeout: 200, description: "does not matter"}
64+
assert match?({:badrpc, _}, @command.run(["na"], opts))
65+
end
66+
67+
test "banner", context do
68+
assert @command.banner([@vhost], context[:opts]) =~
69+
~r/Removing deletion protection/
70+
end
71+
end
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
## This Source Code Form is subject to the terms of the Mozilla Public
2+
## License, v. 2.0. If a copy of the MPL was not distributed with this
3+
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
##
5+
## Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
6+
7+
defmodule EnableVhostDeletionProtectionCommandTest do
8+
use ExUnit.Case, async: false
9+
import TestHelper
10+
11+
@command RabbitMQ.CLI.Ctl.Commands.EnableVhostDeletionProtectionCommand
12+
@inverse_command RabbitMQ.CLI.Ctl.Commands.DisableVhostDeletionProtectionCommand
13+
@vhost "enable-vhost-deletion-protection"
14+
15+
setup_all do
16+
RabbitMQ.CLI.Core.Distribution.start()
17+
{:ok, opts: %{node: get_rabbit_hostname()}}
18+
end
19+
20+
setup context do
21+
on_exit(context, fn -> delete_vhost(context[:vhost]) end)
22+
:ok
23+
end
24+
25+
test "validate: no arguments fails validation" do
26+
assert @command.validate([], %{}) == {:validation_failure, :not_enough_args}
27+
end
28+
29+
test "validate: too many arguments fails validation" do
30+
assert @command.validate(["test", "extra"], %{}) == {:validation_failure, :too_many_args}
31+
end
32+
33+
test "validate: virtual host name without options fails validation" do
34+
assert @command.validate(["a-vhost"], %{}) == :ok
35+
end
36+
37+
test "run: enabling deletion protection succeeds", context do
38+
add_vhost(@vhost)
39+
40+
assert @command.run([@vhost], context[:opts]) == :ok
41+
vh = find_vhost(@vhost)
42+
assert vh[:protected_from_deletion]
43+
44+
assert @inverse_command.run([@vhost], context[:opts]) == :ok
45+
vh = find_vhost(@vhost)
46+
assert !vh[:protected_from_deletion]
47+
48+
delete_vhost(@vhost)
49+
end
50+
51+
test "run: attempt to use a non-existent virtual host fails", context do
52+
vh = "a-non-existent-3882-vhost"
53+
54+
assert match?(
55+
{:error, {:no_such_vhost, _}},
56+
@command.run([vh], Map.merge(context[:opts], %{}))
57+
)
58+
end
59+
60+
test "run: attempt to use an unreachable node returns a nodedown" do
61+
opts = %{node: :jake@thedog, timeout: 200, description: "does not matter"}
62+
assert match?({:badrpc, _}, @command.run(["na"], opts))
63+
end
64+
65+
test "banner", context do
66+
assert @command.banner([@vhost], context[:opts]) =~
67+
~r/Protecting virtual host/
68+
end
69+
end

deps/rabbitmq_cli/test/ctl/update_vhost_metadata_command_test.exs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ defmodule UpdateVhostMetadataCommandTest do
8181
assert vh[:tags] == [:a1, :b2, :c3]
8282
end
8383

84-
test "run: enabling and disabling deletion protection succeeds", context do
84+
test "run: enabling deletion protection succeeds", context do
8585
add_vhost(@vhost)
8686

8787
opts =
@@ -92,7 +92,21 @@ defmodule UpdateVhostMetadataCommandTest do
9292

9393
assert @command.run([@vhost], opts) == :ok
9494
vh = find_vhost(@vhost)
95-
assert vh[:tags] == [:my_tag]
95+
assert vh[:protected_from_deletion]
96+
end
97+
98+
test "run: disabling deletion protection succeeds", context do
99+
add_vhost(@vhost)
100+
101+
opts =
102+
Map.merge(context[:opts], %{
103+
description: "Protected from deletion",
104+
protected_from_deletion: false
105+
})
106+
107+
assert @command.run([@vhost], opts) == :ok
108+
vh = find_vhost(@vhost)
109+
assert !vh[:protected_from_deletion]
96110
end
97111

98112
test "run: vhost tags are coerced to a list", context do

0 commit comments

Comments
 (0)