|
@@ -23,16 +23,19 @@ import java.nio.file.Files;
|
|
|
import java.nio.file.Path;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Locale;
|
|
|
+import java.util.Map;
|
|
|
import java.util.Set;
|
|
|
+import java.util.StringJoiner;
|
|
|
import java.util.stream.Collectors;
|
|
|
import java.util.stream.Stream;
|
|
|
|
|
|
import static org.elasticsearch.cli.Terminal.Verbosity.VERBOSE;
|
|
|
|
|
|
/**
|
|
|
- * A command for the plugin CLI to remove a plugin from Elasticsearch.
|
|
|
+ * A command for the plugin CLI to remove plugins from Elasticsearch.
|
|
|
*/
|
|
|
class RemovePluginCommand extends EnvironmentAwareCommand {
|
|
|
|
|
@@ -44,16 +47,16 @@ class RemovePluginCommand extends EnvironmentAwareCommand {
|
|
|
private final OptionSpec<String> arguments;
|
|
|
|
|
|
RemovePluginCommand() {
|
|
|
- super("removes a plugin from Elasticsearch");
|
|
|
+ super("removes plugins from Elasticsearch");
|
|
|
this.purgeOption = parser.acceptsAll(Arrays.asList("p", "purge"), "Purge plugin configuration files");
|
|
|
- this.arguments = parser.nonOptions("plugin name");
|
|
|
+ this.arguments = parser.nonOptions("plugin id");
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
protected void execute(final Terminal terminal, final OptionSet options, final Environment env) throws Exception {
|
|
|
- final String pluginName = arguments.value(options);
|
|
|
+ final List<String> pluginIds = arguments.values(options);
|
|
|
final boolean purge = options.has(purgeOption);
|
|
|
- execute(terminal, env, pluginName, purge);
|
|
|
+ execute(terminal, env, pluginIds, purge);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -61,55 +64,91 @@ class RemovePluginCommand extends EnvironmentAwareCommand {
|
|
|
*
|
|
|
* @param terminal the terminal to use for input/output
|
|
|
* @param env the environment for the local node
|
|
|
- * @param pluginName the name of the plugin to remove
|
|
|
+ * @param pluginIds the IDs of the plugins to remove
|
|
|
* @param purge if true, plugin configuration files will be removed but otherwise preserved
|
|
|
* @throws IOException if any I/O exception occurs while performing a file operation
|
|
|
- * @throws UserException if plugin name is null
|
|
|
+ * @throws UserException if pluginIds is null or empty
|
|
|
* @throws UserException if plugin directory does not exist
|
|
|
* @throws UserException if the plugin bin directory is not a directory
|
|
|
*/
|
|
|
- void execute(Terminal terminal, Environment env, String pluginName, boolean purge) throws IOException, UserException {
|
|
|
- if (pluginName == null) {
|
|
|
- throw new UserException(ExitCodes.USAGE, "plugin name is required");
|
|
|
+ void execute(Terminal terminal, Environment env, List<String> pluginIds, boolean purge) throws IOException, UserException {
|
|
|
+ if (pluginIds == null || pluginIds.isEmpty()) {
|
|
|
+ throw new UserException(ExitCodes.USAGE, "At least one plugin ID is required");
|
|
|
}
|
|
|
|
|
|
- // first make sure nothing extends this plugin
|
|
|
- List<String> usedBy = new ArrayList<>();
|
|
|
+ ensurePluginsNotUsedByOtherPlugins(env, pluginIds);
|
|
|
+
|
|
|
+ for (String pluginId : pluginIds) {
|
|
|
+ checkCanRemove(env, pluginId, purge);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (String pluginId : pluginIds) {
|
|
|
+ removePlugin(env, terminal, pluginId, purge);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ensurePluginsNotUsedByOtherPlugins(Environment env, List<String> pluginIds) throws IOException, UserException {
|
|
|
+ // First make sure nothing extends this plugin
|
|
|
+ final Map<String, List<String>> usedBy = new HashMap<>();
|
|
|
Set<PluginsService.Bundle> bundles = PluginsService.getPluginBundles(env.pluginsFile());
|
|
|
for (PluginsService.Bundle bundle : bundles) {
|
|
|
for (String extendedPlugin : bundle.plugin.getExtendedPlugins()) {
|
|
|
- if (extendedPlugin.equals(pluginName)) {
|
|
|
- usedBy.add(bundle.plugin.getName());
|
|
|
+ for (String pluginId : pluginIds) {
|
|
|
+ if (extendedPlugin.equals(pluginId)) {
|
|
|
+ usedBy.computeIfAbsent(bundle.plugin.getName(), (_key -> new ArrayList<>())).add(pluginId);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- if (usedBy.isEmpty() == false) {
|
|
|
- throw new UserException(
|
|
|
- PLUGIN_STILL_USED,
|
|
|
- "plugin [" + pluginName + "] cannot be removed" + " because it is extended by other plugins: " + usedBy
|
|
|
- );
|
|
|
+ if (usedBy.isEmpty()) {
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- final Path pluginDir = env.pluginsFile().resolve(pluginName);
|
|
|
- final Path pluginConfigDir = env.configFile().resolve(pluginName);
|
|
|
- final Path removing = env.pluginsFile().resolve(".removing-" + pluginName);
|
|
|
+ final StringJoiner message = new StringJoiner("\n");
|
|
|
+ message.add("Cannot remove plugins because the following are extended by other plugins:");
|
|
|
+ usedBy.forEach((key, value) -> {
|
|
|
+ String s = "\t" + key + " used by " + value;
|
|
|
+ message.add(s);
|
|
|
+ });
|
|
|
+
|
|
|
+ throw new UserException(PLUGIN_STILL_USED, message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void checkCanRemove(Environment env, String pluginId, boolean purge) throws UserException {
|
|
|
+ final Path pluginDir = env.pluginsFile().resolve(pluginId);
|
|
|
+ final Path pluginConfigDir = env.configFile().resolve(pluginId);
|
|
|
+ final Path removing = env.pluginsFile().resolve(".removing-" + pluginId);
|
|
|
|
|
|
- terminal.println("-> removing [" + pluginName + "]...");
|
|
|
/*
|
|
|
* If the plugin does not exist and the plugin config does not exist, fail to the user that the plugin is not found, unless there's
|
|
|
* a marker file left from a previously failed attempt in which case we proceed to clean up the marker file. Or, if the plugin does
|
|
|
* not exist, the plugin config does, and we are not purging, again fail to the user that the plugin is not found.
|
|
|
*/
|
|
|
- if ((!Files.exists(pluginDir) && !Files.exists(pluginConfigDir) && !Files.exists(removing))
|
|
|
- || (!Files.exists(pluginDir) && Files.exists(pluginConfigDir) && !purge)) {
|
|
|
+ if ((Files.exists(pluginDir) == false && Files.exists(pluginConfigDir) == false && Files.exists(removing) == false)
|
|
|
+ || (Files.exists(pluginDir) == false && Files.exists(pluginConfigDir) && purge == false)) {
|
|
|
final String message = String.format(
|
|
|
Locale.ROOT,
|
|
|
"plugin [%s] not found; run 'elasticsearch-plugin list' to get list of installed plugins",
|
|
|
- pluginName
|
|
|
+ pluginId
|
|
|
);
|
|
|
throw new UserException(ExitCodes.CONFIG, message);
|
|
|
}
|
|
|
|
|
|
+ final Path pluginBinDir = env.binFile().resolve(pluginId);
|
|
|
+ if (Files.exists(pluginBinDir)) {
|
|
|
+ if (Files.isDirectory(pluginBinDir) == false) {
|
|
|
+ throw new UserException(ExitCodes.IO_ERROR, "bin dir for " + pluginId + " is not a directory");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void removePlugin(Environment env, Terminal terminal, String pluginId, boolean purge) throws IOException {
|
|
|
+ final Path pluginDir = env.pluginsFile().resolve(pluginId);
|
|
|
+ final Path pluginConfigDir = env.configFile().resolve(pluginId);
|
|
|
+ final Path removing = env.pluginsFile().resolve(".removing-" + pluginId);
|
|
|
+
|
|
|
+ terminal.println("-> removing [" + pluginId + "]...");
|
|
|
+
|
|
|
final List<Path> pluginPaths = new ArrayList<>();
|
|
|
|
|
|
/*
|
|
@@ -123,11 +162,8 @@ class RemovePluginCommand extends EnvironmentAwareCommand {
|
|
|
terminal.println(VERBOSE, "removing [" + pluginDir + "]");
|
|
|
}
|
|
|
|
|
|
- final Path pluginBinDir = env.binFile().resolve(pluginName);
|
|
|
+ final Path pluginBinDir = env.binFile().resolve(pluginId);
|
|
|
if (Files.exists(pluginBinDir)) {
|
|
|
- if (Files.isDirectory(pluginBinDir) == false) {
|
|
|
- throw new UserException(ExitCodes.IO_ERROR, "bin dir for " + pluginName + " is not a directory");
|
|
|
- }
|
|
|
try (Stream<Path> paths = Files.list(pluginBinDir)) {
|
|
|
pluginPaths.addAll(paths.collect(Collectors.toList()));
|
|
|
}
|
|
@@ -180,7 +216,6 @@ class RemovePluginCommand extends EnvironmentAwareCommand {
|
|
|
// finally, add the marker file
|
|
|
pluginPaths.add(removing);
|
|
|
|
|
|
- IOUtils.rm(pluginPaths.toArray(new Path[pluginPaths.size()]));
|
|
|
+ IOUtils.rm(pluginPaths.toArray(new Path[0]));
|
|
|
}
|
|
|
-
|
|
|
}
|