Skip to content

Commit

Permalink
Move declarative plugin sync to server cli (#87273)
Browse files Browse the repository at this point in the history
When running in Docker, the elasticsearch-plugins.yml allows configuring
plugins that should be installed in the system. Upon Elasticsearch
starting up, plugins are installed/removed to match the configured
plugins. However, this happens late in startup, and it would be nice to
keep the main Elasticsearch process from ever writing outside the
configured data directories. Now that the server cli has been moved to
Java, this is possible.

This commit moves invocation of the plugins sync command into the server
cli. Note that the sync plugins action should probably be reworked as it
can be implement Command directly now. However, this commit tries to be
the minimal change possible to remove plugin cli knowledge from server.
  • Loading branch information
rjernst committed Jun 1, 2022
1 parent f695819 commit 4b44413
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.elasticsearch.cli.UserException;
import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.PluginDescriptor;
import org.elasticsearch.plugins.PluginsSynchronizer;
import org.elasticsearch.xcontent.cbor.CborXContent;
import org.elasticsearch.xcontent.yaml.YamlXContent;

Expand Down Expand Up @@ -42,7 +41,7 @@
* This action cannot be called from the command line. It is used exclusively by Elasticsearch on startup, but only
* if the config file exists and the distribution type allows it.
*/
public class SyncPluginsAction implements PluginsSynchronizer {
public class SyncPluginsAction {
public static final String ELASTICSEARCH_PLUGINS_YML = "elasticsearch-plugins.yml";
public static final String ELASTICSEARCH_PLUGINS_YML_CACHE = ".elasticsearch-plugins.yml.cache";

Expand Down Expand Up @@ -77,7 +76,6 @@ public static void ensureNoConfigFile(Environment env) throws UserException {
*
* @throws Exception if anything goes wrong
*/
@Override
public void execute() throws Exception {
final Path configPath = this.env.configFile().resolve(ELASTICSEARCH_PLUGINS_YML);
final Path previousConfigPath = this.env.pluginsFile().resolve(ELASTICSEARCH_PLUGINS_YML_CACHE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.plugins.cli;

import joptsimple.OptionSet;

import org.elasticsearch.Build;
import org.elasticsearch.cli.CliToolProvider;
import org.elasticsearch.cli.Command;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.ProcessInfo;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.cli.EnvironmentAwareCommand;
import org.elasticsearch.env.Environment;

import java.nio.file.Files;

import static org.elasticsearch.plugins.cli.SyncPluginsAction.ELASTICSEARCH_PLUGINS_YML;

public class SyncPluginsCliProvider implements CliToolProvider {
@Override
public String name() {
return "sync-plugins";
}

@Override
public Command create() {
return new EnvironmentAwareCommand("sync installed plugins from elasticsearch-plugins.yml") {
@Override
public void execute(Terminal terminal, OptionSet options, Environment env, ProcessInfo processInfo) throws Exception {
var action = new SyncPluginsAction(terminal, env);
if (Files.exists(env.configFile().resolve(ELASTICSEARCH_PLUGINS_YML)) == false) {
return;
}
if (Build.CURRENT.type() != Build.Type.DOCKER) {
throw new UserException(
ExitCodes.CONFIG,
"Can only use [elasticsearch-plugins.yml] config file with distribution type [docker]"
);
}
try {
action.execute();
} catch (PluginSyncException e) {
throw new UserException(ExitCodes.CONFIG, ELASTICSEARCH_PLUGINS_YML + ": " + e.getMessage());
}
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
org.elasticsearch.plugins.cli.PluginCliProvider
org.elasticsearch.plugins.cli.SyncPluginsCliProvider
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ private static List<PluginInfo> getPluginInfo(Path plugins) throws IOException {
final List<Path> pluginDirs = Files.list(plugins).collect(Collectors.toList());

for (Path pluginDir : pluginDirs) {
if (Files.isDirectory(pluginDir) == false) {
continue; // validation is done elsewhere in startup. here we just ignore errant files
}
final List<String> jarFiles = new ArrayList<>();
final Properties props = new Properties();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
final SecureString keystorePassword = getKeystorePassword(env.configFile(), terminal);
env = autoConfigureSecurity(terminal, options, processInfo, env, keystorePassword);

// install/remove plugins from elasticsearch-plugins.yml
syncPlugins(terminal, env, processInfo);

ServerArgs args = createArgs(options, env, keystorePassword, processInfo);
this.server = startServer(terminal, processInfo, args, env.pluginsFile());

Expand Down Expand Up @@ -171,6 +174,15 @@ private Environment autoConfigureSecurity(
return env;
}

private void syncPlugins(Terminal terminal, Environment env, ProcessInfo processInfo) throws Exception {
String pluginCliLibs = "lib/tools/plugin-cli";
Command cmd = loadTool("sync-plugins", pluginCliLibs);
assert cmd instanceof EnvironmentAwareCommand;
@SuppressWarnings("raw")
var syncPlugins = (EnvironmentAwareCommand) cmd;
syncPlugins.execute(terminal, syncPlugins.parseOptions(new String[0]), env, processInfo);
}

private void validatePidFile(Path pidFile) throws UserException {
Path parent = pidFile.getParent();
if (parent != null && Files.exists(parent) && Files.isDirectory(parent) == false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,20 @@ public void testAutoConfigOkErrors() throws Exception {
assertAutoConfigError(ExitCodes.NOOP, ExitCodes.OK);
}

public void testSyncPlugins() throws Exception {
AtomicBoolean syncPluginsCalled = new AtomicBoolean(false);
syncPluginsCallback = (t, options, env, processInfo) -> syncPluginsCalled.set(true);
assertOk();
assertThat(syncPluginsCalled.get(), is(true));
}

public void testSyncPluginsError() throws Exception {
syncPluginsCallback = (t, options, env, processInfo) -> { throw new UserException(ExitCodes.CONFIG, "sync plugins failed"); };
int gotMainExitCode = executeMain();
assertThat(gotMainExitCode, equalTo(ExitCodes.CONFIG));
assertThat(terminal.getErrorOutput(), containsString("sync plugins failed"));
}

public void assertKeystorePassword(String password) throws Exception {
terminal.reset();
boolean hasPassword = password != null && password.isEmpty() == false;
Expand Down Expand Up @@ -298,10 +312,18 @@ interface AutoConfigMethod {
AutoConfigMethod autoConfigCallback;
private final MockAutoConfigCli AUTO_CONFIG_CLI = new MockAutoConfigCli();

interface SyncPluginsMethod {
void syncPlugins(Terminal terminal, OptionSet options, Environment env, ProcessInfo processInfo) throws UserException;
}

SyncPluginsMethod syncPluginsCallback;
private final MockSyncPluginsCli SYNC_PLUGINS_CLI = new MockSyncPluginsCli();

@Before
public void resetCommand() {
argsValidator = null;
autoConfigCallback = null;
syncPluginsCallback = null;
mockServerExitCode = 0;
}

Expand All @@ -327,6 +349,24 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
}
}

private class MockSyncPluginsCli extends EnvironmentAwareCommand {
MockSyncPluginsCli() {
super("mock sync plugins tool");
}

@Override
protected void execute(Terminal terminal, OptionSet options, ProcessInfo processInfo) throws Exception {
fail("Called wrong execute method, must call the one that takes already parsed env");
}

@Override
public void execute(Terminal terminal, OptionSet options, Environment env, ProcessInfo processInfo) throws Exception {
if (syncPluginsCallback != null) {
syncPluginsCallback.syncPlugins(terminal, options, env, processInfo);
}
}
}

private class MockServerProcess extends ServerProcess {
boolean detachCalled = false;
boolean waitForCalled = false;
Expand Down Expand Up @@ -370,12 +410,16 @@ void reset() {
@Override
protected Command newCommand() {
return new ServerCli() {

@Override
protected Command loadTool(String toolname, String libs) {
assertThat(toolname, equalTo("auto-configure-node"));
assertThat(libs, equalTo("modules/x-pack-core,modules/x-pack-security,lib/tools/security-cli"));
return AUTO_CONFIG_CLI;
if (toolname.equals("auto-configure-node")) {
assertThat(libs, equalTo("modules/x-pack-core,modules/x-pack-security,lib/tools/security-cli"));
return AUTO_CONFIG_CLI;
} else if (toolname.equals("sync-plugins")) {
assertThat(libs, equalTo("lib/tools/plugin-cli"));
return SYNC_PLUGINS_CLI;
}
throw new AssertionError("Unknown tool: " + toolname);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.elasticsearch.packaging.util.FileUtils;
import org.elasticsearch.packaging.util.Installation;
import org.elasticsearch.packaging.util.Packages;
import org.elasticsearch.packaging.util.Platforms;
import org.elasticsearch.packaging.util.ServerUtils;
import org.elasticsearch.packaging.util.Shell;
Expand Down Expand Up @@ -150,12 +151,16 @@ public void test31RemoveFailsIfConfigFilePresent() throws IOException {
*/
public void test32FailsToStartWhenPluginsConfigExists() throws Exception {
try {
Packages.JournaldWrapper journaldWrapper = null;
if (distribution().isPackage()) {
journaldWrapper = new Packages.JournaldWrapper(sh);
}
Files.writeString(installation.config("elasticsearch-plugins.yml"), "content doesn't matter for this test");
Shell.Result result = runElasticsearchStartCommand(null, false, false);
assertElasticsearchFailure(
result,
"Can only use [elasticsearch-plugins.yml] config file with distribution type [docker]",
null
journaldWrapper
);
} finally {
FileUtils.rm(installation.config("elasticsearch-plugins.yml"));
Expand Down
1 change: 0 additions & 1 deletion server/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@
exports org.elasticsearch.action.termvectors;
exports org.elasticsearch.action.update;
exports org.elasticsearch.bootstrap;
exports org.elasticsearch.bootstrap.plugins;
exports org.elasticsearch.client.internal;
exports org.elasticsearch.client.internal.node;
exports org.elasticsearch.client.internal.support;
Expand Down
16 changes: 0 additions & 16 deletions server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.StringHelper;
import org.elasticsearch.Build;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.bootstrap.plugins.PluginsManager;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.filesystem.FileSystemNatives;
import org.elasticsearch.common.inject.CreationException;
Expand Down Expand Up @@ -316,20 +314,6 @@ static void init(
// setDefaultUncaughtExceptionHandler
Thread.setDefaultUncaughtExceptionHandler(new ElasticsearchUncaughtExceptionHandler());

if (PluginsManager.configExists(environment)) {
if (Build.CURRENT.type() == Build.Type.DOCKER) {
try {
PluginsManager.syncPlugins(environment);
} catch (Exception e) {
throw new BootstrapException(e);
}
} else {
throw new BootstrapException(
new ElasticsearchException("Can only use [elasticsearch-plugins.yml] config file with distribution type [docker]")
);
}
}

INSTANCE.setup(true, environment, pidFile);

try {
Expand Down

This file was deleted.

0 comments on commit 4b44413

Please sign in to comment.