Startup: Remove getopt parsing in shell script, use java CLITool

In order to ensure, we have the same experience across operating systems
and shells, this commit uses the java CLI parser instead of the shell
getopt parsing to parse arguments.

This also allows for support for paths, which contain spaces.

Also commons-cli depdency was upgraded to 1.3.1 and tests have been added.

Changes

* new exit code, OK_AND_EXIT, allowing to tell the caller to exit, as everything
  went as expected (e.g. when running a version output)

BWC breaking:

* execute() returns an ExitStatus instead of an integer, otherwise there is no
  possibility to signal by a command, if the JVM should be exited after a run.
  This affects plugins, that have command line tools
* -v used to be version, but is a verbose flag by default in the current CLI infra,
  must be -V or --version now
* -X has been removed - the current implementation was useless anyway, as
  it prefixed those properties with "es.". You should use
  ES_JAVA_OPTS/JAVA_OPTS for JVM configuration
This commit is contained in:
Alexander Reelsen 2015-07-30 10:28:18 +02:00
parent b3b34668e8
commit 69d7f1a78a
18 changed files with 672 additions and 196 deletions

View File

@ -19,12 +19,13 @@
package org.elasticsearch.bootstrap;
import org.apache.lucene.util.StringHelper;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.StringHelper;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.Version;
import org.elasticsearch.common.PidFile;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.CreationException;
@ -222,10 +223,17 @@ public class Bootstrap {
}
public static void main(String[] args) {
BootstrapCLIParser bootstrapCLIParser = new BootstrapCLIParser();
CliTool.ExitStatus status = bootstrapCLIParser.execute(args);
if (CliTool.ExitStatus.OK != status) {
System.exit(status.status());
}
System.setProperty("es.logger.prefix", "");
INSTANCE = new Bootstrap();
boolean foreground = System.getProperty("es.foreground", System.getProperty("es-foreground")) != null;
boolean foreground = !"false".equals(System.getProperty("es.foreground", System.getProperty("es-foreground")));
// handle the wrapper system property, if its a service, don't run as a service
if (System.getProperty("wrapper.service", "XXX").equalsIgnoreCase("true")) {
foreground = false;

View File

@ -0,0 +1,167 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.bootstrap;
import com.google.common.base.Strings;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.elasticsearch.Build;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.monitor.jvm.JvmInfo;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.optionBuilder;
public class BootstrapCLIParser extends CliTool {
private static final CliToolConfig CONFIG = CliToolConfig.config("elasticsearch", BootstrapCLIParser.class)
.cmds(Start.CMD, Version.CMD)
.build();
public BootstrapCLIParser() {
super(CONFIG);
}
public BootstrapCLIParser(Terminal terminal) {
super(CONFIG, terminal);
}
@Override
protected Command parse(String cmdName, CommandLine cli) throws Exception {
switch (cmdName.toLowerCase(Locale.ROOT)) {
case Start.NAME:
return Start.parse(terminal, cli);
case Version.NAME:
return Version.parse(terminal, cli);
default:
assert false : "should never get here, if the user enters an unknown command, an error message should be shown before parse is called";
return null;
}
}
static class Version extends CliTool.Command {
private static final String NAME = "version";
private static final CliToolConfig.Cmd CMD = cmd(NAME, Version.class).build();
public static Command parse(Terminal terminal, CommandLine cli) {
return new Version(terminal);
}
public Version(Terminal terminal) {
super(terminal);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
terminal.println("Version: %s, Build: %s/%s, JVM: %s", org.elasticsearch.Version.CURRENT, Build.CURRENT.hashShort(), Build.CURRENT.timestamp(), JvmInfo.jvmInfo().version());
return ExitStatus.OK_AND_EXIT;
}
}
static class Start extends CliTool.Command {
private static final String NAME = "start";
private static final CliToolConfig.Cmd CMD = cmd(NAME, Start.class)
.options(
optionBuilder("d", "daemonize").hasArg(false).required(false),
optionBuilder("p", "pidfile").hasArg(true).required(false),
optionBuilder("V", "version").hasArg(false).required(false),
Option.builder("D").argName("property=value").valueSeparator('=').numberOfArgs(2)
)
.stopAtNonOption(true) // needed to parse the --foo.bar options, so this parser must be lenient
.build();
public static Command parse(Terminal terminal, CommandLine cli) {
if (cli.hasOption("V")) {
return Version.parse(terminal, cli);
}
if (cli.hasOption("d")) {
System.setProperty("es.foreground", "false");
}
String pidFile = cli.getOptionValue("pidfile");
if (!Strings.isNullOrEmpty(pidFile)) {
System.setProperty("es.pidfile", pidFile);
}
if (cli.hasOption("D")) {
Properties properties = cli.getOptionProperties("D");
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String key = (String) entry.getKey();
String propertyName = key.startsWith("es.") ? key : "es." + key;
System.setProperty(propertyName, entry.getValue().toString());
}
}
// hacky way to extract all the fancy extra args, there is no CLI tool helper for this
Iterator<String> iterator = cli.getArgList().iterator();
while (iterator.hasNext()) {
String arg = iterator.next();
if (!arg.startsWith("--")) {
throw new IllegalArgumentException("Parameter [" + arg + "]does not start with --");
}
// if there is no = sign, we have to get the next argu
arg = arg.replace("--", "");
if (arg.contains("=")) {
String[] splitArg = arg.split("=", 2);
String key = splitArg[0];
String value = splitArg[1];
System.setProperty("es." + key, value);
} else {
if (iterator.hasNext()) {
String value = iterator.next();
if (value.startsWith("--")) {
throw new IllegalArgumentException("Parameter [" + arg + "] needs value");
}
System.setProperty("es." + arg, value);
} else {
throw new IllegalArgumentException("Parameter [" + arg + "] needs value");
}
}
}
return new Start(terminal);
}
public Start(Terminal terminal) {
super(terminal);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
return ExitStatus.OK;
}
}
}

View File

@ -22,6 +22,7 @@ package org.elasticsearch.common.cli;
import com.google.common.base.Preconditions;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.GnuParser;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Settings;
@ -54,6 +55,7 @@ public abstract class CliTool {
// based on sysexits.h
public static enum ExitStatus {
OK(0),
OK_AND_EXIT(0),
USAGE(64), /* command line usage error */
DATA_ERROR(65), /* data format error */
NO_INPUT(66), /* cannot open input */
@ -77,6 +79,16 @@ public abstract class CliTool {
public int status() {
return status;
}
public static ExitStatus fromStatus(int status) {
for (ExitStatus exitStatus : values()) {
if (exitStatus.status() == status) {
return exitStatus;
}
}
return null;
}
}
protected final Terminal terminal;
@ -98,14 +110,14 @@ public abstract class CliTool {
env = tuple.v2();
}
public final int execute(String... args) {
public final ExitStatus 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;
return ExitStatus.OK_AND_EXIT;
}
CliToolConfig.Cmd cmd;
@ -116,14 +128,14 @@ public abstract class CliTool {
if (args.length == 0) {
terminal.printError("command not specified");
config.printUsage(terminal);
return ExitStatus.USAGE.status;
return ExitStatus.USAGE;
}
String cmdName = args[0];
cmd = config.cmd(cmdName);
if (cmd == null) {
terminal.printError("unknown command [%s]. Use [-h] option to list available commands", cmdName);
return ExitStatus.USAGE.status;
return ExitStatus.USAGE;
}
// we now remove the command name from the args
@ -140,20 +152,19 @@ public abstract class CliTool {
try {
command = parse(cmd, args);
return command.execute(settings, env).status;
return command.execute(settings, env);
} catch (IOException ioe) {
terminal.printError(ioe);
return ExitStatus.IO_ERROR.status;
return ExitStatus.IO_ERROR;
} catch (IllegalArgumentException ilae) {
terminal.printError(ilae);
return ExitStatus.USAGE.status;
return ExitStatus.USAGE;
} catch (Throwable t) {
terminal.printError(t);
if (command == null) {
return ExitStatus.USAGE.status;
return ExitStatus.USAGE;
}
return ExitStatus.CODE_ERROR.status;
return ExitStatus.CODE_ERROR;
}
}
@ -163,12 +174,12 @@ public abstract class CliTool {
}
public Command parse(CliToolConfig.Cmd cmd, String[] args) throws Exception {
CommandLineParser parser = new GnuParser();
CommandLineParser parser = new DefaultParser();
CommandLine cli = parser.parse(CliToolConfig.OptionsSource.HELP.options(), args, true);
if (cli.hasOption("h")) {
return helpCmd(cmd);
}
cli = parser.parse(cmd.options(), args);
cli = parser.parse(cmd.options(), args, cmd.isStopAtNonOption());
Terminal.Verbosity verbosity = Terminal.Verbosity.resolve(cli);
terminal.verbosity(verbosity);
return parse(cmd.name(), cli);
@ -210,7 +221,7 @@ public abstract class CliTool {
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
cmd.printUsage(terminal);
return ExitStatus.OK;
return ExitStatus.OK_AND_EXIT;
}
}

View File

@ -20,6 +20,7 @@
package org.elasticsearch.common.cli;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
@ -90,6 +91,10 @@ public class CliToolConfig {
return new OptionBuilder(shortName, longName);
}
public static Option.Builder optionBuilder(String shortName, String longName) {
return Option.builder(shortName).argName(longName).longOpt(longName);
}
public static OptionGroupBuilder optionGroup(boolean required) {
return new OptionGroupBuilder(required);
}
@ -131,11 +136,13 @@ public class CliToolConfig {
private final String name;
private final Class<? extends CliTool.Command> cmdType;
private final Options options;
private final boolean stopAtNonOption;
private Cmd(String name, Class<? extends CliTool.Command> cmdType, Options options) {
private Cmd(String name, Class<? extends CliTool.Command> cmdType, Options options, boolean stopAtNonOption) {
this.name = name;
this.cmdType = cmdType;
this.options = options;
this.stopAtNonOption = stopAtNonOption;
OptionsSource.VERBOSITY.populate(options);
}
@ -148,16 +155,11 @@ public class CliToolConfig {
}
public Options options() {
// TODO Remove this when commons-cli 1.3 will be released
// and replace by return options;
// See https://issues.apache.org/jira/browse/CLI-183
Options copy = new Options();
for (Object oOption : options.getOptions()) {
Option option = (Option) oOption;
copy.addOption(option);
}
OptionsSource.VERBOSITY.populate(copy);
return copy;
return options;
}
public boolean isStopAtNonOption() {
return stopAtNonOption;
}
public void printUsage(Terminal terminal) {
@ -169,6 +171,7 @@ public class CliToolConfig {
private final String name;
private final Class<? extends CliTool.Command> cmdType;
private Options options = new Options();
private boolean stopAtNonOption = false;
private Builder(String name, Class<? extends CliTool.Command> cmdType) {
this.name = name;
@ -182,6 +185,13 @@ public class CliToolConfig {
return this;
}
public Builder options(Option.Builder... optionBuilders) {
for (int i = 0; i < optionBuilders.length; i++) {
options.addOption(optionBuilders[i].build());
}
return this;
}
public Builder optionGroups(OptionGroupBuilder... optionGroupBuilders) {
for (OptionGroupBuilder builder : optionGroupBuilders) {
options.addOptionGroup(builder.build());
@ -189,8 +199,19 @@ public class CliToolConfig {
return this;
}
/**
* @param stopAtNonOption if <tt>true</tt> an unrecognized argument stops
* the parsing and the remaining arguments are added to the
* args list. If <tt>false</tt> an unrecognized
* argument triggers a ParseException.
*/
public Builder stopAtNonOption(boolean stopAtNonOption) {
this.stopAtNonOption = stopAtNonOption;
return this;
}
public Cmd build() {
return new Cmd(name, cmdType, options);
return new Cmd(name, cmdType, options, stopAtNonOption);
}
}
}

View File

@ -51,7 +51,7 @@ public class PluginManagerCliParser extends CliTool {
public static void main(String[] args) {
Tuple<Settings, Environment> initialSettings = InternalSettingsPreparer.prepareSettings(EMPTY, true, Terminal.DEFAULT);
LogConfigurator.configure(initialSettings.v1());
int status = new PluginManagerCliParser().execute(args);
int status = new PluginManagerCliParser().execute(args).status();
System.exit(status);
}

View File

@ -0,0 +1,26 @@
NAME
start - start Elasticsearcion
SYNOPSIS
elasticsearch start
DESCRIPTION
This command starts Elasticsearch. You can configure it to run in the foreground, write a pid file
and configure arbitrary options that override file-based configuration.
OPTIONS
-h,--help Shows this message
-p,--pidfile <pidfile> Creates a pid file in the specified path on start
-d,--daemonize Starts Elasticsearch in the background
-Dproperty=value Configures an Elasticsearch specific property, like -Dnetwork.host=127.0.0.1
--property=value Configures an elasticsearch specific property, like --network.host 127.0.0.1
--property value

View File

@ -0,0 +1,16 @@
NAME
version - Show version information and exit
SYNOPSIS
elasticsearch version
DESCRIPTION
This command shows Elasticsearch version, timestamp and build information as well as JVM info
OPTIONS
-h,--help Shows this message

View File

@ -0,0 +1,22 @@
NAME
elasticsearch - Manages elasticsearch
SYNOPSIS
elasticsearch <command>
DESCRIPTION
Start elasticsearch and manage plugins
COMMANDS
start Start elasticsearch
version Show version information and exit
NOTES
[*] For usage help on specific commands please type "elasticsearch <command> -h"

View File

@ -0,0 +1,246 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.bootstrap;
import com.google.common.base.Joiner;
import org.elasticsearch.Build;
import org.elasticsearch.Version;
import org.elasticsearch.common.cli.CliTool.ExitStatus;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.monitor.jvm.JvmInfo;
import org.hamcrest.Matcher;
import org.junit.After;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.OK;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.OK_AND_EXIT;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.USAGE;
import static org.hamcrest.Matchers.*;
public class BootstrapCliParserTests extends CliToolTestCase {
private CaptureOutputTerminal terminal = new CaptureOutputTerminal();
private List<String> propertiesToClear = new ArrayList<>();
@After
public void clearProperties() {
for (String property : propertiesToClear) {
System.clearProperty(property);
}
}
public void testThatVersionIsReturned() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
ExitStatus status = parser.execute(args("version"));
assertStatus(status, OK_AND_EXIT);
assertThatTerminalOutput(containsString(Version.CURRENT.toString()));
assertThatTerminalOutput(containsString(Build.CURRENT.hashShort()));
assertThatTerminalOutput(containsString(Build.CURRENT.timestamp()));
assertThatTerminalOutput(containsString(JvmInfo.jvmInfo().version()));
}
public void testThatVersionIsReturnedAsStartParameter() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
ExitStatus status = parser.execute(args("start -V"));
assertStatus(status, OK_AND_EXIT);
assertThatTerminalOutput(containsString(Version.CURRENT.toString()));
assertThatTerminalOutput(containsString(Build.CURRENT.hashShort()));
assertThatTerminalOutput(containsString(Build.CURRENT.timestamp()));
assertThatTerminalOutput(containsString(JvmInfo.jvmInfo().version()));
CaptureOutputTerminal terminal = new CaptureOutputTerminal();
parser = new BootstrapCLIParser(terminal);
status = parser.execute(args("start --version"));
assertStatus(status, OK_AND_EXIT);
assertThatTerminalOutput(containsString(Version.CURRENT.toString()));
assertThatTerminalOutput(containsString(Build.CURRENT.hashShort()));
assertThatTerminalOutput(containsString(Build.CURRENT.timestamp()));
assertThatTerminalOutput(containsString(JvmInfo.jvmInfo().version()));
}
public void testThatPidFileCanBeConfigured() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.pidfile");
ExitStatus status = parser.execute(args("start --pidfile")); // missing pid file
assertStatus(status, USAGE);
// good cases
status = parser.execute(args("start --pidfile /tmp/pid"));
assertStatus(status, OK);
assertSystemProperty("es.pidfile", "/tmp/pid");
System.clearProperty("es.pidfile");
status = parser.execute(args("start -p /tmp/pid"));
assertStatus(status, OK);
assertSystemProperty("es.pidfile", "/tmp/pid");
}
public void testThatParsingDaemonizeWorks() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.foreground");
ExitStatus status = parser.execute(args("start -d"));
assertStatus(status, OK);
assertThat(System.getProperty("es.foreground"), is("false"));
}
public void testThatNotDaemonizingDoesNotConfigureProperties() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.foreground");
ExitStatus status = parser.execute(args("start"));
assertStatus(status, OK);
assertThat(System.getProperty("es.foreground"), is(nullValue()));
}
public void testThatJavaPropertyStyleArgumentsCanBeParsed() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.foo", "es.spam");
ExitStatus status = parser.execute(args("start -Dfoo=bar -Dspam=eggs"));
assertStatus(status, OK);
assertSystemProperty("es.foo", "bar");
assertSystemProperty("es.spam", "eggs");
}
public void testThatJavaPropertyStyleArgumentsWithEsPrefixAreNotPrefixedTwice() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.spam", "es.pidfile");
ExitStatus status = parser.execute(args("start -Des.pidfile=/path/to/foo/elasticsearch/distribution/zip/target/integ-tests/es.pid -Dspam=eggs"));
assertStatus(status, OK);
assertThat(System.getProperty("es.es.pidfile"), is(nullValue()));
assertSystemProperty("es.pidfile", "/path/to/foo/elasticsearch/distribution/zip/target/integ-tests/es.pid");
assertSystemProperty("es.spam", "eggs");
}
public void testThatUnknownLongOptionsCanBeParsed() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.network.host", "es.my.option");
ExitStatus status = parser.execute(args("start --network.host 127.0.0.1 --my.option=true"));
assertStatus(status, OK);
assertSystemProperty("es.network.host", "127.0.0.1");
assertSystemProperty("es.my.option", "true");
}
public void testThatUnknownLongOptionsNeedAValue() throws Exception {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.network.host");
ExitStatus status = parser.execute(args("start --network.host"));
assertStatus(status, USAGE);
assertThatTerminalOutput(containsString("Parameter [network.host] needs value"));
status = parser.execute(args("start --network.host --foo"));
assertStatus(status, USAGE);
assertThatTerminalOutput(containsString("Parameter [network.host] needs value"));
}
public void testParsingErrors() {
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
// unknown params
ExitStatus status = parser.execute(args("version --unknown-param /tmp/pid"));
assertStatus(status, USAGE);
assertThatTerminalOutput(containsString("Unrecognized option: --unknown-param"));
// single dash in extra params
terminal = new CaptureOutputTerminal();
parser = new BootstrapCLIParser(terminal);
status = parser.execute(args("start -network.host 127.0.0.1"));
assertStatus(status, USAGE);
assertThatTerminalOutput(containsString("Parameter [-network.host]does not start with --"));
// never ended parameter
terminal = new CaptureOutputTerminal();
parser = new BootstrapCLIParser(terminal);
status = parser.execute(args("start --network.host"));
assertStatus(status, USAGE);
assertThatTerminalOutput(containsString("Parameter [network.host] needs value"));
// free floating value
terminal = new CaptureOutputTerminal();
parser = new BootstrapCLIParser(terminal);
status = parser.execute(args("start 127.0.0.1"));
assertStatus(status, USAGE);
assertThatTerminalOutput(containsString("Parameter [127.0.0.1]does not start with --"));
}
public void testHelpWorks() throws Exception {
List<Tuple<String, String>> tuples = new ArrayList<>();
tuples.add(new Tuple<>("version --help", "elasticsearch-version.help"));
tuples.add(new Tuple<>("version -h", "elasticsearch-version.help"));
tuples.add(new Tuple<>("start --help", "elasticsearch-start.help"));
tuples.add(new Tuple<>("start -h", "elasticsearch-start.help"));
tuples.add(new Tuple<>("--help", "elasticsearch.help"));
tuples.add(new Tuple<>("-h", "elasticsearch.help"));
for (Tuple<String, String> tuple : tuples) {
terminal = new CaptureOutputTerminal();
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
ExitStatus status = parser.execute(args(tuple.v1()));
assertStatus(status, OK_AND_EXIT);
String expectedDocs = Streams.copyToStringFromClasspath("/org/elasticsearch/bootstrap/" + tuple.v2());
String returnedDocs = Joiner.on("").join(terminal.getTerminalOutput());
assertThat(returnedDocs.trim(), is(expectedDocs.trim()));
}
}
public void testThatSpacesInParametersAreSupported() throws Exception {
// emulates: bin/elasticsearch --node.name "'my node with spaces'" --pidfile "'/tmp/my pid.pid'"
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
registerProperties("es.pidfile", "es.my.param");
ExitStatus status = parser.execute("start", "--pidfile", "foo with space", "--my.param", "my awesome neighbour");
assertStatus(status, OK);
assertSystemProperty("es.pidfile", "foo with space");
assertSystemProperty("es.my.param", "my awesome neighbour");
}
private void registerProperties(String ... systemProperties) {
propertiesToClear.addAll(Arrays.asList(systemProperties));
}
private void assertSystemProperty(String name, String expectedValue) {
String msg = String.format(Locale.ROOT, "Expected property %s to be %s, terminal output was %s", name, expectedValue, terminal.getTerminalOutput());
assertThat(msg, System.getProperty(name), is(expectedValue));
}
private void assertStatus(ExitStatus status, ExitStatus expectedStatus) {
assertThat(String.format(Locale.ROOT, "Expected status to be [%s], but was [%s], terminal output was %s", expectedStatus, status, terminal.getTerminalOutput()), status, is(expectedStatus));
}
private void assertThatTerminalOutput(Matcher<String> matcher) {
assertThat(terminal.getTerminalOutput(), hasItem(matcher));
}
}

View File

@ -33,6 +33,8 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.OK;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.USAGE;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.hamcrest.Matchers.*;
@ -49,12 +51,12 @@ public class CliToolTests extends CliToolTestCase {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) {
executed.set(true);
return CliTool.ExitStatus.OK;
return OK;
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
assertStatus(status, CliTool.ExitStatus.OK);
CliTool.ExitStatus status = tool.execute();
assertStatus(status, OK);
assertCommandHasBeenExecuted(executed);
}
@ -70,7 +72,7 @@ public class CliToolTests extends CliToolTestCase {
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
CliTool.ExitStatus status = tool.execute();
assertStatus(status, CliTool.ExitStatus.USAGE);
assertCommandHasBeenExecuted(executed);
}
@ -87,7 +89,7 @@ public class CliToolTests extends CliToolTestCase {
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
CliTool.ExitStatus status = tool.execute();
assertStatus(status, CliTool.ExitStatus.IO_ERROR);
assertCommandHasBeenExecuted(executed);
}
@ -104,7 +106,7 @@ public class CliToolTests extends CliToolTestCase {
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute();
CliTool.ExitStatus status = tool.execute();
assertStatus(status, CliTool.ExitStatus.CODE_ERROR);
assertCommandHasBeenExecuted(executed);
}
@ -124,14 +126,14 @@ public class CliToolTests extends CliToolTestCase {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
executed[index].set(true);
return CliTool.ExitStatus.OK;
return 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()));
CliTool.ExitStatus status = tool.execute("cmd" + cmdIndex);
assertThat(status, is(OK));
for (int i = 0; i < executed.length; i++) {
assertThat(executed[i].get(), is(i == cmdIndex));
}
@ -152,13 +154,13 @@ public class CliToolTests extends CliToolTestCase {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
executed[index].set(true);
return CliTool.ExitStatus.OK;
return 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()));
CliTool.ExitStatus status = tool.execute("cmd" + count); // "cmd" + count doesn't exist
assertThat(status, is(CliTool.ExitStatus.USAGE));
for (int i = 0; i < executed.length; i++) {
assertThat(executed[i].get(), is(false));
}
@ -176,8 +178,8 @@ public class CliToolTests extends CliToolTestCase {
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
int status = tool.execute(args("-h"));
assertStatus(status, CliTool.ExitStatus.OK);
CliTool.ExitStatus status = tool.execute(args("-h"));
assertStatus(status, CliTool.ExitStatus.OK_AND_EXIT);
assertThat(terminal.getTerminalOutput(), hasSize(3));
assertThat(terminal.getTerminalOutput(), hasItem(containsString("cmd1 help")));
}
@ -189,18 +191,18 @@ public class CliToolTests extends CliToolTestCase {
cmds[0] = new NamedCommand("cmd0", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
return OK;
}
};
cmds[1] = new NamedCommand("cmd1", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
return OK;
}
};
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
int status = tool.execute(args("-h"));
assertStatus(status, CliTool.ExitStatus.OK);
CliTool.ExitStatus status = tool.execute(args("-h"));
assertStatus(status, CliTool.ExitStatus.OK_AND_EXIT);
assertThat(terminal.getTerminalOutput(), hasSize(3));
assertThat(terminal.getTerminalOutput(), hasItem(containsString("tool help")));
}
@ -212,18 +214,18 @@ public class CliToolTests extends CliToolTestCase {
cmds[0] = new NamedCommand("cmd0", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
return OK;
}
};
cmds[1] = new NamedCommand("cmd1", terminal) {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
return CliTool.ExitStatus.OK;
return OK;
}
};
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
int status = tool.execute(args("cmd1 -h"));
assertStatus(status, CliTool.ExitStatus.OK);
CliTool.ExitStatus status = tool.execute(args("cmd1 -h"));
assertStatus(status, CliTool.ExitStatus.OK_AND_EXIT);
assertThat(terminal.getTerminalOutput(), hasSize(3));
assertThat(terminal.getTerminalOutput(), hasItem(containsString("cmd1 help")));
}
@ -264,7 +266,7 @@ public class CliToolTests extends CliToolTestCase {
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) {
executed.set(true);
return CliTool.ExitStatus.OK;
return OK;
}
};
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
@ -298,7 +300,7 @@ public class CliToolTests extends CliToolTestCase {
public CliTool.ExitStatus execute(Settings settings, Environment env) {
promptedSecretValue.set(settings.get("foo.password"));
promptedTextValue.set(settings.get("replace"));
return CliTool.ExitStatus.OK;
return OK;
}
};
@ -316,8 +318,44 @@ public class CliToolTests extends CliToolTestCase {
assertThat(promptedTextValue.get(), is("replaced"));
}
private void assertStatus(int status, CliTool.ExitStatus expectedStatus) {
assertThat(status, is(expectedStatus.status()));
@Test
public void testStopAtNonOptionParsing() throws Exception {
final CliToolConfig.Cmd lenientCommand = cmd("lenient", CliTool.Command.Exit.class).stopAtNonOption(true).build();
final CliToolConfig.Cmd strictCommand = cmd("strict", CliTool.Command.Exit.class).stopAtNonOption(false).build();
final CliToolConfig config = CliToolConfig.config("elasticsearch", CliTool.class).cmds(lenientCommand, strictCommand).build();
final CaptureOutputTerminal terminal = new CaptureOutputTerminal();
final CliTool cliTool = new CliTool(config, terminal) {
@Override
protected Command parse(String cmdName, CommandLine cli) throws Exception {
return new NamedCommand(cmdName, terminal) {
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
return OK;
}
};
}
};
// known parameters, no error
assertStatus(cliTool.execute(args("lenient --verbose")), OK);
assertStatus(cliTool.execute(args("lenient -v")), OK);
// unknown parameters, no error
assertStatus(cliTool.execute(args("lenient --unknown")), OK);
assertStatus(cliTool.execute(args("lenient -u")), OK);
// unknown parameters, error
assertStatus(cliTool.execute(args("strict --unknown")), USAGE);
assertThat(terminal.getTerminalOutput(), hasItem(containsString("Unrecognized option: --unknown")));
terminal.getTerminalOutput().clear();
assertStatus(cliTool.execute(args("strict -u")), USAGE);
assertThat(terminal.getTerminalOutput(), hasItem(containsString("Unrecognized option: -u")));
}
private void assertStatus(CliTool.ExitStatus status, CliTool.ExitStatus expectedStatus) {
assertThat(status, is(expectedStatus));
}
private void assertCommandHasBeenExecuted(AtomicReference<Boolean> executed) {

View File

@ -29,6 +29,7 @@ import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.OK;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.OK_AND_EXIT;
import static org.hamcrest.Matchers.*;
public class PluginManagerCliTests extends CliToolTestCase {
@ -36,22 +37,22 @@ public class PluginManagerCliTests extends CliToolTestCase {
@Test
public void testHelpWorks() throws IOException {
CliToolTestCase.CaptureOutputTerminal terminal = new CliToolTestCase.CaptureOutputTerminal();
assertThat(new PluginManagerCliParser(terminal).execute(args("--help")), is(OK.status()));
assertThat(new PluginManagerCliParser(terminal).execute(args("--help")), is(OK_AND_EXIT));
assertHelp(terminal, "/org/elasticsearch/plugins/plugin.help");
terminal.getTerminalOutput().clear();
assertThat(new PluginManagerCliParser(terminal).execute(args("install -h")), is(OK.status()));
assertThat(new PluginManagerCliParser(terminal).execute(args("install -h")), is(OK_AND_EXIT));
assertHelp(terminal, "/org/elasticsearch/plugins/plugin-install.help");
for (String plugin : PluginManager.OFFICIAL_PLUGINS) {
assertThat(terminal.getTerminalOutput(), hasItem(containsString(plugin)));
}
terminal.getTerminalOutput().clear();
assertThat(new PluginManagerCliParser(terminal).execute(args("remove --help")), is(OK.status()));
assertThat(new PluginManagerCliParser(terminal).execute(args("remove --help")), is(OK_AND_EXIT));
assertHelp(terminal, "/org/elasticsearch/plugins/plugin-remove.help");
terminal.getTerminalOutput().clear();
assertThat(new PluginManagerCliParser(terminal).execute(args("list -h")), is(OK.status()));
assertThat(new PluginManagerCliParser(terminal).execute(args("list -h")), is(OK_AND_EXIT));
assertHelp(terminal, "/org/elasticsearch/plugins/plugin-list.help");
}

View File

@ -18,12 +18,14 @@
*/
package org.elasticsearch.plugins;
import com.google.common.base.Joiner;
import org.apache.http.impl.client.HttpClients;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.Version;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliTool.ExitStatus;
import org.elasticsearch.common.cli.CliToolTestCase.CaptureOutputTerminal;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
@ -50,15 +52,16 @@ import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.OK_AND_EXIT;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.USAGE;
import static org.elasticsearch.common.cli.CliToolTestCase.args;
import static org.elasticsearch.common.io.FileSystemUtilsTests.assertFileContent;
import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.elasticsearch.plugins.PluginInfoTests.writeProperties;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertDirectoryExists;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFileExists;
import static org.hamcrest.Matchers.*;
import static org.elasticsearch.plugins.PluginInfoTests.writeProperties;
@ClusterScope(scope = Scope.TEST, numDataNodes = 0, transportClientRatio = 0.0)
@LuceneTestCase.SuppressFileSystems("*") // TODO: clean up this test to allow extra files
@ -434,15 +437,13 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
@Test
public void testRemovePlugin_NullName_ThrowsException() throws IOException {
int status = new PluginManagerCliParser(terminal).execute(args("remove "));
assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(USAGE.status()));
assertStatus("remove ", USAGE);
}
@Test
public void testRemovePluginWithURLForm() throws Exception {
int status = new PluginManagerCliParser(terminal).execute(args("remove file://whatever"));
assertStatus("remove file://whatever", USAGE);
assertThat(terminal.getTerminalOutput(), hasItem(containsString("Illegal plugin name")));
assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(USAGE.status()));
}
@Test
@ -479,6 +480,33 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
}
}
@Test
public void testHelpWorks() throws IOException {
assertStatus("--help", OK_AND_EXIT);
assertHelp("/org/elasticsearch/plugins/plugin.help");
terminal.getTerminalOutput().clear();
assertStatus("install -h", OK_AND_EXIT);
assertHelp("/org/elasticsearch/plugins/plugin-install.help");
for (String plugin : PluginManager.OFFICIAL_PLUGINS) {
assertThat(terminal.getTerminalOutput(), hasItem(containsString(plugin)));
}
terminal.getTerminalOutput().clear();
assertStatus("remove --help", OK_AND_EXIT);
assertHelp("/org/elasticsearch/plugins/plugin-remove.help");
terminal.getTerminalOutput().clear();
assertStatus("list -h", OK_AND_EXIT);
assertHelp("/org/elasticsearch/plugins/plugin-list.help");
}
private void assertHelp(String classPath) throws IOException {
String expectedDocs = Streams.copyToStringFromClasspath(classPath);
String returnedDocs = Joiner.on("").join(terminal.getTerminalOutput());
assertThat(returnedDocs.trim(), is(expectedDocs.trim()));
}
private Tuple<Settings, Environment> buildInitialSettings() throws IOException {
Settings settings = settingsBuilder()
.put("discovery.zen.ping.multicast.enabled", false)
@ -488,12 +516,12 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
}
private void assertStatusOk(String command) {
assertStatus(command, CliTool.ExitStatus.OK);
assertStatus(command, ExitStatus.OK);
}
private void assertStatus(String command, CliTool.ExitStatus exitStatus) {
int status = new PluginManagerCliParser(terminal).execute(args(command));
assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(exitStatus.status()));
private void assertStatus(String command, ExitStatus exitStatus) {
ExitStatus status = new PluginManagerCliParser(terminal).execute(args(command));
assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(exitStatus));
}
private void assertThatPluginIsListed(String pluginName) {

View File

@ -1 +0,0 @@
2bf96b7aa8b611c177d329452af1dc933e14501c

View File

@ -0,0 +1 @@
1303efbc4b181e5a58bf2e967dc156a3132b97c0

View File

@ -1,16 +1,5 @@
#!/bin/sh
# OPTIONS:
# -d daemonize (run in background)
# -p pidfile write PID to <pidfile>
# -h
# --help print command line options
# -v print elasticsearch version, then exit
# -D prop set JAVA system property
# -X prop set non-standard JAVA system property
# --prop=val
# --prop val set elasticsearch property (i.e. -Des.<prop>=<val>)
# CONTROLLING STARTUP:
#
# This script relies on few environment variables to determine startup
@ -132,120 +121,16 @@ case `uname` in
;;
esac
launch_service()
{
pidpath=$1
daemonized=$2
props=$3
es_parms="-Delasticsearch"
export HOSTNAME=`hostname -s`
if [ "x$pidpath" != "x" ]; then
es_parms="$es_parms -Des.pidfile=$pidpath"
fi
# Make sure we dont use any predefined locale, as we check some exception message strings and rely on english language
# As those strings are created by the OS, they are dependant on the configured locale
LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8
export HOSTNAME=`hostname -s`
# The es-foreground option will tell Elasticsearch not to close stdout/stderr, but it's up to us not to daemonize.
if [ "x$daemonized" = "x" ]; then
es_parms="$es_parms -Des.foreground=yes"
eval exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS $es_parms "\"-Des.path.home=$ES_HOME\"" -cp "\"$ES_CLASSPATH\"" $props \
org.elasticsearch.bootstrap.Elasticsearch
# exec without running it in the background, makes it replace this shell, we'll never get here...
# no need to return something
else
# Startup Elasticsearch, background it, and write the pid.
eval exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS $es_parms "\"-Des.path.home=$ES_HOME\"" -cp "\"$ES_CLASSPATH\"" $props \
org.elasticsearch.bootstrap.Elasticsearch <&- &
return $?
fi
}
# Print command line usage / help
usage() {
echo "Usage: $0 [-vdh] [-p pidfile] [-D prop] [-X prop]"
echo "Start elasticsearch."
echo " -d daemonize (run in background)"
echo " -p pidfile write PID to <pidfile>"
echo " -h"
echo " --help print command line options"
echo " -v print elasticsearch version, then exit"
echo " -D prop set JAVA system property"
echo " -X prop set non-standard JAVA system property"
echo " --prop=val"
echo " --prop val set elasticsearch property (i.e. -Des.<prop>=<val>)"
}
# Parse any long getopt options and put them into properties before calling getopt below
# Be dash compatible to make sure running under ubuntu works
ARGV=""
while [ $# -gt 0 ]
do
case $1 in
--help) ARGV="$ARGV -h"; shift;;
--*=*) properties="$properties -Des.${1#--}"
shift 1
;;
--*) [ $# -le 1 ] && {
echo "Option requires an argument: '$1'."
shift
continue
}
properties="$properties -Des.${1#--}=$2"
shift 2
;;
*) ARGV="$ARGV $1" ; shift
esac
done
# Parse any command line options.
args=`getopt vdhp:D:X: $ARGV`
eval set -- "$args"
while true; do
case $1 in
-v)
eval "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS $es_parms "\"-Des.path.home=$ES_HOME\"" -cp "\"$ES_CLASSPATH\"" $props \
org.elasticsearch.Version
exit 0
;;
-p)
pidfile="$2"
shift 2
;;
-d)
daemonized="yes"
shift
;;
-h)
usage
exit 0
;;
-D)
properties="$properties -D$2"
shift 2
;;
-X)
properties="$properties -X$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Error parsing argument $1!" >&2
usage
exit 1
;;
esac
done
# Start up the service
launch_service "$pidfile" "$daemonized" "$properties"
# manual parsing to find out, if process should be detached
daemonized=`echo $* | grep -E -- '(^-d |-d$| -d |--daemonize$|--daemonize )'`
if [ -z "$daemonized" ] ; then
eval exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS "\"-Des.path.home=$ES_HOME\"" -cp "\"$ES_CLASSPATH\"" \
org.elasticsearch.bootstrap.Elasticsearch start $*
else
eval exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS "\"-Des.path.home=$ES_HOME\"" -cp "\"$ES_CLASSPATH\"" \
org.elasticsearch.bootstrap.Elasticsearch start $* <&- &
fi
exit $?

View File

@ -43,6 +43,6 @@ IF ERRORLEVEL 1 (
EXIT /B %ERRORLEVEL%
)
"%JAVA_HOME%\bin\java" %JAVA_OPTS% %ES_JAVA_OPTS% %ES_PARAMS% !newparams! -cp "%ES_CLASSPATH%" "org.elasticsearch.bootstrap.Elasticsearch"
"%JAVA_HOME%\bin\java" %JAVA_OPTS% %ES_JAVA_OPTS% %ES_PARAMS% !newparams! -cp "%ES_CLASSPATH%" "org.elasticsearch.bootstrap.Elasticsearch" start
ENDLOCAL

View File

@ -820,6 +820,13 @@ For the record, official plugins which can use this new simplified form are:
* elasticsearch-lang-javascript
* elasticsearch-lang-python
=== `/bin/elasticsearch` version needs `-V` parameter
Due to switching to elasticsearchs internal command line parsing
infrastructure for the pluginmanager and the elasticsearch start up
script, the `-v` parameter now stands for `--verbose`, where as `-V` or
`--version` can be used to show the Elasticsearch version and exit.
=== Aliases
Fields used in alias filters no longer have to exist in the mapping upon alias creation time. Alias filters are now

View File

@ -409,7 +409,7 @@
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
<version>1.3.1</version>
</dependency>
<!-- END: dependencies that might be shaded -->