CLITool: Port PluginManager to use CLITool

In order to unify the handling and reuse the CLITool infrastructure
the plugin manager should make use of this as well.

This obsolets the -i and --install options but requires the user
to use `install` as the first argument of the CLI.

This is basically just a port of the existing functionality, which
is also the reason why this is not a refactoring of the plugin manager,
which will come in a separate commit.
This commit is contained in:
Alexander Reelsen 2015-07-21 13:54:18 +02:00
parent 0cf9c3268c
commit 2f54b89a23
20 changed files with 608 additions and 564 deletions

View File

@ -24,6 +24,7 @@ import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.unit.TimeValue;
import java.io.*;
@ -135,22 +136,12 @@ public class HttpDownloadHelper {
/**
* verbose progress system prints to some output stream
*/
@SuppressForbidden(reason = "System#out")
public static class VerboseProgress implements DownloadProgress {
private int dots = 0;
// CheckStyle:VisibilityModifier OFF - bc
PrintWriter writer;
// CheckStyle:VisibilityModifier ON
/**
* Construct a verbose progress reporter.
*
* @param out the output stream.
*/
public VerboseProgress(PrintStream out) {
this.writer = new PrintWriter(out);
}
/**
* Construct a verbose progress reporter.
*

View File

@ -22,20 +22,17 @@ package org.elasticsearch.plugins;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.*;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.Version;
import org.elasticsearch.bootstrap.JarHell;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.http.client.HttpDownloadHelper;
import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.logging.log4j.LogConfigurator;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
import java.io.IOException;
import java.io.OutputStream;
@ -52,27 +49,18 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static org.elasticsearch.common.Strings.hasLength;
import static org.elasticsearch.common.cli.Terminal.Verbosity.VERBOSE;
import static org.elasticsearch.common.io.FileSystemUtils.moveFilesWithoutOverwriting;
import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
/**
*
*/
public class PluginManager {
public static final class ACTION {
public static final int NONE = 0;
public static final int INSTALL = 1;
public static final int REMOVE = 2;
public static final int LIST = 3;
}
public enum OutputMode {
DEFAULT, SILENT, VERBOSE
}
// By default timeout is 0 which means no timeout
public static final TimeValue DEFAULT_TIMEOUT = TimeValue.timeValueMillis(0);
private static final ImmutableSet<String> BLACKLIST = ImmutableSet.<String>builder()
.add("elasticsearch",
"elasticsearch.bat",
@ -81,7 +69,7 @@ public class PluginManager {
"plugin.bat",
"service.bat").build();
private static final ImmutableSet<String> OFFICIAL_PLUGINS = ImmutableSet.<String>builder()
static final ImmutableSet<String> OFFICIAL_PLUGINS = ImmutableSet.<String>builder()
.add(
"elasticsearch-analysis-icu",
"elasticsearch-analysis-kuromoji",
@ -108,9 +96,9 @@ public class PluginManager {
this.timeout = timeout;
}
public void downloadAndExtract(String name) throws IOException {
public void downloadAndExtract(String name, Terminal terminal) throws IOException {
if (name == null) {
throw new IllegalArgumentException("plugin name must be supplied with --install [name].");
throw new IllegalArgumentException("plugin name must be supplied with install [name].");
}
HttpDownloadHelper downloadHelper = new HttpDownloadHelper();
boolean downloaded = false;
@ -118,7 +106,7 @@ public class PluginManager {
if (outputMode == OutputMode.SILENT) {
progress = new HttpDownloadHelper.NullProgress();
} else {
progress = new HttpDownloadHelper.VerboseProgress(SysOut.getOut());
progress = new HttpDownloadHelper.VerboseProgress(terminal.writer());
}
if (!Files.isWritable(environment.pluginsFile())) {
@ -132,13 +120,13 @@ public class PluginManager {
// extract the plugin
final Path extractLocation = pluginHandle.extractedDir(environment);
if (Files.exists(extractLocation)) {
throw new IOException("plugin directory " + extractLocation.toAbsolutePath() + " already exists. To update the plugin, uninstall it first using --remove " + name + " command");
throw new IOException("plugin directory " + extractLocation.toAbsolutePath() + " already exists. To update the plugin, uninstall it first using remove " + name + " command");
}
// first, try directly from the URL provided
if (url != null) {
URL pluginUrl = new URL(url);
log("Trying " + pluginUrl.toExternalForm() + "...");
terminal.println("Trying %s ...", pluginUrl.toExternalForm());
try {
downloadHelper.download(pluginUrl, pluginFile, progress, this.timeout);
downloaded = true;
@ -146,7 +134,7 @@ public class PluginManager {
throw e;
} catch (Exception e) {
// ignore
log("Failed: " + ExceptionsHelper.detailedMessage(e));
terminal.println("Failed: %s", ExceptionsHelper.detailedMessage(e));
}
} else {
if (PluginHandle.isOfficialPlugin(pluginHandle.repo, pluginHandle.user, pluginHandle.version)) {
@ -157,7 +145,7 @@ public class PluginManager {
if (!downloaded) {
// We try all possible locations
for (URL url : pluginHandle.urls()) {
log("Trying " + url.toExternalForm() + "...");
terminal.println("Trying %s ...", url.toExternalForm());
try {
downloadHelper.download(url, pluginFile, progress, this.timeout);
downloaded = true;
@ -165,7 +153,7 @@ public class PluginManager {
} catch (ElasticsearchTimeoutException e) {
throw e;
} catch (Exception e) {
debug("Failed: " + ExceptionsHelper.detailedMessage(e));
terminal.println(VERBOSE, "Failed: %s", ExceptionsHelper.detailedMessage(e));
}
}
}
@ -181,9 +169,7 @@ public class PluginManager {
final List<URL> jars = new ArrayList<>();
ClassLoader loader = PluginManager.class.getClassLoader();
if (loader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) loader).getURLs()) {
jars.add(url);
}
Collections.addAll(jars, ((URLClassLoader) loader).getURLs());
}
// add any jars we find in the plugin to the list
@ -199,7 +185,7 @@ public class PluginManager {
// check combined (current classpath + new jars to-be-added)
try {
JarHell.checkJarHell(jars.toArray(new URL[0]));
JarHell.checkJarHell(jars.toArray(new URL[jars.size()]));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@ -240,24 +226,24 @@ public class PluginManager {
});
}
log("Installed " + name + " into " + extractLocation.toAbsolutePath());
terminal.println("Installed %s into %s", name, extractLocation.toAbsolutePath());
} catch (Exception e) {
log("failed to extract plugin [" + pluginFile + "]: " + ExceptionsHelper.detailedMessage(e));
terminal.printError("failed to extract plugin [%s]: %s", pluginFile, ExceptionsHelper.detailedMessage(e));
return;
} finally {
try {
Files.delete(pluginFile);
} catch (Exception ex) {
log("Failed to delete plugin file" + pluginFile + " " + ex);
terminal.printError("Failed to delete plugin file %s %s", pluginFile, ex);
}
}
if (FileSystemUtils.hasExtensions(extractLocation, ".java")) {
debug("Plugin installation assumed to be site plugin, but contains source code, aborting installation...");
terminal.printError("Plugin installation assumed to be site plugin, but contains source code, aborting installation...");
try {
IOUtils.rm(extractLocation);
} catch(Exception ex) {
debug("Failed to remove site plugin from path " + extractLocation + " - " + ex.getMessage());
terminal.printError("Failed to remove site plugin from path %s - %s", extractLocation, ex.getMessage());
}
throw new IllegalArgumentException("Plugin installation assumed to be site plugin, but contains source code, aborting installation.");
}
@ -267,7 +253,7 @@ public class PluginManager {
Path binFile = extractLocation.resolve("bin");
if (Files.isDirectory(binFile)) {
Path toLocation = pluginHandle.binDir(environment);
debug("Found bin, moving to " + toLocation.toAbsolutePath());
terminal.println(VERBOSE, "Found bin, moving to %s", toLocation.toAbsolutePath());
if (Files.exists(toLocation)) {
IOUtils.rm(toLocation);
}
@ -298,18 +284,18 @@ public class PluginManager {
}
});
} else {
debug("Skipping posix permissions - filestore doesn't support posix permission");
terminal.println(VERBOSE, "Skipping posix permissions - filestore doesn't support posix permission");
}
debug("Installed " + name + " into " + toLocation.toAbsolutePath());
terminal.println(VERBOSE, "Installed %s into %s", name, toLocation.toAbsolutePath());
potentialSitePlugin = false;
}
Path configFile = extractLocation.resolve("config");
if (Files.isDirectory(configFile)) {
Path configDestLocation = pluginHandle.configDir(environment);
debug("Found config, moving to " + configDestLocation.toAbsolutePath());
terminal.println(VERBOSE, "Found config, moving to %s", configDestLocation.toAbsolutePath());
moveFilesWithoutOverwriting(configFile, configDestLocation, ".new");
debug("Installed " + name + " into " + configDestLocation.toAbsolutePath());
terminal.println(VERBOSE, "Installed %s into %s", name, configDestLocation.toAbsolutePath());
potentialSitePlugin = false;
}
@ -317,13 +303,13 @@ public class PluginManager {
// so its probably a _site, and it it does not have a _site in it, move everything to _site
if (!Files.exists(extractLocation.resolve("_site"))) {
if (potentialSitePlugin && !FileSystemUtils.hasExtensions(extractLocation, ".class", ".jar")) {
log("Identified as a _site plugin, moving to _site structure ...");
terminal.println(VERBOSE, "Identified as a _site plugin, moving to _site structure ...");
Path site = extractLocation.resolve("_site");
Path tmpLocation = environment.pluginsFile().resolve(extractLocation.getFileName() + ".tmp");
Files.move(extractLocation, tmpLocation);
Files.createDirectories(extractLocation);
Files.move(tmpLocation, site);
debug("Installed " + name + " into " + site.toAbsolutePath());
terminal.println(VERBOSE, "Installed " + name + " into " + site.toAbsolutePath());
}
}
}
@ -355,9 +341,9 @@ public class PluginManager {
return tmp;
}
public void removePlugin(String name) throws IOException {
public void removePlugin(String name, Terminal terminal) throws IOException {
if (name == null) {
throw new IllegalArgumentException("plugin name must be supplied with --remove [name].");
throw new IllegalArgumentException("plugin name must be supplied with remove [name].");
}
PluginHandle pluginHandle = PluginHandle.parse(name);
boolean removed = false;
@ -365,7 +351,7 @@ public class PluginManager {
checkForForbiddenName(pluginHandle.name);
Path pluginToDelete = pluginHandle.extractedDir(environment);
if (Files.exists(pluginToDelete)) {
debug("Removing: " + pluginToDelete);
terminal.println(VERBOSE, "Removing: %s", pluginToDelete);
try {
IOUtils.rm(pluginToDelete);
} catch (IOException ex){
@ -376,7 +362,7 @@ public class PluginManager {
}
pluginToDelete = pluginHandle.distroFile(environment);
if (Files.exists(pluginToDelete)) {
debug("Removing: " + pluginToDelete);
terminal.println(VERBOSE, "Removing: %s", pluginToDelete);
try {
Files.delete(pluginToDelete);
} catch (Exception ex) {
@ -387,7 +373,7 @@ public class PluginManager {
}
Path binLocation = pluginHandle.binDir(environment);
if (Files.exists(binLocation)) {
debug("Removing: " + binLocation);
terminal.println(VERBOSE, "Removing: %s", binLocation);
try {
IOUtils.rm(binLocation);
} catch (IOException ex){
@ -398,9 +384,9 @@ public class PluginManager {
}
if (removed) {
log("Removed " + name);
terminal.println("Removed %s", name);
} else {
log("Plugin " + name + " not found. Run plugin --list to get list of installed plugins.");
terminal.println("Plugin %s not found. Run plugin --list to get list of installed plugins.", name);
}
}
@ -425,253 +411,18 @@ public class PluginManager {
}
}
public void listInstalledPlugins() throws IOException {
public void listInstalledPlugins(Terminal terminal) throws IOException {
Path[] plugins = getListInstalledPlugins();
log("Installed plugins in " + environment.pluginsFile().toAbsolutePath() + ":");
terminal.println("Installed plugins in %s:", environment.pluginsFile().toAbsolutePath());
if (plugins == null || plugins.length == 0) {
log(" - No plugin detected");
terminal.println(" - No plugin detected");
} else {
for (int i = 0; i < plugins.length; i++) {
log(" - " + plugins[i].getFileName());
for (Path plugin : plugins) {
terminal.println(" - " + plugin.getFileName());
}
}
}
private static final int EXIT_CODE_OK = 0;
private static final int EXIT_CODE_CMD_USAGE = 64;
private static final int EXIT_CODE_IO_ERROR = 74;
private static final int EXIT_CODE_ERROR = 70;
public static void main(String[] args) {
Tuple<Settings, Environment> initialSettings = InternalSettingsPreparer.prepareSettings(EMPTY_SETTINGS, true, Terminal.DEFAULT);
LogConfigurator.configure(initialSettings.v1());
try {
Files.createDirectories(initialSettings.v2().pluginsFile());
} catch (IOException e) {
displayHelp("Unable to create plugins dir: " + initialSettings.v2().pluginsFile());
System.exit(EXIT_CODE_ERROR);
}
String url = null;
OutputMode outputMode = OutputMode.DEFAULT;
String pluginName = null;
TimeValue timeout = DEFAULT_TIMEOUT;
int action = ACTION.NONE;
if (args.length < 1) {
displayHelp(null);
}
try {
for (int c = 0; c < args.length; c++) {
String command = args[c];
switch (command) {
case "-u":
case "--url":
// deprecated versions:
case "url":
case "-url":
url = getCommandValue(args, ++c, "--url");
// Until update is supported, then supplying a URL implies installing
// By specifying this action, we also avoid silently failing without
// dubious checks.
action = ACTION.INSTALL;
break;
case "-v":
case "--verbose":
// deprecated versions:
case "verbose":
case "-verbose":
outputMode = OutputMode.VERBOSE;
break;
case "-s":
case "--silent":
// deprecated versions:
case "silent":
case "-silent":
outputMode = OutputMode.SILENT;
break;
case "-i":
case "--install":
// deprecated versions:
case "install":
case "-install":
pluginName = getCommandValue(args, ++c, "--install");
action = ACTION.INSTALL;
break;
case "-r":
case "--remove":
// deprecated versions:
case "remove":
case "-remove":
pluginName = getCommandValue(args, ++c, "--remove");
action = ACTION.REMOVE;
break;
case "-t":
case "--timeout":
// deprecated versions:
case "timeout":
case "-timeout":
String timeoutValue = getCommandValue(args, ++c, "--timeout");
timeout = TimeValue.parseTimeValue(timeoutValue, DEFAULT_TIMEOUT, command);
break;
case "-l":
case "--list":
action = ACTION.LIST;
break;
case "-h":
case "--help":
displayHelp(null);
break;
default:
displayHelp("Command [" + command + "] unknown.");
// Unknown command. We break...
System.exit(EXIT_CODE_CMD_USAGE);
}
}
} catch (Throwable e) {
displayHelp("Error while parsing options: " + e.getClass().getSimpleName() +
": " + e.getMessage());
System.exit(EXIT_CODE_CMD_USAGE);
}
if (action > ACTION.NONE) {
int exitCode = EXIT_CODE_ERROR; // we fail unless it's reset
PluginManager pluginManager = new PluginManager(initialSettings.v2(), url, outputMode, timeout);
switch (action) {
case ACTION.INSTALL:
try {
pluginManager.log("-> Installing " + Strings.nullToEmpty(pluginName) + "...");
pluginManager.downloadAndExtract(pluginName);
exitCode = EXIT_CODE_OK;
} catch (IOException e) {
exitCode = EXIT_CODE_IO_ERROR;
pluginManager.log("Failed to install " + pluginName + ", reason: " + e.getMessage());
} catch (Throwable e) {
exitCode = EXIT_CODE_ERROR;
displayHelp("Error while installing plugin, reason: " + e.getClass().getSimpleName() +
": " + e.getMessage());
}
break;
case ACTION.REMOVE:
try {
pluginManager.log("-> Removing " + Strings.nullToEmpty(pluginName) + "...");
pluginManager.removePlugin(pluginName);
exitCode = EXIT_CODE_OK;
} catch (IllegalArgumentException e) {
exitCode = EXIT_CODE_CMD_USAGE;
pluginManager.log("Failed to remove " + pluginName + ", reason: " + e.getMessage());
} catch (IOException e) {
exitCode = EXIT_CODE_IO_ERROR;
pluginManager.log("Failed to remove " + pluginName + ", reason: " + e.getMessage());
} catch (Throwable e) {
exitCode = EXIT_CODE_ERROR;
displayHelp("Error while removing plugin, reason: " + e.getClass().getSimpleName() +
": " + e.getMessage());
}
break;
case ACTION.LIST:
try {
pluginManager.listInstalledPlugins();
exitCode = EXIT_CODE_OK;
} catch (Throwable e) {
displayHelp("Error while listing plugins, reason: " + e.getClass().getSimpleName() +
": " + e.getMessage());
}
break;
default:
pluginManager.log("Unknown Action [" + action + "]");
exitCode = EXIT_CODE_ERROR;
}
System.exit(exitCode); // exit here!
}
}
/**
* Get the value for the {@code flag} at the specified {@code arg} of the command line {@code args}.
* <p />
* This is useful to avoid having to check for multiple forms of unset (e.g., " " versus "" versus {@code null}).
* @param args Incoming command line arguments.
* @param arg Expected argument containing the value.
* @param flag The flag whose value is being retrieved.
* @return Never {@code null}. The trimmed value.
* @throws NullPointerException if {@code args} is {@code null}.
* @throws ArrayIndexOutOfBoundsException if {@code arg} is negative.
* @throws IllegalStateException if {@code arg} is &gt;= {@code args.length}.
* @throws IllegalArgumentException if the value evaluates to blank ({@code null} or only whitespace)
*/
private static String getCommandValue(String[] args, int arg, String flag) {
if (arg >= args.length) {
throw new IllegalStateException("missing value for " + flag + ". Usage: " + flag + " [value]");
}
// avoid having to interpret multiple forms of unset
String trimmedValue = Strings.emptyToNull(args[arg].trim());
// If we had a value that is blank, then fail immediately
if (trimmedValue == null) {
throw new IllegalArgumentException(
"value for " + flag + "('" + args[arg] + "') must be set. Usage: " + flag + " [value]");
}
return trimmedValue;
}
private static void displayHelp(String message) {
SysOut.println("Usage:");
SysOut.println(" -u, --url [plugin location] : Set exact URL to download the plugin from");
SysOut.println(" -i, --install [plugin name] : Downloads and installs listed plugins [*]");
SysOut.println(" -t, --timeout [duration] : Timeout setting: 30s, 1m, 1h... (infinite by default)");
SysOut.println(" -r, --remove [plugin name] : Removes listed plugins");
SysOut.println(" -l, --list : List installed plugins");
SysOut.println(" -v, --verbose : Prints verbose messages");
SysOut.println(" -s, --silent : Run in silent mode");
SysOut.println(" -h, --help : Prints this help message");
SysOut.newline();
SysOut.println(" [*] Plugin name could be:");
SysOut.println(" elasticsearch-plugin-name for Elasticsearch 2.0 Core plugin (download from download.elastic.co)");
SysOut.println(" elasticsearch/plugin/version for elasticsearch commercial plugins (download from download.elastic.co)");
SysOut.println(" groupId/artifactId/version for community plugins (download from maven central or oss sonatype)");
SysOut.println(" username/repository for site plugins (download from github master)");
SysOut.newline();
SysOut.println("Elasticsearch Core plugins:");
for (String o : OFFICIAL_PLUGINS) {
SysOut.println(" - " + o);
}
if (message != null) {
SysOut.newline();
SysOut.println("Message:");
SysOut.println(" " + message);
}
}
private void debug(String line) {
if (outputMode == OutputMode.VERBOSE) SysOut.println(line);
}
private void log(String line) {
if (outputMode != OutputMode.SILENT) SysOut.println(line);
}
@SuppressForbidden(reason = "System#out")
static class SysOut {
public static void newline() {
System.out.println();
}
public static void println(String msg) {
System.out.println(msg);
}
public static PrintStream getOut() {
return System.out;
}
}
/**
* Helper class to extract properly user name, repository name, version and plugin name
* from plugin name given by a user.

View File

@ -0,0 +1,215 @@
/*
* 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.plugins;
import com.google.common.base.Strings;
import org.apache.commons.cli.CommandLine;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.logging.log4j.LogConfigurator;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
import org.elasticsearch.plugins.PluginManager.OutputMode;
import java.io.IOException;
import java.util.Locale;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
import static org.elasticsearch.common.settings.Settings.EMPTY;
public class PluginManagerCliParser extends CliTool {
// By default timeout is 0 which means no timeout
public static final TimeValue DEFAULT_TIMEOUT = TimeValue.timeValueMillis(0);
private static final CliToolConfig CONFIG = CliToolConfig.config("plugin", PluginManagerCliParser.class)
.cmds(ListPlugins.CMD, Install.CMD, Remove.CMD)
.build();
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);
System.exit(status);
}
public PluginManagerCliParser() {
super(CONFIG);
}
public PluginManagerCliParser(Terminal terminal) {
super(CONFIG, terminal);
}
@Override
protected Command parse(String cmdName, CommandLine cli) throws Exception {
switch (cmdName.toLowerCase(Locale.ROOT)) {
case Install.NAME:
return Install.parse(terminal, cli);
case ListPlugins.NAME:
return ListPlugins.parse(terminal, cli);
case Remove.NAME:
return Remove.parse(terminal, cli);
default:
assert false : "can't get here as cmd name is validated before this method is called";
return exitCmd(ExitStatus.USAGE);
}
}
/**
* List all installed plugins
*/
static class ListPlugins extends CliTool.Command {
private static final String NAME = "list";
private static final CliToolConfig.Cmd CMD = cmd(NAME, ListPlugins.class).build();
private final OutputMode outputMode;
public static Command parse(Terminal terminal, CommandLine cli) {
OutputMode outputMode = OutputMode.DEFAULT;
if (cli.hasOption("s")) {
outputMode = OutputMode.SILENT;
}
if (cli.hasOption("v")) {
outputMode = OutputMode.VERBOSE;
}
return new ListPlugins(terminal, outputMode);
}
ListPlugins(Terminal terminal, OutputMode outputMode) {
super(terminal);
this.outputMode = outputMode;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
PluginManager pluginManager = new PluginManager(env, null, outputMode, DEFAULT_TIMEOUT);
pluginManager.listInstalledPlugins(terminal);
return ExitStatus.OK;
}
}
/**
* Remove a plugin
*/
static class Remove extends CliTool.Command {
private static final String NAME = "remove";
private static final CliToolConfig.Cmd CMD = cmd(NAME, Remove.class).build();
public static Command parse(Terminal terminal, CommandLine cli) {
String[] args = cli.getArgs();
if (args.length == 0) {
return exitCmd(ExitStatus.USAGE, terminal, "plugin name is missing (type -h for help)");
}
OutputMode outputMode = OutputMode.DEFAULT;
if (cli.hasOption("s")) {
outputMode = OutputMode.SILENT;
}
if (cli.hasOption("v")) {
outputMode = OutputMode.VERBOSE;
}
return new Remove(terminal, outputMode, args[0]);
}
private OutputMode outputMode;
final String pluginName;
Remove(Terminal terminal, OutputMode outputMode, String pluginToRemove) {
super(terminal);
this.outputMode = outputMode;
this.pluginName = pluginToRemove;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
PluginManager pluginManager = new PluginManager(env, null, outputMode, DEFAULT_TIMEOUT);
terminal.println("-> Removing " + Strings.nullToEmpty(pluginName) + "...");
pluginManager.removePlugin(pluginName, terminal);
return ExitStatus.OK;
}
}
/**
* Installs a plugin
*/
static class Install extends Command {
private static final String NAME = "install";
private static final CliToolConfig.Cmd CMD = cmd(NAME, Install.class)
.options(option("u", "url").required(false).hasArg(true))
.options(option("t", "timeout").required(false).hasArg(false))
.build();
static Command parse(Terminal terminal, CommandLine cli) {
String[] args = cli.getArgs();
if ((args == null) || (args.length == 0)) {
return exitCmd(ExitStatus.USAGE, terminal, "plugin name is missing (type -h for help)");
}
String name = args[0];
TimeValue timeout = TimeValue.parseTimeValue(cli.getOptionValue("t"), DEFAULT_TIMEOUT, "cli");
String url = cli.getOptionValue("u");
OutputMode outputMode = OutputMode.DEFAULT;
if (cli.hasOption("s")) {
outputMode = OutputMode.SILENT;
}
if (cli.hasOption("v")) {
outputMode = OutputMode.VERBOSE;
}
return new Install(terminal, name, outputMode, url, timeout);
}
final String name;
private OutputMode outputMode;
final String url;
final TimeValue timeout;
Install(Terminal terminal, String name, OutputMode outputMode, String url, TimeValue timeout) {
super(terminal);
this.name = name;
this.outputMode = outputMode;
this.url = url;
this.timeout = timeout;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
PluginManager pluginManager = new PluginManager(env, url, outputMode, timeout);
terminal.println("-> Installing " + Strings.nullToEmpty(name) + "...");
pluginManager.downloadAndExtract(name, terminal);
return ExitStatus.OK;
}
}
}

View File

@ -0,0 +1,56 @@
NAME
install - Install a plugin
SYNOPSIS
plugin install <name>
DESCRIPTION
This command installs an elasticsearch plugin
<name> can be one of the official plugins, or refer to a github repository, or to one of the official plugins
The notation of just specifying a plugin name, downloads an officially supported plugin.
The notation of 'elasticsearch/plugin/version' allows to easily download a commercial elastic plugin.
The notation of 'groupId/artifactId/version' refers to community plugins using maven central or sonatype
The notation of 'username/repository' refers to a github repository.
EXAMPLES
plugin install elasticsearch-analysis-kuromoji
plugin install elasticsearch/shield/latest
plugin install lmenezes/elasticsearch-kopf
OFFICIAL PLUGINS
The following plugins are officially supported and can be installed by just referring to their name
- elasticsearch-analysis-icu
- elasticsearch-analysis-kuromoji
- elasticsearch-analysis-phonetic
- elasticsearch-analysis-smartcn
- elasticsearch-analysis-stempel
- elasticsearch-cloud-aws
- elasticsearch-cloud-azure
- elasticsearch-cloud-gce
- elasticsearch-delete-by-query
- elasticsearch-lang-javascript
- elasticsearch-lang-python
OPTIONS
-u,--url URL to retrive the plugin from
-t,--timeout Timeout until the plugin download is abort
-v,--verbose Verbose output
-h,--help Shows this message

View File

@ -0,0 +1,12 @@
NAME
list - List all plugins
SYNOPSIS
plugin list
DESCRIPTION
This command lists all installed elasticsearch plugins

View File

@ -0,0 +1,12 @@
NAME
remove - Remove a plugin
SYNOPSIS
plugin remove <name>
DESCRIPTION
This command removes an elasticsearch plugin

View File

@ -0,0 +1,24 @@
NAME
plugin - Manages plugins
SYNOPSIS
plugin <command>
DESCRIPTION
Manage plugins
COMMANDS
install Install a plugin
remove Remove a plugin
list List installed plugins
NOTES
[*] For usage help on specific commands please type "plugin <command> -h"

View File

@ -49,7 +49,7 @@ public abstract class CliToolTestCase extends ElasticsearchTestCase {
System.clearProperty("es.default.path.home");
}
protected static String[] args(String command) {
public static String[] args(String command) {
if (!Strings.hasLength(command)) {
return Strings.EMPTY_ARRAY;
}

View File

@ -18,17 +18,18 @@
*/
package org.elasticsearch.plugins;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import org.apache.http.impl.client.HttpClients;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.action.admin.cluster.node.info.PluginInfo;
import org.elasticsearch.common.cli.CliTool;
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.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
import org.elasticsearch.rest.RestStatus;
@ -37,6 +38,8 @@ import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import org.elasticsearch.test.junit.annotations.Network;
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
import org.elasticsearch.test.rest.client.http.HttpResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@ -46,14 +49,16 @@ import java.nio.file.Path;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
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.test.ElasticsearchIntegrationTest.Scope;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertDirectoryExists;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFileExists;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.*;
@ClusterScope(scope = Scope.TEST, numDataNodes = 0, transportClientRatio = 0.0)
@ -62,20 +67,50 @@ import static org.hamcrest.Matchers.*;
// if its in your classpath, then do not use plugins!!!!!!
public class PluginManagerTests extends ElasticsearchIntegrationTest {
@Test(expected = IllegalArgumentException.class)
public void testDownloadAndExtract_NullName_ThrowsException() throws IOException {
pluginManager(getPluginUrlForResource("plugin_single_folder.zip")).downloadAndExtract(null);
private Tuple<Settings, Environment> initialSettings;
private CaptureOutputTerminal terminal = new CaptureOutputTerminal();
@Before
public void setup() throws Exception {
initialSettings = buildInitialSettings();
System.setProperty("es.default.path.home", initialSettings.v1().get("path.home"));
try {
Files.createDirectories(initialSettings.v2().pluginsFile());
} catch (IOException e) {
throw new RuntimeException(e);
}
Path binDir = initialSettings.v2().homeFile().resolve("bin");
if (!Files.exists(binDir)) {
Files.createDirectories(binDir);
}
Path configDir = initialSettings.v2().configFile();
if (!Files.exists(configDir)) {
Files.createDirectories(configDir);
}
}
@After
public void clearPathHome() {
System.clearProperty("es.default.path.home");
}
@Test
public void testThatPluginNameMustBeSupplied() throws IOException {
String pluginUrl = getPluginUrlForResource("plugin_single_folder.zip");
assertStatus("install --url " + pluginUrl, USAGE);
}
@Test
public void testLocalPluginInstallSingleFolder() throws Exception {
//When we have only a folder in top-level (no files either) we remove that folder while extracting
String pluginName = "plugin-test";
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
downloadAndExtract(pluginName, initialSettings, getPluginUrlForResource("plugin_single_folder.zip"));
String pluginUrl = getPluginUrlForResource("plugin_single_folder.zip");
String installCommand = String.format(Locale.ROOT, "install %s --url %s", pluginName, pluginUrl);
assertStatusOk(installCommand);
internalCluster().startNode(initialSettings.v1());
assertPluginLoaded(pluginName);
assertPluginAvailable(pluginName);
}
@ -83,45 +118,30 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
@Test
public void testLocalPluginInstallWithBinAndConfig() throws Exception {
String pluginName = "plugin-test";
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
Environment env = initialSettings.v2();
Path binDir = env.homeFile().resolve("bin");
if (!Files.exists(binDir)) {
Files.createDirectories(binDir);
}
Path pluginBinDir = binDir.resolve(pluginName);
Path configDir = env.configFile();
if (!Files.exists(configDir)) {
Files.createDirectories(configDir);
}
Path pluginConfigDir =configDir.resolve(pluginName);
try {
PluginManager pluginManager = pluginManager(getPluginUrlForResource("plugin_with_bin_and_config.zip"), initialSettings);
Path pluginConfigDir = env.configFile().resolve(pluginName);
String pluginUrl = getPluginUrlForResource("plugin_with_bin_and_config.zip");
assertStatusOk("install " + pluginName + " --url " + pluginUrl + " --verbose");
pluginManager.downloadAndExtract(pluginName);
terminal.getTerminalOutput().clear();
assertStatusOk("list");
assertThat(terminal.getTerminalOutput(), hasItem(containsString(pluginName)));
Path[] plugins = pluginManager.getListInstalledPlugins();
assertDirectoryExists(pluginBinDir);
assertDirectoryExists(pluginConfigDir);
Path toolFile = pluginBinDir.resolve("tool");
assertFileExists(toolFile);
assertThat(plugins, arrayWithSize(1));
assertDirectoryExists(pluginBinDir);
assertDirectoryExists(pluginConfigDir);
Path toolFile = pluginBinDir.resolve("tool");
assertFileExists(toolFile);
// check that the file is marked executable, without actually checking that we can execute it.
PosixFileAttributeView view = Files.getFileAttributeView(toolFile, PosixFileAttributeView.class);
// the view might be null, on e.g. windows, there is nothing to check there!
if (view != null) {
PosixFileAttributes attributes = view.readAttributes();
assertTrue("unexpected permissions: " + attributes.permissions(),
attributes.permissions().contains(PosixFilePermission.OWNER_EXECUTE));
assertTrue("unexpected permissions: " + attributes.permissions(),
attributes.permissions().contains(PosixFilePermission.OWNER_READ));
}
} finally {
// we need to clean up the copied dirs
IOUtils.rm(pluginBinDir, pluginConfigDir);
// check that the file is marked executable, without actually checking that we can execute it.
PosixFileAttributeView view = Files.getFileAttributeView(toolFile, PosixFileAttributeView.class);
// the view might be null, on e.g. windows, there is nothing to check there!
if (view != null) {
PosixFileAttributes attributes = view.readAttributes();
assertThat(attributes.permissions(), hasItem(PosixFilePermission.OWNER_EXECUTE));
assertThat(attributes.permissions(), hasItem(PosixFilePermission.OWNER_READ));
}
}
@ -131,102 +151,76 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
@Test
public void testLocalPluginInstallWithBinAndConfigInAlreadyExistingConfigDir_7890() throws Exception {
String pluginName = "plugin-test";
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
Environment env = initialSettings.v2();
Path pluginConfigDir = env.configFile().resolve(pluginName);
Path configDir = env.configFile();
if (!Files.exists(configDir)) {
Files.createDirectories(configDir);
}
Path pluginConfigDir = configDir.resolve(pluginName);
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_with_config_v1.zip")));
try {
PluginManager pluginManager = pluginManager(getPluginUrlForResource("plugin_with_config_v1.zip"), initialSettings);
pluginManager.downloadAndExtract(pluginName);
/*
First time, our plugin contains:
- config/test.txt (version1)
*/
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
Path[] plugins = pluginManager.getListInstalledPlugins();
assertThat(plugins, arrayWithSize(1));
// We now remove the plugin
assertStatusOk("remove " + pluginName);
/*
First time, our plugin contains:
- config/test.txt (version1)
*/
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
// We should still have test.txt
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
// We now remove the plugin
pluginManager.removePlugin(pluginName);
// We should still have test.txt
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
// Installing a new plugin version
/*
Second time, our plugin contains:
- config/test.txt (version2)
- config/dir/testdir.txt (version1)
- config/dir/subdir/testsubdir.txt (version1)
*/
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_with_config_v2.zip")));
// Installing a new plugin version
/*
Second time, our plugin contains:
- config/test.txt (version2)
- config/dir/testdir.txt (version1)
- config/dir/subdir/testsubdir.txt (version1)
*/
pluginManager = pluginManager(getPluginUrlForResource("plugin_with_config_v2.zip"), initialSettings);
pluginManager.downloadAndExtract(pluginName);
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
assertFileContent(pluginConfigDir, "test.txt.new", "version2\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
assertFileContent(pluginConfigDir, "test.txt.new", "version2\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1\n");
// Removing
assertStatusOk("remove " + pluginName);
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
assertFileContent(pluginConfigDir, "test.txt.new", "version2\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1\n");
// Removing
pluginManager.removePlugin(pluginName);
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
assertFileContent(pluginConfigDir, "test.txt.new", "version2\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1\n");
// Installing a new plugin version
/*
Third time, our plugin contains:
- config/test.txt (version3)
- config/test2.txt (version1)
- config/dir/testdir.txt (version2)
- config/dir/testdir2.txt (version1)
- config/dir/subdir/testsubdir.txt (version2)
*/
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_with_config_v3.zip")));
// Installing a new plugin version
/*
Third time, our plugin contains:
- config/test.txt (version3)
- config/test2.txt (version1)
- config/dir/testdir.txt (version2)
- config/dir/testdir2.txt (version1)
- config/dir/subdir/testsubdir.txt (version2)
*/
pluginManager = pluginManager(getPluginUrlForResource("plugin_with_config_v3.zip"), initialSettings);
pluginManager.downloadAndExtract(pluginName);
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
assertFileContent(pluginConfigDir, "test2.txt", "version1\n");
assertFileContent(pluginConfigDir, "test.txt.new", "version3\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt.new", "version2\n");
assertFileContent(pluginConfigDir, "dir/testdir2.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt.new", "version2\n");
} finally {
// we need to clean up the copied dirs
IOUtils.rm(pluginConfigDir);
}
assertFileContent(pluginConfigDir, "test.txt", "version1\n");
assertFileContent(pluginConfigDir, "test2.txt", "version1\n");
assertFileContent(pluginConfigDir, "test.txt.new", "version3\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/testdir.txt.new", "version2\n");
assertFileContent(pluginConfigDir, "dir/testdir2.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1\n");
assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt.new", "version2\n");
}
// For #7152
@Test
public void testLocalPluginInstallWithBinOnly_7152() throws Exception {
String pluginName = "plugin-test";
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
Environment env = initialSettings.v2();
Path binDir = env.homeFile().resolve("bin");
if (!Files.exists(binDir)) {
Files.createDirectories(binDir);
}
Path pluginBinDir = binDir.resolve(pluginName);
try {
PluginManager pluginManager = pluginManager(getPluginUrlForResource("plugin_with_bin_only.zip"), initialSettings);
pluginManager.downloadAndExtract(pluginName);
Path[] plugins = pluginManager.getListInstalledPlugins();
assertThat(plugins.length, is(1));
assertDirectoryExists(pluginBinDir);
} finally {
// we need to clean up the copied dirs
IOUtils.rm(pluginBinDir);
}
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_with_bin_only.zip")));
assertThatPluginIsListed(pluginName);
assertDirectoryExists(pluginBinDir);
}
@Test
@ -234,8 +228,7 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
//When we have only a folder in top-level (no files either) but it's called _site, we make it work
//we can either remove the folder while extracting and then re-add it manually or just leave it as it is
String pluginName = "plugin-test";
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
downloadAndExtract(pluginName, initialSettings, getPluginUrlForResource("plugin_folder_site.zip"));
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_folder_site.zip")));
internalCluster().startNode(initialSettings.v1());
@ -247,8 +240,7 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
public void testLocalPluginWithoutFolders() throws Exception {
//When we don't have folders at all in the top-level, but only files, we don't modify anything
String pluginName = "plugin-test";
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
downloadAndExtract(pluginName, initialSettings, getPluginUrlForResource("plugin_without_folders.zip"));
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_without_folders.zip")));
internalCluster().startNode(initialSettings.v1());
@ -260,8 +252,7 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
public void testLocalPluginFolderAndFile() throws Exception {
//When we have a single top-level folder but also files in the top-level, we don't modify anything
String pluginName = "plugin-test";
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
downloadAndExtract(pluginName, initialSettings, getPluginUrlForResource("plugin_folder_file.zip"));
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_folder_file.zip")));
internalCluster().startNode(initialSettings.v1());
@ -269,37 +260,13 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
assertPluginAvailable(pluginName);
}
@Test(expected = IllegalArgumentException.class)
public void testSitePluginWithSourceThrows() throws Exception {
@Test
public void testSitePluginWithSourceDoesNotInstall() throws Exception {
String pluginName = "plugin-with-source";
downloadAndExtract(pluginName, buildInitialSettings(), getPluginUrlForResource("plugin_with_sourcefiles.zip"));
}
private PluginManager pluginManager(String pluginUrl) throws IOException {
return pluginManager(pluginUrl, buildInitialSettings());
}
private Tuple<Settings, Environment> buildInitialSettings() throws IOException {
Settings settings = Settings.settingsBuilder()
.put("discovery.zen.ping.multicast.enabled", false)
.put("http.enabled", true)
.put("path.home", createTempDir()).build();
return InternalSettingsPreparer.prepareSettings(settings, false);
}
/**
* We build a plugin manager instance which wait only for 30 seconds before
* raising an ElasticsearchTimeoutException
*/
private PluginManager pluginManager(String pluginUrl, Tuple<Settings, Environment> initialSettings) throws IOException {
if (!Files.exists(initialSettings.v2().pluginsFile())) {
Files.createDirectories(initialSettings.v2().pluginsFile());
}
return new PluginManager(initialSettings.v2(), pluginUrl, PluginManager.OutputMode.VERBOSE, TimeValue.timeValueSeconds(30));
}
private void downloadAndExtract(String pluginName, Tuple<Settings, Environment> initialSettings, String pluginUrl) throws IOException {
pluginManager(pluginUrl, initialSettings).downloadAndExtract(pluginName);
String cmd = String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_with_sourcefiles.zip"));
int status = new PluginManagerCliParser(terminal).execute(args(cmd));
assertThat(status, is(USAGE.status()));
assertThat(terminal.getTerminalOutput(), hasItem(containsString("Plugin installation assumed to be site plugin, but contains source code, aborting installation")));
}
private void assertPluginLoaded(String pluginName) {
@ -352,61 +319,44 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
@Test
public void testListInstalledEmpty() throws IOException {
Path[] plugins = pluginManager(null).getListInstalledPlugins();
assertThat(plugins, notNullValue());
assertThat(plugins.length, is(0));
assertStatusOk("list");
assertThat(terminal.getTerminalOutput(), hasItem(containsString("No plugin detected")));
}
@Test(expected = IOException.class)
public void testInstallPluginNull() throws IOException {
pluginManager(null).downloadAndExtract("plugin-test");
}
@Test
public void testInstallPlugin() throws IOException {
PluginManager pluginManager = pluginManager(getPluginUrlForResource("plugin_with_classfile.zip"));
pluginManager.downloadAndExtract("plugin-classfile");
Path[] plugins = pluginManager.getListInstalledPlugins();
assertThat(plugins, notNullValue());
assertThat(plugins.length, is(1));
String pluginName = "plugin-classfile";
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_with_classfile.zip")));
assertThatPluginIsListed("plugin-classfile");
}
@Test
public void testInstallSitePlugin() throws IOException {
Tuple<Settings, Environment> initialSettings = buildInitialSettings();
PluginManager pluginManager = pluginManager(getPluginUrlForResource("plugin_without_folders.zip"), initialSettings);
pluginManager.downloadAndExtract("plugin-site");
Path[] plugins = pluginManager.getListInstalledPlugins();
assertThat(plugins, notNullValue());
assertThat(plugins.length, is(1));
String pluginName = "plugin-site";
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginName, getPluginUrlForResource("plugin_without_folders.zip")));
assertThatPluginIsListed(pluginName);
// We want to check that Plugin Manager moves content to _site
assertFileExists(initialSettings.v2().pluginsFile().resolve("plugin-site/_site"));
}
private void singlePluginInstallAndRemove(String pluginShortName, String pluginCoordinates) throws IOException {
logger.info("--> trying to download and install [{}]", pluginShortName);
PluginManager pluginManager = pluginManager(pluginCoordinates);
try {
pluginManager.downloadAndExtract(pluginShortName);
Path[] plugins = pluginManager.getListInstalledPlugins();
assertThat(plugins, notNullValue());
assertThat(plugins.length, is(1));
// We remove it
pluginManager.removePlugin(pluginShortName);
plugins = pluginManager.getListInstalledPlugins();
assertThat(plugins, notNullValue());
assertThat(plugins.length, is(0));
} catch (IOException e) {
logger.warn("--> IOException raised while downloading plugin [{}]. Skipping test.", e, pluginShortName);
} catch (ElasticsearchTimeoutException e) {
logger.warn("--> timeout exception raised while downloading plugin [{}]. Skipping test.", pluginShortName);
private void singlePluginInstallAndRemove(String pluginDescriptor, String pluginName, String pluginCoordinates) throws IOException {
logger.info("--> trying to download and install [{}]", pluginDescriptor);
if (pluginCoordinates == null) {
assertStatusOk(String.format(Locale.ROOT, "install %s --verbose", pluginDescriptor));
} else {
assertStatusOk(String.format(Locale.ROOT, "install %s --url %s --verbose", pluginDescriptor, pluginCoordinates));
}
assertThatPluginIsListed(pluginName);
terminal.getTerminalOutput().clear();
assertStatusOk("remove " + pluginDescriptor);
assertThat(terminal.getTerminalOutput(), hasItem(containsString("Removing " + pluginDescriptor)));
// not listed anymore
terminal.getTerminalOutput().clear();
assertStatusOk("list");
assertThat(terminal.getTerminalOutput(), not(hasItem(containsString(pluginName))));
}
/**
@ -420,7 +370,7 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
@AwaitsFix(bugUrl = "fails with jar hell failures - http://build-us-00.elastic.co/job/es_core_master_oracle_6/519/testReport/")
public void testInstallPluginWithElasticsearchDownloadService() throws IOException {
assumeTrue("download.elastic.co is accessible", isDownloadServiceWorking("download.elastic.co", 80, "/elasticsearch/ci-test.txt"));
singlePluginInstallAndRemove("elasticsearch/elasticsearch-transport-thrift/2.4.0", null);
singlePluginInstallAndRemove("elasticsearch/elasticsearch-transport-thrift/2.4.0", "elasticsearch-transport-thrift", null);
}
/**
@ -435,7 +385,7 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
public void testInstallPluginWithMavenCentral() throws IOException {
assumeTrue("search.maven.org is accessible", isDownloadServiceWorking("search.maven.org", 80, "/"));
assumeTrue("repo1.maven.org is accessible", isDownloadServiceWorking("repo1.maven.org", 443, "/maven2/org/elasticsearch/elasticsearch-transport-thrift/2.4.0/elasticsearch-transport-thrift-2.4.0.pom"));
singlePluginInstallAndRemove("org.elasticsearch/elasticsearch-transport-thrift/2.4.0", null);
singlePluginInstallAndRemove("org.elasticsearch/elasticsearch-transport-thrift/2.4.0", "elasticsearch-transport-thrift", null);
}
/**
@ -448,7 +398,7 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
@Network
public void testInstallPluginWithGithub() throws IOException {
assumeTrue("github.com is accessible", isDownloadServiceWorking("github.com", 443, "/"));
singlePluginInstallAndRemove("elasticsearch/kibana", null);
singlePluginInstallAndRemove("elasticsearch/kibana", "kibana", null);
}
private boolean isDownloadServiceWorking(String host, int port, String resource) {
@ -469,48 +419,38 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
@Test
public void testRemovePlugin() throws Exception {
// We want to remove plugin with plugin short name
singlePluginInstallAndRemove("plugintest", getPluginUrlForResource("plugin_without_folders.zip"));
singlePluginInstallAndRemove("plugintest", "plugintest", getPluginUrlForResource("plugin_without_folders.zip"));
// We want to remove plugin with groupid/artifactid/version form
singlePluginInstallAndRemove("groupid/plugintest/1.0.0", getPluginUrlForResource("plugin_without_folders.zip"));
singlePluginInstallAndRemove("groupid/plugintest/1.0.0", "plugintest", getPluginUrlForResource("plugin_without_folders.zip"));
// We want to remove plugin with groupid/artifactid form
singlePluginInstallAndRemove("groupid/plugintest", getPluginUrlForResource("plugin_without_folders.zip"));
}
@Test(expected = IllegalArgumentException.class)
public void testRemovePlugin_NullName_ThrowsException() throws IOException {
pluginManager(getPluginUrlForResource("plugin_single_folder.zip")).removePlugin(null);
}
@Test(expected = IllegalArgumentException.class)
public void testRemovePluginWithURLForm() throws Exception {
PluginManager pluginManager = pluginManager(null);
pluginManager.removePlugin("file://whatever");
singlePluginInstallAndRemove("groupid/plugintest", "plugintest", getPluginUrlForResource("plugin_without_folders.zip"));
}
@Test
public void testForbiddenPluginName_ThrowsException() throws IOException {
runTestWithForbiddenName(null);
runTestWithForbiddenName("");
runTestWithForbiddenName("elasticsearch");
runTestWithForbiddenName("elasticsearch.bat");
runTestWithForbiddenName("elasticsearch.in.sh");
runTestWithForbiddenName("plugin");
runTestWithForbiddenName("plugin.bat");
runTestWithForbiddenName("service.bat");
runTestWithForbiddenName("ELASTICSEARCH");
runTestWithForbiddenName("ELASTICSEARCH.IN.SH");
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()));
}
private void runTestWithForbiddenName(String name) throws IOException {
try {
pluginManager(null).removePlugin(name);
fail("this plugin name [" + name +
"] should not be allowed");
} catch (IllegalArgumentException e) {
// We expect that error
}
@Test
public void testRemovePluginWithURLForm() throws Exception {
int status = new PluginManagerCliParser(terminal).execute(args("remove file://whatever"));
assertThat(terminal.getTerminalOutput(), hasItem(containsString("Illegal plugin name")));
assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(USAGE.status()));
}
@Test
public void testForbiddenPluginNames() throws IOException {
assertStatus("remove elasticsearch", USAGE);
assertStatus("remove elasticsearch.bat", USAGE);
assertStatus("remove elasticsearch.in.sh", USAGE);
assertStatus("remove plugin", USAGE);
assertStatus("remove plugin.bat", USAGE);
assertStatus("remove service.bat", USAGE);
assertStatus("remove ELASTICSEARCH", USAGE);
assertStatus("remove ELASTICSEARCH.IN.SH", USAGE);
}
@Test
@ -535,6 +475,33 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
}
}
@Test
public void testHelpWorks() throws IOException {
assertStatusOk("--help");
assertHelp("/org/elasticsearch/plugins/plugin.help");
terminal.getTerminalOutput().clear();
assertStatusOk("install -h");
assertHelp("/org/elasticsearch/plugins/plugin-install.help");
for (String plugin : PluginManager.OFFICIAL_PLUGINS) {
assertThat(terminal.getTerminalOutput(), hasItem(containsString(plugin)));
}
terminal.getTerminalOutput().clear();
assertStatusOk("remove --help");
assertHelp("/org/elasticsearch/plugins/plugin-remove.help");
terminal.getTerminalOutput().clear();
assertStatusOk("list -h");
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()));
}
/**
* Retrieve a URL string that represents the resource with the given {@code resourceName}.
* @param resourceName The resource name relative to {@link PluginManagerTests}.
@ -546,4 +513,28 @@ public class PluginManagerTests extends ElasticsearchIntegrationTest {
return "file://" + uri.getPath();
}
private Tuple<Settings, Environment> buildInitialSettings() throws IOException {
Settings settings = settingsBuilder()
.put("discovery.zen.ping.multicast.enabled", false)
.put("http.enabled", true)
.put("path.home", createTempDir()).build();
return InternalSettingsPreparer.prepareSettings(settings, false);
}
private void assertStatusOk(String command) {
assertStatus(command, CliTool.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 assertThatPluginIsListed(String pluginName) {
terminal.getTerminalOutput().clear();
assertStatusOk("list");
String message = String.format(Locale.ROOT, "Terminal output was: %s", terminal.getTerminalOutput());
assertThat(message, terminal.getTerminalOutput(), hasItem(containsString(pluginName)));
}
}

View File

@ -20,7 +20,7 @@ Installing plugins typically take the following form:
[source,sh]
-----------------------------------
bin/plugin --install plugin_name
bin/plugin install plugin_name
-----------------------------------
The plugin will be automatically downloaded in this case from `download.elastic.co` download service using the
@ -30,7 +30,7 @@ For older version of elasticsearch (prior to 2.0.0) or community plugins, you wo
[source,sh]
-----------------------------------
bin/plugin --install <org>/<user/component>/<version>
bin/plugin install <org>/<user/component>/<version>
-----------------------------------
The plugins will be automatically downloaded in this case from `download.elastic.co` (for older plugins),
@ -45,7 +45,7 @@ for example:
[source,sh]
-----------------------------------
bin/plugin --url file:///path/to/plugin --install plugin-name
bin/plugin install plugin-name --url file:///path/to/plugin
-----------------------------------
@ -70,8 +70,8 @@ running:
[source,js]
--------------------------------------------------
bin/plugin --install mobz/elasticsearch-head
bin/plugin --install lukas-vlcek/bigdesk
bin/plugin install mobz/elasticsearch-head
bin/plugin install lukas-vlcek/bigdesk
--------------------------------------------------
Will install both of those site plugins, with `elasticsearch-head`
@ -108,7 +108,7 @@ Removing plugins typically take the following form:
[source,sh]
-----------------------------------
plugin --remove <pluginname>
plugin remove <pluginname>
-----------------------------------
[float]
@ -126,8 +126,8 @@ Note that exit codes could be:
[source,sh]
-----------------------------------
bin/plugin --install mobz/elasticsearch-head --verbose
plugin --remove head --silent
bin/plugin install mobz/elasticsearch-head --verbose
plugin remove head --silent
-----------------------------------
[float]
@ -140,33 +140,25 @@ different values:
[source,sh]
-----------------------------------
# Wait for 30 seconds before failing
bin/plugin --install mobz/elasticsearch-head --timeout 30s
bin/plugin install mobz/elasticsearch-head --timeout 30s
# Wait for 1 minute before failing
bin/plugin --install mobz/elasticsearch-head --timeout 1m
bin/plugin install mobz/elasticsearch-head --timeout 1m
# Wait forever (default)
bin/plugin --install mobz/elasticsearch-head --timeout 0
bin/plugin install mobz/elasticsearch-head --timeout 0
-----------------------------------
[float]
==== Proxy settings
To install a plugin via a proxy, you can pass the proxy details using the environment variables `proxyHost` and `proxyPort`.
On Linux and Mac, here is an example of setting it:
[source,sh]
-----------------------------------
bin/plugin -DproxyHost=host_name -DproxyPort=port_number --install mobz/elasticsearch-head
-----------------------------------
On Windows, here is an example of setting it:
[source,sh]
-----------------------------------
set JAVA_OPTS="-DproxyHost=host_name -DproxyPort=port_number"
bin/plugin --install mobz/elasticsearch-head
bin/plugin install mobz/elasticsearch-head
-----------------------------------
[float]

View File

@ -29,7 +29,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install analysis-icu \
plugin install analysis-icu \
--url file:target/releases/elasticsearch-analysis-icu-X.X.X-SNAPSHOT.zip
```

View File

@ -29,7 +29,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install analysis-kuromoji \
plugin install analysis-kuromoji \
--url file:target/releases/elasticsearch-analysis-kuromoji-X.X.X-SNAPSHOT.zip
```

View File

@ -28,7 +28,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install analysis-phonetic \
plugin install analysis-phonetic \
--url file:target/releases/elasticsearch-analysis-phonetic-X.X.X-SNAPSHOT.zip
```

View File

@ -28,7 +28,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install analysis-smartcn \
plugin install analysis-smartcn \
--url file:target/releases/elasticsearch-analysis-smartcn-X.X.X-SNAPSHOT.zip
```

View File

@ -27,7 +27,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install analysis-stempel \
plugin install analysis-stempel \
--url file:target/releases/elasticsearch-analysis-stempel-X.X.X-SNAPSHOT.zip
```

View File

@ -28,7 +28,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install cloud-aws \
plugin install cloud-aws \
--url file:target/releases/elasticsearch-cloud-aws-X.X.X-SNAPSHOT.zip
```

View File

@ -27,7 +27,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install cloud-azure \
plugin install cloud-azure \
--url file:target/releases/elasticsearch-cloud-azure-X.X.X-SNAPSHOT.zip
```

View File

@ -27,7 +27,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install cloud-gce \
plugin install cloud-gce \
--url file:target/releases/elasticsearch-cloud-gce-X.X.X-SNAPSHOT.zip
```
@ -148,7 +148,7 @@ Install the plugin:
```sh
# Use Plugin Manager to install it
sudo /usr/share/elasticsearch/bin/plugin --install elasticsearch/elasticsearch-cloud-gce/2.2.0
sudo /usr/share/elasticsearch/bin/plugin install elasticsearch/elasticsearch-cloud-gce/2.2.0
# Configure it:
sudo vi /etc/elasticsearch/elasticsearch.yml

View File

@ -27,7 +27,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install lang-javascript \
plugin install lang-javascript \
--url file:target/releases/elasticsearch-lang-javascript-X.X.X-SNAPSHOT.zip
```

View File

@ -27,7 +27,7 @@ To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install lang-python \
plugin install lang-python \
--url file:target/releases/elasticsearch-lang-python-X.X.X-SNAPSHOT.zip
```