Moved ESUsersTool to cores cli tool infra

Also removed the cli tool infra from this repo (as it was moved to core)

Original commit: elastic/x-pack-elasticsearch@0bf7f84ab6
This commit is contained in:
uboness 2014-08-03 23:06:55 +02:00
parent 86546e80ad
commit 605aee5a54
17 changed files with 101 additions and 977 deletions

View File

@ -5,16 +5,16 @@
*/
package org.elasticsearch.shield.authc.esusers.tool;
import org.apache.commons.cli.CommandLine;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.cli.commons.CommandLine;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.support.cli.CliTool;
import org.elasticsearch.shield.support.cli.CliToolConfig;
import org.elasticsearch.shield.support.cli.Terminal;
import java.nio.file.Path;
import java.util.Arrays;
@ -22,8 +22,8 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.shield.support.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.shield.support.cli.CliToolConfig.Builder.option;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
/**
*

View File

@ -1,222 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.support.cli;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.elasticsearch.common.base.Preconditions;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
import java.io.IOException;
import java.util.Locale;
import static org.elasticsearch.common.settings.ImmutableSettings.Builder.EMPTY_SETTINGS;
/**
* A base class for command-line interface tool.
*
* Two modes are supported:
*
* - Singe command mode. The tool exposes a single command that can potentially accept arguments (eg. CLI options).
* - Multi command mode. The tool support multiple command, each for different tasks, each potentially accepts arguments.
*
* In a multi-command mode. The first argument must be the command name. For example, the plugin manager
* can be seen as a multi-command tool with two possible commands: install and uninstall
*
* The tool is configured using a {@link CliToolConfig} which encapsulates the tool's commands and their
* potential options. The tool also comes with out of the box simple help support (the -h/--help option is
* automatically handled) where the help text is configured in a dedicated *.help files located in the same package
* as the tool.
*/
public abstract class CliTool {
// based on sysexits.h
public static enum ExitStatus {
OK(0),
USAGE(64), /* command line usage error */
DATA_ERROR(65), /* data format error */
NO_INPUT(66), /* cannot open input */
NO_USER(67), /* addressee unknown */
NO_HOST(68), /* host name unknown */
UNAVAILABLE(69), /* service unavailable */
CODE_ERROR(70), /* internal software error */
CANT_CREATE(73), /* can't create (user) output file */
IO_ERROR(74), /* input/output error */
TEMP_FAILURE(75), /* temp failure; user is invited to retry */
PROTOCOL(76), /* remote error in protocol */
NOPERM(77), /* permission denied */
CONFIG(78); /* configuration error */
final int status;
private ExitStatus(int status) {
this.status = status;
}
public int status() {
return status;
}
}
protected final Terminal terminal;
protected final Environment env;
protected final Settings settings;
private final CliToolConfig config;
protected CliTool(CliToolConfig config) {
this(config, Terminal.INSTANCE);
}
protected CliTool(CliToolConfig config, Terminal terminal) {
Preconditions.checkArgument(config.cmds().size() != 0, "At least one command must be configured");
this.config = config;
this.terminal = terminal;
Tuple<Settings, Environment> tuple = InternalSettingsPreparer.prepareSettings(EMPTY_SETTINGS, true);
settings = tuple.v1();
env = tuple.v2();
}
public final int execute(String... args) {
// first lets see if the user requests tool help. We're doing it only if
// this is a multi-command tool. If it's a single command tool, the -h/--help
// option will be taken care of on the command level
if (!config.isSingle() && args.length > 0 && (args[0].equals("-h") || args[0].equals("--help"))) {
config.printUsage(terminal);
return ExitStatus.OK.status;
}
CliToolConfig.Cmd cmd = null;
if (config.isSingle()) {
cmd = config.single();
} else {
if (args.length == 0) {
terminal.println("Error: command not specified");
config.printUsage(terminal);
return ExitStatus.USAGE.status;
}
String cmdName = args[0];
cmd = config.cmd(cmdName);
if (cmd == null) {
terminal.println("Error: unknown command [%s]. Use [-h] option to list available commands", cmdName);
return ExitStatus.USAGE.status;
}
// we now remove the command name from the args
if (args.length == 1) {
args = new String[0];
} else {
String[] cmdArgs = new String[args.length - 1];
System.arraycopy(args, 1, cmdArgs, 0, cmdArgs.length);
args = cmdArgs;
}
}
Command command = null;
try {
command = parse(cmd, args);
return command.execute(settings, env).status;
} catch (IOException ioe) {
terminal.println(ioe.getMessage());
return ExitStatus.IO_ERROR.status;
} catch (Exception e) {
terminal.println(e.getMessage());
if (command == null) {
return ExitStatus.USAGE.status;
}
return ExitStatus.CODE_ERROR.status;
}
}
public Command parse(String cmdName, String[] args) throws Exception {
CliToolConfig.Cmd cmd = config.cmd(cmdName);
return parse(cmd, args);
}
public Command parse(CliToolConfig.Cmd cmd, String[] args) throws Exception {
CommandLineParser parser = new GnuParser();
CommandLine cli = parser.parse(cmd.options(), args);
if (cli.hasOption("h")) {
return helpCmd(cmd);
}
return parse(cmd.name(), cli);
}
protected Command.Help helpCmd(CliToolConfig.Cmd cmd) {
return new Command.Help(cmd, terminal);
}
protected static Command.Exit exitCmd(ExitStatus status) {
return new Command.Exit(null, status, null);
}
protected static Command.Exit exitCmd(ExitStatus status, Terminal terminal, String msg, Object... args) {
return new Command.Exit(String.format(Locale.ROOT, msg, args), status, terminal);
}
protected abstract Command parse(String cmdName, CommandLine cli) throws Exception;
public static abstract class Command {
protected final Terminal terminal;
protected Command(Terminal terminal) {
this.terminal = terminal;
}
public abstract ExitStatus execute(Settings settings, Environment env) throws Exception;
public static class Help extends Command {
private final CliToolConfig.Cmd cmd;
private Help(CliToolConfig.Cmd cmd, Terminal terminal) {
super(terminal);
this.cmd = cmd;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
cmd.printUsage(terminal);
return ExitStatus.OK;
}
}
public static class Exit extends Command {
private final String msg;
private final ExitStatus status;
private Exit(String msg, ExitStatus status, Terminal terminal) {
super(terminal);
this.msg = msg;
this.status = status;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
if (msg != null) {
terminal.println(msg);
}
return status;
}
public ExitStatus status() {
return status;
}
}
}
}

View File

@ -1,183 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.support.cli;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.elasticsearch.common.collect.ImmutableMap;
import java.util.Collection;
/**
*
*/
public class CliToolConfig {
public static Builder config(String name, Class<? extends CliTool> toolType) {
return new Builder(name, toolType);
}
private final Class<? extends CliTool> toolType;
private final String name;
private final ImmutableMap<String, Cmd> cmds;
private static final HelpPrinter helpPrinter = new HelpPrinter();
private CliToolConfig(String name, Class<? extends CliTool> toolType, Cmd[] cmds) {
this.name = name;
this.toolType = toolType;
ImmutableMap.Builder<String, Cmd> cmdsBuilder = ImmutableMap.builder();
for (int i = 0; i < cmds.length; i++) {
cmdsBuilder.put(cmds[i].name, cmds[i]);
}
this.cmds = cmdsBuilder.build();
}
public boolean isSingle() {
return cmds.size() == 1;
}
public Cmd single() {
assert isSingle() : "Requesting single command on a multi-command tool";
return cmds.values().iterator().next();
}
public Class<? extends CliTool> toolType() {
return toolType;
}
public String name() {
return name;
}
public Collection<Cmd> cmds() {
return cmds.values();
}
public Cmd cmd(String name) {
return cmds.get(name);
}
public void printUsage(Terminal terminal) {
helpPrinter.print(this, terminal);
}
public static class Builder {
public static Cmd.Builder cmd(String name, Class<? extends CliTool.Command> cmdType) {
return new Cmd.Builder(name, cmdType);
}
public static OptionBuilder option(String shortName, String longName) {
return new OptionBuilder(shortName, longName);
}
private final Class<? extends CliTool> toolType;
private final String name;
private Cmd[] cmds;
private Builder(String name, Class<? extends CliTool> toolType) {
this.name = name;
this.toolType = toolType;
}
public Builder cmds(Cmd.Builder... cmds) {
this.cmds = new Cmd[cmds.length];
for (int i = 0; i < cmds.length; i++) {
this.cmds[i] = cmds[i].build();
}
return this;
}
public Builder cmds(Cmd... cmds) {
this.cmds = cmds;
return this;
}
public CliToolConfig build() {
return new CliToolConfig(name, toolType, cmds);
}
}
public static class Cmd {
private final String name;
private final Class<? extends CliTool.Command> cmdType;
private final Options options;
private Cmd(String name, Class<? extends CliTool.Command> cmdType, Options options) {
this.name = name;
this.cmdType = cmdType;
this.options = options;
this.options.addOption(new OptionBuilder("h", "help").required(false).build());
}
public Class<? extends CliTool.Command> cmdType() {
return cmdType;
}
public String name() {
return name;
}
public Options options() {
return options;
}
public void printUsage(Terminal terminal) {
helpPrinter.print(this, terminal);
}
public static class Builder {
private final String name;
private final Class<? extends CliTool.Command> cmdType;
private Options options = new Options();
private Builder(String name, Class<? extends CliTool.Command> cmdType) {
this.name = name;
this.cmdType = cmdType;
}
public Builder options(OptionBuilder... optionBuilder) {
for (int i = 0; i < optionBuilder.length; i++) {
options.addOption(optionBuilder[i].build());
}
return this;
}
public Cmd build() {
return new Cmd(name, cmdType, options);
}
}
}
public static class OptionBuilder {
private final Option option;
private OptionBuilder(String shortName, String longName) {
option = new Option(shortName, "");
option.setLongOpt(longName);
option.setArgName(longName);
}
public OptionBuilder required(boolean required) {
option.setRequired(required);
return this;
}
public OptionBuilder hasArg(boolean optional) {
option.setOptionalArg(optional);
option.setArgs(1);
return this;
}
public Option build() {
return option;
}
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.support.cli;
import org.elasticsearch.common.base.Charsets;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
*
*/
public class HelpPrinter {
public void print(CliToolConfig config, Terminal terminal) {
URL url = config.toolType().getResource(config.name() + ".help");
print(url, terminal);
}
public void print(CliToolConfig.Cmd cmd, Terminal terminal) {
URL url = cmd.cmdType().getResource(cmd.name() + ".help");
print(url, terminal);
}
private static void print(URL url, Terminal terminal) {
terminal.println();
try {
Path helpFile = Paths.get(url.toURI());
for (String line : Files.readAllLines(helpFile, Charsets.UTF_8)) {
terminal.println(line);
}
} catch (IOException | URISyntaxException e) {
e.printStackTrace(terminal.writer());
}
terminal.println();
}
}

View File

@ -1,108 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.support.cli;
import java.io.*;
import java.util.Locale;
/**
*
*/
public abstract class Terminal {
public static final Terminal INSTANCE = ConsoleTerminal.supported() ? new ConsoleTerminal() : new SystemTerminal();
public abstract String readText(String text, Object... args);
public abstract char[] readSecret(String text, Object... args);
public abstract void println();
public abstract void println(String msg, Object... args);
public abstract void print(String msg, Object... args);
public abstract PrintWriter writer();
public static abstract class Base extends Terminal {
@Override
public void println() {
println("");
}
@Override
public void println(String msg, Object... args) {
print(msg + System.lineSeparator(), args);
}
}
private static class ConsoleTerminal extends Base {
final Console console = System.console();
static boolean supported() {
return System.console() != null;
}
private ConsoleTerminal() {
}
@Override
public void print(String msg, Object... args) {
console.printf(msg, args);
console.flush();
}
@Override
public String readText(String text, Object... args) {
return console.readLine(text, args);
}
@Override
public char[] readSecret(String text, Object... args) {
return console.readPassword(text, args);
}
@Override
public PrintWriter writer() {
return console.writer();
}
}
private static class SystemTerminal extends Base {
private final PrintWriter printWriter = new PrintWriter(System.out);
@Override
public void print(String msg, Object... args) {
System.out.print(String.format(Locale.ROOT, msg, args));
}
@Override
public String readText(String text, Object... args) {
print(text, args);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
return reader.readLine();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
@Override
public char[] readSecret(String text, Object... args) {
return readText(text, args).toCharArray();
}
@Override
public PrintWriter writer() {
return printWriter;
}
}
}

View File

@ -0,0 +1,22 @@
NAME
passwd - Changes the password of an existing native user
SYNOPSIS
esusers passwd <username> [-p <password>]
DESCRIPTION
The passwd command changes passwords for native user accounts. The tool
prompts twice for a replacement password. The second entry is compared
against the first and both are required to match in order for the
password to be changed. If non-default users file is used (a different
file location is configured in elasticsearch.yml) the appropriate file
will be resolved from the settings.
OPTIONS
-h,--help Shows this message
-p,--password <password> The new password for the user

View File

@ -0,0 +1,28 @@
NAME
useradd - Adds a native user
SYNOPSIS
esusers useradd <username> [-p <password>] [-r <roles>]
DESCRIPTION
Adds a native user to elasticsearch (via internal realm). The user will
be added to the users file and its roles will be added to the
users_roles file. If non-default files are used (different file
locations are configured in elasticsearch.yml) the appropriate files
will be resolved from the settings and the user and its roles will be
added to them.
OPTIONS
-h,--help Shows this message
-p,--password <password> The user password
-r,--roles <roles> Comma-separated list of the roles of the user
SEE ALSO
[1] esusers userdel

View File

@ -0,0 +1,24 @@
NAME
userdel - Delete an existing native user
SYNOPSIS
esusers userdel <username>
DESCRIPTION
Removes an existing native user from elasticsearch. The user will be
removed from the users file and its roles will be removed to the
users_roles file. If non-default files are used (different file
locations are configured in elasticsearch.yml) the appropriate files
will be resolved from the settings and the user and its roles will be
removed to them.
OPTIONS
-h,--help Shows this message
SEE ALSO
[1] esusers useradd

View File

@ -1,22 +1,26 @@
SYNTAX:
NAME
esusers <command>
esusers - Manages elasticsearch native users
DESCRIPTION:
SYNOPSIS
This tool manages all native security aspects in elasticsearch, saving
the administratorfrom needing to modify security related fiels manually.
This tool provides several commands for different security management
tasks
esusers <command>
COMMANDS:
DESCRIPTION
passwd Changes passwords for a native user
This tool manages all native security aspects in elasticsearch, saving
the administratorfrom needing to modify security related fiels manually.
This tool provides several commands for different security management
tasks
useradd Adds a new native user to the system
COMMANDS
userdel Removes an existing native user from the system
passwd Changes passwords for a native user
NOTES:
useradd Adds a new native user to the system
[1] For usage help on specific commands please type "security <command> -h"
userdel Removes an existing native user from the system
NOTES
[*] For usage help on specific commands please type "security <command> -h"

View File

@ -1,18 +0,0 @@
USAGE:
esusers passwd <username> [-p <password>]
DESCRIPTION:
The passwd command changes passwords for native user accounts. The tool
prompts twice for a replacement password. The second entry is compared
against the first and both are required to match in order for the
password to be changed. If non-default users file is used (a different
file location is configured in elasticsearch.yml) the appropriate file
will be resolved from the settings.
OPTIONS:
-h,--help Shows this message
-p,--password <password> The new password for the user

View File

@ -1,21 +0,0 @@
USAGE:
esusers useradd <username> [-p <password>] [-r <roles>]
DESCRIPTION:
Adds a native user to elasticsearch (via internal realm). The user will
be added to the users file and its roles will be added to the
users_roles file. If non-default files are used (different file
locations are configured in elasticsearch.yml) the appropriate files
will be resolved from the settings and the user and its roles will be
added to them.
OPTIONS:
-h,--help Shows this message
-p,--password <password> The user password
-r,--roles <roles> Comma-separated list of the roles of the
user

View File

@ -1,16 +0,0 @@
USAGE:
esusers userdel <username>
DESCRIPTION:
Removes an existing native user from elasticsearch. The user will be
removed from the users file and its roles will be removed to the
users_roles file. If non-default files are used (different file
locations are configured in elasticsearch.yml) the appropriate files
will be resolved from the settings and the user and its roles will be
removed to them.
OPTIONS:
-h,--help Shows this message

View File

@ -6,13 +6,13 @@
package org.elasticsearch.shield.authc.esusers.tool;
import com.google.common.base.Charsets;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.support.cli.CliToolTestCase;
import org.elasticsearch.shield.support.cli.CliTool;
import org.elasticsearch.shield.support.cli.Terminal;
import org.junit.Test;
import java.io.BufferedWriter;
@ -365,4 +365,5 @@ public class ESUsersToolTests extends CliToolTestCase {
CliTool.ExitStatus status = cmd.execute(settings, env);
assertThat(status, is(CliTool.ExitStatus.NO_USER));
}
}

View File

@ -1,56 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.support.cli;
import org.elasticsearch.common.Strings;
import org.elasticsearch.test.ElasticsearchTestCase;
import java.io.PrintWriter;
/**
*
*/
public class CliToolTestCase extends ElasticsearchTestCase {
protected static String[] args(String command) {
if (!Strings.hasLength(command)) {
return Strings.EMPTY_ARRAY;
}
return command.split("\\s+");
}
public static class TerminalMock extends Terminal {
@Override
public void println() {
}
@Override
public void println(String msg, Object... args) {
}
@Override
public String readText(String text, Object... args) {
return null;
}
@Override
public char[] readSecret(String text, Object... args) {
return new char[0];
}
@Override
public void print(String msg, Object... args) {
}
@Override
public PrintWriter writer() {
return null;
}
}
}

View File

@ -1,285 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.support.cli;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.cli.CommandLine;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.shield.support.cli.CliToolConfig.Builder.cmd;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
*
*/
public class CliToolTests extends CliToolTestCase {
@Test
public void testOK() throws Exception {
Terminal terminal = new TerminalMock();
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) {
executed.set(true);
return CliTool.ExitStatus.OK;
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
assertThat(executed.get(), is(true));
assertThat(status, is(CliTool.ExitStatus.OK.status()));
}
@Test
public void testUsageError() throws Exception {
Terminal terminal = new TerminalMock();
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) {
executed.set(true);
return CliTool.ExitStatus.USAGE;
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
assertThat(executed.get(), is(true));
assertThat(status, is(CliTool.ExitStatus.USAGE.status()));
}
@Test
public void testIOError() throws Exception {
Terminal terminal = new TerminalMock();
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
executed.set(true);
throw new IOException("io error");
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
assertThat(executed.get(), is(true));
assertThat(status, is(CliTool.ExitStatus.IO_ERROR.status()));
}
@Test
public void testCodeError() throws Exception {
Terminal terminal = new TerminalMock();
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
executed.set(true);
throw new Exception("random error");
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
assertThat(executed.get(), is(true));
assertThat(status, is(CliTool.ExitStatus.CODE_ERROR.status()));
}
public void testMultiCommand() {
Terminal terminal = new TerminalMock();
int count = randomIntBetween(2, 7);
final AtomicReference<Boolean>[] executed = new AtomicReference[count];
for (int i = 0; i < executed.length; i++) {
executed[i] = new AtomicReference<>(false);
}
NamedCommand[] cmds = new NamedCommand[count];
for (int i = 0; i < count; i++) {
final int index = i;
cmds[i] = new NamedCommand("cmd" + index, terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
executed[index].set(true);
return CliTool.ExitStatus.OK;
}
};
}
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
int cmdIndex = randomIntBetween(0, count-1);
int status = tool.execute("cmd" + cmdIndex);
assertThat(status, is(CliTool.ExitStatus.OK.status()));
for (int i = 0; i < executed.length; i++) {
assertThat(executed[i].get(), is(i == cmdIndex));
}
}
public void testMultiCommand_UnknownCommand() {
Terminal terminal = new TerminalMock();
int count = randomIntBetween(2, 7);
final AtomicReference<Boolean>[] executed = new AtomicReference[count];
for (int i = 0; i < executed.length; i++) {
executed[i] = new AtomicReference<>(false);
}
NamedCommand[] cmds = new NamedCommand[count];
for (int i = 0; i < count; i++) {
final int index = i;
cmds[i] = new NamedCommand("cmd" + index, terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
executed[index].set(true);
return CliTool.ExitStatus.OK;
}
};
}
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
int status = tool.execute("cmd" + count); // "cmd" + count doesn't exist
assertThat(status, is(CliTool.ExitStatus.USAGE.status()));
for (int i = 0; i < executed.length; i++) {
assertThat(executed[i].get(), is(false));
}
}
@Test
public void testSingleCommand_ToolHelp() throws Exception {
final AtomicReference<Boolean> helpWritten = new AtomicReference<>(false);
Terminal terminal = new TerminalMock() {
@Override
public void println(String msg, Object... args) {
assertThat(msg, equalTo("cmd1 help"));
helpWritten.set(true);
}
};
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
final NamedCommand cmd = new NamedCommand("cmd1", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
executed.set(true);
throw new IOException("io error");
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute(args("-h"));
assertThat(status, is(CliTool.ExitStatus.OK.status()));
assertThat(helpWritten.get(), is(true));
}
public void testMultiCommand_ToolHelp() {
final AtomicReference<Boolean> helpWritten = new AtomicReference<>(false);
Terminal terminal = new TerminalMock() {
@Override
public void println(String msg, Object... args) {
assertThat(msg, equalTo("tool help"));
helpWritten.set(true);
}
};
NamedCommand[] cmds = new NamedCommand[2];
cmds[0] = new NamedCommand("cmd0", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
}
};
cmds[1] = new NamedCommand("cmd1", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
}
};
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
int status = tool.execute(args("-h"));
assertThat(status, is(CliTool.ExitStatus.OK.status()));
assertThat(helpWritten.get(), is(true));
}
public void testMultiCommand_CmdHelp() {
final AtomicReference<Boolean> helpWritten = new AtomicReference<>(false);
Terminal terminal = new TerminalMock() {
@Override
public void println(String msg, Object... args) {
assertThat(msg, equalTo("cmd1 help"));
helpWritten.set(true);
}
};
NamedCommand[] cmds = new NamedCommand[2];
cmds[0] = new NamedCommand("cmd0", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
}
};
cmds[1] = new NamedCommand("cmd1", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
}
};
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
int status = tool.execute(args("cmd1 -h"));
assertThat(status, is(CliTool.ExitStatus.OK.status()));
assertThat(helpWritten.get(), is(true));
}
private static class SingleCmdTool extends CliTool {
private final Command command;
private SingleCmdTool(String name, Terminal terminal, NamedCommand command) {
super(CliToolConfig.config(name, SingleCmdTool.class)
.cmds(cmd(command.name, command.getClass()))
.build(), terminal);
this.command = command;
}
@Override
protected Command parse(String cmdName, CommandLine cli) throws Exception {
return command;
}
}
private static class MultiCmdTool extends CliTool {
private final Map<String, Command> commands;
private MultiCmdTool(String name, Terminal terminal, NamedCommand... commands) {
super(CliToolConfig.config(name, MultiCmdTool.class)
.cmds(cmds(commands))
.build(), terminal);
ImmutableMap.Builder<String, Command> commandByName = ImmutableMap.builder();
for (int i = 0; i < commands.length; i++) {
commandByName.put(commands[i].name, commands[i]);
}
this.commands = commandByName.build();
}
@Override
protected Command parse(String cmdName, CommandLine cli) throws Exception {
return commands.get(cmdName);
}
private static CliToolConfig.Cmd[] cmds(NamedCommand... commands) {
CliToolConfig.Cmd[] cmds = new CliToolConfig.Cmd[commands.length];
for (int i = 0; i < commands.length; i++) {
cmds[i] = cmd(commands[i].name, commands[i].getClass()).build();
}
return cmds;
}
}
private static abstract class NamedCommand extends CliTool.Command {
private final String name;
private NamedCommand(String name, Terminal terminal) {
super(terminal);
this.name = name;
}
}
}