diff --git a/docs/reference/modules/plugins.asciidoc b/docs/reference/modules/plugins.asciidoc index 1dddf19a94f..5d0be647848 100644 --- a/docs/reference/modules/plugins.asciidoc +++ b/docs/reference/modules/plugins.asciidoc @@ -89,6 +89,19 @@ will not start. A list of the currently loaded plugins can be retrieved using the <>. +[float] +==== Removing plugins + +Removing plugins can either be done manually by removing them under the +`plugins` directory, or using the `plugin` script. + +Removing plugins typically take the following form: + +[source,shell] +----------------------------------- +plugin --remove +----------------------------------- + [float] === Known Plugins diff --git a/src/main/java/org/elasticsearch/plugins/PluginManager.java b/src/main/java/org/elasticsearch/plugins/PluginManager.java index 9bf0abb644d..b052a2a1fb0 100644 --- a/src/main/java/org/elasticsearch/plugins/PluginManager.java +++ b/src/main/java/org/elasticsearch/plugins/PluginManager.java @@ -35,10 +35,9 @@ import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -97,7 +96,13 @@ public class PluginManager { throw new IOException("plugin directory " + environment.pluginsFile() + " is read only"); } - File pluginFile = new File(environment.pluginsFile(), name + ".zip"); + PluginHandle pluginHandle = PluginHandle.parse(name); + File pluginFile = pluginHandle.distroFile(environment); + // extract the plugin + File extractLocation = pluginHandle.extractedDir(environment); + if (extractLocation.exists()) { + throw new IOException("plugin directory " + extractLocation.getAbsolutePath() + " already exists. To update the plugin, uninstall it first using -remove " + name + " command"); + } // first, try directly from the URL provided boolean downloaded = false; @@ -116,92 +121,17 @@ public class PluginManager { } } - // now, try as a path name... if (!downloaded) { - if (name.indexOf('/') != -1) { - // github repo - String[] elements = name.split("/"); - String userName = elements[0]; - String repoName = elements[1]; - String version = null; - if (elements.length > 2) { - version = elements[2]; - } - // the installation file should not include the userName, just the repoName - name = repoName; - if (name.startsWith("elasticsearch-")) { - // remove elasticsearch- prefix - name = name.substring("elasticsearch-".length()); - } else if (name.startsWith("es-")) { - // remove es- prefix - name = name.substring("es-".length()); - } - - // update the plugin file name to reflect the extracted name - pluginFile = new File(environment.pluginsFile(), name + ".zip"); - - if (version != null) { - URL pluginUrl = new URL("http://download.elasticsearch.org/" + userName + "/" + repoName + "/" + repoName + "-" + version + ".zip"); - System.out.println("Trying " + pluginUrl.toExternalForm() + "..."); - try { - downloadHelper.download(pluginUrl, pluginFile, new HttpDownloadHelper.VerboseProgress(System.out)); - downloaded = true; - } catch (Exception e) { - if (verbose) { - System.out.println("Failed: " + ExceptionsHelper.detailedMessage(e)); - } - } - if (!downloaded) { - // try maven, see if its there... (both central and sonatype) - pluginUrl = new URL("http://search.maven.org/remotecontent?filepath=" + userName.replace('.', '/') + "/" + repoName + "/" + version + "/" + repoName + "-" + version + ".zip"); - System.out.println("Trying " + pluginUrl.toExternalForm() + "..."); - try { - downloadHelper.download(pluginUrl, pluginFile, new HttpDownloadHelper.VerboseProgress(System.out)); - downloaded = true; - } catch (Exception e) { - if (verbose) { - System.out.println("Failed: " + ExceptionsHelper.detailedMessage(e)); - } - } - if (!downloaded) { - pluginUrl = new URL("https://oss.sonatype.org/service/local/repositories/releases/content/" + userName.replace('.', '/') + "/" + repoName + "/" + version + "/" + repoName + "-" + version + ".zip"); - System.out.println("Trying " + pluginUrl.toExternalForm() + "..."); - try { - downloadHelper.download(pluginUrl, pluginFile, new HttpDownloadHelper.VerboseProgress(System.out)); - downloaded = true; - } catch (Exception e) { - if (verbose) { - System.out.println("Failed: " + ExceptionsHelper.detailedMessage(e)); - } - } - } - } - if (!downloaded) { - // try it as a site plugin tagged - pluginUrl = new URL("https://github.com/" + userName + "/" + repoName + "/archive/v" + version + ".zip"); - System.out.println("Trying " + pluginUrl.toExternalForm() + "... (assuming site plugin)"); - try { - downloadHelper.download(pluginUrl, pluginFile, new HttpDownloadHelper.VerboseProgress(System.out)); - downloaded = true; - } catch (Exception e1) { - // ignore - if (verbose) { - System.out.println("Failed: " + ExceptionsHelper.detailedMessage(e1)); - } - } - } - } else { - // assume site plugin, download master.... - URL pluginUrl = new URL("https://github.com/" + userName + "/" + repoName + "/archive/master.zip"); - System.out.println("Trying " + pluginUrl.toExternalForm() + "... (assuming site plugin)"); - try { - downloadHelper.download(pluginUrl, pluginFile, new HttpDownloadHelper.VerboseProgress(System.out)); - downloaded = true; - } catch (Exception e2) { - // ignore - if (verbose) { - System.out.println("Failed: " + ExceptionsHelper.detailedMessage(e2)); - } + // We try all possible locations + for (URL url: pluginHandle.urls()) { + System.out.println("Trying " + url.toExternalForm() + "..."); + try { + downloadHelper.download(url, pluginFile, new HttpDownloadHelper.VerboseProgress(System.out)); + downloaded = true; + break; + } catch (Exception e) { + if (verbose) { + System.out.println("Failed: " + ExceptionsHelper.detailedMessage(e)); } } } @@ -211,11 +141,6 @@ public class PluginManager { throw new IOException("failed to download out of all possible locations..., use -verbose to get detailed information"); } - // extract the plugin - File extractLocation = new File(environment.pluginsFile(), name); - if (extractLocation.exists()) { - throw new IOException("plugin directory " + extractLocation.getAbsolutePath() + " already exists. To update the plugin, uninstall it first using -remove " + name + " command"); - } ZipFile zipFile = null; try { zipFile = new ZipFile(pluginFile); @@ -259,7 +184,7 @@ public class PluginManager { File binFile = new File(extractLocation, "bin"); if (binFile.exists() && binFile.isDirectory()) { - File toLocation = new File(new File(environment.homeFile(), "bin"), name); + File toLocation = pluginHandle.binDir(environment); System.out.println("Found bin, moving to " + toLocation.getAbsolutePath()); FileSystemUtils.deleteRecursively(toLocation); binFile.renameTo(toLocation); @@ -282,22 +207,38 @@ public class PluginManager { } public void removePlugin(String name) throws IOException { - File pluginToDelete = new File(environment.pluginsFile(), name); + PluginHandle pluginHandle = PluginHandle.parse(name); + boolean removed = false; + + File pluginToDelete = pluginHandle.extractedDir(environment); if (pluginToDelete.exists()) { FileSystemUtils.deleteRecursively(pluginToDelete, true); + removed = true; } - pluginToDelete = new File(environment.pluginsFile(), name + ".zip"); + pluginToDelete = pluginHandle.distroFile(environment); if (pluginToDelete.exists()) { pluginToDelete.delete(); + removed = true; } - File binLocation = new File(new File(environment.homeFile(), "bin"), name); + File binLocation = pluginHandle.binDir(environment); if (binLocation.exists()) { FileSystemUtils.deleteRecursively(binLocation); + removed = true; } + if (removed) { + System.out.println("Removed " + name); + } else { + System.out.println("Plugin " + name + " not found. Run plugin --list to get list of installed plugins."); + } + } + + public File[] getListInstalledPlugins() { + File[] plugins = environment.pluginsFile().listFiles(); + return plugins; } public void listInstalledPlugins() { - File[] plugins = environment.pluginsFile().listFiles(); + File[] plugins = getListInstalledPlugins(); System.out.println("Installed plugins:"); if (plugins == null || plugins.length == 0) { System.out.println(" - No plugin detected in " + environment.pluginsFile().getAbsolutePath()); @@ -461,4 +402,95 @@ public class PluginManager { System.out.println(" " + message); } } + + /** + * Helper class to extract properly user name, repository name, version and plugin name + * from plugin name given by a user. + */ + static class PluginHandle { + + final String name; + final String version; + final String user; + final String repo; + + PluginHandle(String name, String version, String user, String repo) { + this.name = name; + this.version = version; + this.user = user; + this.repo = repo; + } + + List urls() { + List urls = new ArrayList(); + if (version != null) { + // Elasticsearch download service + addUrl(urls, "http://download.elasticsearch.org/" + user + "/" + repo + "/" + repo + "-" + version + ".zip"); + // Maven central repository + addUrl(urls, "http://search.maven.org/remotecontent?filepath=" + user.replace('.', '/') + "/" + repo + "/" + version + "/" + repo + "-" + version + ".zip"); + // Sonatype repository + addUrl(urls, "https://oss.sonatype.org/service/local/repositories/releases/content/" + user.replace('.', '/') + "/" + repo + "/" + version + "/" + repo + "-" + version + ".zip"); + // Github repository + addUrl(urls, "https://github.com/" + user + "/" + repo + "/archive/v" + version + ".zip"); + } + // Github repository for master branch (assume site) + addUrl(urls, "https://github.com/" + user + "/" + repo + "/archive/master.zip"); + return urls; + } + + private static void addUrl(List urls, String url) { + try { + URL _url = new URL(url); + urls.add(new URL(url)); + } catch (MalformedURLException e) { + // We simply ignore malformed URL + } + } + + File distroFile(Environment env) { + return new File(env.pluginsFile(), name + ".zip"); + } + + File extractedDir(Environment env) { + return new File(env.pluginsFile(), name); + } + + File binDir(Environment env) { + return new File(new File(env.homeFile(), "bin"), name); + } + + static PluginHandle parse(String name) { + String[] elements = name.split("/"); + // We first consider the simplest form: pluginname + String repo = elements[0]; + String user = null; + String version = null; + + // We consider the form: username/pluginname + if (elements.length > 1) { + user = elements[0]; + repo = elements[1]; + + // We consider the form: username/pluginname/version + if (elements.length > 2) { + version = elements[2]; + } + } + + if (repo.startsWith("elasticsearch-")) { + // remove elasticsearch- prefix + String endname = repo.substring("elasticsearch-".length()); + return new PluginHandle(endname, version, user, repo); + } + + if (name.startsWith("es-")) { + // remove es- prefix + String endname = repo.substring("es-".length()); + return new PluginHandle(endname, version, user, repo); + } + + return new PluginHandle(repo, version, user, repo); + } + } + } diff --git a/src/test/java/org/elasticsearch/test/integration/plugin/PluginManagerTests.java b/src/test/java/org/elasticsearch/test/integration/plugin/PluginManagerTests.java index 80cac62e864..791d588f118 100644 --- a/src/test/java/org/elasticsearch/test/integration/plugin/PluginManagerTests.java +++ b/src/test/java/org/elasticsearch/test/integration/plugin/PluginManagerTests.java @@ -38,11 +38,10 @@ import org.junit.Before; import org.junit.Test; import java.io.File; +import java.io.IOException; import java.net.URL; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.*; public class PluginManagerTests extends AbstractNodesTests { @@ -102,14 +101,17 @@ public class PluginManagerTests extends AbstractNodesTests { assertPluginAvailable(node, pluginName); } - private static void downloadAndExtract(String pluginName, String pluginUrl) throws Exception { + private static PluginManager pluginManager(String pluginUrl) { Tuple initialSettings = InternalSettingsPerparer.prepareSettings( ImmutableSettings.settingsBuilder().build(), false); if (!initialSettings.v2().pluginsFile().exists()) { FileSystemUtils.mkdirs(initialSettings.v2().pluginsFile()); } - PluginManager pluginManager = new PluginManager(initialSettings.v2(), pluginUrl); - pluginManager.downloadAndExtract(pluginName, false); + return new PluginManager(initialSettings.v2(), pluginUrl); + } + + private static void downloadAndExtract(String pluginName, String pluginUrl) throws IOException { + pluginManager(pluginUrl).downloadAndExtract(pluginName, false); } private Node startNode() { @@ -152,4 +154,30 @@ public class PluginManagerTests extends AbstractNodesTests { private void deletePluginsFolder() { FileSystemUtils.deleteRecursively(new File(PLUGIN_DIR)); } + + private void singlePluginInstallAndRemove(String pluginName, String pluginCoordinates) throws IOException { + PluginManager pluginManager = pluginManager(pluginCoordinates); + pluginManager.downloadAndExtract("plugin", false); + File[] plugins = pluginManager.getListInstalledPlugins(); + assertThat(plugins, notNullValue()); + assertThat(plugins.length, is(1)); + + // We remove it + pluginManager.removePlugin(pluginName); + plugins = pluginManager.getListInstalledPlugins(); + assertThat(plugins, notNullValue()); + assertThat(plugins.length, is(0)); + } + + @Test + public void testRemovePlugin() throws Exception { + // We want to remove plugin with plugin short name + singlePluginInstallAndRemove("plugin", "file://".concat(PluginManagerTests.class.getResource("plugin_without_folders.zip").getFile())); + + // We want to remove plugin with groupid/artifactid/version form + singlePluginInstallAndRemove("groupid/plugin/1.0.0", "file://".concat(PluginManagerTests.class.getResource("plugin_without_folders.zip").getFile())); + + // We want to remove plugin with groupid/artifactid form + singlePluginInstallAndRemove("groupid/plugin", "file://".concat(PluginManagerTests.class.getResource("plugin_without_folders.zip").getFile())); + } }