Reworking command outputs

This commit is contained in:
Daniil Fedotov 2016-08-22 12:58:37 +01:00
parent 17c2fceb24
commit 4c13a51ac8
25 changed files with 356 additions and 389 deletions

View File

@ -16,7 +16,6 @@
defmodule RabbitMQ.CLI.Ctl.Commands.AuthenticateUserCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def validate(args, _) when length(args) < 2, do: {:validation_failure, :not_enough_args}
@ -40,8 +39,9 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AuthenticateUserCommand do
def flags, do: @flags
defp format_output({:refused, user, _, _} = result, _) do
{:error, RabbitMQ.CLI.ExitCodes.exit_code_for(result),
["Error: failed to authenticate user \"#{user}\""]}
def output({:refused, user, _, _}, _) do
{:error, RabbitMQ.CLI.ExitCodes.exit_dataerr,
"Error: failed to authenticate user \"#{user}\""}
end
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -16,7 +16,6 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ChangeClusterNodeTypeCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def flags, do: []
def switches(), do: []
@ -37,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
@ -64,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,7 +16,6 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ForceResetCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}
@ -27,11 +26,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ForceResetCommand 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"
@ -39,4 +34,10 @@ 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
use RabbitMQ.CLI.DefaultOutput
end

View File

@ -32,61 +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()
end
end
# Help command should always exit with usage
def output(result, _) do
{:error, ExitCodes.exit_usage, result}
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
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
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
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
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
defp base_usage(tool_name) do
["Usage:",
"#{tool_name} [-n <node>] [-q] <command> [<command options>]"]
end
def print_base_usage(tool_name, command) do
["Usage:",
"#{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
@ -96,7 +95,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.HelpCommand do
str
end
defp print_options_usage() do
defp options_usage() do
["
General options:
-n node
@ -124,7 +123,7 @@ Some commands accept an optional virtual host parameter for which
to display results. The default value is \"/\".\n"]
end
defp print_commands() do
defp commands() do
["Commands:" |
# Enum.map obtains the usage string for each command module.
# Enum.each prints them all.
@ -136,7 +135,7 @@ to display results. The default value is \"/\".\n"]
|> 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
[command.usage_additional()]
else
@ -144,7 +143,7 @@ to display results. The default value is \"/\".\n"]
end
end
defp print_input_types() do
defp input_types() do
[Helpers.commands
|> Map.values
|> Enum.filter_map(

View File

@ -18,7 +18,6 @@ defmodule RabbitMQ.CLI.Ctl.Commands.JoinClusterCommand do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags [
:disc, # --disc is accepted for consistency's sake.
:ram
@ -51,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
@ -71,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

@ -15,7 +15,6 @@
defmodule RabbitMQ.CLI.Ctl.Commands.NodeHealthCheckCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
defp default_opts() do
%{timeout: 70000}
@ -56,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

@ -57,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

@ -16,22 +16,17 @@
defmodule RabbitMQ.CLI.Ctl.Commands.ResetCommand 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, :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"
@ -39,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

@ -18,7 +18,6 @@ defmodule RabbitMQ.CLI.Ctl.Commands.UpdateClusterNodesCommand do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
def flags, do: []
def switches(), do: []
@ -33,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
@ -53,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

@ -16,7 +16,6 @@
defmodule RabbitMQ.CLI.Ctl.Commands.WaitCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput
@flags []
def merge_defaults(args, opts), do: {args, opts}
@ -38,6 +37,21 @@ defmodule RabbitMQ.CLI.Ctl.Commands.WaitCommand do
def banner(_, %{node: node_name}), do: "Waiting for node #{node_name} ..."
def output({:error, :process_not_running}, _) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
"Error: process is not running."}
end
def output({:error, {:garbage_in_pid_file, _}}, _) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
"Error: garbage in pid file."}
end
def output({:error, {:could_not_read_pid, err}}, _) do
{:error, RabbitMQ.CLI.ExitCodes.exit_software,
"Error: could not read pid. Detail: #{err}"}
end
use RabbitMQ.CLI.DefaultOutput
defp wait_for_application(node, pid_file, :rabbit_and_plugins) do
case read_pid_file(pid_file, true) do
{:error, _} = err -> err

View File

@ -15,57 +15,69 @@
defmodule RabbitMQ.CLI.DefaultOutput do
alias RabbitMQ.CLI.Ctl.Helpers, as: Helpers
alias RabbitMQ.CLI.ExitCodes, as: ExitCodes
defmacro __using__(_opts) do
# When calles as `use RabbitMQ.CLI.DefaultOutput`
# will define output/2 function as default one
defmacro __using__(_) do
quote do
def normalize_output(:ok), do: :ok
def normalize_output({:ok, _} = input), do: input
def normalize_output({:badrpc, :nodedown} = input), do: input
def normalize_output({:badrpc, :timeout} = input), do: input
def normalize_output({:refused, _, _, _} = input), do: input
def normalize_output({:bad_option, _} = input), do: input
def normalize_output({:error, _} = input), do: input
def normalize_output(unknown) when is_atom(unknown), do: {:error, unknown}
def normalize_output({unknown, _} = input) when is_atom(unknown), do: {:error, input}
def normalize_output(result) when not is_atom(result), do: {:ok, result}
def output(result, opts) do
result
|> normalize_output()
|> format_output(opts)
end
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(: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
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({:bad_option, _} = 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

@ -40,10 +40,6 @@ defmodule RabbitMQ.CLI.ExitCodes do
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({:refused, _, _, _}), do: exit_dataerr
def exit_code_for({:healthcheck_failed, _}), do: exit_software
def exit_code_for({:join_cluster_failed, _}), do: exit_software
def exit_code_for({:reset_failed, _}), do: exit_software
def exit_code_for({:error, _}), do: exit_software
end

View File

@ -0,0 +1,30 @@
## 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
def format_error(err, _) do
case is_binary(err) do
true -> err;
false -> inspect(err)
end
end
def format_output(output, _) do
case is_binary(output) do
true -> output;
false -> inspect(output)
end
end
end

View File

@ -0,0 +1,111 @@
## 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({:error, exit_code, error_string}, options) do
formatter = get_formatter(options)
{:error, exit_code, formatter.format_error(error_string, options)}
end
def format_output(:ok, _) do
:ok
end
def format_output({:ok, output}, options) do
formatter = get_formatter(options)
{:ok, formatter.format_output(output, options)}
end
def format_output({:stream, stream}, options) do
formatter = get_formatter(options)
{:stream, Stream.map(stream,
fn({:error, err}) ->
{:error, formatter.format_error(err, options)};
(output) ->
formatter.format_output(output, options)
end)}
end
def print_output({:error, exit_code, string}, options) do
printer = get_printer(options)
printer.print_error(string, options)
exit_code
end
def print_output(:ok, options) do
printer = get_printer(options)
printer.print_ok(options)
ExitCodes.exit_ok
end
def print_output({:ok, single_value}, options) do
printer = get_printer(options)
printer.print_output(single_value, options)
ExitCodes.exit_ok
end
def print_output({:stream, stream}, options) do
printer = get_printer(options)
printer.start_collection(options)
exit_code = case print_output_stream(stream, printer, options) do
:ok -> ExitCodes.exit_ok;
{:error, _} = err -> ExitCodes.exit_code_for(err)
end
printer.finish_collection(options)
exit_code
end
def print_output_stream(stream, printer, options) do
Enum.reduce_while(stream, :ok,
fn
({:error, err}, _) ->
printer.print_error(err, options)
{:halt, {:error, err}};
(val, _) ->
printer.print_output(val, options)
{: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 = String.to_atom("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

@ -13,18 +13,17 @@
## The Initial Developer of the Original Code is GoPivotal, Inc.
## Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.Printers.InspectPrinter do
defmodule RabbitMQ.CLI.Printers.StdIO do
def start_collection(_), do: :ok
def finish_collection(_), do: :ok
def print_error(err, _) do
# IO.puts(err)
IO.inspect({:error, err})
IO.puts(err)
end
def print_output(output, _) do
IO.inspect(output)
IO.puts(output)
end
def print_ok(_) do

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

@ -18,11 +18,13 @@ defmodule RabbitMQCtl do
alias RabbitMQ.CLI.Distribution, as: Distribution
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
@ -31,74 +33,23 @@ defmodule RabbitMQCtl do
end
def main(unparsed_command) do
{parsed_cmd, options, invalid} = parse(unparsed_command)
{printer, print_options} = get_printer(options)
case try_run_command(parsed_cmd, options, invalid) do
{:validation_failure, _} = invalid ->
error_strings = validation_error(invalid, unparsed_command)
{:error, exit_code_for(invalid), error_strings}
error = validation_error(invalid, unparsed_command)
{:error, exit_code_for(invalid), error}
cmd_result -> cmd_result
end
|> print_output(printer, print_options)
|> Output.format_output(options)
|> Output.print_output(options)
|> exit_program
end
def get_printer(%{printer: printer} = opts) do
module_name = String.to_atom("RabbitMQ.CLI.Printers." <>
Mix.Utils.camelize(printer))
printer = case Code.ensure_loaded(module_name) do
{:module, _} -> module_name;
{:error, :nofile} -> default_printer
end
{printer, opts}
end
def get_printer(opts) do
{default_printer, opts}
end
def default_printer() do
RabbitMQ.CLI.Printers.InspectPrinter
end
def print_output({:error, exit_code, strings}, printer, print_options) do
printer.print_error(Enum.join(strings, "\n"), print_options)
exit_code
end
def print_output(:ok, printer, print_options) do
printer.print_ok(print_options)
exit_ok
end
def print_output({:ok, single_value}, printer, print_options) do
printer.print_output(single_value, print_options)
exit_ok
end
def print_output({:stream, stream}, printer, print_options) do
printer.start_collection(print_options)
exit_code = case print_output_stream(stream, printer, print_options) do
:ok -> exit_ok;
{:error, _} = err -> exit_code_for(err)
end
printer.finish_collection(print_options)
exit_code
end
def print_output_stream(stream, printer, print_options) do
Enum.reduce_while(stream, :ok,
fn
({:error, err}, _) ->
printer.print_error(err, print_options)
{:halt, {:error, err}};
(val, _) ->
printer.print_output(val, print_options)
{:cont, :ok}
end)
end
def try_run_command(parsed_cmd, options, invalid) do
case {is_command?(parsed_cmd), invalid} do
## No such command
{false, _} ->
usage_strings = HelpCommand.all_usage()
{:error, exit_usage, usage_strings};
usage_string = HelpCommand.all_usage()
{:error, exit_usage, usage_string};
## Invalid options
{_, [_|_]} ->
{:validation_failure, {:bad_option, invalid}};
@ -172,57 +123,20 @@ defmodule RabbitMQCtl do
end
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({:healthcheck_failed, message} = result, _) do
IO.puts "Error: healthcheck failed. Message: #{message}"
result
end
defp validation_error({:validation_failure, err_detail}, unparsed_command) do
{[command_name | _], _, _} = parse(unparsed_command)
err = format_validation_error(err_detail, command_name) # TODO format the error better
base_error = ["Error: #{err}", "Given:\n\t#{unparsed_command |> Enum.join(" ")}"]
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]
base_error ++ HelpCommand.print_base_usage(HelpCommand.program_name(), command)
HelpCommand.base_usage(HelpCommand.program_name(), command)
false ->
base_error ++ HelpCommand.all_usage()
HelpCommand.all_usage()
end
base_error <> "\n" <> usage
end
defp format_validation_error(:not_enough_args, _), do: "not enough arguments."
@ -238,20 +152,7 @@ defmodule RabbitMQCtl do
end
Enum.join([header | for {key, val} <- opts do "#{key} : #{val}" end], "\n")
end
defp format_validation_error(err), do: inspect err
# 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
# end
# exit_program(code)
# end
defp format_validation_error(err, _), do: inspect err
defp invalid_flags(command, opts) do
Map.take(opts, Map.keys(opts) -- (command.flags ++ global_flags))

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

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

View File

@ -27,54 +27,53 @@ defmodule HelpCommandTest do
end
test "basic usage info is printed" do
assert Enum.join(@command.run([], %{}), "\n") =~ ~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 Enum.join(
@command.run([command], %{}),
"\n") =~ ~r/#{command}/
end)
fn(command) ->
assert @command.run([command], %{}) =~ ~r/#{command}/
end)
end
test "Command info is printed" do
assert Enum.join(@command.run([], %{}), "\n") =~ ~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 Enum.join(@command.run([], %{}), "\n") =~ ~r/\n #{command}.*\n/
end)
assert @command.run([], %{}) =~ ~r/\n #{command}.*\n/
end)
end
test "Info items are defined for existing commands" do
assert Enum.join(@command.run([], %{}), "\n") =~ ~r/\n\<vhostinfoitem\> .*\n/
assert Enum.join(@command.run([], %{}), "\n") =~ ~r/\n\<queueinfoitem\> .*\n/
assert Enum.join(@command.run([], %{}), "\n") =~ ~r/\n\<exchangeinfoitem\> .*\n/
assert Enum.join(@command.run([], %{}), "\n") =~ ~r/\n\<bindinginfoitem\> .*\n/
assert Enum.join(@command.run([], %{}), "\n") =~ ~r/\n\<connectioninfoitem\> .*\n/
assert Enum.join(@command.run([], %{}), "\n") =~ ~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 Enum.join(@command.run(["list_vhosts"], %{}), "\n") =~ ~r/\n\<vhostinfoitem\> .*/
assert Enum.join(@command.run(["list_queues"], %{}), "\n") =~ ~r/\n\<queueinfoitem\> .*/
assert Enum.join(@command.run(["list_exchanges"], %{}), "\n") =~ ~r/\n\<exchangeinfoitem\> .*/
assert Enum.join(@command.run(["list_bindings"], %{}), "\n") =~ ~r/\n\<bindinginfoitem\> .*/
assert Enum.join(@command.run(["list_connections"], %{}), "\n") =~ ~r/\n\<connectioninfoitem\> .*/
assert Enum.join(@command.run(["list_channels"], %{}), "\n") =~ ~r/\n\<channelinfoitem\> .*/
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 Enum.join(@command.run([], %{}), "\n") =~ ~r/Usage:/
assert @command.run([], %{}) =~ ~r/Usage:/
end
test "Extra arguments also produce help command" do
assert Enum.join(@command.run(["extra1", "extra2"], %{}), "\n") =~ ~r/Usage:/
end
assert @command.run(["extra1", "extra2"], %{}) =~ ~r/Usage:/
end
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

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
@ -64,14 +64,14 @@ defmodule RabbitMQCtlTest do
command = []
assert capture_io(fn ->
error_check(command, exit_ok)
end) =~ ~r/Usage:\\n/
end) =~ ~r/Usage:\n/
end
test "Empty command with options shows usage, but is ok" do
command = ["-n", "sandwich@pastrami"]
assert capture_io(fn ->
error_check(command, exit_ok)
end) =~ ~r/Usage:\\n/
end) =~ ~r/Usage:\n/
end
test "Short names without host connect properly" do
@ -90,14 +90,14 @@ defmodule RabbitMQCtlTest do
command = ["status", "extra"]
assert capture_io(fn ->
error_check(command, exit_usage)
end) =~ ~r/Given:\\n\\t.*\\nUsage:\\n.* status/
end) =~ ~r/Given:\n\t.*\nUsage:\n.* status/
end
test "Insufficient arguments return a usage error" do
command = ["list_user_permissions"]
assert capture_io(fn ->
error_check(command, exit_usage)
end) =~ ~r/Given:\\n\\t.*\\nUsage:\\n.* list_user_permissions/
end) =~ ~r/Given:\n\t.*\nUsage:\n.* list_user_permissions/
end
test "A bad argument returns a data error" do

View File

@ -57,7 +57,7 @@ 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 match?({:error, :mnesia_unexpectedly_running}, @command.run([], context[:opts]))
assert vhost_exists? "some_vhost"
end

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