Skip to content

Commit

Permalink
Add support for a Jenkins version argument (CLI and env var) + Make J…
Browse files Browse the repository at this point in the history
…enkins 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
  • Loading branch information
oleg-nenashev authored Dec 1, 2020
1 parent 9156665 commit 0adab4b
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 12 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 " +
Expand Down Expand Up @@ -139,6 +148,7 @@ Config setup() {
.withJenkinsUcExperimental(getExperimentalUpdateCenter())
.withJenkinsIncrementalsRepoMirror(getIncrementalsMirror())
.withJenkinsPluginInfo(getPluginInfo())
.withJenkinsVersion(getJenkinsVersion())
.withJenkinsWar(getJenkinsWar())
.withShowWarnings(isShowWarnings())
.withShowAllWarnings(isShowAllWarnings())
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Plugin> latestVersionsOfPlugins = pm.getLatestVersionsOfPlugins(cfg.getPlugins());
OutputFormat outputFormat = options.getOutputFormat() == null ? OutputFormat.STDOUT : options.getOutputFormat();
String output;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<VersionNumber> {

public VersionNumberHandler(CmdLineParser parser, OptionDef option, Setter<VersionNumber> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<Plugin> plugins;
private boolean verbose;
Expand All @@ -42,6 +54,7 @@ private Config(
boolean showAvailableUpdates,
boolean showPluginsToBeDownloaded,
boolean verbose,
VersionNumber jenkinsVersion,
String jenkinsWar,
List<Plugin> plugins,
URL jenkinsUc,
Expand All @@ -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;
Expand Down Expand Up @@ -96,6 +110,7 @@ public boolean isVerbose() {
return verbose;
}

@CheckForNull
public String getJenkinsWar() {
return jenkinsWar;
}
Expand All @@ -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;
}
Expand All @@ -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<Plugin> plugins = new ArrayList<>();
private URL jenkinsUc = Settings.DEFAULT_UPDATE_CENTER;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -253,6 +289,7 @@ public Config build() {
showAvailableUpdates,
showPluginsToBeDownloaded,
verbose,
jenkinsVersion,
jenkinsWar,
plugins,
jenkinsUc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public class PluginManager {
private List<Plugin> failedPlugins;
private File refDir;
private String jenkinsUcLatest;
private File jenkinsWarFile;
private @CheckForNull VersionNumber jenkinsVersion;
private @CheckForNull File jenkinsWarFile;
private Map<String, Plugin> installedPluginVersions;
private Map<String, Plugin> bundledPluginVersions;
private Map<String, List<SecurityWarning>> allSecurityWarnings;
Expand All @@ -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<>();
Expand Down Expand Up @@ -145,7 +148,7 @@ public void start(boolean downloadUc) {
"at a time");
}

VersionNumber jenkinsVersion = getJenkinsVersionFromWar();
VersionNumber jenkinsVersion = getJenkinsVersion();
if (downloadUc) {
getUCJson(jenkinsVersion);
}
Expand Down Expand Up @@ -432,12 +435,13 @@ public boolean warningExists(Plugin plugin) {
public void checkVersionCompatibility(VersionNumber jenkinsVersion, List<Plugin> 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()));
}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1206,6 +1231,11 @@ public Map<String, Plugin> installedPlugins() {
public Map<String, Plugin> bundledPlugins() {
Map<String, Plugin> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit 0adab4b

Please sign in to comment.