diff --git a/build.gradle b/build.gradle index e498101a16a..4484d3fc14c 100644 --- a/build.gradle +++ b/build.gradle @@ -231,6 +231,7 @@ subprojects { "org.elasticsearch.gradle:build-tools:${version}": ':build-tools', "org.elasticsearch:rest-api-spec:${version}": ':rest-api-spec', "org.elasticsearch:elasticsearch:${version}": ':core', + "org.elasticsearch:elasticsearch-cli:${version}": ':core:cli', "org.elasticsearch.client:elasticsearch-rest-client:${version}": ':client:rest', "org.elasticsearch.client:elasticsearch-rest-client-sniffer:${version}": ':client:sniffer', "org.elasticsearch.client:elasticsearch-rest-high-level-client:${version}": ':client:rest-high-level', diff --git a/core/build.gradle b/core/build.gradle index fe60cd8b1cf..b5ad8eb5c32 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -58,7 +58,7 @@ dependencies { compile 'org.elasticsearch:securesm:1.1' // utilities - compile 'net.sf.jopt-simple:jopt-simple:5.0.2' + compile "org.elasticsearch:elasticsearch-cli:${version}" compile 'com.carrotsearch:hppc:0.7.1' // time handling, remove with java 8 time @@ -265,6 +265,12 @@ if (JavaVersion.current() > JavaVersion.VERSION_1_8) { dependencyLicenses { mapping from: /lucene-.*/, to: 'lucene' mapping from: /jackson-.*/, to: 'jackson' + dependencies = project.configurations.runtime.fileCollection { + it.group.startsWith('org.elasticsearch') == false || + // keep the following org.elasticsearch jars in + (it.name == 'jna' || + it.name == 'securesm') + } } if (isEclipse == false || project.path == ":core-tests") { diff --git a/core/cli/build.gradle b/core/cli/build.gradle new file mode 100644 index 00000000000..fc93523f6b7 --- /dev/null +++ b/core/cli/build.gradle @@ -0,0 +1,36 @@ +/* + * 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. + */ + +import org.elasticsearch.gradle.precommit.PrecommitTasks + +apply plugin: 'elasticsearch.build' + +archivesBaseName = 'elasticsearch-cli' + +dependencies { + compile 'net.sf.jopt-simple:jopt-simple:5.0.2' +} + +test.enabled = false +// Since CLI does not depend on :core, it cannot run the jarHell task +jarHell.enabled = false + +forbiddenApisMain { + signaturesURLs = [PrecommitTasks.getResource('/forbidden/jdk-signatures.txt')] +} diff --git a/core/licenses/jopt-simple-5.0.2.jar.sha1 b/core/cli/licenses/jopt-simple-5.0.2.jar.sha1 similarity index 100% rename from core/licenses/jopt-simple-5.0.2.jar.sha1 rename to core/cli/licenses/jopt-simple-5.0.2.jar.sha1 diff --git a/core/licenses/jopt-simple-LICENSE.txt b/core/cli/licenses/jopt-simple-LICENSE.txt similarity index 100% rename from core/licenses/jopt-simple-LICENSE.txt rename to core/cli/licenses/jopt-simple-LICENSE.txt diff --git a/core/licenses/jopt-simple-NOTICE.txt b/core/cli/licenses/jopt-simple-NOTICE.txt similarity index 100% rename from core/licenses/jopt-simple-NOTICE.txt rename to core/cli/licenses/jopt-simple-NOTICE.txt diff --git a/core/src/main/java/org/elasticsearch/cli/Command.java b/core/cli/src/main/java/org/elasticsearch/cli/Command.java similarity index 81% rename from core/src/main/java/org/elasticsearch/cli/Command.java rename to core/cli/src/main/java/org/elasticsearch/cli/Command.java index a60dece2611..78a9f31283d 100644 --- a/core/src/main/java/org/elasticsearch/cli/Command.java +++ b/core/cli/src/main/java/org/elasticsearch/cli/Command.java @@ -23,11 +23,6 @@ import joptsimple.OptionException; import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.OptionSpec; -import org.apache.logging.log4j.Level; -import org.apache.lucene.util.SetOnce; -import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.common.logging.LogConfigurator; -import org.elasticsearch.common.settings.Settings; import java.io.Closeable; import java.io.IOException; @@ -55,12 +50,13 @@ public abstract class Command implements Closeable { this.description = description; } - final SetOnce<Thread> shutdownHookThread = new SetOnce<>(); + private Thread shutdownHookThread; /** Parses options for this command from args and executes it. */ public final int main(String[] args, Terminal terminal) throws Exception { if (addShutdownHook()) { - shutdownHookThread.set(new Thread(() -> { + + shutdownHookThread = new Thread(() -> { try { this.close(); } catch (final IOException e) { @@ -75,16 +71,11 @@ public abstract class Command implements Closeable { throw new AssertionError(impossible); } } - })); - Runtime.getRuntime().addShutdownHook(shutdownHookThread.get()); + }); + Runtime.getRuntime().addShutdownHook(shutdownHookThread); } - if (shouldConfigureLoggingWithoutConfig()) { - // initialize default for es.logger.level because we will not read the log4j2.properties - final String loggerLevel = System.getProperty("es.logger.level", Level.INFO.name()); - final Settings settings = Settings.builder().put("logger.level", loggerLevel).build(); - LogConfigurator.configureWithoutConfig(settings); - } + beforeExecute(); try { mainWithoutErrorHandling(args, terminal); @@ -103,14 +94,10 @@ public abstract class Command implements Closeable { } /** - * Indicate whether or not logging should be configured without reading a log4j2.properties. Most commands should do this because we do - * not configure logging for CLI tools. Only commands that configure logging on their own should not do this. - * - * @return true if logging should be configured without reading a log4j2.properties file + * Setup method to be executed before parsing or execution of the command being run. Any exceptions thrown by the + * method will not be cleanly caught by the parser. */ - protected boolean shouldConfigureLoggingWithoutConfig() { - return true; - } + protected void beforeExecute() {} /** * Executes the command, but all errors are thrown. @@ -166,6 +153,11 @@ public abstract class Command implements Closeable { return true; } + /** Gets the shutdown hook thread if it exists **/ + Thread getShutdownHookThread() { + return shutdownHookThread; + } + @Override public void close() throws IOException { diff --git a/core/src/main/java/org/elasticsearch/cli/ExitCodes.java b/core/cli/src/main/java/org/elasticsearch/cli/ExitCodes.java similarity index 100% rename from core/src/main/java/org/elasticsearch/cli/ExitCodes.java rename to core/cli/src/main/java/org/elasticsearch/cli/ExitCodes.java diff --git a/core/src/main/java/org/elasticsearch/cli/MultiCommand.java b/core/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java similarity index 100% rename from core/src/main/java/org/elasticsearch/cli/MultiCommand.java rename to core/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java diff --git a/core/cli/src/main/java/org/elasticsearch/cli/SuppressForbidden.java b/core/cli/src/main/java/org/elasticsearch/cli/SuppressForbidden.java new file mode 100644 index 00000000000..882414a0eaa --- /dev/null +++ b/core/cli/src/main/java/org/elasticsearch/cli/SuppressForbidden.java @@ -0,0 +1,34 @@ +/* + * 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.cli; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to suppress forbidden-apis errors inside a whole class, a method, or a field. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +public @interface SuppressForbidden { + String reason(); +} + diff --git a/core/src/main/java/org/elasticsearch/cli/Terminal.java b/core/cli/src/main/java/org/elasticsearch/cli/Terminal.java similarity index 99% rename from core/src/main/java/org/elasticsearch/cli/Terminal.java rename to core/cli/src/main/java/org/elasticsearch/cli/Terminal.java index d42e3475dc4..85abd616774 100644 --- a/core/src/main/java/org/elasticsearch/cli/Terminal.java +++ b/core/cli/src/main/java/org/elasticsearch/cli/Terminal.java @@ -19,8 +19,6 @@ package org.elasticsearch.cli; -import org.elasticsearch.common.SuppressForbidden; - import java.io.BufferedReader; import java.io.Console; import java.io.IOException; diff --git a/core/src/main/java/org/elasticsearch/cli/UserException.java b/core/cli/src/main/java/org/elasticsearch/cli/UserException.java similarity index 100% rename from core/src/main/java/org/elasticsearch/cli/UserException.java rename to core/cli/src/main/java/org/elasticsearch/cli/UserException.java diff --git a/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java b/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java index d9d19a56a2f..44fcb37d083 100644 --- a/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java +++ b/core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java @@ -22,7 +22,9 @@ package org.elasticsearch.cli; import joptsimple.OptionSet; import joptsimple.OptionSpec; import joptsimple.util.KeyValuePair; +import org.apache.logging.log4j.Level; import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.logging.LogConfigurator; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.node.InternalSettingsPreparer; @@ -102,6 +104,26 @@ public abstract class EnvironmentAwareCommand extends Command { } } + @Override + protected final void beforeExecute() { + if (shouldConfigureLoggingWithoutConfig()) { + // initialize default for es.logger.level because we will not read the log4j2.properties + final String loggerLevel = System.getProperty("es.logger.level", Level.INFO.name()); + final Settings settings = Settings.builder().put("logger.level", loggerLevel).build(); + LogConfigurator.configureWithoutConfig(settings); + } + } + + /** + * Indicate whether or not logging should be configured without reading a log4j2.properties. Most commands should do this because we do + * not configure logging for CLI tools. Only commands that configure logging on their own should not do this. + * + * @return true if logging should be configured without reading a log4j2.properties file + */ + protected boolean shouldConfigureLoggingWithoutConfig() { + return true; + } + /** Execute the command with the initialized {@link Environment}. */ protected abstract void execute(Terminal terminal, OptionSet options, Environment env) throws Exception; diff --git a/distribution/tools/plugin-cli/build.gradle b/distribution/tools/plugin-cli/build.gradle index ae3dca9ef87..374690e65ac 100644 --- a/distribution/tools/plugin-cli/build.gradle +++ b/distribution/tools/plugin-cli/build.gradle @@ -21,6 +21,7 @@ apply plugin: 'elasticsearch.build' dependencies { provided "org.elasticsearch:elasticsearch:${version}" + provided "org.elasticsearch:elasticsearch-cli:${version}" testCompile "org.elasticsearch.test:framework:${version}" testCompile 'com.google.jimfs:jimfs:1.1' testCompile 'com.google.guava:guava:18.0' diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/cli/EvilCommandTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/cli/EvilCommandTests.java index 33d9bbe7782..7c51f8afe69 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/cli/EvilCommandTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/cli/EvilCommandTests.java @@ -49,11 +49,11 @@ public class EvilCommandTests extends ESTestCase { }; final MockTerminal terminal = new MockTerminal(); command.main(new String[0], terminal); - assertNotNull(command.shutdownHookThread.get()); + assertNotNull(command.getShutdownHookThread()); // successful removal here asserts that the runtime hook was installed in Command#main - assertTrue(Runtime.getRuntime().removeShutdownHook(command.shutdownHookThread.get())); - command.shutdownHookThread.get().run(); - command.shutdownHookThread.get().join(); + assertTrue(Runtime.getRuntime().removeShutdownHook(command.getShutdownHookThread())); + command.getShutdownHookThread().run(); + command.getShutdownHookThread().join(); assertTrue(closed.get()); final String output = terminal.getOutput(); if (shouldThrow) { diff --git a/settings.gradle b/settings.gradle index d7184f647c6..3a4afaee2be 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,7 @@ List projects = [ 'build-tools', 'rest-api-spec', 'core', + 'core:cli', 'docs', 'client:rest', 'client:rest-high-level', diff --git a/test/framework/build.gradle b/test/framework/build.gradle index 09382763057..558bb2c851c 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -22,6 +22,7 @@ import org.elasticsearch.gradle.precommit.PrecommitTasks; dependencies { compile "org.elasticsearch.client:elasticsearch-rest-client:${version}" compile "org.elasticsearch:elasticsearch:${version}" + compile "org.elasticsearch:elasticsearch-cli:${version}" compile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" compile "junit:junit:${versions.junit}" compile "org.hamcrest:hamcrest-all:${versions.hamcrest}"