All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.plugins.PluginManager Maven / Gradle / Ivy

There is a newer version: 8.14.1
Show newest version
/*
 * 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.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.*;
import org.elasticsearch.bootstrap.JarHell;
import org.elasticsearch.common.Strings;
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.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.PluginsService.Bundle;

import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.*;
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;

/**
 *
 */
public class PluginManager {

    public static final String PROPERTY_SUPPORT_STAGING_URLS = "es.plugins.staging";

    public enum OutputMode {
        DEFAULT, SILENT, VERBOSE
    }

    private static final ImmutableSet BLACKLIST = ImmutableSet.builder()
            .add("elasticsearch",
                    "elasticsearch.bat",
                    "elasticsearch.in.sh",
                    "plugin",
                    "plugin.bat",
                    "service.bat").build();

    static final Set MODULES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
            "lang-expression",
            "lang-groovy")));

    static final ImmutableSet OFFICIAL_PLUGINS = ImmutableSet.builder()
            .add(
                    "analysis-icu",
                    "analysis-kuromoji",
                    "analysis-phonetic",
                    "analysis-smartcn",
                    "analysis-stempel",
                    "cloud-aws",
                    "cloud-azure",
                    "cloud-gce",
                    "delete-by-query",
                    "discovery-multicast",
                    "lang-javascript",
                    "lang-python",
                    "mapper-attachments",
                    "mapper-murmur3",
                    "mapper-size"
            ).build();

    private final Environment environment;
    private URL url;
    private OutputMode outputMode;
    private TimeValue timeout;

    public PluginManager(Environment environment, URL url, OutputMode outputMode, TimeValue timeout) {
        this.environment = environment;
        this.url = url;
        this.outputMode = outputMode;
        this.timeout = timeout;
    }

    public void downloadAndExtract(String name, Terminal terminal, boolean batch) throws IOException {
        if (name == null && url == null) {
            throw new IllegalArgumentException("plugin name or url must be supplied with install.");
        }

        if (!Files.exists(environment.pluginsFile())) {
            terminal.println("Plugins directory [%s] does not exist. Creating...", environment.pluginsFile());
            Files.createDirectory(environment.pluginsFile());
        }

        if (!Environment.isWritable(environment.pluginsFile())) {
            throw new IOException("plugin directory " + environment.pluginsFile() + " is read only");
        }

        PluginHandle pluginHandle;
        if (name != null) {
            pluginHandle = PluginHandle.parse(name);
            checkForForbiddenName(pluginHandle.name);
        } else {
            // if we have no name but url, use temporary name that will be overwritten later
            pluginHandle = new PluginHandle("temp_name" + new Random().nextInt(), null, null);
        }

        Path pluginFile = download(pluginHandle, terminal);
        extract(pluginHandle, terminal, pluginFile, batch);
    }

    private Path download(PluginHandle pluginHandle, Terminal terminal) throws IOException {
        Path pluginFile = pluginHandle.newDistroFile(environment);

        HttpDownloadHelper downloadHelper = new HttpDownloadHelper();
        boolean downloaded = false;
        boolean verified = false;
        HttpDownloadHelper.DownloadProgress progress;
        if (outputMode == OutputMode.SILENT) {
            progress = new HttpDownloadHelper.NullProgress();
        } else {
            progress = new HttpDownloadHelper.VerboseProgress(terminal.writer());
        }

        // first, try directly from the URL provided
        if (url != null) {
            URL pluginUrl = url;
            boolean isSecureProcotol = "https".equalsIgnoreCase(pluginUrl.getProtocol());
            boolean isAuthInfoSet = !Strings.isNullOrEmpty(pluginUrl.getUserInfo());
            if (isAuthInfoSet && !isSecureProcotol) {
                throw new IOException("Basic auth is only supported for HTTPS!");
            }

            terminal.println("Trying %s ...", pluginUrl.toExternalForm());
            try {
                downloadHelper.download(pluginUrl, pluginFile, progress, this.timeout);
                downloaded = true;
                terminal.println("Verifying %s checksums if available ...", pluginUrl.toExternalForm());
                Tuple sha1Info = pluginHandle.newChecksumUrlAndFile(environment, pluginUrl, "sha1");
                verified = downloadHelper.downloadAndVerifyChecksum(sha1Info.v1(), pluginFile,
                        sha1Info.v2(), progress, this.timeout, HttpDownloadHelper.SHA1_CHECKSUM);
                Tuple md5Info = pluginHandle.newChecksumUrlAndFile(environment, pluginUrl, "md5");
                verified = verified || downloadHelper.downloadAndVerifyChecksum(md5Info.v1(), pluginFile,
                        md5Info.v2(), progress, this.timeout, HttpDownloadHelper.MD5_CHECKSUM);
            } catch (ElasticsearchTimeoutException | ElasticsearchCorruptionException e) {
                throw e;
            } catch (Exception e) {
                // ignore
                terminal.println("Failed: %s", ExceptionsHelper.detailedMessage(e));
            }
        } else {
            if (PluginHandle.isOfficialPlugin(pluginHandle.name, pluginHandle.user, pluginHandle.version)) {
                checkForOfficialPlugins(pluginHandle.name);
            }
        }

        if (!downloaded && url == null) {
            // We try all possible locations
            for (URL url : pluginHandle.urls()) {
                terminal.println("Trying %s ...", url.toExternalForm());
                try {
                    downloadHelper.download(url, pluginFile, progress, this.timeout);
                    downloaded = true;
                    terminal.println("Verifying %s checksums if available ...", url.toExternalForm());
                    Tuple sha1Info = pluginHandle.newChecksumUrlAndFile(environment, url, "sha1");
                    verified = downloadHelper.downloadAndVerifyChecksum(sha1Info.v1(), pluginFile,
                            sha1Info.v2(), progress, this.timeout, HttpDownloadHelper.SHA1_CHECKSUM);
                    Tuple md5Info = pluginHandle.newChecksumUrlAndFile(environment, url, "md5");
                    verified = verified || downloadHelper.downloadAndVerifyChecksum(md5Info.v1(), pluginFile,
                            md5Info.v2(), progress, this.timeout, HttpDownloadHelper.MD5_CHECKSUM);
                    break;
                } catch (ElasticsearchTimeoutException | ElasticsearchCorruptionException e) {
                    throw e;
                } catch (Exception e) {
                    terminal.println(VERBOSE, "Failed: %s", ExceptionsHelper.detailedMessage(e));
                }
            }
        }

        if (!downloaded) {
            // try to cleanup what we downloaded
            IOUtils.deleteFilesIgnoringExceptions(pluginFile);
            throw new IOException("failed to download out of all possible locations..., use --verbose to get detailed information");
        }

        if (verified == false) {
            terminal.println("NOTE: Unable to verify checksum for downloaded plugin (unable to find .sha1 or .md5 file to verify)");
        }
        return pluginFile;
    }

    private void extract(PluginHandle pluginHandle, Terminal terminal, Path pluginFile, boolean batch) throws IOException {
        // unzip plugin to a staging temp dir, named for the plugin
        Path tmp = Files.createTempDirectory(environment.tmpFile(), null);
        Path root = tmp.resolve(pluginHandle.name);
        unzipPlugin(pluginFile, root);

        // find the actual root (in case its unzipped with extra directory wrapping)
        root = findPluginRoot(root);

        // read and validate the plugin descriptor
        PluginInfo info = PluginInfo.readFromProperties(root);
        terminal.println(VERBOSE, "%s", info);

        // don't let luser install plugin as a module... 
        // they might be unavoidably in maven central and are packaged up the same way)
        if (MODULES.contains(info.getName())) {
            throw new IOException("plugin '" + info.getName() + "' cannot be installed like this, it is a system module");
        }

        // update name in handle based on 'name' property found in descriptor file
        pluginHandle = new PluginHandle(info.getName(), pluginHandle.version, pluginHandle.user);
        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 " + pluginHandle.name + "' command");
        }

        // check for jar hell before any copying
        if (info.isJvm()) {
            jarHellCheck(root, info.isIsolated());
        }

        // read optional security policy (extra permissions)
        // if it exists, confirm or warn the user
        Path policy = root.resolve(PluginInfo.ES_PLUGIN_POLICY);
        if (Files.exists(policy)) {
            PluginSecurity.readPolicy(policy, terminal, environment, batch);
        }

        // install plugin
        FileSystemUtils.copyDirectoryRecursively(root, extractLocation);
        terminal.println("Installed %s into %s", pluginHandle.name, extractLocation.toAbsolutePath());

        // cleanup
        tryToDeletePath(terminal, tmp, pluginFile);

        // take care of bin/ by moving and applying permissions if needed
        Path sourcePluginBinDirectory = extractLocation.resolve("bin");
        Path destPluginBinDirectory = pluginHandle.binDir(environment);
        boolean needToCopyBinDirectory = Files.exists(sourcePluginBinDirectory);
        if (needToCopyBinDirectory) {
            if (Files.exists(destPluginBinDirectory) && !Files.isDirectory(destPluginBinDirectory)) {
                tryToDeletePath(terminal, extractLocation);
                throw new IOException("plugin bin directory " + destPluginBinDirectory + " is not a directory");
            }

            try {
                copyBinDirectory(sourcePluginBinDirectory, destPluginBinDirectory, pluginHandle.name, terminal);
            } catch (IOException e) {
                // rollback and remove potentially before installed leftovers
                terminal.printError("Error copying bin directory [%s] to [%s], cleaning up, reason: %s", sourcePluginBinDirectory, destPluginBinDirectory, ExceptionsHelper.detailedMessage(e));
                tryToDeletePath(terminal, extractLocation, pluginHandle.binDir(environment));
                throw e;
            }

        }

        Path sourceConfigDirectory = extractLocation.resolve("config");
        Path destConfigDirectory = pluginHandle.configDir(environment);
        boolean needToCopyConfigDirectory = Files.exists(sourceConfigDirectory);
        if (needToCopyConfigDirectory) {
            if (Files.exists(destConfigDirectory) && !Files.isDirectory(destConfigDirectory)) {
                tryToDeletePath(terminal, extractLocation, destPluginBinDirectory);
                throw new IOException("plugin config directory " + destConfigDirectory + " is not a directory");
            }

            try {
                terminal.println(VERBOSE, "Found config, moving to %s", destConfigDirectory.toAbsolutePath());
                moveFilesWithoutOverwriting(sourceConfigDirectory, destConfigDirectory, ".new");

                if (Environment.getFileStore(destConfigDirectory).supportsFileAttributeView(PosixFileAttributeView.class)) {
                    //We copy owner, group and permissions from the parent ES_CONFIG directory, assuming they were properly set depending
                    // on how es was installed in the first place: can be root:elasticsearch (750) if es was installed from rpm/deb packages
                    // or most likely elasticsearch:elasticsearch if installed from tar/zip. As for permissions we don't rely on umask.
                    final PosixFileAttributes parentDirAttributes = Files.getFileAttributeView(destConfigDirectory.getParent(), PosixFileAttributeView.class).readAttributes();
                    //for files though, we make sure not to copy execute permissions from the parent dir and leave them untouched
                    final Set baseFilePermissions = new HashSet<>();
                    for (PosixFilePermission posixFilePermission : parentDirAttributes.permissions()) {
                        switch (posixFilePermission) {
                            case OWNER_EXECUTE:
                            case GROUP_EXECUTE:
                            case OTHERS_EXECUTE:
                                break;
                            default:
                                baseFilePermissions.add(posixFilePermission);
                        }
                    }
                    Files.walkFileTree(destConfigDirectory, new SimpleFileVisitor() {
                        @Override
                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                            if (attrs.isRegularFile()) {
                                Set newFilePermissions = new HashSet<>(baseFilePermissions);
                                Set currentFilePermissions = Files.getPosixFilePermissions(file);
                                for (PosixFilePermission posixFilePermission : currentFilePermissions) {
                                    switch (posixFilePermission) {
                                        case OWNER_EXECUTE:
                                        case GROUP_EXECUTE:
                                        case OTHERS_EXECUTE:
                                            newFilePermissions.add(posixFilePermission);
                                    }
                                }
                                setPosixFileAttributes(file, parentDirAttributes.owner(), parentDirAttributes.group(), newFilePermissions);
                            }
                            return FileVisitResult.CONTINUE;
                        }

                        @Override
                        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                            setPosixFileAttributes(dir, parentDirAttributes.owner(), parentDirAttributes.group(), parentDirAttributes.permissions());
                            return FileVisitResult.CONTINUE;
                        }
                    });
                } else {
                    terminal.println(VERBOSE, "Skipping posix permissions - filestore doesn't support posix permission");
                }

                terminal.println(VERBOSE, "Installed %s into %s", pluginHandle.name, destConfigDirectory.toAbsolutePath());
            } catch (IOException e) {
                terminal.printError("Error copying config directory [%s] to [%s], cleaning up, reason: %s", sourceConfigDirectory, destConfigDirectory, ExceptionsHelper.detailedMessage(e));
                tryToDeletePath(terminal, extractLocation, destPluginBinDirectory, destConfigDirectory);
                throw e;
            }
        }
    }

    private static void setPosixFileAttributes(Path path, UserPrincipal owner, GroupPrincipal group, Set permissions) throws IOException {
        PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
        fileAttributeView.setOwner(owner);
        fileAttributeView.setGroup(group);
        fileAttributeView.setPermissions(permissions);
    }

    static void tryToDeletePath(Terminal terminal, Path ... paths) {
        for (Path path : paths) {
            try {
                IOUtils.rm(path);
            } catch (IOException e) {
                terminal.printError(e);
            }
        }
    }

    private void copyBinDirectory(Path sourcePluginBinDirectory, Path destPluginBinDirectory, String pluginName, Terminal terminal) throws IOException {
        boolean canCopyFromSource = Files.exists(sourcePluginBinDirectory) && Files.isReadable(sourcePluginBinDirectory) && Files.isDirectory(sourcePluginBinDirectory);
        if (canCopyFromSource) {
            terminal.println(VERBOSE, "Found bin, moving to %s", destPluginBinDirectory.toAbsolutePath());
            if (Files.exists(destPluginBinDirectory)) {
                IOUtils.rm(destPluginBinDirectory);
            }
            try {
                Files.createDirectories(destPluginBinDirectory.getParent());
                FileSystemUtils.move(sourcePluginBinDirectory, destPluginBinDirectory);
            } catch (IOException e) {
                throw new IOException("Could not move [" + sourcePluginBinDirectory + "] to [" + destPluginBinDirectory + "]", e);
            }
            if (Environment.getFileStore(destPluginBinDirectory).supportsFileAttributeView(PosixFileAttributeView.class)) {
                final PosixFileAttributes parentDirAttributes = Files.getFileAttributeView(destPluginBinDirectory.getParent(), PosixFileAttributeView.class).readAttributes();
                //copy permissions from parent bin directory
                final Set filePermissions = new HashSet<>();
                for (PosixFilePermission posixFilePermission : parentDirAttributes.permissions()) {
                    switch (posixFilePermission) {
                        case OWNER_EXECUTE:
                        case GROUP_EXECUTE:
                        case OTHERS_EXECUTE:
                            break;
                        default:
                            filePermissions.add(posixFilePermission);
                    }
                }
                // add file execute permissions to existing perms, so execution will work.
                filePermissions.add(PosixFilePermission.OWNER_EXECUTE);
                filePermissions.add(PosixFilePermission.GROUP_EXECUTE);
                filePermissions.add(PosixFilePermission.OTHERS_EXECUTE);
                Files.walkFileTree(destPluginBinDirectory, new SimpleFileVisitor() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        if (attrs.isRegularFile()) {
                            setPosixFileAttributes(file, parentDirAttributes.owner(), parentDirAttributes.group(), filePermissions);
                        }
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        setPosixFileAttributes(dir, parentDirAttributes.owner(), parentDirAttributes.group(), parentDirAttributes.permissions());
                        return FileVisitResult.CONTINUE;
                    }
                });
            } else {
                terminal.println(VERBOSE, "Skipping posix permissions - filestore doesn't support posix permission");
            }
            terminal.println(VERBOSE, "Installed %s into %s", pluginName, destPluginBinDirectory.toAbsolutePath());
        }
    }

    /** we check whether we need to remove the top-level folder while extracting
     *  sometimes (e.g. github) the downloaded archive contains a top-level folder which needs to be removed
     */
    private Path findPluginRoot(Path dir) throws IOException {
        if (Files.exists(dir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES))) {
            return dir;
        } else {
            final Path[] topLevelFiles = FileSystemUtils.files(dir);
            if (topLevelFiles.length == 1 && Files.isDirectory(topLevelFiles[0])) {
                Path subdir = topLevelFiles[0];
                if (Files.exists(subdir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES))) {
                    return subdir;
                }
            }
        }
        throw new RuntimeException("Could not find plugin descriptor '" + PluginInfo.ES_PLUGIN_PROPERTIES + "' in plugin zip");
    }

    /** check a candidate plugin for jar hell before installing it */
    private void jarHellCheck(Path candidate, boolean isolated) throws IOException {
        // create list of current jars in classpath
        final List jars = new ArrayList<>();
        jars.addAll(Arrays.asList(JarHell.parseClassPath()));

        // read existing bundles. this does some checks on the installation too.
        List bundles = PluginsService.getPluginBundles(environment.pluginsFile());

        // if we aren't isolated, we need to jarhellcheck against any other non-isolated plugins
        // thats always the first bundle
        if (isolated == false) {
            jars.addAll(bundles.get(0).urls);
        }

        // add plugin jars to the list
        Path pluginJars[] = FileSystemUtils.files(candidate, "*.jar");
        for (Path jar : pluginJars) {
            jars.add(jar.toUri().toURL());
        }

        // check combined (current classpath + new jars to-be-added)
        try {
            JarHell.checkJarHell(jars.toArray(new URL[jars.size()]));
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    private void unzipPlugin(Path zip, Path target) throws IOException {
        Files.createDirectories(target);

        try (ZipInputStream zipInput = new ZipInputStream(Files.newInputStream(zip))) {
            ZipEntry entry;
            byte[] buffer = new byte[8192];
            while ((entry = zipInput.getNextEntry()) != null) {
                Path targetFile = target.resolve(entry.getName());

                // be on the safe side: do not rely on that directories are always extracted
                // before their children (although this makes sense, but is it guaranteed?)
                Files.createDirectories(targetFile.getParent());
                if (entry.isDirectory() == false) {
                    try (OutputStream out = Files.newOutputStream(targetFile)) {
                        int len;
                        while((len = zipInput.read(buffer)) >= 0) {
                            out.write(buffer, 0, len);
                        }
                    }
                }
                zipInput.closeEntry();
            }
        }
    }

    public void removePlugin(String name, Terminal terminal) throws IOException {
        if (name == null) {
            throw new IllegalArgumentException("plugin name must be supplied with remove [name].");
        }
        PluginHandle pluginHandle = PluginHandle.parse(name);
        boolean removed = false;

        checkForForbiddenName(pluginHandle.name);
        Path pluginToDelete = pluginHandle.extractedDir(environment);
        if (Files.exists(pluginToDelete)) {
            terminal.println(VERBOSE, "Removing: %s", pluginToDelete);
            try {
                IOUtils.rm(pluginToDelete);
            } catch (IOException ex){
                throw new IOException("Unable to remove " + pluginHandle.name + ". Check file permissions on " +
                        pluginToDelete.toString(), ex);
            }
            removed = true;
        }
        Path binLocation = pluginHandle.binDir(environment);
        if (Files.exists(binLocation)) {
            terminal.println(VERBOSE, "Removing: %s", binLocation);
            try {
                IOUtils.rm(binLocation);
            } catch (IOException ex){
                throw new IOException("Unable to remove " + pluginHandle.name + ". Check file permissions on " +
                        binLocation.toString(), ex);
            }
            removed = true;
        }

        if (removed) {
            terminal.println("Removed %s", name);
        } else {
            terminal.println("Plugin %s not found. Run \"plugin list\" to get list of installed plugins.", name);
        }
    }

    static void checkForForbiddenName(String name) {
        if (!hasLength(name) || BLACKLIST.contains(name.toLowerCase(Locale.ROOT))) {
            throw new IllegalArgumentException("Illegal plugin name: " + name);
        }
    }

    protected static void checkForOfficialPlugins(String name) {
        // We make sure that users can use only new short naming for official plugins only
        if (!OFFICIAL_PLUGINS.contains(name)) {
            throw new IllegalArgumentException(name +
                    " is not an official plugin so you should install it using elasticsearch/" +
                    name + "/latest naming form.");
        }
    }

    public Path[] getListInstalledPlugins() throws IOException {
        if (!Files.exists(environment.pluginsFile())) {
            return new Path[0];
        }

        try (DirectoryStream stream = Files.newDirectoryStream(environment.pluginsFile())) {
            return Iterators.toArray(stream.iterator(), Path.class);
        }
    }

    public void listInstalledPlugins(Terminal terminal) throws IOException {
        Path[] plugins = getListInstalledPlugins();
        terminal.println("Installed plugins in %s:", environment.pluginsFile().toAbsolutePath());
        if (plugins == null || plugins.length == 0) {
            terminal.println("    - No plugin detected");
        } else {
            for (Path plugin : plugins) {
                terminal.println("    - " + plugin.getFileName());
            }
        }
    }

    /**
     * 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 version;
        final String user;
        final String name;

        PluginHandle(String name, String version, String user) {
            this.version = version;
            this.user = user;
            this.name = name;
        }

        List urls() {
            List urls = new ArrayList<>();
            if (version != null) {
                // Elasticsearch new download service uses groupId org.elasticsearch.plugin from 2.0.0
                if (user == null) {
                    if (!Strings.isNullOrEmpty(System.getProperty(PROPERTY_SUPPORT_STAGING_URLS))) {
                        addUrl(urls, String.format(Locale.ROOT, "https://download.elastic.co/elasticsearch/staging/%s-%s/org/elasticsearch/plugin/%s/%s/%s-%s.zip", version, Build.CURRENT.hashShort(), name, version, name, version));
                    }
                    addUrl(urls, String.format(Locale.ROOT, "https://download.elastic.co/elasticsearch/release/org/elasticsearch/plugin/%s/%s/%s-%s.zip", name, version, name, version));
                } else {
                    // Elasticsearch old download service
                    addUrl(urls, String.format(Locale.ROOT, "https://download.elastic.co/%1$s/%2$s/%2$s-%3$s.zip", user, name, version));
                    // Maven central repository
                    addUrl(urls, String.format(Locale.ROOT, "https://search.maven.org/remotecontent?filepath=%1$s/%2$s/%3$s/%2$s-%3$s.zip", user.replace('.', '/'), name, version));
                    // Sonatype repository
                    addUrl(urls, String.format(Locale.ROOT, "https://oss.sonatype.org/service/local/repositories/releases/content/%1$s/%2$s/%3$s/%2$s-%3$s.zip", user.replace('.', '/'), name, version));
                    // Github repository
                    addUrl(urls, String.format(Locale.ROOT, "https://github.com/%1$s/%2$s/archive/%3$s.zip", user, name, version));
                }
            }
            if (user != null) {
                // Github repository for master branch (assume site)
                addUrl(urls, String.format(Locale.ROOT, "https://github.com/%1$s/%2$s/archive/master.zip", user, name));
            }
            return urls;
        }

        private static void addUrl(List urls, String url) {
            try {
                urls.add(new URL(url));
            } catch (MalformedURLException e) {
                // We simply ignore malformed URL
            }
        }

        Path newDistroFile(Environment env) throws IOException {
            return Files.createTempFile(env.tmpFile(), name, ".zip");
        }

        Tuple newChecksumUrlAndFile(Environment env, URL originalUrl, String suffix) throws IOException {
            URL newUrl = new URL(originalUrl.toString() + "." + suffix);
            return new Tuple<>(newUrl, Files.createTempFile(env.tmpFile(), name, ".zip." + suffix));
        }

        Path extractedDir(Environment env) {
            return env.pluginsFile().resolve(name);
        }

        Path binDir(Environment env) {
            return env.binFile().resolve(name);
        }

        Path configDir(Environment env) {
            return env.configFile().resolve(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 (isOfficialPlugin(repo, user, version)) {
                return new PluginHandle(repo, Version.CURRENT.number(), null);
            }

            return new PluginHandle(repo, version, user);
        }

        static boolean isOfficialPlugin(String repo, String user, String version) {
            return version == null && user == null && !Strings.isNullOrEmpty(repo);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy