Merge pull request #100 from rabbitmq/rabbitmq-cli-90

Command error and output reporting framework
This commit is contained in:
Michael Klishin 2016-09-05 14:29:24 +04:00 committed by GitHub
commit e1e79f6e46
98 changed files with 987 additions and 431 deletions

View File

@ -23,4 +23,7 @@ defmodule RabbitMQ.CLI.CommandBehaviour do
@callback run(List.t, Map.t) :: any
@callback switches() :: Keyword.t
@callback aliases() :: Keyword.t
# Coerces run/2 return value into the standard command output form
# that is then formatted, printed and returned as an exit code.
@callback output(any, Map.t) :: :ok | {:ok, any} | {:stream, Enum.t} | {:error, ExitCodes.exit_code, [String.t]}
end

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.AddUserCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.AddVhostCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def validate([], _), do: {:validation_failure, :not_enough_args}

View File

@ -38,4 +38,11 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AuthenticateUserCommand do
def banner([username, _password], _), do: "Authenticating user \"#{username}\" ..."
def flags, do: @flags
def output({:refused, user, msg, args}, _) do
{:error, RabbitMQ.CLI.ExitCodes.exit_dataerr,
"Error: failed to authenticate user \"#{user}\"\n" <>
to_string(:io_lib.format(msg, args))}
end
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -15,6 +15,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.CancelSyncQueueCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults([_|_] = args, opts) do
{args, Map.merge(default_opts, opts)}

View File

@ -36,22 +36,14 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ChangeClusterNodeTypeCommand do
def validate(_, _), do: {:validation_failure, :too_many_args}
def run([node_type_arg], %{node: node_name}) do
ret = case normalize_type(String.to_atom(node_type_arg)) do
:ram ->
:rabbit_misc.rpc_call(node_name,
:rabbit_mnesia, :change_cluster_node_type, [:ram]
);
:disc ->
:rabbit_misc.rpc_call(node_name,
:rabbit_mnesia, :change_cluster_node_type, [:disc]
)
end
case ret do
{:error, reason} ->
{:change_node_type_failed, {reason, node_name}}
result ->
result
case normalize_type(String.to_atom(node_type_arg)) do
:ram ->
:rabbit_misc.rpc_call(node_name,
:rabbit_mnesia, :change_cluster_node_type, [:ram]
);
:disc ->
:rabbit_misc.rpc_call(node_name,
:rabbit_mnesia, :change_cluster_node_type, [:disc])
end
end
@ -63,6 +55,12 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ChangeClusterNodeTypeCommand do
"Turning #{node_name} into a #{node_type} node"
end
def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
end
use RabbitMQ.CLI.DefaultOutput
defp normalize_type(:ram) do
:ram
end

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ChangePasswordCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ClearParameterCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def switches(), do: []

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ClearPasswordCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def validate([], _), do: {:validation_failure, :not_enough_args}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ClearPermissionsCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def merge_defaults(args, opts) do

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ClearPolicyCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def switches(), do: []

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.CloseConnectionCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ClusterStatusCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.DeleteUserCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def validate([], _), do: {:validation_failure, :not_enough_args}
def validate(args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.DeleteVhostCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def validate([], _), do: {:validation_failure, :not_enough_args}
def validate([_|_] = args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.EnvironmentCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def validate([_|_], _), do: {:validation_failure, :too_many_args}
def validate(_, _), do: :ok

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.EvalCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ForceBootCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,21 +16,18 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ForceResetCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}
def validate([_|_] = args, _) when length(args) > 0, do: {:validation_failure, :too_many_args}
def validate([_|_] = args, _) when length(args) > 0, do: {:validation_failure, :too_many_args}
def validate([], _), do: :ok
def switches(), do: []
def aliases(), do: []
def run([], %{node: node_name}) do
case :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :force_reset, []) do
{:error, reason} ->
{:reset_failed, {reason, node_name}}
result -> result
end
:rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :force_reset, [])
end
def usage, do: "force_reset"
@ -38,4 +35,9 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ForceResetCommand do
def flags, do: @flags
def banner(_, %{node: node_name}), do: "Forcefully resetting node #{node_name} ..."
def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
end
end

View File

@ -20,6 +20,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ForgetClusterNodeCommand do
import RabbitMQ.CLI.Coerce
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def flags, do: [:offline]
def switches(), do: [offline: :boolean]
@ -42,9 +43,12 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ForgetClusterNodeCommand do
end
def run([node_to_remove], %{node: node_name, offline: true}) do
become(node_name)
:rabbit_event.start_link()
:rabbit_mnesia.forget_cluster_node(to_atom(node_to_remove), true)
Stream.concat([
become(node_name),
RabbitMQ.CLI.Ctl.Helpers.defer(fn() ->
:rabbit_event.start_link()
:rabbit_mnesia.forget_cluster_node(to_atom(node_to_remove), true)
end)])
end
def run([node_to_remove], %{node: node_name, offline: false}) do
@ -66,12 +70,17 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ForgetClusterNodeCommand do
:error_logger.tty(false)
case :net_adm.ping(node_name) do
:pong -> exit({:node_running, node_name});
:pang -> ok = :net_kernel.stop()
IO.puts(" * Impersonating node: #{node_name}...")
{:ok, _} = Distribution.start_as(node_name)
IO.puts(" done")
dir = :mnesia.system_info(:directory)
IO.puts(" * Mnesia directory: #{dir}...")
:pang -> :ok = :net_kernel.stop()
Stream.concat([
[" * Impersonating node: #{node_name}..."],
RabbitMQ.CLI.Ctl.Helpers.defer(fn() ->
{:ok, _} = Distribution.start_as(node_name)
" done"
end),
RabbitMQ.CLI.Ctl.Helpers.defer(fn() ->
dir = :mnesia.system_info(:directory)
" * Mnesia directory: #{dir}..."
end)])
end
end
end

View File

@ -20,6 +20,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.HelpCommand do
alias RabbitMQ.CLI.ExitCodes, as: ExitCodes
@behaviour RabbitMQ.CLI.CommandBehaviour
# use RabbitMQ.CLI.DefaultOutput
@flags []
def validate(_, _), do: :ok
def merge_defaults(args, opts), do: {args, opts}
@ -31,57 +32,60 @@ defmodule RabbitMQ.CLI.Ctl.Commands.HelpCommand do
case Helpers.is_command?(command_name) do
true ->
command = Helpers.commands[command_name]
print_base_usage(program_name(), command)
print_options_usage
print_input_types(command);
Enum.join([base_usage(program_name(), command)] ++
options_usage ++
input_types(command), "\n");
false ->
all_usage()
ExitCodes.exit_usage
end
end
def run(_, _) do
all_usage()
end
# Help command should always exit with usage
def output(result, _) do
{:error, ExitCodes.exit_usage, result}
end
def program_name() do
String.to_atom(Path.basename(:escript.script_name()))
end
def all_usage() do
print_base_usage(program_name())
print_options_usage
print_commands
print_input_types
:ok
Enum.join(base_usage(program_name()) ++
options_usage ++
commands ++
input_types, "\n")
end
def usage(), do: "help <command>"
defp print_base_usage(tool_name = :'rabbitmqctl') do
IO.puts "Usage:"
IO.puts "#{tool_name} [-n <node>] [-t <timeout>] [-l] [-q] <command> [<command options>]"
defp base_usage(tool_name = :'rabbitmqctl') do
["Usage:",
"#{tool_name} [-n <node>] [-t <timeout>] [-l] [-q] <command> [<command options>]"]
end
defp print_base_usage(tool_name = :'rabbitmq-plugins') do
IO.puts "Usage:"
IO.puts "#{tool_name} [-n <node>] [-q] <command> [<command options>]"
defp base_usage(tool_name = :'rabbitmq-plugins') do
["Usage:",
"#{tool_name} [-n <node>] [-q] <command> [<command options>]"]
end
defp print_base_usage(tool_name = :'rabbitmq_plugins') do
IO.puts "Usage:"
IO.puts "#{tool_name} [-n <node>] [-q] <command> [<command options>]"
defp base_usage(tool_name = :'rabbitmq_plugins') do
["Usage:",
"#{tool_name} [-n <node>] [-q] <command> [<command options>]"]
end
defp print_base_usage(tool_name) do
IO.puts "Usage:"
IO.puts "#{tool_name} [-n <node>] [-q] <command> [<command options>]"
defp base_usage(tool_name) do
["Usage:",
"#{tool_name} [-n <node>] [-q] <command> [<command options>]"]
end
def print_base_usage(tool_name, command) do
IO.puts "Usage:"
IO.puts "#{tool_name} [-n <node>] [-t <timeout>] [-q] " <>
flatten_string(command.usage())
def base_usage(tool_name, command) do
Enum.join(["Usage:",
"#{tool_name} [-n <node>] [-t <timeout>] [-q] " <>
flatten_string(command.usage())], "\n")
end
defp flatten_string(list) when is_list(list) do
@ -91,8 +95,8 @@ defmodule RabbitMQ.CLI.Ctl.Commands.HelpCommand do
str
end
defp print_options_usage() do
IO.puts "
defp options_usage() do
["
General options:
-n node
-q
@ -116,40 +120,36 @@ If RabbitMQ broker uses long node names for erlang distribution, \"longnames\"
option should be specified.
Some commands accept an optional virtual host parameter for which
to display results. The default value is \"/\".\n"
to display results. The default value is \"/\".\n"]
end
defp print_commands() do
IO.puts "Commands:"
# Enum.map obtains the usage string for each command module.
# Enum.each prints them all.
Helpers.commands
|> Map.values
|> Enum.sort
|> Enum.map(&(&1.usage))
|> List.flatten
|> Enum.each(fn(cmd_usage) -> IO.puts " #{cmd_usage}" end)
:ok
defp commands() do
["Commands:" |
# Enum.map obtains the usage string for each command module.
# Enum.each prints them all.
Helpers.commands
|> Map.values
|> Enum.sort
|> Enum.map(&(&1.usage))
|> List.flatten
|> Enum.map(fn(cmd_usage) -> " #{cmd_usage}" end)]
end
defp print_input_types(command) do
defp input_types(command) do
if :erlang.function_exported(command, :usage_additional, 0) do
IO.puts(command.usage_additional())
[command.usage_additional()]
else
:ok
[]
end
end
defp print_input_types() do
Helpers.commands
|> Map.values
|> Enum.filter_map(
&:erlang.function_exported(&1, :usage_additional, 0),
&(&1.usage_additional))
|> Enum.join("\n\n")
|> IO.puts
defp input_types() do
[Helpers.commands
|> Map.values
|> Enum.filter_map(
&:erlang.function_exported(&1, :usage_additional, 0),
&(&1.usage_additional))
|> Enum.join("\n\n")]
end
def banner(_,_), do: nil

View File

@ -50,17 +50,11 @@ defmodule RabbitMQ.CLI.Ctl.Commands.JoinClusterCommand do
true -> :ram
_ -> :disc
end
ret = :rabbit_misc.rpc_call(node_name,
:rabbit_misc.rpc_call(node_name,
:rabbit_mnesia,
:join_cluster,
[Helpers.parse_node(target_node), node_type]
)
case ret do
{:error, reason} ->
{:join_cluster_failed, {reason, node_name}}
result ->
result
end
end
def usage() do
@ -70,4 +64,14 @@ defmodule RabbitMQ.CLI.Ctl.Commands.JoinClusterCommand do
def banner([target_node], %{node: node_name}) do
"Clustering node #{node_name} with #{target_node}"
end
def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
end
def output({:error, :cannot_cluster_node_with_itself}, %{node: node_name}) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
"Error: cannot cluster node with itself: #{node_name}"}
end
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -19,6 +19,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListBindingsCommand do
alias RabbitMQ.CLI.Ctl.RpcStream, as: RpcStream
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@info_keys ~w(source_name source_kind destination_name destination_kind routing_key arguments)a

View File

@ -21,6 +21,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListChannelsCommand do
alias RabbitMQ.CLI.Ctl.InfoKeys, as: InfoKeys
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@info_keys ~w(pid connection name number user vhost transactional
confirm consumer_count messages_unacknowledged

View File

@ -20,6 +20,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand do
alias RabbitMQ.CLI.Ctl.RpcStream, as: RpcStream
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@info_keys ~w(pid name port host peer_port peer_host ssl ssl_protocol
ssl_key_exchange ssl_cipher ssl_hash peer_cert_subject

View File

@ -20,6 +20,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListConsumersCommand do
alias RabbitMQ.CLI.Ctl.RpcStream, as: RpcStream
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@info_keys ~w(queue_name channel_pid consumer_tag
ack_required prefetch_count arguments)a

View File

@ -19,6 +19,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListExchangesCommand do
alias RabbitMQ.CLI.Ctl.RpcStream, as: RpcStream
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@info_keys ~w(name type durable auto_delete internal arguments policy)a

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ListParametersCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def merge_defaults([], opts) do
{[], Map.merge(%{vhost: "/"}, opts)}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ListPermissionsCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def merge_defaults(args, opts) do

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ListPoliciesCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def switches(), do: []

View File

@ -23,6 +23,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListQueuesCommand do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@info_keys ~w(name durable auto_delete
arguments policy pid owner_pid exclusive exclusive_consumer_pid

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ListUserPermissionsCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def validate([], _), do: {:validation_failure, :not_enough_args}
def validate([_|_] = args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ListUsersCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -18,6 +18,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListVhostsCommand do
alias RabbitMQ.CLI.Ctl.InfoKeys, as: InfoKeys
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
@info_keys ~w(name tracing)a

View File

@ -55,4 +55,10 @@ defmodule RabbitMQ.CLI.Ctl.Commands.NodeHealthCheckCommand do
other
end
end
def output({:healthcheck_failed, message}, _) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
"Error: healthcheck failed. Message: #{message}"}
end
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.PurgeQueueCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def flags, do: []

View File

@ -21,6 +21,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.RenameClusterNodeCommand do
import RabbitMQ.CLI.Coerce
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def flags, do: [:mnesia_dir, :rabbitmq_home]
def switches(), do: [mnesia_dir: :string, rabbitmq_home: :string]
@ -56,7 +57,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.RenameClusterNodeCommand do
try do
:rabbit_mnesia_rename.rename(node_name, node_pairs)
catch _, reason ->
{:error, reason}
{:rename_failed, reason}
end
end

View File

@ -26,6 +26,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ReportCommand do
alias RabbitMQ.CLI.Ctl.Commands.ListPermissionsCommand, as: ListPermissionsCommand
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def switches(), do: []

View File

@ -19,18 +19,14 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ResetCommand do
@flags []
def merge_defaults(args, opts), do: {args, opts}
def validate([_|_] = args, _) when length(args) > 0, do: {:validation_failure, :too_many_args}
def validate([_|_] = args, _) when length(args) > 0, do: {:validation_failure, :too_many_args}
def validate([], _), do: :ok
def switches(), do: []
def aliases(), do: []
def run([], %{node: node_name}) do
case :rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :reset, []) do
{:error, reason} ->
{:reset_failed, {reason, node_name}}
result -> result
end
:rabbit_misc.rpc_call(node_name, :rabbit_mnesia, :reset, [])
end
def usage, do: "reset"
@ -38,4 +34,11 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ResetCommand do
def flags, do: @flags
def banner(_, %{node: node_name}), do: "Resetting node #{node_name} ..."
def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
end
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.RotateLogsCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.SetClusterNameCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def switches(), do: []

View File

@ -18,6 +18,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.SetDiskFreeLimitCommand do
import RabbitMQ.CLI.Ctl.Helpers, only: [memory_unit_absolute: 2]
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}
def switches(), do: []

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.SetParameterCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def switches(), do: []

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.SetPermissionsCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def merge_defaults(args, opts) do

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.SetPolicyCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def switches(), do: [priority: :integer, apply_to: :string]
def aliases(), do: []

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.SetUserTagsCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -18,6 +18,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.SetVmMemoryHighWatermarkCommand do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}
def switches(), do: []

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.StartAppCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.StatusCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.StopAppCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.StopCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}

View File

@ -15,6 +15,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.SyncQueueCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults([_|_] = args, opts) do
{args, Map.merge(default_opts, opts)}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.TraceOffCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def validate([_|_], _), do: {:validation_failure, :too_many_args}

View File

@ -16,6 +16,7 @@
defmodule RabbitMQ.CLI.Ctl.Commands.TraceOnCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [:vhost]
def validate([_|_], _), do: {:validation_failure, :too_many_args}

View File

@ -32,17 +32,11 @@ defmodule RabbitMQ.CLI.Ctl.Commands.UpdateClusterNodesCommand do
def validate(_, _), do: {:validation_failure, :too_many_args}
def run([seed_node], %{node: node_name}) do
ret = :rabbit_misc.rpc_call(node_name,
:rabbit_misc.rpc_call(node_name,
:rabbit_mnesia,
:update_cluster_nodes,
[Helpers.parse_node(seed_node)]
)
case ret do
{:error, reason} ->
{:join_cluster_failed, {reason, node_name}}
result ->
result
end
end
def usage() do
@ -52,4 +46,14 @@ defmodule RabbitMQ.CLI.Ctl.Commands.UpdateClusterNodesCommand do
def banner([seed_node], %{node: node_name}) do
"Will seed #{node_name} from #{seed_node} on next start"
end
def output({:error, :mnesia_unexpectedly_running}, %{node: node_name}) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
RabbitMQ.CLI.DefaultOutput.mnesia_running_error(node_name)}
end
def output({:error, :cannot_cluster_node_with_itself}, %{node: node_name}) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
"Error: cannot cluster node with itself: #{node_name}"}
end
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -37,12 +37,46 @@ defmodule RabbitMQ.CLI.Ctl.Commands.WaitCommand do
def banner(_, %{node: node_name}), do: "Waiting for node #{node_name} ..."
def output({:error, err}, opts) do
case format_error(err) do
:undefined -> RabbitMQ.CLI.DefaultOutput.output({:error, err}, opts);
error_str -> {:error, RabbitMQ.CLI.ExitCodes.exit_software, error_str}
end
end
def output({:stream, stream}, opts) do
Stream.map(stream,
fn({:error, err}) ->
{:error,
case format_error(err) do
:undefined -> err;
error_str -> error_str
end}
end)
end
use RabbitMQ.CLI.DefaultOutput
defp format_error(:process_not_running) do
"Error: process is not running."
end
defp format_error({:garbage_in_pid_file, _}) do
"Error: garbage in pid file."
end
defp format_error({:could_not_read_pid, err}) do
"Error: could not read pid. Detail: #{err}"
end
defp format_error(_) do
:undefined
end
defp wait_for_application(node, pid_file, :rabbit_and_plugins) do
case read_pid_file(pid_file, true) do
{:error, _} = err -> err
pid ->
IO.puts "pid is #{pid}"
wait_for_startup(node, pid)
{:stream, Stream.concat([["pid is #{pid}"],
RabbitMQ.CLI.Ctl.Helpers.defer(
fn() ->
wait_for_startup(node, pid)
end)])}
end
end

View File

@ -73,7 +73,7 @@ defmodule RabbitMQ.CLI.Ctl.Helpers do
def power_as_int(num, x, y), do: round(num * (:math.pow(x, y)))
def global_flags, do: [:node, :quiet, :timeout, :longnames]
def global_flags, do: [:node, :quiet, :timeout, :longnames, :formatter, :printer, :file]
def nodes_in_cluster(node, timeout \\ :infinity) do
case :rpc.call(node, :rabbit_mnesia, :cluster_nodes, [:running], timeout) do
@ -126,4 +126,12 @@ defmodule RabbitMQ.CLI.Ctl.Helpers do
def node_running?(node) do
:net_adm.ping(node) == :pong
end
# Convert function to stream
def defer(fun) do
Stream.iterate(:ok, fn(_) -> fun.() end)
|> Stream.drop(1)
|> Stream.take(1)
end
end

View File

@ -34,7 +34,10 @@ defmodule RabbitMQ.CLI.Ctl.Parser do
quiet: :boolean,
timeout: :integer,
vhost: :string,
longnames: :boolean]
longnames: :boolean,
formatter: :string,
printer: :string,
file: :string]
end
defp build_switches(default) do

View File

@ -0,0 +1,82 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.DefaultOutput do
alias RabbitMQ.CLI.ExitCodes, as: ExitCodes
# When `use RabbitMQ.CLI.DefaultOutput` is invoked,
# this will define output/2 that delegates to RabbitMQ.CLI.DefaultOutput.output/2.
defmacro __using__(_) do
quote do
def output(result, opts) do
RabbitMQ.CLI.DefaultOutput.output(result, opts)
end
end
end
def output(result, opts) do
result
|> normalize_output()
|> format_output(opts)
end
def mnesia_running_error(node_name) do
"Mnesia is still running on node #{node_name}.\n" <>
"Please stop RabbitMQ with rabbitmqctl stop_app first."
end
defp normalize_output(:ok), do: :ok
defp normalize_output({:ok, _} = input), do: input
defp normalize_output({:badrpc, :nodedown} = input), do: input
defp normalize_output({:badrpc, :timeout} = input), do: input
defp normalize_output({:error, _} = input), do: input
defp normalize_output({:error_string, _} = input), do: input
defp normalize_output(unknown) when is_atom(unknown), do: {:error, unknown}
defp normalize_output({unknown, _} = input) when is_atom(unknown), do: {:error, input}
defp normalize_output(result) when not is_atom(result), do: {:ok, result}
defp format_output({:badrpc, :nodedown} = result, opts) do
{:error, ExitCodes.exit_code_for(result),
"Error: unable to connect to node '#{opts[:node]}': nodedown"}
end
defp format_output({:badrpc, :timeout} = result, opts) do
{:error, ExitCodes.exit_code_for(result),
"Error: {timeout, #{opts[:timeout]}}"}
end
defp format_output({:error, err} = result, _) do
string_err = string_or_inspect(err)
{:error, ExitCodes.exit_code_for(result), "Error:\n#{string_err}"}
end
defp format_output({:error_string, error_string}, _) do
{:error, ExitCodes.exit_software, error_string}
end
defp format_output(:ok, _) do
:ok
end
defp format_output({:ok, output}, _) do
case Enumerable.impl_for(output) do
nil -> {:ok, output};
_ -> {:stream, output}
end
end
defp string_or_inspect(val) do
case String.Chars.impl_for(val) do
nil -> inspect(val);
_ -> to_string(val)
end
end
end

View File

@ -21,10 +21,25 @@ defmodule RabbitMQ.CLI.ExitCodes do
@exit_software 70
@exit_tempfail 75
@type exit_code :: integer
def exit_ok, do: @exit_ok
def exit_usage, do: @exit_usage
def exit_dataerr, do: @exit_dataerr
def exit_unavailable, do: @exit_unavailable
def exit_software, do: @exit_software
def exit_tempfail, do: @exit_tempfail
def exit_code_for({:validation_failure, :not_enough_args}), do: exit_usage
def exit_code_for({:validation_failure, :too_many_args}), do: exit_usage
def exit_code_for({:validation_failure, {:not_enough_args, _}}), do: exit_usage
def exit_code_for({:validation_failure, {:too_many_args, _}}), do: exit_usage
def exit_code_for({:validation_failure, {:bad_argument, _}}), do: exit_dataerr
def exit_code_for({:validation_failure, :bad_argument}), do: exit_dataerr
def exit_code_for({:validation_failure, {:bad_option, _}}), do: exit_usage
def exit_code_for({:validation_failure, _}), do: exit_usage
def exit_code_for({:badrpc, :timeout}), do: exit_tempfail
def exit_code_for({:badrpc, :nodedown}), do: exit_unavailable
def exit_code_for({:error, _}), do: exit_software
end

View File

@ -0,0 +1,21 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
# Formats returned values e.g. to human-readable text or JSON.
defmodule RabbitMQ.CLI.Formatters.FormatterBehaviour do
@callback format_error(String.t, Map.t) :: String.t
@callback format_output(any, Map.t) :: String.t
@callback format_stream(Enumerable.t, Map.t) :: Enumerable.t
end

View File

@ -0,0 +1,49 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.Formatters.Inspect do
@behaviour RabbitMQ.CLI.Formatters.FormatterBehaviour
def format_error(err, _) when is_binary(err) do
err
end
def format_output(output, _) do
res = case is_binary(output) do
true -> output;
false -> inspect(output)
end
res
end
def format_stream(stream, options) do
elements = Stream.scan(stream, :empty,
fn(element, previous) ->
separator = case previous do
:empty -> "";
_ -> ","
end
format_element(element, separator, options)
end)
Stream.concat([["["], elements, ["]"]])
end
def format_element({:error, err}, separator, options) do
{:error, separator <> format_error(err, options)}
end
def format_element(val, separator, options) do
separator <> format_output(val, options)
end
end

View File

@ -0,0 +1,52 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
# Basic JSON formatter. Supports 1-level of
# collection using start/finish_collection.
# Primary purpose is to translate stream from CTL,
# so there is no need for multiple collection levels
defmodule RabbitMQ.CLI.Formatters.Json do
@behaviour RabbitMQ.CLI.Formatters.FormatterBehaviour
def format_error(err, _) when is_binary(err) do
{:ok, json} = JSON.encode(%{error: err})
json
end
def format_output(output, _) do
{:ok, json} = JSON.encode(output)
json
end
def format_stream(stream, options) do
elements = Stream.scan(stream, :empty,
fn(element, previous) ->
separator = case previous do
:empty -> "";
_ -> ","
end
format_element(element, separator, options)
end)
Stream.concat([["["], elements, ["]"]])
end
def format_element({:error, err}, separator, options) do
{:error, separator <> format_error(err, options)}
end
def format_element(val, separator, options) do
separator <> format_output(val, options)
end
end

View File

@ -0,0 +1,109 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.Output do
alias RabbitMQ.CLI.ExitCodes, as: ExitCodes
def format_output(output, options) do
formatter = get_formatter(options)
result = format_output_0(output, formatter, options)
result
end
def format_output_0({:error, exit_code, error_string}, formatter, options) do
{:error, exit_code, formatter.format_error(error_string, options)}
end
def format_output_0(:ok, _, _) do
:ok
end
def format_output_0({:ok, output}, formatter, options) do
{:ok, formatter.format_output(output, options)}
end
def format_output_0({:stream, stream}, formatter, options) do
{:stream, formatter.format_stream(stream, options)}
end
def print_output(output, options) do
printer = get_printer(options)
{:ok, printer_state} = printer.init(options)
result = print_output_0(output, printer, printer_state)
printer.finish(printer_state)
result
end
def print_output_0({:error, exit_code, string}, printer, printer_state) do
printer.print_error(string, printer_state)
exit_code
end
def print_output_0(:ok, printer, printer_state) do
printer.print_ok(printer_state)
ExitCodes.exit_ok
end
def print_output_0({:ok, single_value}, printer, printer_state) do
printer.print_output(single_value, printer_state)
ExitCodes.exit_ok
end
def print_output_0({:stream, stream}, printer, printer_state) do
case print_output_stream(stream, printer, printer_state) do
:ok -> ExitCodes.exit_ok;
{:error, _} = err -> ExitCodes.exit_code_for(err)
end
end
def print_output_stream(stream, printer, printer_state) do
Enum.reduce_while(stream, :ok,
fn
({:error, err}, _) ->
printer.print_error(err, printer_state)
{:halt, {:error, err}};
(val, _) ->
printer.print_output(val, printer_state)
{:cont, :ok}
end)
end
def get_printer(%{printer: printer}) do
module_name = String.to_atom("RabbitMQ.CLI.Printers." <>
Mix.Utils.camelize(printer))
case Code.ensure_loaded(module_name) do
{:module, _} -> module_name;
{:error, :nofile} -> default_printer
end
end
def get_printer(_) do
default_printer
end
def get_formatter(%{formatter: formatter}) do
module_name = Module.safe_concat("RabbitMQ.CLI.Formatters", Mix.Utils.camelize(formatter))
case Code.ensure_loaded(module_name) do
{:module, _} -> module_name;
{:error, :nofile} -> default_formatter
end
end
def get_formatter(_) do
default_formatter
end
def default_printer() do
RabbitMQ.CLI.Printers.StdIO
end
def default_formatter() do
RabbitMQ.CLI.Formatters.Inspect
end
end

View File

@ -20,6 +20,7 @@ defmodule RabbitMQ.CLI.Plugins.Commands.DisableCommand do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults(args, opts) do
{args, Map.merge(%{online: true, offline: false}, opts)}

View File

@ -20,6 +20,7 @@ defmodule RabbitMQ.CLI.Plugins.Commands.EnableCommand do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults(args, opts) do
{args, Map.merge(%{online: true, offline: false}, opts)}

View File

@ -23,6 +23,7 @@ defmodule RabbitMQ.CLI.Plugins.Commands.ListCommand do
alias RabbitMQ.CLI.Plugins.Helpers, as: PluginHelpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults([], opts), do: merge_defaults([".*"], opts)
def merge_defaults(args, opts), do: {args, Map.merge(default_opts, opts)}

View File

@ -20,6 +20,7 @@ defmodule RabbitMQ.CLI.Plugins.Commands.SetCommand do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def merge_defaults(args, %{offline: false, online: false} = opts) do
{args, Map.merge(%{online: true, offline: false}, opts)}

View File

@ -0,0 +1,40 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.Printers.File do
def init(options) do
file = options[:file]
case File.open(file) do
{:ok, io_device} -> {:ok, %{device: io_device}};
{:error, err} -> {:error, err}
end
end
def finish(%{device: io_device}) do
:ok = File.close(io_device)
end
def print_error(err, %{device: io_device}) do
IO.puts(io_device, err)
end
def print_output(output, %{device: io_device}) do
IO.puts(io_device, output)
end
def print_ok(_) do
:ok
end
end

View File

@ -0,0 +1,34 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.Printers.StdIO do
def init(_), do: {:ok, :ok}
def finish(_), do: :ok
def print_error(nil, _), do: :ok
def print_error(err, _) do
IO.puts(err)
end
def print_output(nil, _), do: :ok
def print_output(output, _) do
IO.puts(output)
end
def print_ok(_) do
:ok
end
end

View File

@ -1,32 +0,0 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.StandardCodes do
def map_to_standard_code(:ok), do: :ok
def map_to_standard_code({:ok, _} = input), do: input
def map_to_standard_code({:badrpc, :nodedown} = input), do: input
def map_to_standard_code({:badrpc, :timeout} = input), do: input
def map_to_standard_code({:refused, _, _, _} = input), do: input
def map_to_standard_code({:bad_option, _} = input), do: input
def map_to_standard_code({:error, _} = input), do: input
def map_to_standard_code({:join_cluster_failed, _} = input), do: input
def map_to_standard_code({:reset_failed, _} = input), do: input
def map_to_standard_code({:validation_failure, _} = input), do: input
def map_to_standard_code({:healthcheck_failed, _} = input), do: input
def map_to_standard_code(unknown) when is_atom(unknown), do: {:error, unknown}
def map_to_standard_code({unknown, _} = input) when is_atom(unknown), do: {:error, input}
def map_to_standard_code(result) when not is_atom(result), do: {:ok, result}
end

View File

@ -16,14 +16,15 @@
defmodule RabbitMQCtl do
alias RabbitMQ.CLI.Distribution, as: Distribution
alias RabbitMQ.CLI.StandardCodes, as: StandardCodes
alias RabbitMQ.CLI.Ctl.Commands.HelpCommand, as: HelpCommand
alias RabbitMQ.CLI.Output, as: Output
import RabbitMQ.CLI.Ctl.Helpers
import RabbitMQ.CLI.Ctl.Parser
import RabbitMQ.CLI.ExitCodes
def main(["--auto-complete", "./rabbitmqctl " <> str]) do
auto_complete(str)
end
@ -32,24 +33,32 @@ defmodule RabbitMQCtl do
end
def main(unparsed_command) do
{parsed_cmd, options, invalid} = parse(unparsed_command)
case try_run_command(parsed_cmd, options, invalid) do
{:validation_failure, _} = invalid ->
error = validation_error(invalid, unparsed_command)
{:error, exit_code_for(invalid), error}
cmd_result -> cmd_result
end
|> Output.format_output(options)
|> Output.print_output(options)
|> exit_program
end
def try_run_command(parsed_cmd, options, invalid) do
case {is_command?(parsed_cmd), invalid} do
## No such command
{false, _} ->
HelpCommand.all_usage() |> handle_exit(exit_usage);
usage_string = HelpCommand.all_usage()
{:error, exit_usage, usage_string};
## Invalid options
{_, [_|_]} ->
print_standard_messages({:bad_option, invalid}, unparsed_command)
|> handle_exit
{:validation_failure, {:bad_option, invalid}};
## Command valid
{true, []} ->
effective_options = options |> merge_all_defaults |> normalize_node
Distribution.start(effective_options)
effective_options
|> run_command(parsed_cmd)
|> StandardCodes.map_to_standard_code
|> print_standard_messages(unparsed_command)
|> handle_exit
run_command(effective_options, parsed_cmd)
end
end
@ -81,7 +90,7 @@ defmodule RabbitMQCtl do
connect_to_rabbitmq(node)
end
defp run_command(_, []), do: HelpCommand.all_usage()
defp run_command(_, []), do: {:error, exit_ok, HelpCommand.all_usage()}
defp run_command(options, [command_name | arguments]) do
with_command(command_name,
fn(command) ->
@ -92,10 +101,12 @@ defmodule RabbitMQCtl do
:ok ->
print_banner(command, arguments, options)
maybe_connect_to_rabbitmq(command_name, options[:node])
execute_command(command, arguments, options)
command.run(arguments, options)
|> command.output(options)
err -> err
end
result -> {:bad_option, result}
result -> {:validation_failure, {:bad_option, result}}
end
end)
end
@ -112,163 +123,40 @@ defmodule RabbitMQCtl do
end
end
defp execute_command(command, arguments, options) do
command.run(arguments, options)
end
defp print_standard_messages({:badrpc, :nodedown} = result, unparsed_command) do
{_, options, _} = parse(unparsed_command)
IO.puts "Error: unable to connect to node '#{options[:node]}': nodedown"
result
end
defp print_standard_messages({:badrpc, :timeout} = result, unparsed_command) do
{_, options, _} = parse(unparsed_command)
IO.puts "Error: {timeout, #{options[:timeout]}}"
result
end
defp print_standard_messages({:too_many_args, _} = result, unparsed_command) do
{[cmd | _], _, _} = parse(unparsed_command)
IO.puts "Error: too many arguments."
IO.puts "Given:\n\t#{unparsed_command |> Enum.join(" ")}"
HelpCommand.run([cmd], %{})
result
end
defp print_standard_messages({:not_enough_args, _} = result, unparsed_command) do
{[cmd | _], _, _} = parse(unparsed_command)
IO.puts "Error: not enough arguments."
IO.puts "Given:\n\t#{unparsed_command |> Enum.join(" ")}"
HelpCommand.run([cmd], %{})
result
end
defp print_standard_messages({:refused, user, _, _} = result, _) do
IO.puts "Error: failed to authenticate user \"#{user}\""
result
end
defp print_standard_messages(
{failed_command,
{:mnesia_unexpectedly_running, node_name}} = result, _)
when
failed_command == :reset_failed or
failed_command == :join_cluster_failed or
failed_command == :rename_node_failed or
failed_command == :change_node_type_failed
do
IO.puts "Mnesia is still running on node #{node_name}."
IO.puts "Please stop RabbitMQ with rabbitmqctl stop_app first."
result
end
defp print_standard_messages({:error, :process_not_running} = result, _) do
IO.puts "Error: process is not running."
result
end
defp print_standard_messages({:error, {:garbage_in_pid_file, _}} = result, _) do
IO.puts "Error: garbage in pid file."
result
end
defp print_standard_messages({:error, {:could_not_read_pid, err}} = result, _) do
IO.puts "Error: could not read pid. Detail: #{err}"
result
end
defp print_standard_messages({:error, err} = result, _) do
IO.puts "Error:"
IO.inspect err
result
end
defp print_standard_messages({:healthcheck_failed, message} = result, _) do
IO.puts "Error: healthcheck failed. Message: #{message}"
result
end
defp print_standard_messages({:bad_option, opt} = result, unparsed_command) do
case parse(unparsed_command) do
{[cmd | _], _, _} ->
IO.puts "Error: invalid options for this command."
IO.puts "Given:\n\t#{unparsed_command |> Enum.join(" ")}"
HelpCommand.run([cmd], %{})
result;
_ ->
IO.puts "Error: invalid options"
IO.inspect opt
HelpCommand.all_usage()
result
end
end
defp print_standard_messages({:validation_failure, err_detail} = result, unparsed_command) do
defp validation_error({:validation_failure, err_detail}, unparsed_command) do
{[command_name | _], _, _} = parse(unparsed_command)
err = format_validation_error(err_detail) # TODO format the error better
IO.puts "Error: #{err}"
IO.puts "Given:\n\t#{unparsed_command |> Enum.join(" ")}"
err = format_validation_error(err_detail, command_name) # TODO format the error better
base_error = "Error: #{err}\nGiven:\n\t#{unparsed_command |> Enum.join(" ")}"
case is_command?(command_name) do
usage = case is_command?(command_name) do
true ->
command = commands[command_name]
HelpCommand.print_base_usage(HelpCommand.program_name(), command)
HelpCommand.base_usage(HelpCommand.program_name(), command)
false ->
HelpCommand.all_usage()
exit_usage
end
result
base_error <> "\n" <> usage
end
defp print_standard_messages(result, _) do
result
end
defp format_validation_error(:not_enough_args), do: "not enough arguments."
defp format_validation_error({:not_enough_args, detail}), do: "not enough arguments. #{detail}"
defp format_validation_error(:too_many_args), do: "too many arguments."
defp format_validation_error({:too_many_args, detail}), do: "too many arguments. #{detail}"
defp format_validation_error(:bad_argument), do: "Bad argument."
defp format_validation_error({:bad_argument, detail}), do: "Bad argument. #{detail}"
defp format_validation_error(err), do: inspect err
defp handle_exit({:validation_failure, :not_enough_args}), do: exit_program(exit_usage)
defp handle_exit({:validation_failure, :too_many_args}), do: exit_program(exit_usage)
defp handle_exit({:validation_failure, {:not_enough_args, _}}), do: exit_program(exit_usage)
defp handle_exit({:validation_failure, {:too_many_args, _}}), do: exit_program(exit_usage)
defp handle_exit({:validation_failure, {:bad_argument, _}}), do: exit_program(exit_dataerr)
defp handle_exit({:validation_failure, :bad_argument}), do: exit_program(exit_dataerr)
defp handle_exit({:validation_failure, _}), do: exit_program(exit_usage)
defp handle_exit({:bad_option, _} = _err), do: exit_program(exit_usage)
defp handle_exit({:badrpc, :timeout}), do: exit_program(exit_tempfail)
defp handle_exit({:badrpc, :nodedown}), do: exit_program(exit_unavailable)
defp handle_exit({:refused, _, _, _}), do: exit_program(exit_dataerr)
defp handle_exit({:healthcheck_failed, _}), do: exit_program(exit_software)
defp handle_exit({:join_cluster_failed, _}), do: exit_program(exit_software)
defp handle_exit({:reset_failed, _}), do: exit_program(exit_software)
defp handle_exit({:error, _}), do: exit_program(exit_software)
defp handle_exit(true), do: handle_exit(:ok, exit_ok)
defp handle_exit(:ok), do: handle_exit(:ok, exit_ok)
defp handle_exit({:ok, result}), do: handle_exit({:ok, result}, exit_ok)
defp handle_exit(result) when is_list(result), do: handle_exit({:ok, result}, exit_ok)
defp handle_exit(:ok, code), do: exit_program(code)
defp handle_exit({:ok, result}, code) do
case Enumerable.impl_for(result) do
nil -> IO.inspect result;
_ -> result |> Stream.map(&IO.inspect/1) |> Stream.run
defp format_validation_error(:not_enough_args, _), do: "not enough arguments."
defp format_validation_error({:not_enough_args, detail}, _), do: "not enough arguments. #{detail}"
defp format_validation_error(:too_many_args, _), do: "too many arguments."
defp format_validation_error({:too_many_args, detail}, _), do: "too many arguments. #{detail}"
defp format_validation_error(:bad_argument, _), do: "Bad argument."
defp format_validation_error({:bad_argument, detail}, _), do: "Bad argument. #{detail}"
defp format_validation_error({:bad_option, opts}, command_name) do
header = case is_command?(command_name) do
true -> "Invalid options for this command:";
false -> "Invalid options:"
end
exit_program(code)
Enum.join([header | for {key, val} <- opts do "#{key} : #{val}" end], "\n")
end
defp format_validation_error(err, _), do: inspect err
defp invalid_flags(command, opts) do
Map.keys(opts) -- (command.flags ++ global_flags)
Map.take(opts, Map.keys(opts) -- (command.flags ++ global_flags))
|> Map.to_list
end
defp exit_program(code) do

View File

@ -41,7 +41,7 @@ defmodule RabbitMQCtl.MixfileBase do
|> add_modules(Mix.env)
end
defp add_modules(app, :test) do
path = Mix.Project.compile_path
mods = modules_from(Path.wildcard("#{path}/*.beam"))
@ -103,6 +103,9 @@ defmodule RabbitMQCtl.MixfileBase do
:amqp,
git: "https://github.com/pma/amqp.git",
branch: "master"
},
{
:json, "~> 0.3.0"
}
]
end

View File

@ -65,7 +65,7 @@ defmodule AddVhostCommandTest do
test "run: Adding the same host twice results in a host exists message", context do
add_vhost context[:vhost]
assert @command.run([context[:vhost]], context[:opts]) ==
assert @command.run([context[:vhost]], context[:opts]) ==
{:error, {:vhost_already_exists, context[:vhost]}}
assert list_vhosts |> Enum.count(fn(record) -> record[:name] == context[:vhost] end) == 1

View File

@ -77,6 +77,15 @@ defmodule AuthenticateUserCommandTest do
assert @command.banner([context[:user], context[:password]], context[:opts])
=~ ~r/Authenticating user/
assert @command.banner([context[:user], context[:password]], context[:opts])
=~ ~r/"#{context[:user]}"/
=~ ~r/"#{context[:user]}"/
end
test "output: refused error", context do
user = "example_user"
exit_code = RabbitMQ.CLI.ExitCodes.exit_dataerr
assert match?({:error, ^exit_code,
"Error: failed to authenticate user \"example_user\"\n" <>
"Unable to foo"},
@command.output({:refused, user, "Unable to ~s", ["foo"]}, context[:opts]))
end
end

View File

@ -70,7 +70,7 @@ defmodule ChangeClusterNodeTypeCommandTest do
test "run: request to a node with running RabbitMQ app fails", context do
assert match?(
{:change_node_type_failed, {:mnesia_unexpectedly_running, _}},
{:error, :mnesia_unexpectedly_running},
@command.run(["ram"], context[:opts]))
end
@ -90,4 +90,12 @@ defmodule ChangeClusterNodeTypeCommandTest do
assert @command.banner(["ram"], context[:opts]) =~
~r/Turning #{get_rabbit_hostname} into a ram node/
end
test "output mnesia is running error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code,
"Mnesia is still running on node " <> _},
@command.output({:error, :mnesia_unexpectedly_running}, context[:opts]))
end
end

View File

@ -64,7 +64,7 @@ defmodule ClearParameterCommandTest do
end
test "validate: argument validation" do
assert @command.validate(["one", "two"], %{}) == :ok
assert @command.validate(["one", "two"], %{}) == :ok
assert @command.validate([], %{}) == {:validation_failure, :not_enough_args}
assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args}
assert @command.validate(["this", "is", "many"], %{}) == {:validation_failure, :too_many_args}

View File

@ -56,7 +56,7 @@ defmodule ClusterStatusCommandTest do
test "banner", context do
s = @command.banner([], context[:opts])
assert s =~ ~r/Cluster status of node/
assert s =~ ~r/#{get_rabbit_hostname}/
end

View File

@ -121,6 +121,7 @@ end
defmodule RabbitMQ.CLI.Ctl.Commands.DuckCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def usage(), do: ["duck"]
def flags(), do: []
def validate(_,_), do: :ok
@ -133,6 +134,7 @@ end
defmodule RabbitMQ.CLI.Ctl.Commands.GrayGooseCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def usage(), do: ["gray_goose"]
def flags(), do: []
def validate(_,_), do: :ok
@ -151,6 +153,7 @@ end
defmodule RabbitMQ.CLI.Plugins.Commands.StorkCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def usage(), do: ["stork"]
def flags(), do: []
def validate(_,_), do: :ok
@ -163,6 +166,7 @@ end
defmodule RabbitMQ.CLI.Plugins.Commands.HeronCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def usage(), do: ["heron"]
def flags(), do: []
def validate(_,_), do: :ok
@ -177,6 +181,7 @@ end
defmodule RabbitMQ.CLI.Custom.Commands.CrowCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def usage(), do: ["crow"]
def flags(), do: []
def validate(_,_), do: :ok
@ -190,6 +195,7 @@ end
defmodule RabbitMQ.CLI.Custom.Commands.RavenCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def usage(), do: ["raven"]
def flags(), do: []
def validate(_,_), do: :ok
@ -202,6 +208,7 @@ end
defmodule RabbitMQ.CLI.Seagull.Commands.SeagullCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def usage(), do: ["seagull"]
def flags(), do: []
def validate(_,_), do: :ok

View File

@ -0,0 +1,157 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule DefaultOutputTest do
use ExUnit.Case, async: false
import RabbitMQ.CLI.ExitCodes
test "ok is passed as is" do
assert match?(:ok, ExampleCommand.output(:ok, %{}))
end
test "ok with message is passed as is" do
assert match?({:ok, :message}, ExampleCommand.output({:ok, :message}, %{}))
assert match?({:ok, {:complex, "message"}}, ExampleCommand.output({:ok, {:complex, "message"}}, %{}))
end
test "enumerable is passed as stream" do
assert match?({:stream, 'list'}, ExampleCommand.output({:ok, 'list'}, %{}))
assert match?({:stream, 'list'}, ExampleCommand.output('list', %{}))
assert match?({:stream, [1,2,3]}, ExampleCommand.output({:ok, [1,2,3]}, %{}))
assert match?({:stream, [1,2,3]}, ExampleCommand.output([1,2,3], %{}))
stream = Stream.timer(10000)
assert match?({:stream, ^stream}, ExampleCommand.output({:ok, stream}, %{}))
assert match?({:stream, ^stream}, ExampleCommand.output(stream, %{}))
end
test "badrpc nodedown error" do
exit_code = exit_unavailable
node = :example@node
assert match?({:error, ^exit_code, "Error: unable to connect to node 'example@node': nodedown"},
ExampleCommand.output({:badrpc, :nodedown}, %{node: node}))
end
test "badrpc timeout error" do
exit_code = exit_tempfail
timeout = 1000
assert match?({:error, ^exit_code, "Error: {timeout, 1000}"},
ExampleCommand.output({:badrpc, :timeout}, %{timeout: timeout}))
end
test "generic error" do
exit_code = exit_software
assert match?({:error, ^exit_code, "Error:\nerror message"},
ExampleCommand.output({:error, "error message"}, %{}))
end
test "inspect arbitrary error" do
exit_code = exit_software
error = %{i: [am: "arbitrary", error: 1]}
inspected = inspect(error)
assert match?({:error, ^exit_code, "Error:\n" <> ^inspected},
ExampleCommand.output({:error, error}, %{}))
end
test "atom error" do
exit_code = exit_software
assert match?({:error, ^exit_code, "Error:\nerror_message"},
ExampleCommand.output({:error, :error_message}, %{}))
end
test "unknown atom is error" do
exit_code = exit_software
assert match?({:error, ^exit_code, "Error:\nerror_message"},
ExampleCommand.output(:error_message, %{}))
end
test "unknown tuple is error" do
exit_code = exit_software
assert match?({:error, ^exit_code, "Error:\n{:left, :right}"},
ExampleCommand.output({:left, :right}, %{}))
end
test "error_string is error" do
exit_code = exit_software
assert match?({:error, ^exit_code, "I am string"},
ExampleCommand.output({:error_string, "I am string"}, %{}))
end
test "non atom value is ok" do
val = "foo"
assert match?({:ok, ^val}, ExampleCommand.output(val, %{}))
val = 125
assert match?({:ok, ^val}, ExampleCommand.output(val, %{}))
val = 100.2
assert match?({:ok, ^val}, ExampleCommand.output(val, %{}))
val = {:one, :two, :three}
assert match?({:ok, ^val}, ExampleCommand.output(val, %{}))
end
test "custom output function can be defined" do
assert match?({:error, 125, "Non standard"},
ExampleCommandWithCustomOutput.output(:non_standard_output, %{}))
end
test "default output works even if custom output is defined" do
assert match?(:ok, ExampleCommandWithCustomOutput.output(:ok, %{}))
assert match?({:ok, {:complex, "message"}},
ExampleCommandWithCustomOutput.output({:ok, {:complex, "message"}}, %{}))
assert match?({:stream, [1,2,3]}, ExampleCommandWithCustomOutput.output({:ok, [1,2,3]}, %{}))
assert match?({:stream, [1,2,3]}, ExampleCommandWithCustomOutput.output([1,2,3], %{}))
exit_code = exit_unavailable
node = :example@node
assert match?({:error, ^exit_code, "Error: unable to connect to node 'example@node': nodedown"},
ExampleCommandWithCustomOutput.output({:badrpc, :nodedown}, %{node: node}))
exit_code = exit_tempfail
timeout = 1000
assert match?({:error, ^exit_code, "Error: {timeout, 1000}"},
ExampleCommandWithCustomOutput.output({:badrpc, :timeout}, %{timeout: timeout}))
exit_code = exit_software
error = %{i: [am: "arbitrary", error: 1]}
inspected = inspect(error)
assert match?({:error, ^exit_code, "Error:\n" <> ^inspected},
ExampleCommandWithCustomOutput.output({:error, error}, %{}))
assert match?({:error, ^exit_code, "I am string"},
ExampleCommandWithCustomOutput.output({:error_string, "I am string"}, %{}))
val = "foo"
assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{}))
val = 125
assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{}))
val = 100.2
assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{}))
val = {:one, :two, :three}
assert match?({:ok, ^val}, ExampleCommandWithCustomOutput.output(val, %{}))
end
end
defmodule ExampleCommand do
use RabbitMQ.CLI.DefaultOutput
end
defmodule ExampleCommandWithCustomOutput do
def output(:non_standard_output, _) do
{:error, 125, "Non standard"}
end
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -41,7 +41,7 @@ defmodule DeleteVhostCommandTest do
end
test "validate: argument count validates" do
assert @command.validate(["tst"], %{}) == :ok
assert @command.validate(["tst"], %{}) == :ok
assert @command.validate([], %{}) == {:validation_failure, :not_enough_args}
assert @command.validate(["test", "extra"], %{}) == {:validation_failure, :too_many_args}
end

View File

@ -37,7 +37,7 @@ defmodule EnvironmentCommandTest do
end
test "validate: argument count validates" do
assert @command.validate([], %{}) == :ok
assert @command.validate([], %{}) == :ok
assert @command.validate(["extra"], %{}) == {:validation_failure, :too_many_args}
end

View File

@ -57,7 +57,7 @@ defmodule ForceResetCommandTest do
test "run: reset request to an active node with a running rabbit app fails", context do
add_vhost "some_vhost"
assert vhost_exists? "some_vhost"
assert match?({:reset_failed, {:mnesia_unexpectedly_running, _}}, @command.run([], context[:opts]))
assert match?({:error, :mnesia_unexpectedly_running}, @command.run([], context[:opts]))
assert vhost_exists? "some_vhost"
end
@ -71,4 +71,12 @@ defmodule ForceResetCommandTest do
test "banner", context do
assert @command.banner([], context[:opts]) =~ ~r/Forcefully resetting node #{get_rabbit_hostname}/
end
test "output mnesia is running error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code,
"Mnesia is still running on node " <> _},
@command.output({:error, :mnesia_unexpectedly_running}, context[:opts]))
end
end

View File

@ -16,7 +16,6 @@
defmodule HelpCommandTest do
use ExUnit.Case, async: false
import ExUnit.CaptureIO
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@ -27,55 +26,53 @@ defmodule HelpCommandTest do
end
test "basic usage info is printed" do
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/Default node is \"rabbit@server\"/
assert @command.run([], %{}) =~ ~r/Default node is \"rabbit@server\"/
end
test "command usage info is printed if command is specified" do
Helpers.commands
|> Map.keys
|> Enum.each(
fn(command) -> assert capture_io(
fn -> @command.run([command], %{}) end
) =~ ~r/#{command}/
end)
fn(command) ->
assert @command.run([command], %{}) =~ ~r/#{command}/
end)
end
test "Command info is printed" do
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/Commands:\n/
assert @command.run([], %{}) =~ ~r/Commands:\n/
# Checks to verify that each module's command appears in the list.
Helpers.commands
|> Map.keys
|> Enum.each(
fn(command) -> assert capture_io(
fn -> @command.run([], %{}) end
) =~ ~r/\n #{command}.*\n/
end)
fn(command) ->
assert @command.run([], %{}) =~ ~r/\n #{command}.*\n/
end)
end
test "Info items are defined for existing commands" do
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/\n\<vhostinfoitem\> .*\n/
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/\n\<queueinfoitem\> .*\n/
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/\n\<exchangeinfoitem\> .*\n/
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/\n\<bindinginfoitem\> .*\n/
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/\n\<connectioninfoitem\> .*\n/
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/\n\<channelinfoitem\> .*\n/
assert @command.run([], %{}) =~ ~r/\n\<vhostinfoitem\> .*\n/
assert @command.run([], %{}) =~ ~r/\n\<queueinfoitem\> .*\n/
assert @command.run([], %{}) =~ ~r/\n\<exchangeinfoitem\> .*\n/
assert @command.run([], %{}) =~ ~r/\n\<bindinginfoitem\> .*\n/
assert @command.run([], %{}) =~ ~r/\n\<connectioninfoitem\> .*\n/
assert @command.run([], %{}) =~ ~r/\n\<channelinfoitem\> .*\n/
end
test "Info items are printed for selected command" do
assert capture_io(fn -> @command.run(["list_vhosts"], %{}) end) =~ ~r/\n\<vhostinfoitem\> .*\n/
assert capture_io(fn -> @command.run(["list_queues"], %{}) end) =~ ~r/\n\<queueinfoitem\> .*\n/
assert capture_io(fn -> @command.run(["list_exchanges"], %{}) end) =~ ~r/\n\<exchangeinfoitem\> .*\n/
assert capture_io(fn -> @command.run(["list_bindings"], %{}) end) =~ ~r/\n\<bindinginfoitem\> .*\n/
assert capture_io(fn -> @command.run(["list_connections"], %{}) end) =~ ~r/\n\<connectioninfoitem\> .*\n/
assert capture_io(fn -> @command.run(["list_channels"], %{}) end) =~ ~r/\n\<channelinfoitem\> .*\n/
assert @command.run(["list_vhosts"], %{}) =~ ~r/\n\<vhostinfoitem\> .*/
assert @command.run(["list_queues"], %{}) =~ ~r/\n\<queueinfoitem\> .*/
assert @command.run(["list_exchanges"], %{}) =~ ~r/\n\<exchangeinfoitem\> .*/
assert @command.run(["list_bindings"], %{}) =~ ~r/\n\<bindinginfoitem\> .*/
assert @command.run(["list_connections"], %{}) =~ ~r/\n\<connectioninfoitem\> .*/
assert @command.run(["list_channels"], %{}) =~ ~r/\n\<channelinfoitem\> .*/
end
test "No arguments also produce help command" do
assert capture_io(fn -> @command.run([], %{}) end) =~ ~r/Usage:/
assert @command.run([], %{}) =~ ~r/Usage:/
end
test "Extra arguments also produce help command" do
assert capture_io(fn -> @command.run(["extra1", "extra2"], %{}) end) =~ ~r/Usage:/
end
assert @command.run(["extra1", "extra2"], %{}) =~ ~r/Usage:/
end
end

View File

@ -19,7 +19,7 @@ defmodule HelpersTest do
import TestHelper
@subject RabbitMQ.CLI.Ctl.Helpers
setup_all do
RabbitMQ.CLI.Distribution.start()
on_exit([], fn -> :net_kernel.stop() end)

View File

@ -69,17 +69,17 @@ defmodule JoinClusterCommandTest do
test "run: joining self is invalid", context do
stop_rabbitmq_app
assert match?(
{:join_cluster_failed, {:cannot_cluster_node_with_itself, _}},
{:error, :cannot_cluster_node_with_itself},
@command.run([context[:opts][:node]], context[:opts]))
start_rabbitmq_app
end
# TODO
#test "run: request to an active node fails", context do
# assert match?(
# {:join_cluster_failed, {:mnesia_unexpectedly_running, _}},
# @command.run([context[:opts][:node]], context[:opts]))
#end
test "run: request to an active node fails", context do
assert match?(
{:error, :mnesia_unexpectedly_running},
@command.run([context[:opts][:node]], context[:opts]))
end
test "run: request to a non-existent node returns nodedown", context do
target = :jake@thedog
@ -109,4 +109,12 @@ defmodule JoinClusterCommandTest do
assert @command.banner(["a"], context[:opts]) =~
~r/Clustering node #{get_rabbit_hostname} with a/
end
test "output mnesia is running error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code,
"Mnesia is still running on node " <> _},
@command.output({:error, :mnesia_unexpectedly_running}, context[:opts]))
end
end

View File

@ -40,9 +40,9 @@ defmodule ListBindingsCommandTest do
default_keys = ~w(source_name source_kind destination_name destination_kind routing_key arguments)
declare_queue("test_queue", @vhost)
:timer.sleep(100)
{keys, _} = @command.merge_defaults([], context[:opts])
assert default_keys == keys
assert default_keys == keys
end
test "validate: returns bad_info_key on a single bad arg", context do

View File

@ -82,7 +82,7 @@ defmodule ListChannelsCommandTest do
with_channel("/", fn(_channel1) ->
with_channel("/", fn(_channel2) ->
all_channels = run_command_to_list(@command, [["pid", "user", "connection"], context[:opts]])
channels = Enum.filter(all_channels,
channels = Enum.filter(all_channels,
fn(ch) ->
not Enum.member?(existent_channels, ch[:pid])
end)
@ -105,13 +105,13 @@ defmodule ListChannelsCommandTest do
{:ok, _} = AMQP.Channel.open(conn)
{:ok, _} = AMQP.Channel.open(conn)
all_channels = run_command_to_list(@command, [["pid", "user", "connection"], context[:opts]])
channels = Enum.filter(all_channels,
channels = Enum.filter(all_channels,
fn(ch) ->
not Enum.member?(existent_channels, ch[:pid])
end)
chan1 = Enum.at(channels, 0)
chan2 = Enum.at(channels, 1)
chan2 = Enum.at(channels, 1)
assert Keyword.keys(chan1) == ~w(pid user connection)a
assert Keyword.keys(chan2) == ~w(pid user connection)a
assert "guest" == chan1[:user]

View File

@ -1,7 +1,7 @@
defmodule ListConnectionsCommandTest do
use ExUnit.Case, async: false
import TestHelper
@command RabbitMQ.CLI.Ctl.Commands.ListConnectionsCommand
@user "guest"
@default_timeout 15000

View File

@ -77,7 +77,7 @@ defmodule ListUsersCommandTest do
@tag test_timeout: 0
test "run: timeout causes command to return a bad RPC", context do
assert @command.run([], context[:opts]) ==
assert @command.run([], context[:opts]) ==
{:badrpc, :timeout}
end

View File

@ -162,7 +162,7 @@ defmodule EnablePluginsCommandTest do
enabled: [:amqp_client, :rabbitmq_metronome]} ==
@command.run(["rabbitmq_metronome"], context[:opts])
assert {:ok, [xs]} = :file.consult(context[:opts][:enabled_plugins_file])
assert_equal_sets([:rabbitmq_metronome], xs)
assert_equal_sets([:amqp_client, :rabbitmq_metronome], currently_active_plugins(context))

View File

@ -50,7 +50,7 @@ defmodule SetPluginsCommandTest do
end)
:erlang.disconnect_node(node)
:net_kernel.stop()
# :net_kernel.stop()
{:ok, opts: opts}
end

View File

@ -54,7 +54,7 @@ defmodule RabbitMQCtlTest do
command = ["authenticate_user", "kirk", "makeitso"]
assert capture_io(
fn -> error_check(command, exit_dataerr)
end) =~ ~r/Error: failed to authenticate user "kirk"/
end) =~ ~r/Error: failed to authenticate user \"kirk\"/
delete_user "kirk"
end
@ -114,7 +114,7 @@ defmodule RabbitMQCtlTest do
command1 = ["--invalid=true", "list_permissions", "-p", "/"]
assert capture_io(fn ->
error_check(command1, exit_usage)
end) =~ ~r/Error: invalid options for this command/
end) =~ ~r/Error: Invalid options for this command/
command2 = ["--node", "rabbit", "status", "quack"]
assert capture_io(fn ->
@ -155,11 +155,11 @@ defmodule RabbitMQCtlTest do
command1 = ["status", "--nod=rabbit"]
assert capture_io(fn ->
error_check(command1, exit_usage)
end) =~ ~r/Error: invalid options for this command/
end) =~ ~r/Error: Invalid options for this command/
command2 = ["list_permissions", "-o", "/"]
assert capture_io(fn ->
error_check(command2, exit_usage)
end) =~ ~r/Error: invalid options for this command/
end) =~ ~r/Error: Invalid options for this command/
end
end

View File

@ -46,7 +46,7 @@ defmodule ResetCommandTest do
test "run: reset request to an active node with a stopped rabbit app succeeds", context do
add_vhost "some_vhost"
#ensure the vhost really does exist
assert vhost_exists? "some_vhost"
assert vhost_exists? "some_vhost"
stop_rabbitmq_app
assert :ok == @command.run([], context[:opts])
start_rabbitmq_app
@ -56,9 +56,9 @@ defmodule ResetCommandTest do
test "run: reset request to an active node with a running rabbit app fails", context do
add_vhost "some_vhost"
assert vhost_exists? "some_vhost"
assert match?({:reset_failed, {:mnesia_unexpectedly_running, _}}, @command.run([], context[:opts]))
assert vhost_exists? "some_vhost"
assert vhost_exists? "some_vhost"
assert match?({:error, :mnesia_unexpectedly_running}, @command.run([], context[:opts]))
assert vhost_exists? "some_vhost"
end
test "run: request to a non-existent node returns nodedown" do
@ -71,4 +71,12 @@ defmodule ResetCommandTest do
test "banner", context do
assert @command.banner([], context[:opts]) =~ ~r/Resetting node #{get_rabbit_hostname}/
end
test "output mnesia is running error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code,
"Mnesia is still running on node " <> _},
@command.output({:error, :mnesia_unexpectedly_running}, context[:opts]))
end
end

View File

@ -41,7 +41,7 @@ defmodule RotateLogsCommandTest do
end
test "run: request to a named, active node succeeds", context do
assert @command.run([], context[:opts]) == :ok
assert @command.run([], context[:opts]) == :ok
end
test "run: request to a non-existent node returns nodedown" do

View File

@ -1,73 +0,0 @@
## The contents of this file are subject to the Mozilla Public License
## Version 1.1 (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.mozilla.org/MPL/
##
## Software distributed under the License is distributed on an "AS IS"
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
## the License for the specific language governing rights and
## limitations under the License.
##
## The Original Code is RabbitMQ.
##
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule StandardCodesTest do
use ExUnit.Case, async: false
alias RabbitMQ.CLI.StandardCodes, as: StandardCodes
test "An :ok is unchanged" do
assert_unchanged :ok
assert_unchanged {:ok, "input"}
end
test "A node_down is unchanged" do
assert_unchanged {:badrpc, :nodedown}
end
test "A time_out is unchanged" do
assert_unchanged {:badrpc, :timeout}
end
test "A refused is unchanged" do
assert_unchanged {:refused, "name", "string", []}
end
test "A validation failure is unchanged" do
assert_unchanged {:validation_failure, :bad_argument}
assert_unchanged {:validation_failure, :too_many_args}
assert_unchanged {:validation_failure, :not_enough_args}
end
test "A properly-tagged error is unchanged" do
assert_unchanged {:error, {:reason, "excuse"}}
end
test "An unidentified atom is converted to an error" do
assert_error_wrapped :unknown_symbol
end
test "A tuple beginning with an unidentified atom is converted to an error" do
assert_error_wrapped {:error_string, "explanation"}
end
test "A data return is converted to {:ok, <data>}" do
assert StandardCodes.map_to_standard_code(["sandwich"]) == {:ok, ["sandwich"]}
end
test "A 'bad option' message is unchanged" do
assert_unchanged {:bad_option, [:sandwich]}
end
defp assert_unchanged(input) do
assert StandardCodes.map_to_standard_code(input) == input
end
defp assert_error_wrapped(input) do
assert StandardCodes.map_to_standard_code(input) == {:error, input}
end
end

View File

@ -54,7 +54,7 @@ defmodule UpdateClusterNodesCommandTest do
test "run: specifying self as seed node fails validation", context do
stop_rabbitmq_app
assert match?(
{:join_cluster_failed, {:cannot_cluster_node_with_itself, _}},
{:error, :cannot_cluster_node_with_itself},
@command.run([context[:opts][:node]], context[:opts]))
start_rabbitmq_app
end
@ -85,4 +85,12 @@ defmodule UpdateClusterNodesCommandTest do
assert @command.banner(["a"], context[:opts]) =~
~r/Will seed #{get_rabbit_hostname} from a on next start/
end
test "output mnesia is running error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code,
"Mnesia is still running on node " <> _},
@command.output({:error, :mnesia_unexpectedly_running}, context[:opts]))
end
end

View File

@ -26,7 +26,7 @@ defmodule WaitCommandTest do
on_exit([], fn ->
:erlang.disconnect_node(get_rabbit_hostname)
:net_kernel.stop()
# :net_kernel.stop()
end)
:ok
@ -44,4 +44,32 @@ defmodule WaitCommandTest do
test "banner", context do
assert @command.banner([], context[:opts]) =~ ~r/Waiting for node #{get_rabbit_hostname}/
end
test "output: process not running error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code, "Error: process is not running."},
@command.output({:error, :process_not_running}, context[:opts]))
end
test "output: garbage in pid file error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code, "Error: garbage in pid file."},
@command.output({:error, {:garbage_in_pid_file, "somefile"}}, context[:opts]))
end
test "output: could not read pid error", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code, "Error: could not read pid. Detail: something wrong"},
@command.output({:error, {:could_not_read_pid, "something wrong"}}, context[:opts]))
end
test "output: default output is fine", context do
exit_code = RabbitMQ.CLI.ExitCodes.exit_software
assert match?({:error, ^exit_code, "Error:\nmessage"}, @command.output({:error, "message"}, context[:opts]))
assert match?({:error, ^exit_code, "Error:\nmessage"}, @command.output({:error, :message}, context[:opts]))
assert match?({:error, ^exit_code, "Error:\nmessage"}, @command.output(:message, context[:opts]))
assert match?({:ok, "ok"}, @command.output({:ok, "ok"}, context[:opts]))
assert match?(:ok, @command.output(:ok, context[:opts]))
assert match?({:ok, "ok"}, @command.output("ok", context[:opts]))
end
end