Merge pull request #16443 from rjernst/cli_terminal
CliTool: Cleanup and document Terminal
This commit is contained in:
commit
5c0f09b03a
|
@ -100,10 +100,10 @@ public abstract class CheckFileCommand extends CliTool.Command {
|
|||
Set<PosixFilePermission> permissionsBeforeWrite = entry.getValue();
|
||||
Set<PosixFilePermission> permissionsAfterWrite = Files.getPosixFilePermissions(entry.getKey());
|
||||
if (!permissionsBeforeWrite.equals(permissionsAfterWrite)) {
|
||||
terminal.printWarn("The file permissions of [" + entry.getKey() + "] have changed "
|
||||
terminal.println(Terminal.Verbosity.SILENT, "WARNING: The file permissions of [" + entry.getKey() + "] have changed "
|
||||
+ "from [" + PosixFilePermissions.toString(permissionsBeforeWrite) + "] "
|
||||
+ "to [" + PosixFilePermissions.toString(permissionsAfterWrite) + "]");
|
||||
terminal.printWarn("Please ensure that the user account running Elasticsearch has read access to this file!");
|
||||
terminal.println(Terminal.Verbosity.SILENT, "Please ensure that the user account running Elasticsearch has read access to this file!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ public abstract class CheckFileCommand extends CliTool.Command {
|
|||
String ownerBeforeWrite = entry.getValue();
|
||||
String ownerAfterWrite = Files.getOwner(entry.getKey()).getName();
|
||||
if (!ownerAfterWrite.equals(ownerBeforeWrite)) {
|
||||
terminal.printWarn("WARN: Owner of file [" + entry.getKey() + "] used to be [" + ownerBeforeWrite + "], but now is [" + ownerAfterWrite + "]");
|
||||
terminal.println(Terminal.Verbosity.SILENT, "WARNING: Owner of file [" + entry.getKey() + "] used to be [" + ownerBeforeWrite + "], but now is [" + ownerAfterWrite + "]");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ public abstract class CheckFileCommand extends CliTool.Command {
|
|||
String groupBeforeWrite = entry.getValue();
|
||||
String groupAfterWrite = Files.readAttributes(entry.getKey(), PosixFileAttributes.class).group().getName();
|
||||
if (!groupAfterWrite.equals(groupBeforeWrite)) {
|
||||
terminal.printWarn("WARN: Group of file [" + entry.getKey() + "] used to be [" + groupBeforeWrite + "], but now is [" + groupAfterWrite + "]");
|
||||
terminal.println(Terminal.Verbosity.SILENT, "WARNING: Group of file [" + entry.getKey() + "] used to be [" + groupBeforeWrite + "], but now is [" + groupAfterWrite + "]");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ public abstract class CliTool {
|
|||
} else {
|
||||
|
||||
if (args.length == 0) {
|
||||
terminal.printError("command not specified");
|
||||
terminal.println(Terminal.Verbosity.SILENT, "ERROR: command not specified");
|
||||
config.printUsage(terminal);
|
||||
return ExitStatus.USAGE;
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ public abstract class CliTool {
|
|||
String cmdName = args[0];
|
||||
cmd = config.cmd(cmdName);
|
||||
if (cmd == null) {
|
||||
terminal.printError("unknown command [" + cmdName + "]. Use [-h] option to list available commands");
|
||||
terminal.println(Terminal.Verbosity.SILENT, "ERROR: unknown command [" + cmdName + "]. Use [-h] option to list available commands");
|
||||
return ExitStatus.USAGE;
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ public abstract class CliTool {
|
|||
try {
|
||||
return parse(cmd, args).execute(settings, env);
|
||||
} catch (UserError error) {
|
||||
terminal.printError(error.getMessage());
|
||||
terminal.println(Terminal.Verbosity.SILENT, "ERROR: " + error.getMessage());
|
||||
return error.exitStatus;
|
||||
}
|
||||
}
|
||||
|
@ -165,8 +165,14 @@ public abstract class CliTool {
|
|||
// the stack trace into cli parsing lib is not important
|
||||
throw new UserError(ExitStatus.USAGE, e.toString());
|
||||
}
|
||||
Terminal.Verbosity verbosity = Terminal.Verbosity.resolve(cli);
|
||||
terminal.verbosity(verbosity);
|
||||
|
||||
if (cli.hasOption("v")) {
|
||||
terminal.setVerbosity(Terminal.Verbosity.VERBOSE);
|
||||
} else if (cli.hasOption("s")) {
|
||||
terminal.setVerbosity(Terminal.Verbosity.SILENT);
|
||||
} else {
|
||||
terminal.setVerbosity(Terminal.Verbosity.NORMAL);
|
||||
}
|
||||
return parse(cmd.name(), cli);
|
||||
}
|
||||
|
||||
|
@ -224,7 +230,7 @@ public abstract class CliTool {
|
|||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
if (msg != null) {
|
||||
if (status != ExitStatus.OK) {
|
||||
terminal.printError(msg);
|
||||
terminal.println(Terminal.Verbosity.SILENT, "ERROR: " + msg);
|
||||
} else {
|
||||
terminal.println(msg);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class HelpPrinter {
|
|||
}
|
||||
|
||||
private static void print(Class clazz, String name, final Terminal terminal) {
|
||||
terminal.println(Terminal.Verbosity.SILENT);
|
||||
terminal.println(Terminal.Verbosity.SILENT, "");
|
||||
try (InputStream input = clazz.getResourceAsStream(name + HELP_FILE_EXT)) {
|
||||
Streams.readAllLines(input, new Callback<String>() {
|
||||
@Override
|
||||
|
@ -52,6 +52,6 @@ public class HelpPrinter {
|
|||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
terminal.println();
|
||||
terminal.println(Terminal.Verbosity.SILENT, "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,114 +19,71 @@
|
|||
|
||||
package org.elasticsearch.common.cli;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Console;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Locale;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
|
||||
/**
|
||||
*
|
||||
* A Terminal wraps access to reading input and writing output for a {@link CliTool}.
|
||||
*
|
||||
* The available methods are similar to those of {@link Console}, with the ability
|
||||
* to read either normal text or a password, and the ability to print a line
|
||||
* of text. Printing is also gated by the {@link Verbosity} of the terminal,
|
||||
* which allows {@link #println(Verbosity,String)} calls which act like a logger,
|
||||
* only actually printing if the verbosity level of the terminal is above
|
||||
* the verbosity of the message.
|
||||
*/
|
||||
@SuppressForbidden(reason = "System#out")
|
||||
public abstract class Terminal {
|
||||
|
||||
public static final Terminal DEFAULT = ConsoleTerminal.supported() ? new ConsoleTerminal() : new SystemTerminal();
|
||||
/** The default terminal implementation, which will be a console if available, or stdout/stderr if not. */
|
||||
public static final Terminal DEFAULT = ConsoleTerminal.isSupported() ? new ConsoleTerminal() : new SystemTerminal();
|
||||
|
||||
public static enum Verbosity {
|
||||
SILENT(0), NORMAL(1), VERBOSE(2);
|
||||
|
||||
private final int level;
|
||||
|
||||
private Verbosity(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public boolean enabled(Verbosity verbosity) {
|
||||
return level >= verbosity.level;
|
||||
}
|
||||
|
||||
public static Verbosity resolve(CommandLine cli) {
|
||||
if (cli.hasOption("s")) {
|
||||
return SILENT;
|
||||
}
|
||||
if (cli.hasOption("v")) {
|
||||
return VERBOSE;
|
||||
}
|
||||
return NORMAL;
|
||||
}
|
||||
/** Defines the available verbosity levels of messages to be printed. */
|
||||
public enum Verbosity {
|
||||
SILENT, /* always printed */
|
||||
NORMAL, /* printed when no options are given to cli */
|
||||
VERBOSE /* printed only when cli is passed verbose option */
|
||||
}
|
||||
|
||||
/** The current verbosity for the terminal, defaulting to {@link Verbosity#NORMAL}. */
|
||||
private Verbosity verbosity = Verbosity.NORMAL;
|
||||
|
||||
public Terminal() {
|
||||
this(Verbosity.NORMAL);
|
||||
}
|
||||
|
||||
public Terminal(Verbosity verbosity) {
|
||||
/** Sets the verbosity of the terminal. */
|
||||
void setVerbosity(Verbosity verbosity) {
|
||||
this.verbosity = verbosity;
|
||||
}
|
||||
|
||||
public void verbosity(Verbosity verbosity) {
|
||||
this.verbosity = verbosity;
|
||||
}
|
||||
/** Reads clear text from the terminal input. See {@link Console#readLine()}. */
|
||||
public abstract String readText(String prompt);
|
||||
|
||||
public Verbosity verbosity() {
|
||||
return verbosity;
|
||||
}
|
||||
/** Reads password text from the terminal input. See {@link Console#readPassword()}}. */
|
||||
public abstract char[] readSecret(String prompt);
|
||||
|
||||
public abstract String readText(String text, Object... args);
|
||||
/** Print a message directly to the terminal. */
|
||||
protected abstract void doPrint(String msg);
|
||||
|
||||
public abstract char[] readSecret(String text, Object... args);
|
||||
|
||||
protected abstract void printStackTrace(Throwable t);
|
||||
|
||||
public void println() {
|
||||
println(Verbosity.NORMAL);
|
||||
}
|
||||
|
||||
public void println(String msg) {
|
||||
/** Prints a line to the terminal at {@link Verbosity#NORMAL} verbosity level. */
|
||||
public final void println(String msg) {
|
||||
println(Verbosity.NORMAL, msg);
|
||||
}
|
||||
|
||||
public void print(String msg) {
|
||||
print(Verbosity.NORMAL, msg);
|
||||
}
|
||||
|
||||
public void println(Verbosity verbosity) {
|
||||
println(verbosity, "");
|
||||
}
|
||||
|
||||
public void println(Verbosity verbosity, String msg) {
|
||||
print(verbosity, msg + System.lineSeparator());
|
||||
}
|
||||
|
||||
public void print(Verbosity verbosity, String msg) {
|
||||
if (this.verbosity.enabled(verbosity)) {
|
||||
doPrint(msg);
|
||||
/** Prints a line to the terminal at {@code verbosity} level. */
|
||||
public final void println(Verbosity verbosity, String msg) {
|
||||
if (this.verbosity.ordinal() >= verbosity.ordinal()) {
|
||||
doPrint(msg + System.lineSeparator());
|
||||
}
|
||||
}
|
||||
|
||||
public void printError(String msg) {
|
||||
println(Verbosity.SILENT, "ERROR: " + msg);
|
||||
}
|
||||
|
||||
public void printWarn(String msg) {
|
||||
println(Verbosity.SILENT, "WARN: " + msg);
|
||||
}
|
||||
|
||||
protected abstract void doPrint(String msg);
|
||||
|
||||
private static class ConsoleTerminal extends Terminal {
|
||||
|
||||
final Console console = System.console();
|
||||
private static final Console console = System.console();
|
||||
|
||||
static boolean supported() {
|
||||
return System.console() != null;
|
||||
static boolean isSupported() {
|
||||
return console != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -136,35 +93,29 @@ public abstract class Terminal {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
return console.readLine(text, args);
|
||||
public String readText(String prompt) {
|
||||
return console.readLine("%s", prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return console.readPassword(text, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStackTrace(Throwable t) {
|
||||
t.printStackTrace(console.writer());
|
||||
public char[] readSecret(String prompt) {
|
||||
return console.readPassword("%s", prompt);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "System#out")
|
||||
private static class SystemTerminal extends Terminal {
|
||||
|
||||
private final PrintWriter printWriter = new PrintWriter(System.out);
|
||||
|
||||
@Override
|
||||
@SuppressForbidden(reason = "System#out")
|
||||
public void doPrint(String msg) {
|
||||
System.out.print(msg);
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
print(text);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
public String readText(String text) {
|
||||
doPrint(text);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset()));
|
||||
try {
|
||||
return reader.readLine();
|
||||
} catch (IOException ioe) {
|
||||
|
@ -173,13 +124,8 @@ public abstract class Terminal {
|
|||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return readText(text, args).toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStackTrace(Throwable t) {
|
||||
t.printStackTrace(printWriter);
|
||||
public char[] readSecret(String text) {
|
||||
return readText(text).toCharArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,8 +237,8 @@ public class InternalSettingsPreparer {
|
|||
}
|
||||
|
||||
if (secret) {
|
||||
return new String(terminal.readSecret("Enter value for [" + key + "]: ", key));
|
||||
return new String(terminal.readSecret("Enter value for [" + key + "]: "));
|
||||
}
|
||||
return terminal.readText("Enter value for [" + key + "]: ", key);
|
||||
return terminal.readText("Enter value for [" + key + "]: ");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class PluginSecurity {
|
|||
PermissionCollection permissions = parsePermissions(terminal, file, environment.tmpFile());
|
||||
List<Permission> requested = Collections.list(permissions.elements());
|
||||
if (requested.isEmpty()) {
|
||||
terminal.print(Verbosity.VERBOSE, "plugin has a policy file with no additional permissions");
|
||||
terminal.println(Verbosity.VERBOSE, "plugin has a policy file with no additional permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ class PluginSecurity {
|
|||
terminal.println(Verbosity.NORMAL, "See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html");
|
||||
terminal.println(Verbosity.NORMAL, "for descriptions of what these permissions allow and the associated risks.");
|
||||
if (!batch) {
|
||||
terminal.println(Verbosity.NORMAL);
|
||||
terminal.println(Verbosity.NORMAL, "");
|
||||
String text = terminal.readText("Continue with installation? [y/N]");
|
||||
if (!text.equalsIgnoreCase("y")) {
|
||||
throw new RuntimeException("installation aborted by user");
|
||||
|
|
|
@ -22,9 +22,6 @@ package org.elasticsearch.common.cli;
|
|||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TerminalTests extends CliToolTestCase {
|
||||
public void testVerbosity() throws Exception {
|
||||
CaptureOutputTerminal terminal = new CaptureOutputTerminal(Terminal.Verbosity.SILENT);
|
||||
|
@ -49,14 +46,14 @@ public class TerminalTests extends CliToolTestCase {
|
|||
}
|
||||
|
||||
private void assertPrinted(CaptureOutputTerminal logTerminal, Terminal.Verbosity verbosity, String text) {
|
||||
logTerminal.print(verbosity, text);
|
||||
assertThat(logTerminal.getTerminalOutput(), hasSize(1));
|
||||
assertThat(logTerminal.getTerminalOutput(), hasItem(text));
|
||||
logTerminal.println(verbosity, text);
|
||||
assertEquals(1, logTerminal.getTerminalOutput().size());
|
||||
assertTrue(logTerminal.getTerminalOutput().get(0).contains(text));
|
||||
logTerminal.terminalOutput.clear();
|
||||
}
|
||||
|
||||
private void assertNotPrinted(CaptureOutputTerminal logTerminal, Terminal.Verbosity verbosity, String text) {
|
||||
logTerminal.print(verbosity, text);
|
||||
logTerminal.println(verbosity, text);
|
||||
assertThat(logTerminal.getTerminalOutput(), hasSize(0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,22 +81,14 @@ public class InternalSettingsPreparerTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testReplacePromptPlaceholders() {
|
||||
final List<String> replacedSecretProperties = new ArrayList<>();
|
||||
final List<String> replacedTextProperties = new ArrayList<>();
|
||||
final Terminal terminal = new CliToolTestCase.MockTerminal() {
|
||||
@Override
|
||||
public char[] readSecret(String message, Object... args) {
|
||||
for (Object arg : args) {
|
||||
replacedSecretProperties.add((String) arg);
|
||||
}
|
||||
public char[] readSecret(String message) {
|
||||
return "replaced".toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String message, Object... args) {
|
||||
for (Object arg : args) {
|
||||
replacedTextProperties.add((String) arg);
|
||||
}
|
||||
public String readText(String message) {
|
||||
return "text";
|
||||
}
|
||||
};
|
||||
|
@ -112,8 +104,6 @@ public class InternalSettingsPreparerTests extends ESTestCase {
|
|||
.put("replace_me", InternalSettingsPreparer.TEXT_PROMPT_VALUE);
|
||||
Settings settings = InternalSettingsPreparer.prepareEnvironment(builder.build(), terminal).settings();
|
||||
|
||||
assertThat(replacedSecretProperties.size(), is(1));
|
||||
assertThat(replacedTextProperties.size(), is(1));
|
||||
assertThat(settings.get("password.replace"), equalTo("replaced"));
|
||||
assertThat(settings.get("replace_me"), equalTo("text"));
|
||||
|
||||
|
|
|
@ -45,9 +45,6 @@ import static org.hamcrest.Matchers.hasItem;
|
|||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@SuppressForbidden(reason = "modifies system properties intentionally")
|
||||
public class CliToolTests extends CliToolTestCase {
|
||||
public void testOK() throws Exception {
|
||||
|
@ -233,16 +230,14 @@ public class CliToolTests extends CliToolTestCase {
|
|||
final AtomicReference<String> promptedTextValue = new AtomicReference<>(null);
|
||||
final Terminal terminal = new MockTerminal() {
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
public char[] readSecret(String text) {
|
||||
counter.incrementAndGet();
|
||||
assertThat(args, arrayContaining((Object) "foo.password"));
|
||||
return "changeit".toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
public String readText(String text) {
|
||||
counter.incrementAndGet();
|
||||
assertThat(args, arrayContaining((Object) "replace"));
|
||||
return "replaced";
|
||||
}
|
||||
};
|
||||
|
|
|
@ -61,34 +61,18 @@ public abstract class CliToolTestCase extends ESTestCase {
|
|||
*/
|
||||
public static class MockTerminal extends Terminal {
|
||||
|
||||
public MockTerminal() {
|
||||
super(Verbosity.NORMAL);
|
||||
}
|
||||
|
||||
public MockTerminal(Verbosity verbosity) {
|
||||
super(verbosity);
|
||||
}
|
||||
@Override
|
||||
protected void doPrint(String msg) {}
|
||||
|
||||
@Override
|
||||
protected void doPrint(String msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
public String readText(String prompt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
public char[] readSecret(String prompt) {
|
||||
return new char[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStackTrace(Throwable t) {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,11 +83,11 @@ public abstract class CliToolTestCase extends ESTestCase {
|
|||
List<String> terminalOutput = new ArrayList<>();
|
||||
|
||||
public CaptureOutputTerminal() {
|
||||
super(Verbosity.NORMAL);
|
||||
this(Verbosity.NORMAL);
|
||||
}
|
||||
|
||||
public CaptureOutputTerminal(Verbosity verbosity) {
|
||||
super(verbosity);
|
||||
setVerbosity(verbosity);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,16 +95,6 @@ public abstract class CliToolTestCase extends ESTestCase {
|
|||
terminalOutput.add(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String msg) {
|
||||
doPrint(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStackTrace(Throwable t) {
|
||||
terminalOutput.add(ExceptionsHelper.stackTrace(t));
|
||||
}
|
||||
|
||||
public List<String> getTerminalOutput() {
|
||||
return terminalOutput;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue