Make it possible to update virtual host tags

Currently they can only be specified at creation time
This commit is contained in:
Michael Klishin 2021-05-01 21:48:50 +03:00
parent 6539ad7fb1
commit a5373d71d6
No known key found for this signature in database
GPG Key ID: E80EDCFA0CDB21EE
5 changed files with 252 additions and 14 deletions

View File

@ -14,7 +14,7 @@
-export([add/2, add/4, delete/2, exists/1, with/2, with_user_and_vhost/3, assert/1, update/2,
set_limits/2, vhost_cluster_state/1, is_running_on_all_nodes/1, await_running_on_all_nodes/2,
list/0, count/0, list_names/0, all/0]).
-export([parse_tags/1, update_metadata/2, tag_with/2, untag_from/2]).
-export([parse_tags/1, update_metadata/2, tag_with/2, untag_from/2, update_tags/2, update_tags/3]).
-export([lookup/1]).
-export([info/1, info/2, info_all/0, info_all/1, info_all/2, info_all/3]).
-export([dir/1, msg_store_dir_path/1, msg_store_dir_wildcard/0]).
@ -26,6 +26,9 @@
%% API
%%
-type vhost_tag() :: atom() | string() | binary().
-export_type([vhost_tag/0]).
recover() ->
%% Clear out remnants of old incarnation, in case we restarted
%% faster than other nodes handled DOWN messages from us.
@ -377,6 +380,39 @@ update_metadata(VHostName, Fun) ->
vhost:set_metadata(Record, Meta)
end).
-spec update_tags(vhost:name(), [vhost_tag()], rabbit_types:username()) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
update_tags(VHostName, Tags, ActingUser) ->
ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
try
R = rabbit_misc:execute_mnesia_transaction(fun() ->
update_tags(VHostName, ConvertedTags)
end),
rabbit_log:info("Successfully set tags for virtual host '~s' to ~p", [VHostName, ConvertedTags]),
rabbit_event:notify(vhost_tags_set, [{name, VHostName},
{tags, ConvertedTags},
{user_who_performed_action, ActingUser}]),
R
catch
throw:{error, {no_such_vhost, _}} = Error ->
rabbit_log:warning("Failed to set tags for virtual host '~s': the virtual host does not exist", [VHostName]),
throw(Error);
throw:Error ->
rabbit_log:warning("Failed to set tags for virtual host '~s': ~p", [VHostName, Error]),
throw(Error);
exit:Error ->
rabbit_log:warning("Failed to set tags for virtual host '~s': ~p", [VHostName, Error]),
exit(Error)
end.
-spec update_tags(vhost:name(), [vhost_tag()]) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
update_tags(VHostName, Tags) ->
ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
update(VHostName, fun(Record) ->
Meta0 = vhost:get_metadata(Record),
Meta = maps:update(tags, ConvertedTags, Meta0),
vhost:set_metadata(Record, Meta)
end).
-spec tag_with(vhost:name(), [atom()]) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
tag_with(VHostName, Tags) when is_list(Tags) ->
update_metadata(VHostName, fun(#{tags := Tags0} = Meta) ->

View File

@ -0,0 +1,60 @@
## This Source Code Form is subject to the terms of the Mozilla Public
## License, v. 2.0. If a copy of the MPL was not distributed with this
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
##
## Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved.
defmodule RabbitMQ.CLI.Ctl.Commands.SetVhostTagsCommand do
alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}
@behaviour RabbitMQ.CLI.CommandBehaviour
def merge_defaults(args, opts), do: {args, opts}
def validate([], _) do
{:validation_failure, :not_enough_args}
end
def validate(_, _), do: :ok
use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
def run([vhost | tags], %{node: node_name}) do
case :rabbit_misc.rpc_call(
node_name, :rabbit_vhost, :update_tags, [vhost, tags, Helpers.cli_acting_user()]) do
{:error, _} = err -> err
{:badrpc, _} = err -> err
_ -> :ok
end
end
def output({:error, {:no_such_vhost, vhost}}, %{node: node_name, formatter: "json"}) do
{:error, %{"result" => "error", "node" => node_name, "message" => "Virtual host \"#{vhost}\" does not exists"}}
end
def output({:error, {:no_such_vhost, vhost}}, _) do
{:error, ExitCodes.exit_dataerr(), "Virtual host \"#{vhost}\" does not exist"}
end
use RabbitMQ.CLI.DefaultOutput
def usage, do: "set_vhost_tags <vhost> <tag> [...]"
def usage_additional() do
[
["<vhost>", "Self-explanatory"],
["<tags>", "Space separated list of tags"]
]
end
def usage_doc_guides() do
[
DocGuide.virtual_hosts()
]
end
def help_section(), do: :virtual_hosts
def description(), do: "Sets virtual host tags"
def banner([vhost | tags], _) do
"Setting tags for virtual host \"#{vhost}\" to [#{tags |> Enum.join(", ")}] ..."
end
end

View File

@ -40,10 +40,10 @@ defmodule SetUserTagsCommandTest do
test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do
opts = %{node: :jake@thedog, timeout: 200}
assert match?({:badrpc, _}, @command.run([@user, :imperator], opts))
assert match?({:badrpc, _}, @command.run([@user, :emperor], opts))
end
@tag user: @user, tags: [:imperator]
@tag user: @user, tags: [:emperor]
test "run: on a single optional argument, add a flag to the user", context do
@command.run(
[context[:user] | context[:tags]],
@ -58,16 +58,16 @@ defmodule SetUserTagsCommandTest do
assert result[:tags] == context[:tags]
end
@tag user: "interloper", tags: [:imperator]
test "run: on an invalid user, get a no such user error", context do
@tag user: "interloper", tags: [:emperor]
test "run: when user does not exist, returns an error", context do
assert @command.run(
[context[:user] | context[:tags]],
context[:opts]
) == {:error, {:no_such_user, context[:user]}}
end
@tag user: @user, tags: [:imperator, :generalissimo]
test "run: on multiple optional arguments, add all flags to the user", context do
@tag user: @user, tags: [:emperor, :generalissimo]
test "run: with multiple optional arguments, adds multiple tags", context do
@command.run(
[context[:user] | context[:tags]],
context[:opts]
@ -81,8 +81,8 @@ defmodule SetUserTagsCommandTest do
assert result[:tags] == context[:tags]
end
@tag user: @user, tags: [:imperator]
test "run: with no optional arguments, clear user tags", context do
@tag user: @user, tags: [:emperor]
test "run: without optional arguments, clears user tags", context do
set_user_tags(context[:user], context[:tags])
@ -96,7 +96,7 @@ defmodule SetUserTagsCommandTest do
assert result[:tags] == []
end
@tag user: @user, tags: [:imperator]
@tag user: @user, tags: [:emperor]
test "run: identical calls are idempotent", context do
set_user_tags(context[:user], context[:tags])
@ -114,8 +114,8 @@ defmodule SetUserTagsCommandTest do
assert result[:tags] == context[:tags]
end
@tag user: @user, old_tags: [:imperator], new_tags: [:generalissimo]
test "run: if different tags exist, overwrite them", context do
@tag user: @user, old_tags: [:emperor], new_tags: [:generalissimo]
test "run: overwrites existing tags", context do
set_user_tags(context[:user], context[:old_tags])

View File

@ -0,0 +1,139 @@
## This Source Code Form is subject to the terms of the Mozilla Public
## License, v. 2.0. If a copy of the MPL was not distributed with this
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
##
## Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
defmodule SetVhostTagsCommandTest do
use ExUnit.Case, async: false
import TestHelper
@command RabbitMQ.CLI.Ctl.Commands.SetVhostTagsCommand
@vhost "vhost99-tests"
setup_all do
RabbitMQ.CLI.Core.Distribution.start()
add_vhost(@vhost)
on_exit([], fn ->
delete_vhost(@vhost)
end)
:ok
end
setup context do
add_vhost(context[:vhost])
on_exit([], fn -> delete_vhost(context[:vhost]) end)
{:ok, opts: %{node: get_rabbit_hostname()}}
end
test "validate: on an incorrect number of arguments, returns an error" do
assert @command.validate([], %{}) == {:validation_failure, :not_enough_args}
end
test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do
opts = %{node: :jake@thedog, timeout: 200}
assert match?({:badrpc, _}, @command.run([@vhost, :qa], opts))
end
@tag vhost: @vhost
test "run: with a single optional argument, adds a single tag", context do
@command.run([context[:vhost], :qa], context[:opts])
result = Enum.find(
list_vhosts(),
fn(record) -> record[:vhost] == context[:vhost] end
)
assert result[:tags] == context[:tags]
end
@tag vhost: "non/ex1st3nT"
test "run: when virtual host does not exist, reports an error", context do
delete_vhost(context[:vhost])
assert @command.run(
[context[:vhost]],
context[:opts]
) == {:error, {:no_such_vhost, context[:vhost]}}
end
@tag user: @vhost, tags: [:qa, :limited]
test "run: with multiple optional arguments, adds multiple tags", context do
@command.run(
[context[:vhost] | context[:tags]],
context[:opts]
)
result = Enum.find(
list_vhosts(),
fn(record) -> record[:vhost] == context[:vhost] end
)
assert result[:tags] == context[:tags]
end
@tag user: @vhost, tags: [:qa]
test "run: with no optional arguments, clears virtual host tags", context do
set_vhost_tags(context[:vhost], context[:tags])
@command.run([context[:vhost]], context[:opts])
result = Enum.find(
list_vhosts(),
fn(record) -> record[:vhost] == context[:vhost] end
)
assert result[:tags] == []
end
@tag user: @vhost, tags: [:qa]
test "run: identical calls are idempotent", context do
set_vhost_tags(context[:vhost], context[:tags])
assert @command.run(
[context[:vhost] | context[:tags]],
context[:opts]
) == :ok
result = Enum.find(
list_vhosts(),
fn(record) -> record[:vhost] == context[:vhost] end
)
assert result[:tags] == context[:tags]
end
@tag user: @vhost, old_tags: [:qa], new_tags: [:limited]
test "run: overwrites existing tags them", context do
set_vhost_tags(context[:vhost], context[:old_tags])
assert @command.run(
[context[:vhost] | context[:new_tags]],
context[:opts]
) == :ok
result = Enum.find(
list_vhosts(),
fn(record) -> record[:vhost] == context[:vhost] end
)
assert result[:tags] == context[:new_tags]
end
@tag user: @vhost, tags: ["abc"]
test "banner", context do
assert @command.banner(
[context[:vhost] | context[:tags]],
context[:opts]
)
=~ ~r/Setting tags for virtual host \"#{context[:vhost]}\" to \[#{context[:tags]}\] \.\.\./
end
end

View File

@ -86,8 +86,11 @@ defmodule TestHelper do
end
def set_user_tags(name, tags) do
:rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :set_tags,
[name, tags, "acting-user"])
:rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :set_tags, [name, tags, "acting-user"])
end
def set_vhost_tags(name, tags) do
:rpc.call(get_rabbit_hostname(), :rabbit_vhost, :update_tags, [name, tags, "acting-user"])
end
def authenticate_user(name, password) do