/*
* 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.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.Version;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.io.PathUtilsForTesting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PosixPermissionsResetter;
import org.junit.After;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
@LuceneTestCase.SuppressFileSystems("*")
public class InstallPluginCommandTests extends ESTestCase {
private static InstallPluginCommand SKIP_JARHELL_COMMAND = new InstallPluginCommand() {
@Override
void jarHellCheck(Path candidate, Path pluginsDir) throws Exception {
// no jarhell check
}
};
private static InstallPluginCommand DEFAULT_COMMAND = new InstallPluginCommand();
private final Function<String, Path> temp;
private final FileSystem fs;
private final boolean isPosix;
private final boolean isReal;
private final String javaIoTmpdir;
@SuppressForbidden(reason = "sets java.io.tmpdir")
public InstallPluginCommandTests(FileSystem fs, Function<String, Path> temp) {
this.fs = fs;
this.temp = temp;
this.isPosix = fs.supportedFileAttributeViews().contains("posix");
this.isReal = fs == PathUtils.getDefaultFileSystem();
PathUtilsForTesting.installMock(fs);
javaIoTmpdir = System.getProperty("java.io.tmpdir");
System.setProperty("java.io.tmpdir", temp.apply("tmpdir").toString());
}
@After
@SuppressForbidden(reason = "resets java.io.tmpdir")
public void tearDown() throws Exception {
System.setProperty("java.io.tmpdir", javaIoTmpdir);
PathUtilsForTesting.teardown();
super.tearDown();
}
@ParametersFactory
public static Iterable<Object[]> parameters() {
class Parameter {
private final FileSystem fileSystem;
private final Function<String, Path> temp;
Parameter(FileSystem fileSystem, String root) {
this(fileSystem, s -> {
try {
return Files.createTempDirectory(fileSystem.getPath(root), s);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
Parameter(FileSystem fileSystem, Function<String, Path> temp) {
this.fileSystem = fileSystem;
this.temp = temp;
}
}
List<Parameter> parameters = new ArrayList<>();
parameters.add(new Parameter(Jimfs.newFileSystem(Configuration.windows()), "c:\\"));
parameters.add(new Parameter(Jimfs.newFileSystem(toPosix(Configuration.osX())), "/"));
parameters.add(new Parameter(Jimfs.newFileSystem(toPosix(Configuration.unix())), "/"));
parameters.add(new Parameter(PathUtils.getDefaultFileSystem(), LuceneTestCase::createTempDir ));
return parameters.stream().map(p -> new Object[] { p.fileSystem, p.temp }).collect(Collectors.toList());
}
private static Configuration toPosix(Configuration configuration) {
return configuration.toBuilder().setAttributeViews("basic", "owner", "posix", "unix").build();
}
/** Creates a test environment with bin, config and plugins directories. */
static Tuple<Path, Environment> createEnv(FileSystem fs, Function<String, Path> temp) throws IOException {
Path home = temp.apply("install-plugin-command-tests");
Files.createDirectories(home.resolve("bin"));
Files.createFile(home.resolve("bin").resolve("elasticsearch"));
Files.createDirectories(home.resolve("config"));
Files.createFile(home.resolve("config").resolve("elasticsearch.yml"));
Path plugins = Files.createDirectories(home.resolve("plugins"));
assertTrue(Files.exists(plugins));
Settings settings = Settings.builder()
.put("path.home", home)
.build();
return Tuple.tuple(home, new Environment(settings));
}
static Path createPluginDir(Function<String, Path> temp) throws IOException {
return temp.apply("pluginDir");
}
/** creates a fake jar file with empty class files */
static void writeJar(Path jar, String... classes) throws IOException {
try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(jar))) {
for (String clazz : classes) {
stream.putNextEntry(new ZipEntry(clazz + ".class")); // no package names, just support simple classes
}
}
}
static Path writeZip(Path structure, String prefix) throws IOException {
Path zip = createTempDir().resolve(structure.getFileName() + ".zip");
try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zip))) {
Files.walkFileTree(structure, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String target = (prefix == null ? "" : prefix + "/") + structure.relativize(file).toString();
stream.putNextEntry(new ZipEntry(target));
Files.copy(file, stream);
return FileVisitResult.CONTINUE;
}
});
}
return zip;
}
/** creates a plugin .zip and returns the url for testing */
static String createPluginUrl(String name, Path structure) throws IOException {
return createPlugin(name, structure, false).toUri().toURL().toString();
}
static Path createPlugin(String name, Path structure, boolean createSecurityPolicyFile) throws IOException {
PluginTestUtil.writeProperties(structure,
"description", "fake desc",
"name", name,
"version", "1.0",
"elasticsearch.version", Version.CURRENT.toString(),
"java.version", System.getProperty("java.specification.version"),
"classname", "FakePlugin");
if (createSecurityPolicyFile) {
String securityPolicyContent = "grant {\n permission java.lang.RuntimePermission \"setFactory\";\n};\n";
Files.write(structure.resolve("plugin-security.policy"), securityPolicyContent.getBytes(StandardCharsets.UTF_8));
}
writeJar(structure.resolve("plugin.jar"), "FakePlugin");
return writeZip(structure, "elasticsearch");
}
static MockTerminal installPlugin(String pluginUrl, Path home) throws Exception {
return installPlugin(pluginUrl, home, SKIP_JARHELL_COMMAND);
}
static MockTerminal installPlugin(String pluginUrl, Path home, InstallPluginCommand command) throws Exception {
Environment env = new Environment(Settings.builder().put("path.home", home).build());
MockTerminal terminal = new MockTerminal();
command.execute(terminal, pluginUrl, true, env);
return terminal;
}
void assertPlugin(String name, Path original, Environment env) throws IOException {
Path got = env.pluginsFile().resolve(name);
assertTrue("dir " + name + " exists", Files.exists(got));
if (isPosix) {
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(got);
assertThat(
perms,
containsInAnyOrder(
PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE,
PosixFilePermission.OWNER_EXECUTE,
PosixFilePermission.GROUP_READ,
PosixFilePermission.GROUP_EXECUTE,
PosixFilePermission.OTHERS_READ,
PosixFilePermission.OTHERS_EXECUTE));
}
assertTrue("jar was copied", Files.exists(got.resolve("plugin.jar")));
assertFalse("bin was not copied", Files.exists(got.resolve("bin")));
assertFalse("config was not copied", Files.exists(got.resolve("config")));
if (Files.exists(original.resolve("bin"))) {
Path binDir = env.binFile().resolve(name);
assertTrue("bin dir exists", Files.exists(binDir));
assertTrue("bin is a dir", Files.isDirectory(binDir));
PosixFileAttributes binAttributes = null;
if (isPosix) {
binAttributes = Files.readAttributes(env.binFile(), PosixFileAttributes.class);
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(binDir)) {
for (Path file : stream) {
assertFalse("not a dir", Files.isDirectory(file));
if (isPosix) {
PosixFileAttributes attributes = Files.readAttributes(file, PosixFileAttributes.class);
assertEquals(InstallPluginCommand.BIN_FILES_PERMS, attributes.permissions());
}
}
}
}
if (Files.exists(original.resolve("config"))) {
Path configDir = env.configFile().resolve(name);
assertTrue("config dir exists", Files.exists(configDir));
assertTrue("config is a dir", Files.isDirectory(configDir));
UserPrincipal user = null;
GroupPrincipal group = null;
if (isPosix) {
PosixFileAttributes configAttributes =
Files.getFileAttributeView(env.configFile(), PosixFileAttributeView.class).readAttributes();
user = configAttributes.owner();
group = configAttributes.group();
PosixFileAttributes attributes = Files.getFileAttributeView(configDir, PosixFileAttributeView.class).readAttributes();
assertThat(attributes.owner(), equalTo(user));
assertThat(attributes.group(), equalTo(group));
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(configDir)) {
for (Path file : stream) {
assertFalse("not a dir", Files.isDirectory(file));
if (isPosix) {
PosixFileAttributes attributes = Files.readAttributes(file, PosixFileAttributes.class);
if (user != null) {
assertThat(attributes.owner(), equalTo(user));
}
if (group != null) {
assertThat(attributes.group(), equalTo(group));
}
}
}
}
}
assertInstallCleaned(env);
}
void assertInstallCleaned(Environment env) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(env.pluginsFile())) {
for (Path file : stream) {
if (file.getFileName().toString().startsWith(".installing")) {
fail("Installation dir still exists, " + file);
}
}
}
}
public void testMissingPluginId() throws IOException {
final Tuple<Path, Environment> env = createEnv(fs, temp);
final UserException e = expectThrows(UserException.class, () -> installPlugin(null, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("plugin id is required"));
}
public void testSomethingWorks() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
String pluginZip = createPluginUrl("fake", pluginDir);
installPlugin(pluginZip, env.v1());
assertPlugin("fake", pluginDir, env.v2());
}
public void testSpaceInUrl() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
String pluginZip = createPluginUrl("fake", pluginDir);
Path pluginZipWithSpaces = createTempFile("foo bar", ".zip");
try (InputStream in = FileSystemUtils.openFileURLStream(new URL(pluginZip))) {
Files.copy(in, pluginZipWithSpaces, StandardCopyOption.REPLACE_EXISTING);
}
installPlugin(pluginZipWithSpaces.toUri().toURL().toString(), env.v1());
assertPlugin("fake", pluginDir, env.v2());
}
public void testMalformedUrlNotMaven() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
// has two colons, so it appears similar to maven coordinates
MalformedURLException e = expectThrows(MalformedURLException.class, () -> installPlugin("://host:1234", env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("no protocol"));
}
public void testUnknownPlugin() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
UserException e = expectThrows(UserException.class, () -> installPlugin("foo", env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("Unknown plugin foo"));
}
public void testPluginsDirReadOnly() throws Exception {
assumeTrue("posix and filesystem", isPosix && isReal);
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
try (PosixPermissionsResetter pluginsAttrs = new PosixPermissionsResetter(env.v2().pluginsFile())) {
pluginsAttrs.setPermissions(new HashSet<>());
String pluginZip = createPluginUrl("fake", pluginDir);
IOException e = expectThrows(IOException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains(env.v2().pluginsFile().toString()));
}
assertInstallCleaned(env.v2());
}
public void testBuiltinModule() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
String pluginZip = createPluginUrl("lang-painless", pluginDir);
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("is a system module"));
assertInstallCleaned(env.v2());
}
public void testJarHell() throws Exception {
// jar hell test needs a real filesystem
assumeTrue("real filesystem", isReal);
Tuple<Path, Environment> environment = createEnv(fs, temp);
Path pluginDirectory = createPluginDir(temp);
writeJar(pluginDirectory.resolve("other.jar"), "FakePlugin");
String pluginZip = createPluginUrl("fake", pluginDirectory); // adds plugin.jar with FakePlugin
IllegalStateException e = expectThrows(IllegalStateException.class,
() -> installPlugin(pluginZip, environment.v1(), DEFAULT_COMMAND));
assertTrue(e.getMessage(), e.getMessage().contains("jar hell"));
assertInstallCleaned(environment.v2());
}
public void testIsolatedPlugins() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
// these both share the same FakePlugin class
Path pluginDir1 = createPluginDir(temp);
String pluginZip1 = createPluginUrl("fake1", pluginDir1);
installPlugin(pluginZip1, env.v1());
Path pluginDir2 = createPluginDir(temp);
String pluginZip2 = createPluginUrl("fake2", pluginDir2);
installPlugin(pluginZip2, env.v1());
assertPlugin("fake1", pluginDir1, env.v2());
assertPlugin("fake2", pluginDir2, env.v2());
}
public void testExistingPlugin() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
String pluginZip = createPluginUrl("fake", pluginDir);
installPlugin(pluginZip, env.v1());
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("already exists"));
assertInstallCleaned(env.v2());
}
public void testBin() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin");
Files.createDirectory(binDir);
Files.createFile(binDir.resolve("somescript"));
String pluginZip = createPluginUrl("fake", pluginDir);
installPlugin(pluginZip, env.v1());
assertPlugin("fake", pluginDir, env.v2());
}
public void testBinNotDir() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin");
Files.createFile(binDir);
String pluginZip = createPluginUrl("fake", pluginDir);
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("not a directory"));
assertInstallCleaned(env.v2());
}
public void testBinContainsDir() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path dirInBinDir = pluginDir.resolve("bin").resolve("foo");
Files.createDirectories(dirInBinDir);
Files.createFile(dirInBinDir.resolve("somescript"));
String pluginZip = createPluginUrl("fake", pluginDir);
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("Directories not allowed in bin dir for plugin"));
assertInstallCleaned(env.v2());
}
public void testBinConflict() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin");
Files.createDirectory(binDir);
Files.createFile(binDir.resolve("somescript"));
String pluginZip = createPluginUrl("elasticsearch", pluginDir);
FileAlreadyExistsException e = expectThrows(FileAlreadyExistsException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains(env.v2().binFile().resolve("elasticsearch").toString()));
assertInstallCleaned(env.v2());
}
public void testBinPermissions() throws Exception {
assumeTrue("posix filesystem", isPosix);
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin");
Files.createDirectory(binDir);
Files.createFile(binDir.resolve("somescript"));
String pluginZip = createPluginUrl("fake", pluginDir);
try (PosixPermissionsResetter binAttrs = new PosixPermissionsResetter(env.v2().binFile())) {
Set<PosixFilePermission> perms = binAttrs.getCopyPermissions();
// make sure at least one execute perm is missing, so we know we forced it during installation
perms.remove(PosixFilePermission.GROUP_EXECUTE);
binAttrs.setPermissions(perms);
installPlugin(pluginZip, env.v1());
assertPlugin("fake", pluginDir, env.v2());
}
}
public void testPluginPermissions() throws Exception {
assumeTrue("posix filesystem", isPosix);
final Tuple<Path, Environment> env = createEnv(fs, temp);
final Path pluginDir = createPluginDir(temp);
final Path resourcesDir = pluginDir.resolve("resources");
final Path platformDir = pluginDir.resolve("platform");
final Path platformNameDir = platformDir.resolve("linux-x86_64");
final Path platformBinDir = platformNameDir.resolve("bin");
Files.createDirectories(platformBinDir);
Files.createFile(pluginDir.resolve("fake-" + Version.CURRENT.toString() + ".jar"));
Files.createFile(platformBinDir.resolve("fake_executable"));
Files.createDirectory(resourcesDir);
Files.createFile(resourcesDir.resolve("resource"));
final String pluginZip = createPluginUrl("fake", pluginDir);
installPlugin(pluginZip, env.v1());
assertPlugin("fake", pluginDir, env.v2());
final Path fake = env.v2().pluginsFile().resolve("fake");
final Path resources = fake.resolve("resources");
final Path platform = fake.resolve("platform");
final Path platformName = platform.resolve("linux-x86_64");
final Path bin = platformName.resolve("bin");
assert755(fake);
assert644(fake.resolve("fake-" + Version.CURRENT + ".jar"));
assert755(resources);
assert644(resources.resolve("resource"));
assert755(platform);
assert755(platformName);
assert755(bin.resolve("fake_executable"));
}
private void assert644(final Path path) throws IOException {
final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
assertTrue(permissions.contains(PosixFilePermission.OWNER_READ));
assertTrue(permissions.contains(PosixFilePermission.OWNER_WRITE));
assertFalse(permissions.contains(PosixFilePermission.OWNER_EXECUTE));
assertTrue(permissions.contains(PosixFilePermission.GROUP_READ));
assertFalse(permissions.contains(PosixFilePermission.GROUP_WRITE));
assertFalse(permissions.contains(PosixFilePermission.GROUP_EXECUTE));
assertTrue(permissions.contains(PosixFilePermission.OTHERS_READ));
assertFalse(permissions.contains(PosixFilePermission.OTHERS_WRITE));
assertFalse(permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
}
private void assert755(final Path path) throws IOException {
final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
assertTrue(permissions.contains(PosixFilePermission.OWNER_READ));
assertTrue(permissions.contains(PosixFilePermission.OWNER_WRITE));
assertTrue(permissions.contains(PosixFilePermission.OWNER_EXECUTE));
assertTrue(permissions.contains(PosixFilePermission.GROUP_READ));
assertFalse(permissions.contains(PosixFilePermission.GROUP_WRITE));
assertTrue(permissions.contains(PosixFilePermission.GROUP_EXECUTE));
assertTrue(permissions.contains(PosixFilePermission.OTHERS_READ));
assertFalse(permissions.contains(PosixFilePermission.OTHERS_WRITE));
assertTrue(permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
}
public void testConfig() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config");
Files.createDirectory(configDir);
Files.createFile(configDir.resolve("custom.yaml"));
String pluginZip = createPluginUrl("fake", pluginDir);
installPlugin(pluginZip, env.v1());
assertPlugin("fake", pluginDir, env.v2());
}
public void testExistingConfig() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path envConfigDir = env.v2().configFile().resolve("fake");
Files.createDirectories(envConfigDir);
Files.write(envConfigDir.resolve("custom.yaml"), "existing config".getBytes(StandardCharsets.UTF_8));
Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config");
Files.createDirectory(configDir);
Files.write(configDir.resolve("custom.yaml"), "new config".getBytes(StandardCharsets.UTF_8));
Files.createFile(configDir.resolve("other.yaml"));
String pluginZip = createPluginUrl("fake", pluginDir);
installPlugin(pluginZip, env.v1());
assertPlugin("fake", pluginDir, env.v2());
List<String> configLines = Files.readAllLines(envConfigDir.resolve("custom.yaml"), StandardCharsets.UTF_8);
assertEquals(1, configLines.size());
assertEquals("existing config", configLines.get(0));
assertTrue(Files.exists(envConfigDir.resolve("other.yaml")));
}
public void testConfigNotDir() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config");
Files.createFile(configDir);
String pluginZip = createPluginUrl("fake", pluginDir);
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("not a directory"));
assertInstallCleaned(env.v2());
}
public void testConfigContainsDir() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path dirInConfigDir = pluginDir.resolve("config").resolve("foo");
Files.createDirectories(dirInConfigDir);
Files.createFile(dirInConfigDir.resolve("myconfig.yml"));
String pluginZip = createPluginUrl("fake", pluginDir);
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("Directories not allowed in config dir for plugin"));
assertInstallCleaned(env.v2());
}
public void testConfigConflict() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config");
Files.createDirectory(configDir);
Files.createFile(configDir.resolve("myconfig.yml"));
String pluginZip = createPluginUrl("elasticsearch.yml", pluginDir);
FileAlreadyExistsException e = expectThrows(FileAlreadyExistsException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains(env.v2().configFile().resolve("elasticsearch.yml").toString()));
assertInstallCleaned(env.v2());
}
public void testMissingDescriptor() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Files.createFile(pluginDir.resolve("fake.yml"));
String pluginZip = writeZip(pluginDir, "elasticsearch").toUri().toURL().toString();
NoSuchFileException e = expectThrows(NoSuchFileException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("plugin-descriptor.properties"));
assertInstallCleaned(env.v2());
}
public void testMissingDirectory() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Files.createFile(pluginDir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES));
String pluginZip = writeZip(pluginDir, null).toUri().toURL().toString();
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("`elasticsearch` directory is missing in the plugin zip"));
assertInstallCleaned(env.v2());
}
public void testZipRelativeOutsideEntryName() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path zip = createTempDir().resolve("broken.zip");
try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zip))) {
stream.putNextEntry(new ZipEntry("elasticsearch/../blah"));
}
String pluginZip = zip.toUri().toURL().toString();
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("resolving outside of plugin directory"));
}
public void testOfficialPluginsHelpSorted() throws Exception {
MockTerminal terminal = new MockTerminal();
new InstallPluginCommand() {
@Override
protected boolean addShutdownHook() {
return false;
}
}.main(new String[] { "--help" }, terminal);
try (BufferedReader reader = new BufferedReader(new StringReader(terminal.getOutput()))) {
String line = reader.readLine();
// first find the beginning of our list of official plugins
while (line.endsWith("may be installed by name:") == false) {
line = reader.readLine();
}
// now check each line compares greater than the last, until we reach an empty line
String prev = reader.readLine();
line = reader.readLine();
while (line != null && line.trim().isEmpty() == false) {
assertTrue(prev + " < " + line, prev.compareTo(line) < 0);
prev = line;
line = reader.readLine();
}
}
}
public void testOfficialPluginsIncludesXpack() throws Exception {
MockTerminal terminal = new MockTerminal();
new InstallPluginCommand() {
@Override
protected boolean addShutdownHook() {
return false;
}
}.main(new String[] { "--help" }, terminal);
assertTrue(terminal.getOutput(), terminal.getOutput().contains("x-pack"));
}
public void testInstallMisspelledOfficialPlugins() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
UserException e = expectThrows(UserException.class, () -> installPlugin("xpack", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin xpack, did you mean [x-pack]?"));
e = expectThrows(UserException.class, () -> installPlugin("analysis-smartnc", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin analysis-smartnc, did you mean [analysis-smartcn]?"));
e = expectThrows(UserException.class, () -> installPlugin("repository", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin repository, did you mean any of [repository-s3, repository-gcs]?"));
e = expectThrows(UserException.class, () -> installPlugin("unknown_plugin", env.v1()));
assertThat(e.getMessage(), containsString("Unknown plugin unknown_plugin"));
}
public void testBatchFlag() throws Exception {
MockTerminal terminal = new MockTerminal();
installPlugin(terminal, true);
assertThat(terminal.getOutput(), containsString("WARNING: plugin requires additional permissions"));
}
public void testQuietFlagDisabled() throws Exception {
MockTerminal terminal = new MockTerminal();
terminal.setVerbosity(randomFrom(Terminal.Verbosity.NORMAL, Terminal.Verbosity.VERBOSE));
installPlugin(terminal, false);
assertThat(terminal.getOutput(), containsString("100%"));
}
public void testQuietFlagEnabled() throws Exception {
MockTerminal terminal = new MockTerminal();
terminal.setVerbosity(Terminal.Verbosity.SILENT);
installPlugin(terminal, false);
assertThat(terminal.getOutput(), not(containsString("100%")));
}
public void testPluginAlreadyInstalled() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
String pluginZip = createPluginUrl("fake", pluginDir);
installPlugin(pluginZip, env.v1());
final UserException e = expectThrows(UserException.class,
() -> installPlugin(pluginZip, env.v1(), randomFrom(SKIP_JARHELL_COMMAND, DEFAULT_COMMAND)));
assertThat(
e.getMessage(),
equalTo("plugin directory [" + env.v2().pluginsFile().resolve("fake") + "] already exists; " +
"if you need to update the plugin, uninstall it first using command 'remove fake'"));
}
private void installPlugin(MockTerminal terminal, boolean isBatch) throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
// if batch is enabled, we also want to add a security policy
String pluginZip = createPlugin("fake", pluginDir, isBatch).toUri().toURL().toString();
SKIP_JARHELL_COMMAND.execute(terminal, pluginZip, isBatch, env.v2());
}
public void assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash) throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
Path pluginZip = createPlugin(name, pluginDir, false);
InstallPluginCommand command = new InstallPluginCommand() {
@Override
Path downloadZipAndChecksum(Terminal terminal, String urlString, Path tmpDir) throws Exception {
assertEquals(url, urlString);
Path downloadedPath = tmpDir.resolve("downloaded.zip");
Files.copy(pluginZip, downloadedPath);
return downloadedPath;
}
@Override
boolean urlExists(Terminal terminal, String urlString) throws IOException {
return urlString.equals(url);
}
@Override
String getStagingHash() {
return stagingHash;
}
@Override
void jarHellCheck(Path candidate, Path pluginsDir) throws Exception {
// no jarhell check
}
};
installPlugin(pluginId, env.v1(), command);
assertPlugin(name, pluginDir, env.v2());
}
public void testOfficalPlugin() throws Exception {
String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip";
assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null);
}
public void testOfficalPluginStaging() throws Exception {
String url = "https://staging.elastic.co/" + Version.CURRENT + "-abc123/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-"
+ Version.CURRENT + ".zip";
assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, "abc123");
}
public void testOfficalPlatformPlugin() throws Exception {
String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Platforms.PLATFORM_NAME +
"-" + Version.CURRENT + ".zip";
assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null);
}
public void testOfficalPlatformPluginStaging() throws Exception {
String url = "https://staging.elastic.co/" + Version.CURRENT + "-abc123/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-"
+ Platforms.PLATFORM_NAME + "-"+ Version.CURRENT + ".zip";
assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, "abc123");
}
public void testMavenPlugin() throws Exception {
String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip";
assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null);
}
public void testMavenPlatformPlugin() throws Exception {
String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-" + Platforms.PLATFORM_NAME + "-1.0.0.zip";
assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null);
}
// TODO: test checksum (need maven/official below)
}