Skip to content

Commit

Permalink
Abstract a logging interface for plugin actions
Browse files Browse the repository at this point in the history
Part of elastic#70219.

In order to share the plugin install and remove actions with the
Elasticsearch server, introduce a `PluginLogger` interface for the
action classes to use instead of referencing `Terminal` directly.
  • Loading branch information
pugnascotia committed Sep 28, 2021
1 parent dfd65be commit 8abde31
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.elasticsearch.bootstrap.PluginPolicyInfo;
import org.elasticsearch.bootstrap.PolicyUtil;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.hash.MessageDigests;
import org.elasticsearch.common.io.Streams;
Expand All @@ -38,6 +37,7 @@
import org.elasticsearch.env.Environment;
import org.elasticsearch.jdk.JarHell;
import org.elasticsearch.plugins.Platforms;
import org.elasticsearch.plugins.PluginLogger;
import org.elasticsearch.plugins.PluginInfo;
import org.elasticsearch.plugins.PluginsService;

Expand Down Expand Up @@ -82,8 +82,6 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static org.elasticsearch.cli.Terminal.Verbosity.VERBOSE;

/**
* A command for the plugin cli to install a plugin into elasticsearch.
* <p>
Expand Down Expand Up @@ -178,12 +176,12 @@ class InstallPluginAction implements Closeable {
PLUGIN_FILES_PERMS = Collections.unmodifiableSet(PosixFilePermissions.fromString("rw-r--r--"));
}

private final Terminal terminal;
private final PluginLogger logger;
private Environment env;
private boolean batch;

InstallPluginAction(Terminal terminal, Environment env, boolean batch) {
this.terminal = terminal;
InstallPluginAction(PluginLogger logger, Environment env, boolean batch) {
this.logger = logger;
this.env = env;
this.batch = batch;
}
Expand All @@ -204,7 +202,7 @@ void execute(List<PluginDescriptor> plugins) throws Exception {
final Map<String, List<Path>> deleteOnFailures = new LinkedHashMap<>();
for (final PluginDescriptor plugin : plugins) {
final String pluginId = plugin.getId();
terminal.println("-> Installing " + pluginId);
logger.info("-> Installing " + pluginId);
try {
if ("x-pack".equals(pluginId)) {
handleInstallXPack(buildFlavor());
Expand All @@ -217,14 +215,14 @@ void execute(List<PluginDescriptor> plugins) throws Exception {
final Path extractedZip = unzip(pluginZip, env.pluginsFile());
deleteOnFailure.add(extractedZip);
final PluginInfo pluginInfo = installPlugin(extractedZip, deleteOnFailure);
terminal.println("-> Installed " + pluginInfo.getName());
logger.info("-> Installed " + pluginInfo.getName());
// swap the entry by plugin id for one with the installed plugin name, it gives a cleaner error message for URL installs
deleteOnFailures.remove(pluginId);
deleteOnFailures.put(pluginInfo.getName(), deleteOnFailure);
} catch (final Exception installProblem) {
terminal.println("-> Failed installing " + pluginId);
logger.info("-> Failed installing " + pluginId);
for (final Map.Entry<String, List<Path>> deleteOnFailureEntry : deleteOnFailures.entrySet()) {
terminal.println("-> Rolling back " + deleteOnFailureEntry.getKey());
logger.info("-> Rolling back " + deleteOnFailureEntry.getKey());
boolean success = false;
try {
IOUtils.rm(deleteOnFailureEntry.getValue().toArray(new Path[0]));
Expand All @@ -235,16 +233,16 @@ void execute(List<PluginDescriptor> plugins) throws Exception {
exceptionWhileRemovingFiles
);
installProblem.addSuppressed(exception);
terminal.println("-> Failed rolling back " + deleteOnFailureEntry.getKey());
logger.info("-> Failed rolling back " + deleteOnFailureEntry.getKey());
}
if (success) {
terminal.println("-> Rolled back " + deleteOnFailureEntry.getKey());
logger.info("-> Rolled back " + deleteOnFailureEntry.getKey());
}
}
throw installProblem;
}
}
terminal.println("-> Please restart Elasticsearch to activate any plugins installed");
logger.info("-> Please restart Elasticsearch to activate any plugins installed");
}

Build.Flavor buildFlavor() {
Expand Down Expand Up @@ -273,7 +271,7 @@ private Path download(PluginDescriptor plugin, Path tmpDir) throws Exception {

if (OFFICIAL_PLUGINS.contains(pluginId)) {
final String url = getElasticUrl(getStagingHash(), Version.CURRENT, isSnapshot(), pluginId, Platforms.PLATFORM_NAME);
terminal.println("-> Downloading " + pluginId + " from elastic");
logger.info("-> Downloading " + pluginId + " from elastic");
return downloadAndValidate(url, tmpDir, true);
}

Expand All @@ -283,7 +281,7 @@ private Path download(PluginDescriptor plugin, Path tmpDir) throws Exception {
String[] coordinates = pluginUrl.split(":");
if (coordinates.length == 3 && pluginUrl.contains("/") == false && pluginUrl.startsWith("file:") == false) {
String mavenUrl = getMavenUrl(coordinates, Platforms.PLATFORM_NAME);
terminal.println("-> Downloading " + pluginId + " from maven central");
logger.info("-> Downloading " + pluginId + " from maven central");
return downloadAndValidate(mavenUrl, tmpDir, false);
}

Expand All @@ -297,7 +295,7 @@ private Path download(PluginDescriptor plugin, Path tmpDir) throws Exception {
}
throw new UserException(ExitCodes.USAGE, msg);
}
terminal.println("-> Downloading " + URLDecoder.decode(pluginUrl, StandardCharsets.UTF_8));
logger.info("-> Downloading " + URLDecoder.decode(pluginUrl, StandardCharsets.UTF_8));
return downloadZip(pluginUrl, tmpDir);
}

Expand Down Expand Up @@ -384,7 +382,7 @@ private String getMavenUrl(String[] coordinates, String platform) throws IOExcep
// pkg private for tests to manipulate
@SuppressForbidden(reason = "Make HEAD request using URLConnection.connect()")
boolean urlExists(String urlString) throws IOException {
terminal.println(VERBOSE, "Checking if url exists: " + urlString);
logger.debug("Checking if url exists: " + urlString);
URL url = new URL(urlString);
assert "https".equals(url.getProtocol()) : "Only http urls can be checked";
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
Expand Down Expand Up @@ -414,15 +412,15 @@ private List<String> checkMisspelledPlugin(String pluginId) {
// pkg private for tests
@SuppressForbidden(reason = "We use getInputStream to download plugins")
Path downloadZip(String urlString, Path tmpDir) throws IOException {
terminal.println(VERBOSE, "Retrieving zip from " + urlString);
logger.debug("Retrieving zip from " + urlString);
URL url = new URL(urlString);
Path zip = Files.createTempFile(tmpDir, null, ".zip");
URLConnection urlConnection = url.openConnection();
urlConnection.addRequestProperty("User-Agent", "elasticsearch-plugin-installer");
try (
InputStream in = batch
? urlConnection.getInputStream()
: new TerminalProgressInputStream(urlConnection.getInputStream(), urlConnection.getContentLength(), terminal)
: new TerminalProgressInputStream(urlConnection.getInputStream(), urlConnection.getContentLength(), logger)
) {
// must overwrite since creating the temp file above actually created the file
Files.copy(in, zip, StandardCopyOption.REPLACE_EXISTING);
Expand All @@ -445,13 +443,13 @@ void setBatch(boolean batch) {
*/
private class TerminalProgressInputStream extends ProgressInputStream {

private final Terminal terminal;
private final PluginLogger logger;
private int width = 50;
private final boolean enabled;

TerminalProgressInputStream(InputStream is, int expectedTotalSize, Terminal terminal) {
TerminalProgressInputStream(InputStream is, int expectedTotalSize, PluginLogger logger) {
super(is, expectedTotalSize);
this.terminal = terminal;
this.logger = logger;
this.enabled = expectedTotalSize > 0;
}

Expand All @@ -469,7 +467,7 @@ public void onProgress(int percent) {
if (percent == 100) {
sb.append("\n");
}
terminal.print(Terminal.Verbosity.NORMAL, String.format(Locale.ROOT, sb.toString(), percent + "%"));
logger.info(String.format(Locale.ROOT, sb.toString(), percent + "%"));
}
}
}
Expand Down Expand Up @@ -509,7 +507,7 @@ private Path downloadAndValidate(final String urlString, final Path tmpDir, fina
String digestAlgo = "SHA-512";
if (checksumUrl == null && officialPlugin == false) {
// fallback to sha1, until 7.0, but with warning
terminal.println(
logger.info(
"Warning: sha512 not found, falling back to sha1. This behavior is deprecated and will be removed in a "
+ "future release. Please update the plugin to use a sha512 checksum."
);
Expand Down Expand Up @@ -784,7 +782,7 @@ private PluginInfo loadPluginInfo(Path pluginRoot) throws Exception {

PluginsService.checkForFailedPluginRemovals(env.pluginsFile());

terminal.println(VERBOSE, info.toString());
logger.debug(info.toString());

// check for jar hell before any copying
jarHellCheck(info, pluginRoot, env.pluginsFile(), env.modulesFile());
Expand Down Expand Up @@ -834,11 +832,11 @@ void jarHellCheck(PluginInfo candidateInfo, Path candidateDir, Path pluginsDir,
*/
private PluginInfo installPlugin(Path tmpRoot, List<Path> deleteOnFailure) throws Exception {
final PluginInfo info = loadPluginInfo(tmpRoot);
checkCanInstallationProceed(terminal, Build.CURRENT.flavor(), info);
checkCanInstallationProceed(logger, Build.CURRENT.flavor(), info);
PluginPolicyInfo pluginPolicy = PolicyUtil.getPluginPolicyInfo(tmpRoot, env.tmpFile());
if (pluginPolicy != null) {
Set<String> permissions = PluginSecurity.getPermissionDescriptions(pluginPolicy, env.tmpFile());
PluginSecurity.confirmPolicyExceptions(terminal, permissions, batch);
PluginSecurity.confirmPolicyExceptions(logger, permissions, batch);
}

final Path destination = env.pluginsFile().resolve(info.getName());
Expand Down Expand Up @@ -994,7 +992,7 @@ public void close() throws IOException {
IOUtils.rm(pathsToDeleteOnShutdown.toArray(new Path[pathsToDeleteOnShutdown.size()]));
}

static void checkCanInstallationProceed(Terminal terminal, Build.Flavor flavor, PluginInfo info) throws Exception {
static void checkCanInstallationProceed(PluginLogger logger, Build.Flavor flavor, PluginInfo info) throws Exception {
if (info.isLicensed() == false) {
return;
}
Expand All @@ -1010,7 +1008,7 @@ static void checkCanInstallationProceed(Terminal terminal, Build.Flavor flavor,
"",
"This plugin is covered by the Elastic license, but this",
"installation of Elasticsearch is: [" + flavor + "]."
).forEach(terminal::errorPrintln);
).forEach(logger::error);

throw new UserException(ExitCodes.NOPERM, "Plugin license is incompatible with [" + flavor + "] installation");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th
.collect(Collectors.toList());
final boolean isBatch = options.has(batchOption);

InstallPluginAction action = new InstallPluginAction(terminal, env, isBatch);
InstallPluginAction action = new InstallPluginAction(new TerminalLogger(terminal), env, isBatch);
action.execute(plugins);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import org.elasticsearch.bootstrap.PluginPolicyInfo;
import org.elasticsearch.bootstrap.PolicyUtil;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.Terminal.Verbosity;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.plugins.PluginLogger;

import java.io.IOException;
import java.net.URL;
Expand All @@ -32,32 +31,32 @@ public class PluginSecurity {
/**
* prints/confirms policy exceptions with the user
*/
static void confirmPolicyExceptions(Terminal terminal, Set<String> permissions, boolean batch) throws UserException {
static void confirmPolicyExceptions(PluginLogger logger, Set<String> permissions, boolean batch) throws UserException {
List<String> requested = new ArrayList<>(permissions);
if (requested.isEmpty()) {
terminal.println(Verbosity.VERBOSE, "plugin has a policy file with no additional permissions");
logger.debug("plugin has a policy file with no additional permissions");
} else {

// sort permissions in a reasonable order
Collections.sort(requested);

terminal.errorPrintln(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
terminal.errorPrintln(Verbosity.NORMAL, "@ WARNING: plugin requires additional permissions @");
terminal.errorPrintln(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
logger.warn("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
logger.warn("@ WARNING: plugin requires additional permissions @");
logger.warn("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
// print all permissions:
for (String permission : requested) {
terminal.errorPrintln(Verbosity.NORMAL, "* " + permission);
logger.warn("* " + permission);
}
terminal.errorPrintln(Verbosity.NORMAL, "See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html");
terminal.errorPrintln(Verbosity.NORMAL, "for descriptions of what these permissions allow and the associated risks.");
prompt(terminal, batch);
logger.warn("See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html");
logger.warn("for descriptions of what these permissions allow and the associated risks.");
prompt(logger, batch);
}
}

private static void prompt(final Terminal terminal, final boolean batch) throws UserException {
private static void prompt(final PluginLogger logger, final boolean batch) throws UserException {
if (batch == false) {
terminal.println(Verbosity.NORMAL, "");
String text = terminal.readText("Continue with installation? [y/N]");
logger.info("");
String text = logger.readText("Continue with installation? [y/N]");
if (text.equalsIgnoreCase("y") == false) {
throw new UserException(ExitCodes.DATA_ERROR, "installation aborted by user");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
package org.elasticsearch.plugins.cli;

import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.PluginLogger;
import org.elasticsearch.plugins.PluginsService;

import java.io.IOException;
Expand All @@ -29,8 +29,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.elasticsearch.cli.Terminal.Verbosity.VERBOSE;

/**
* An action for the plugin CLI to remove plugins from Elasticsearch.
*/
Expand All @@ -40,19 +38,19 @@ class RemovePluginAction {
/** A plugin cannot be removed because it is extended by another plugin. */
static final int PLUGIN_STILL_USED = 11;

private final Terminal terminal;
private final PluginLogger logger;
private final Environment env;
private final boolean purge;

/**
* Creates a new action.
*
* @param terminal the terminal to use for input/output
* @param logger the terminal to use for input/output
* @param env the environment for the local node
* @param purge if true, plugin configuration files will be removed but otherwise preserved
*/
RemovePluginAction(Terminal terminal, Environment env, boolean purge) {
this.terminal = terminal;
RemovePluginAction(PluginLogger logger, Environment env, boolean purge) {
this.logger = logger;
this.env = env;
this.purge = purge;
}
Expand Down Expand Up @@ -145,7 +143,7 @@ private void removePlugin(PluginDescriptor plugin) throws IOException {
final Path pluginConfigDir = env.configFile().resolve(pluginId);
final Path removing = env.pluginsFile().resolve(".removing-" + pluginId);

terminal.println("-> removing [" + pluginId + "]...");
logger.info("-> removing [" + pluginId + "]...");

final List<Path> pluginPaths = new ArrayList<>();

Expand All @@ -157,7 +155,7 @@ private void removePlugin(PluginDescriptor plugin) throws IOException {
try (Stream<Path> paths = Files.list(pluginDir)) {
pluginPaths.addAll(paths.collect(Collectors.toList()));
}
terminal.println(VERBOSE, "removing [" + pluginDir + "]");
logger.debug("removing [" + pluginDir + "]");
}

final Path pluginBinDir = env.binFile().resolve(pluginId);
Expand All @@ -166,7 +164,7 @@ private void removePlugin(PluginDescriptor plugin) throws IOException {
pluginPaths.addAll(paths.collect(Collectors.toList()));
}
pluginPaths.add(pluginBinDir);
terminal.println(VERBOSE, "removing [" + pluginBinDir + "]");
logger.debug("removing [" + pluginBinDir + "]");
}

if (Files.exists(pluginConfigDir)) {
Expand All @@ -175,7 +173,7 @@ private void removePlugin(PluginDescriptor plugin) throws IOException {
pluginPaths.addAll(paths.collect(Collectors.toList()));
}
pluginPaths.add(pluginConfigDir);
terminal.println(VERBOSE, "removing [" + pluginConfigDir + "]");
logger.debug("removing [" + pluginConfigDir + "]");
} else {
/*
* By default we preserve the config files in case the user is upgrading the plugin, but we print a message so the user
Expand All @@ -186,7 +184,7 @@ private void removePlugin(PluginDescriptor plugin) throws IOException {
"-> preserving plugin config files [%s] in case of upgrade; use --purge if not needed",
pluginConfigDir
);
terminal.println(message);
logger.info(message);
}
}

Expand All @@ -205,7 +203,7 @@ private void removePlugin(PluginDescriptor plugin) throws IOException {
* We need to suppress the marker file already existing as we could be in this state if a previous removal attempt failed and
* the user is attempting to remove the plugin again.
*/
terminal.println(VERBOSE, "marker file [" + removing + "] already exists");
logger.debug("marker file [" + removing + "] already exists");
}

// add the plugin directory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class RemovePluginCommand extends EnvironmentAwareCommand {
protected void execute(final Terminal terminal, final OptionSet options, final Environment env) throws Exception {
final List<PluginDescriptor> plugins = arguments.values(options).stream().map(PluginDescriptor::new).collect(Collectors.toList());

final RemovePluginAction action = new RemovePluginAction(terminal, env, options.has(purgeOption));
final RemovePluginAction action = new RemovePluginAction(new TerminalLogger(terminal), env, options.has(purgeOption));
action.execute(plugins);
}
}
Loading

0 comments on commit 8abde31

Please sign in to comment.