From 0adab4ba326a21f1d615e4a61a16cf3631492607 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 1 Dec 2020 09:08:57 +0100 Subject: [PATCH] Add support for a Jenkins version argument (CLI and env var) + Make Jenkins WAR parameter optional in the Plugin Manager Lib API and the implementation (#232) * Add support for a Jenkins version argument (CLI and env var) * Make Jenkins WAR option in the Plugin Manager Lib API and the implementation * Simplify the version number parser, add test * Rename the argument to follow the naming pattern * Remove unused import --- README.md | 3 ++ .../tools/pluginmanager/cli/CliOptions.java | 28 +++++++++++ .../jenkins/tools/pluginmanager/cli/Main.java | 2 +- .../cli/VersionNumberHandler.java | 31 ++++++++++++ .../pluginmanager/cli/CliOptionsTest.java | 8 +++ .../tools/pluginmanager/config/Config.java | 39 ++++++++++++++- .../pluginmanager/impl/PluginManager.java | 50 +++++++++++++++---- .../pluginmanager/impl/PluginManagerTest.java | 11 ++++ 8 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/VersionNumberHandler.java diff --git a/README.md b/README.md index 5675cd8e..5a885602 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ jenkins-plugin-cli --plugin-file /your/path/to/plugins.txt --plugins delivery-pi * `--plugin-file` or `-f`: (optional) Path to the plugins.txt, or plugins.yaml file, which contains a list of plugins to install. If this file does not exist, or if the file exists, but does not have a .txt or .yaml/.yml extension, then an error will be thrown. * `--plugin-download-directory` or `-d`: (optional) Directory in which to install plugins. This configuration can also be made via the PLUGIN_DIR environment variable. The directory will be first deleted, then recreated. If no directory configuration is provided, the defaults are C:\ProgramData\Jenkins\Reference\Plugins if the detected operating system is Microsoft Windows, or /usr/share/jenkins/ref/plugins otherwise. * `--plugins` or `-p`: (optional) List of plugins to install (see plugin format below), separated by a space. +* `--jenkins-version`: (optional) Version of Jenkins to be used. + If not specified, the plugin manager will try to extract it from the WAR file or other sources. + The argument can be also set using the `JENKINS_VERSION` environment variable. * `--war` or `-w`: (optional) Path to Jenkins war file. If no war file is entered, will default to /usr/share/jenkins/jenkins.war or C:\ProgramData\Jenkins\jenkins.war, depending on the user's OS. Plugins that are already included in the Jenkins war will only be downloaded if their required version is newer than the one included. * `--list` or `-l`: (optional) Lists plugin names and versions of: installed plugins (plugins that already exist in the plugin directory), bundled plugins (non-detached plugins that exist in the war file), plugins that will be downloaded (highest required versions of the requested plugins and dependencies that are not already installed), and the effective plugin set (the highest versions of all plugins that are already installed or will be installed) * `--verbose`: (optional) Set to true to show additional information about plugin dependencies and the download process diff --git a/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/CliOptions.java b/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/CliOptions.java index f599cad5..76df514d 100644 --- a/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/CliOptions.java +++ b/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/CliOptions.java @@ -2,6 +2,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import hudson.util.VersionNumber; import io.jenkins.tools.pluginmanager.config.Config; import io.jenkins.tools.pluginmanager.config.OutputFormat; import io.jenkins.tools.pluginmanager.config.PluginInputException; @@ -17,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +import javax.annotation.CheckForNull; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.kohsuke.args4j.Option; @@ -41,7 +43,14 @@ class CliOptions { handler = StringArrayOptionHandler.class) private String[] plugins = new String[0]; + @Option(name = "--jenkins-version", usage = "Jenkins version to be used. " + + "If undefined, Plugin Manager will use alternative ways to retrieve the version, e.g. from WAR", + handler = VersionNumberHandler.class) + @CheckForNull + private VersionNumber jenkinsVersion; + @Option(name = "--war", aliases = {"-w"}, usage = "Path to Jenkins war file") + @CheckForNull private String jenkinsWarFile; @Option(name = "--list", aliases = {"-l"}, usage = "Lists all plugins currently installed and if given a list of " + @@ -139,6 +148,7 @@ Config setup() { .withJenkinsUcExperimental(getExperimentalUpdateCenter()) .withJenkinsIncrementalsRepoMirror(getIncrementalsMirror()) .withJenkinsPluginInfo(getPluginInfo()) + .withJenkinsVersion(getJenkinsVersion()) .withJenkinsWar(getJenkinsWar()) .withShowWarnings(isShowWarnings()) .withShowAllWarnings(isShowAllWarnings()) @@ -204,6 +214,24 @@ private File getPluginDir() { return new File(Settings.DEFAULT_PLUGIN_DIR_LOCATION); } + @CheckForNull + private VersionNumber getJenkinsVersion() { + if (jenkinsVersion != null) { + return jenkinsVersion; + } + + String fromEnv = System.getenv("JENKINS_VERSION"); + if (StringUtils.isNotBlank(fromEnv)) { + try { + return new VersionNumber(fromEnv); + } catch (Exception ex) { + throw new VersionNotFoundException("Failed to parse the version from JENKINS_VERSION=" + fromEnv, ex); + } + } + + return null; + } + /** * Gets the user specified Jenkins war from the CLI option and sets this in the configuration class */ diff --git a/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/Main.java b/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/Main.java index b0525a80..c9edf997 100644 --- a/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/Main.java +++ b/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/Main.java @@ -49,7 +49,7 @@ public static void main(String[] args) throws IOException { PluginManager pm = new PluginManager(cfg); if (options.isShowAvailableUpdates()) { - pm.getUCJson(pm.getJenkinsVersionFromWar()); + pm.getUCJson(pm.getJenkinsVersion()); List latestVersionsOfPlugins = pm.getLatestVersionsOfPlugins(cfg.getPlugins()); OutputFormat outputFormat = options.getOutputFormat() == null ? OutputFormat.STDOUT : options.getOutputFormat(); String output; diff --git a/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/VersionNumberHandler.java b/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/VersionNumberHandler.java new file mode 100644 index 00000000..ebc564f9 --- /dev/null +++ b/plugin-management-cli/src/main/java/io/jenkins/tools/pluginmanager/cli/VersionNumberHandler.java @@ -0,0 +1,31 @@ +package io.jenkins.tools.pluginmanager.cli; + +import hudson.util.VersionNumber; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OneArgumentOptionHandler; +import org.kohsuke.args4j.spi.Setter; + +// TODO(oleg_nenashev): Move to the VersionNumber lib? + +public class VersionNumberHandler extends OneArgumentOptionHandler { + + public VersionNumberHandler(CmdLineParser parser, OptionDef option, Setter setter) { + super(parser, option, setter); + } + + @Override + protected VersionNumber parse(String argument) throws NumberFormatException, CmdLineException { + try { + return new VersionNumber(argument); + } catch (Exception ex) { + throw new CmdLineException("Failed to parse the version number", ex); + } + } + + @Override + public String getDefaultMetaVariable() { + return null; + } +} diff --git a/plugin-management-cli/src/test/java/io/jenkins/tools/pluginmanager/cli/CliOptionsTest.java b/plugin-management-cli/src/test/java/io/jenkins/tools/pluginmanager/cli/CliOptionsTest.java index e06848a0..3aebde7b 100644 --- a/plugin-management-cli/src/test/java/io/jenkins/tools/pluginmanager/cli/CliOptionsTest.java +++ b/plugin-management-cli/src/test/java/io/jenkins/tools/pluginmanager/cli/CliOptionsTest.java @@ -1,5 +1,6 @@ package io.jenkins.tools.pluginmanager.cli; +import hudson.util.VersionNumber; import io.jenkins.tools.pluginmanager.config.Config; import io.jenkins.tools.pluginmanager.config.PluginInputException; import io.jenkins.tools.pluginmanager.config.Settings; @@ -170,6 +171,13 @@ public void setupWarTest() throws CmdLineException { assertThat(cfg.getJenkinsWar()).isEqualTo(jenkinsWar); } + @Test + public void setupJenkinsVersion() throws CmdLineException { + parser.parseArgument("--jenkins-version", "2.263.1"); + Config cfg = options.setup(); + assertThat(cfg.getJenkinsVersion()).isEqualTo(new VersionNumber("2.263.1")); + } + @Test public void setupPluginDirTest() throws CmdLineException, IOException { String pluginDir = temporaryFolder.newFolder("plugins").toString(); diff --git a/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/config/Config.java b/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/config/Config.java index 45ddeee5..6b717fa6 100644 --- a/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/config/Config.java +++ b/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/config/Config.java @@ -1,10 +1,12 @@ package io.jenkins.tools.pluginmanager.config; +import hudson.util.VersionNumber; import io.jenkins.tools.pluginmanager.impl.Plugin; import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.List; +import javax.annotation.CheckForNull; /** * Configuration for the plugin installation manager tool. @@ -22,6 +24,16 @@ public class Config { private boolean showAllWarnings; private boolean showAvailableUpdates; private boolean showPluginsToBeDownloaded; + + /** + * Explicitly passed Jenkins version. + */ + @CheckForNull + private VersionNumber jenkinsVersion; + /** + * Path to the Jenkins WAR file. + */ + @CheckForNull private String jenkinsWar; private List plugins; private boolean verbose; @@ -42,6 +54,7 @@ private Config( boolean showAvailableUpdates, boolean showPluginsToBeDownloaded, boolean verbose, + VersionNumber jenkinsVersion, String jenkinsWar, List plugins, URL jenkinsUc, @@ -59,6 +72,7 @@ private Config( this.showAvailableUpdates = showAvailableUpdates; this.showPluginsToBeDownloaded = showPluginsToBeDownloaded; this.verbose = verbose; + this.jenkinsVersion = jenkinsVersion; this.jenkinsWar = jenkinsWar; this.plugins = plugins; this.jenkinsUc = jenkinsUc; @@ -96,6 +110,7 @@ public boolean isVerbose() { return verbose; } + @CheckForNull public String getJenkinsWar() { return jenkinsWar; } @@ -120,6 +135,16 @@ public URL getJenkinsPluginInfo() { return jenkinsPluginInfo; } + /** + * Get Jenkins version to be used by the plugin manager. + * @return Jenkins version. + * When {@code null}, instructs the plugin manager to use alternative version retrieval mechanisms. + */ + @CheckForNull + public VersionNumber getJenkinsVersion() { + return jenkinsVersion; + } + public boolean doDownload() { return doDownload; } @@ -145,6 +170,7 @@ public static class Builder { private boolean showAvailableUpdates; private boolean showPluginsToBeDownloaded; private boolean verbose; + private VersionNumber jenkinsVersion; private String jenkinsWar; private List plugins = new ArrayList<>(); private URL jenkinsUc = Settings.DEFAULT_UPDATE_CENTER; @@ -185,7 +211,17 @@ public Builder withShowPluginsToBeDownloaded(boolean showPluginsToBeDownloaded) return this; } - public Builder withJenkinsWar(String jenkinsWar) { + /** + * Sets Jenkins version to be used for retrieving compatible plugins. + * @param jenkinsVersion Jenkins version. + * {@code null} to make undefined and to force alternative version retrieval logic. + */ + public Builder withJenkinsVersion(@CheckForNull VersionNumber jenkinsVersion) { + this.jenkinsVersion = jenkinsVersion; + return this; + } + + public Builder withJenkinsWar(@CheckForNull String jenkinsWar) { this.jenkinsWar = jenkinsWar; return this; } @@ -253,6 +289,7 @@ public Config build() { showAvailableUpdates, showPluginsToBeDownloaded, verbose, + jenkinsVersion, jenkinsWar, plugins, jenkinsUc, diff --git a/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/impl/PluginManager.java b/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/impl/PluginManager.java index b62ffef2..804dc30a 100644 --- a/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/impl/PluginManager.java +++ b/plugin-management-library/src/main/java/io/jenkins/tools/pluginmanager/impl/PluginManager.java @@ -70,7 +70,8 @@ public class PluginManager { private List failedPlugins; private File refDir; private String jenkinsUcLatest; - private File jenkinsWarFile; + private @CheckForNull VersionNumber jenkinsVersion; + private @CheckForNull File jenkinsWarFile; private Map installedPluginVersions; private Map bundledPluginVersions; private Map> allSecurityWarnings; @@ -96,7 +97,9 @@ public class PluginManager { public PluginManager(Config cfg) { this.cfg = cfg; refDir = cfg.getPluginDir(); - jenkinsWarFile = new File(cfg.getJenkinsWar()); + jenkinsVersion = cfg.getJenkinsVersion(); + final String warArg = cfg.getJenkinsWar(); + jenkinsWarFile = warArg != null ? new File(warArg) : null; failedPlugins = new ArrayList<>(); installedPluginVersions = new HashMap<>(); bundledPluginVersions = new HashMap<>(); @@ -145,7 +148,7 @@ public void start(boolean downloadUc) { "at a time"); } - VersionNumber jenkinsVersion = getJenkinsVersionFromWar(); + VersionNumber jenkinsVersion = getJenkinsVersion(); if (downloadUc) { getUCJson(jenkinsVersion); } @@ -432,12 +435,13 @@ public boolean warningExists(Plugin plugin) { public void checkVersionCompatibility(VersionNumber jenkinsVersion, List pluginsToBeDownloaded) { if (jenkinsVersion != null && !StringUtils.isEmpty(jenkinsVersion.toString())) { for (Plugin p : pluginsToBeDownloaded) { - if (p.getJenkinsVersion() != null) { - if (p.getJenkinsVersion().isNewerThan(jenkinsVersion)) { + final VersionNumber pluginJenkinsVersion = p.getJenkinsVersion(); + if (pluginJenkinsVersion!= null) { + if (pluginJenkinsVersion.isNewerThan(jenkinsVersion)) { throw new VersionCompatibilityException( - String.format("%n%s (%s) requires a greater version of Jenkins (%s) than %s in %s", - p.getName(), p.getVersion().toString(), p.getJenkinsVersion().toString(), - jenkinsVersion.toString(), jenkinsWarFile.toString())); + String.format("%n%s (%s) requires a greater version of Jenkins (%s) than %s", + p.getName(), p.getVersion().toString(), pluginJenkinsVersion.toString(), + jenkinsVersion.toString())); } } } @@ -1144,15 +1148,36 @@ public String getAttributeFromManifest(File file, String key) { return null; } + /** + * Gets Jenkins version using one of the available methods. + * @return Jenkins version or {@code null} if it cannot be determined + */ + @CheckForNull + public VersionNumber getJenkinsVersion() { + if (jenkinsVersion != null) { + return jenkinsVersion; + } + if (jenkinsWarFile != null) { + return getJenkinsVersionFromWar(); + } + System.out.println("Unable to determine Jenkins version"); + return null; + } + /** * Gets the Jenkins version from the manifest in the Jenkins war specified in the Config class * - * @return Jenkins version + * @return Jenkins version or {@code null} if the version cannot be determined */ + @CheckForNull public VersionNumber getJenkinsVersionFromWar() { + if (jenkinsWarFile == null) { + System.out.println("Unable to get Jenkins version from the WAR file: WAR file path is not defined."); + return null; + } String version = getAttributeFromManifest(jenkinsWarFile, "Jenkins-Version"); if (StringUtils.isEmpty(version)) { - System.out.println("Unable to get version from war file"); + System.out.println("Unable to get Jenkins version from the WAR file " + jenkinsWarFile.getPath()); return null; } logVerbose("Jenkins version: " + version); @@ -1206,6 +1231,11 @@ public Map installedPlugins() { public Map bundledPlugins() { Map bundledPlugins = new HashMap<>(); + if (jenkinsWarFile == null) { + System.out.println("WAR file is not defined, cannot retrieve the bundled plugins"); + return bundledPlugins; + } + if (jenkinsWarFile.exists()) { Path path = Paths.get(jenkinsWarFile.toString()); URI jenkinsWarUri; diff --git a/plugin-management-library/src/test/java/io/jenkins/tools/pluginmanager/impl/PluginManagerTest.java b/plugin-management-library/src/test/java/io/jenkins/tools/pluginmanager/impl/PluginManagerTest.java index 6df51057..78b913b3 100644 --- a/plugin-management-library/src/test/java/io/jenkins/tools/pluginmanager/impl/PluginManagerTest.java +++ b/plugin-management-library/src/test/java/io/jenkins/tools/pluginmanager/impl/PluginManagerTest.java @@ -1071,6 +1071,17 @@ public void getJenkinsVersionFromWarTest() { .isEqualByComparingTo(new VersionNumber("2.164.1")); } + @Test + public void getJenkinsVersionFromArg() { + //the only time the file for a particular war string is created is in the PluginManager constructor + Config config = Config.builder() + .withJenkinsVersion(new VersionNumber("2.263.1")) + .build(); + PluginManager pluginManager = new PluginManager(config); + assertThat(pluginManager.getJenkinsVersion()) + .isEqualByComparingTo(new VersionNumber("2.263.1")); + } + @Test public void bundledPluginsTest() { URL warURL = this.getClass().getResource("/bundledplugintest.war");