Drop --[no-]interactive support

Per discussion with @lukebakken.

It serves no purpose and to make scripting with stream
redirection work we had to make validation changes that make
that flag irrelevant and even confusing.

The only downside of this behavior is that something like

rabbitmqctl add_user --silent "a-username"

(without a password or redirected stream, with suppressed output)
would "hang" waiting for stdin input. If --silent is omitted
there would be an input prompt, making it clearer what's going on.

Closes #365 with a different behavior from the originally suggested.
This commit is contained in:
Michael Klishin 2019-07-11 03:49:37 +03:00 committed by Luke Bakken
parent b890fe787b
commit 37450cc0dc
7 changed files with 61 additions and 87 deletions

View File

@ -17,9 +17,14 @@ defmodule RabbitMQ.CLI.Core.Input do
alias RabbitMQ.CLI.Core.Config
def infer_password(prompt, opts) do
case Config.output_less?(opts) do
val = case Config.output_less?(opts) do
true -> IO.gets("")
false -> IO.gets(prompt)
end |> String.trim()
end
case val do
:eof -> :eof
s -> String.trim(s)
end
end
end

View File

@ -18,18 +18,10 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AddUserCommand do
@behaviour RabbitMQ.CLI.CommandBehaviour
def switches, do: [interactive: :boolean]
def aliases, do: [i: :interactive]
def merge_defaults(args, opts) do
{args, Map.merge(%{interactive: true}, opts)}
end
use RabbitMQ.CLI.Core.MergesNoDefaults
def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
def validate(args, _) when length(args) > 2, do: {:validation_failure, :too_many_args}
def validate([_], %{interactive: false}) do
{:validation_failure, {:not_enough_args, "password must be provided as an argument in non-interactive mode"}}
end
def validate([_], _), do: :ok
def validate(["", _], _) do
{:validation_failure, {:bad_argument, "user cannot be an empty string"}}
@ -43,14 +35,15 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AddUserCommand do
# e.g. when a user only authenticates using X.509 certificates.
# Credential validators can be used to require passwords of a certain length
# or following a certain pattern. This is a core server responsibility. MK.
password = Input.infer_password("Password: ", opts)
:rabbit_misc.rpc_call(
node_name,
:rabbit_auth_backend_internal,
:add_user,
[username, password, Helpers.cli_acting_user()]
)
case Input.infer_password("Password: ", opts) do
:eof -> {:error, :not_enough_args}
password -> :rabbit_misc.rpc_call(
node_name,
:rabbit_auth_backend_internal,
:add_user,
[username, password, Helpers.cli_acting_user()]
)
end
end
def run([username, password], %{node: node_name}) do
:rabbit_misc.rpc_call(
@ -66,9 +59,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AddUserCommand do
def usage_additional() do
[
["<username>", "Self-explanatory"],
["<password>", "Password this user will authenticate with. Use a blank string to disable password-based authentication."],
["--interactive", "Indicates that password might be provided via stdin. This is the default."],
["--no-interactive", "Indicates that password must not be read from stdin."]
["<password>", "Password this user will authenticate with. Use a blank string to disable password-based authentication."]
]
end
@ -84,6 +75,9 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AddUserCommand do
def banner([username | _], _), do: "Adding user \"#{username}\" ..."
def output({:error, :not_enough_args}, _) do
{:error, ExitCodes.exit_software(), "Password is not provided via argument or stdin"}
end
def output({:error, {:user_already_exists, username}}, _) do
{:error, ExitCodes.exit_software(), "User \"#{username}\" already exists"}
end

View File

@ -14,36 +14,33 @@
## Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.Ctl.Commands.AuthenticateUserCommand do
alias RabbitMQ.CLI.Core.{DocGuide, Input}
alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Input}
@behaviour RabbitMQ.CLI.CommandBehaviour
def switches, do: [interactive: :boolean]
def aliases, do: [i: :interactive]
def merge_defaults(args, opts) do
{args, Map.merge(%{interactive: true}, opts)}
end
use RabbitMQ.CLI.Core.MergesNoDefaults
def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
def validate(args, _) when length(args) > 2, do: {:validation_failure, :too_many_args}
def validate([_], %{interactive: false}) do
{:validation_failure, {:not_enough_args, "password must be provided as an argument in non-interactive mode"}}
end
def validate([_], _), do: :ok
def validate([_, _], _), do: :ok
use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
def run([user], %{node: node_name} = opts) do
password = Input.infer_password("Password: ", opts)
:rabbit_misc.rpc_call(
node_name,
:rabbit_access_control,
:check_user_pass_login,
[user, password]
)
# note: blank passwords are currently allowed, they make sense
# e.g. when a user only authenticates using X.509 certificates.
# Credential validators can be used to require passwords of a certain length
# or following a certain pattern. This is a core server responsibility. MK.
case Input.infer_password("Password: ", opts) do
:eof -> {:error, :not_enough_args}
password -> :rabbit_misc.rpc_call(
node_name,
:rabbit_access_control,
:check_user_pass_login,
[user, password]
)
end
end
def run([user, password], %{node: node_name}) do
:rabbit_misc.rpc_call(
@ -54,14 +51,12 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AuthenticateUserCommand do
)
end
def usage, do: "authenticate_user <username> <password> [--interactive]"
def usage, do: "authenticate_user <username> <password>"
def usage_additional() do
[
["<username>", "Username to use"],
["<password>", "Password to use. Can be entered via stdin in interactive mode."],
["--interactive", "Indicates that password might be provided via stdin. This is the default."],
["--no-interactive", "Indicates that password must not be read from stdin."]
["<password>", "Password to use. Can be entered via stdin in interactive mode."]
]
end
@ -77,12 +72,14 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AuthenticateUserCommand do
def banner([username | _], _), do: "Authenticating user \"#{username}\" ..."
def output({:error, :not_enough_args}, _) do
{:error, ExitCodes.exit_software(), "Password is not provided via argument or stdin"}
end
def output({:refused, user, msg, args}, _) do
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_dataerr(),
"Error: failed to authenticate user \"#{user}\"\n" <>
to_string(:io_lib.format(msg, args))}
end
def output({:ok, _user}, _) do
{:ok, "Success"}
end

View File

@ -14,26 +14,15 @@
## Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved.
defmodule RabbitMQ.CLI.Ctl.Commands.ChangePasswordCommand do
alias RabbitMQ.CLI.Core.{DocGuide, Helpers, Input}
alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers, Input}
@behaviour RabbitMQ.CLI.CommandBehaviour
def switches, do: [interactive: :boolean]
def aliases, do: [i: :interactive]
def merge_defaults(args, opts) do
{args, Map.merge(%{interactive: true}, opts)}
end
use RabbitMQ.CLI.Core.MergesNoDefaults
def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
def validate(args, _) when length(args) > 2, do: {:validation_failure, :too_many_args}
def validate([_], %{interactive: false}) do
{:validation_failure, {:not_enough_args, "password must be provided as an argument in non-interactive mode"}}
end
def validate([_], _), do: :ok
def validate(["", _], _) do
{:validation_failure, {:bad_argument, "user cannot be an empty string"}}
end
def validate([_, _], _), do: :ok
use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
@ -43,14 +32,15 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ChangePasswordCommand do
# e.g. when a user only authenticates using X.509 certificates.
# Credential validators can be used to require passwords of a certain length
# or following a certain pattern. This is a core server responsibility. MK.
password = Input.infer_password("Password: ", opts)
:rabbit_misc.rpc_call(
node_name,
:rabbit_auth_backend_internal,
:change_password,
[username, password, Helpers.cli_acting_user()]
)
case Input.infer_password("Password: ", opts) do
:eof -> {:error, :not_enough_args}
password -> :rabbit_misc.rpc_call(
node_name,
:rabbit_auth_backend_internal,
:change_password,
[username, password, Helpers.cli_acting_user()]
)
end
end
def run([username, password], %{node: node_name}) do
:rabbit_misc.rpc_call(
@ -61,6 +51,9 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ChangePasswordCommand do
)
end
def output({:error, :not_enough_args}, _) do
{:error, ExitCodes.exit_software(), "Password is not provided via argument or stdin"}
end
use RabbitMQ.CLI.DefaultOutput
def usage, do: "change_password <username> <password>"

View File

@ -40,17 +40,12 @@ defmodule AddUserCommandTest do
{:validation_failure, :too_many_args}
end
test "validate: one argument in non-interactive mode fails" do
assert match?({:validation_failure, {:not_enough_args, _}},
@command.validate(["user"], %{interactive: false}))
end
test "validate: two arguments passes" do
assert @command.validate(["user", "password"], %{}) == :ok
end
test "validate: one argument in interactive mode passes" do
assert @command.validate(["user"], %{interactive: true}) == :ok
test "validate: one argument passes" do
assert @command.validate(["user"], %{}) == :ok
end
@tag user: "", password: "password"

View File

@ -43,19 +43,14 @@ defmodule AuthenticateUserCommandTest do
{:validation_failure, :too_many_args}
end
test "validate: one argument in non-interactive mode fails" do
assert match?({:validation_failure, {:not_enough_args, _}},
@command.validate(["user"], %{interactive: false}))
test "validate: one argument passes" do
assert @command.validate(["user"], %{}) == :ok
end
test "validate: two arguments passes" do
assert @command.validate(["user", "password"], %{}) == :ok
end
test "validate: one argument in interactive mode passes" do
assert @command.validate(["user"], %{interactive: true}) == :ok
end
@tag user: @user, password: @password
test "run: a valid username and password returns okay", context do
assert {:ok, _} = @command.run([context[:user], context[:password]], context[:opts])

View File

@ -40,17 +40,12 @@ defmodule ChangePasswordCommandTest do
{:validation_failure, :too_many_args}
end
test "validate: one argument in non-interactive mode fails" do
assert match?({:validation_failure, {:not_enough_args, _}},
@command.validate(["user"], %{interactive: false}))
end
test "validate: two arguments passes" do
assert @command.validate(["user", "password"], %{}) == :ok
end
test "validate: one argument in interactive mode passes" do
assert @command.validate(["user"], %{interactive: true}) == :ok
test "validate: one argument passes" do
assert @command.validate(["user"], %{}) == :ok
end
@tag user: @user, password: "new_password"