/*
* 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 org.apache.commons.cli.CommandLine;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
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.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Locale;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
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) {
// initialize default for es.logger.level because we will not read the logging.yml
String loggerLevel = System.getProperty("es.logger.level", "INFO");
// Set the appender for all potential log files to terminal so that other components that use the logger print out the
// same terminal.
// The reason for this is that the plugin cli cannot be configured with a file appender because when the plugin command is
// executed there is no way of knowing where the logfiles should be placed. For example, if elasticsearch
// is run as service then the logs should be at /var/log/elasticsearch but when started from the tar they should be at es.home/logs.
// Therefore we print to Terminal.
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.builder()
.put("appender.terminal.type", "terminal")
.put("rootLogger", "${es.logger.level}, terminal")
.put("es.logger.level", loggerLevel)
.build(), Terminal.DEFAULT);
// configure but do not read the logging conf file
LogConfigurator.configure(env.settings(), false);
int status = new PluginManagerCliParser().execute(args).status();
exit(status);
}
@SuppressForbidden(reason = "Allowed to exit explicitly from #main()")
private static void exit(int status) {
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.coalesceToEmpty(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("t", "timeout").required(false).hasArg(false))
.options(option("b", "batch").required(false))
.build();
static Command parse(Terminal terminal, CommandLine cli) {
String[] args = cli.getArgs();
// install [plugin-name/url]
if ((args == null) || (args.length == 0)) {
return exitCmd(ExitStatus.USAGE, terminal, "plugin name or url is missing (type -h for help)");
}
String name = args[0];
URL optionalPluginUrl = null;
// try parsing cli argument as URL
try {
optionalPluginUrl = new URL(name);
name = null;
} catch (MalformedURLException e) {
// we tried to parse the cli argument as url and failed
// continue treating it as a symbolic plugin name like `analysis-icu` etc.
}
TimeValue timeout = TimeValue.parseTimeValue(cli.getOptionValue("t"), DEFAULT_TIMEOUT, "cli");
OutputMode outputMode = OutputMode.DEFAULT;
if (cli.hasOption("s")) {
outputMode = OutputMode.SILENT;
}
if (cli.hasOption("v")) {
outputMode = OutputMode.VERBOSE;
}
boolean batch = System.console() == null;
if (cli.hasOption("b")) {
batch = true;
}
return new Install(terminal, name, outputMode, optionalPluginUrl, timeout, batch);
}
final String name;
private OutputMode outputMode;
final URL url;
final TimeValue timeout;
final boolean batch;
Install(Terminal terminal, String name, OutputMode outputMode, URL url, TimeValue timeout, boolean batch) {
super(terminal);
this.name = name;
this.outputMode = outputMode;
this.url = url;
this.timeout = timeout;
this.batch = batch;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
PluginManager pluginManager = new PluginManager(env, url, outputMode, timeout);
if (name != null) {
terminal.println("-> Installing " + Strings.coalesceToEmpty(name) + "...");
} else {
terminal.println("-> Installing from " + URLDecoder.decode(url.toString(), "UTF-8") + "...");
}
pluginManager.downloadAndExtract(name, terminal, batch);
return ExitStatus.OK;
}
}
}