From 43ddbdfc1cd651e0430289766637a11717494e88 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Fri, 25 Mar 2016 18:23:25 +0100 Subject: [PATCH 1/9] [JENKINS-21486] Fix plugin dependencies resolution * Check that dependencies are enabled. A disabled optional dependency will not prevent a plugin from loading. * Check versions of dependencies declared by a plugin before loading it. If any dependency (even optional) is older than what is required, then the plugin isn't loaded. This should prevent use cases where a plugin is loaded but one of its dependencies is too old so that : * its @Extension annotated classes cannot be loaded, causing the full Jenkins to blow up with crapload of exceptions which are tedious to investigate to understand the root cause. * NoSuchMethodError and the likes at runtime even though boot has completed. Version check (for setups where version list is manually crafted but yet works) can be disabled by starting Jenkins with -Dhudson.PluginWrapper.dependenciesVersionCheck.enabled=true Minor fixes done while implementing this change : * Fix version parsing in PluginWrapper.Dependency * Dynamic plugin load didn't check for disabled flag --- core/src/main/java/hudson/PluginManager.java | 3 +- core/src/main/java/hudson/PluginWrapper.java | 91 ++++++++++++++++-- .../test/java/hudson/PluginManagerTest.java | 74 ++++++++++++++ .../test/java/hudson/PluginManagerUtil.java | 7 ++ .../test/java/hudson/PluginWrapperTest.java | 26 +++++ .../test/resources/plugins/dependee-0.0.2.hpi | Bin 0 -> 4671 bytes .../test/resources/plugins/depender-0.0.2.hpi | Bin 0 -> 5500 bytes .../plugins/mandatory-depender-0.0.2.hpi | Bin 0 -> 5587 bytes 8 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 test/src/test/java/hudson/PluginWrapperTest.java create mode 100644 test/src/test/resources/plugins/dependee-0.0.2.hpi create mode 100644 test/src/test/resources/plugins/depender-0.0.2.hpi create mode 100644 test/src/test/resources/plugins/mandatory-depender-0.0.2.hpi diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 92416858e700..941dc0cdc26f 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -844,7 +844,8 @@ public void dynamicLoad(File arc, boolean removeExisting) throws IOException, In // so existing plugins can't be depending on this newly deployed one. plugins.add(p); - activePlugins.add(p); + if (p.isActive()) + activePlugins.add(p); synchronized (((UberClassLoader) uberClassLoader).loaded) { ((UberClassLoader) uberClassLoader).loaded.clear(); } diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index f61b96e7cf20..31feb91c66c2 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -88,6 +88,12 @@ */ @ExportedBean public class PluginWrapper implements Comparable, ModelObject { + /** + * A plugin won't be loaded unless his declared dependencies are present and match the required minimal version. + * This can be set to false to disable the version check (legacy behaviour) + */ + private static final boolean ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK = Boolean.parseBoolean(System.getProperty(PluginWrapper.class.getName()+"." + "dependenciesVersionCheck.enabled", "true")); + /** * {@link PluginManager} to which this belongs to. */ @@ -229,7 +235,7 @@ public Dependency(String s) { @Override public String toString() { - return shortName + " (" + version + ")"; + return shortName + " (" + version + ") " + (optional ? "optional" : ""); } } @@ -394,6 +400,21 @@ private String getVersionOf(Manifest manifest) { return "???"; } + /** + * Returns the required Jenkins core version of this plugin. + * @return the required Jenkins core version of this plugin. + * @since XXX + */ + @Exported + public @CheckForNull String getRequiredCoreVersion() { + String v = manifest.getMainAttributes().getValue("Jenkins-Version"); + if (v!= null) return v; + + v = manifest.getMainAttributes().getValue("Hudson-Version"); + if (v!= null) return v; + return null; + } + /** * Returns the version number of this plugin */ @@ -524,19 +545,71 @@ public boolean hasLicensesXml() { * thrown if one or several mandatory dependencies doesn't exists. */ /*package*/ void resolvePluginDependencies() throws IOException { - List missingDependencies = new ArrayList<>(); + if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK) { + String requiredCoreVersion = getRequiredCoreVersion(); + if (requiredCoreVersion == null) { + LOGGER.warning(shortName + " doesn't declare required core version."); + } else { + if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { + throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); + } + } + } + List missingDependencies = new ArrayList(); + List obsoleteDependencies = new ArrayList(); + List disabledDependencies = new ArrayList(); // make sure dependencies exist for (Dependency d : dependencies) { - if (parent.getPlugin(d.shortName) == null) - missingDependencies.add(d); - } - if (!missingDependencies.isEmpty()) - throw new MissingDependencyException(this.shortName, missingDependencies); + PluginWrapper dependency = parent.getPlugin(d.shortName); + if (dependency == null) { + missingDependencies.add(d.toString()); + } else { + if (dependency.isActive()) { + if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { + obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + } + } else { + disabledDependencies.add(d.toString()); + } + } + } // add the optional dependencies that exists for (Dependency d : optionalDependencies) { - if (parent.getPlugin(d.shortName) != null) - dependencies.add(d); + PluginWrapper dependency = parent.getPlugin(d.shortName); + if (dependency != null && dependency.isActive()) { + if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { + obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + } else { + dependencies.add(d); + } + } + } + StringBuilder messageBuilder = new StringBuilder(); + if (!missingDependencies.isEmpty()) { + boolean plural = missingDependencies.size() > 1; + messageBuilder.append(plural ? "Dependencies " : "Dependency ") + .append(Util.join(missingDependencies, ", ")) + .append(" ").append(plural ? "don't" : "doesn't") + .append(" exist. "); + } + if (!disabledDependencies.isEmpty()) { + boolean plural = disabledDependencies.size() > 1; + messageBuilder.append(plural ? "Dependencies " : "Dependency ") + .append(Util.join(missingDependencies, ", ")) + .append(" ").append(plural ? "are" : "is") + .append(" disabled. "); + } + if (!obsoleteDependencies.isEmpty()) { + boolean plural = obsoleteDependencies.size() > 1; + messageBuilder.append(plural ? "Dependencies " : "Dependency ") + .append(Util.join(obsoleteDependencies, ", ")) + .append(" ").append(plural ? "are" : "is") + .append(" older than required."); + } + String message = messageBuilder.toString(); + if (!message.isEmpty()) { + throw new IOException(message); } } diff --git a/test/src/test/java/hudson/PluginManagerTest.java b/test/src/test/java/hudson/PluginManagerTest.java index 42847950cdfb..e7bb69de1bdd 100644 --- a/test/src/test/java/hudson/PluginManagerTest.java +++ b/test/src/test/java/hudson/PluginManagerTest.java @@ -47,6 +47,7 @@ import org.apache.commons.io.FileUtils; import org.apache.tools.ant.filters.StringInputStream; import static org.junit.Assert.*; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -339,6 +340,75 @@ private String callDependerValue() throws Exception { assertTrue(r.jenkins.getExtensionList("org.jenkinsci.plugins.dependencytest.dependee.DependeeExtensionPoint").isEmpty()); } + @Issue("JENKINS-21486") + @Test public void installPluginWithObsoleteDependencyFails() throws Exception { + // Load dependee 0.0.1. + { + dynamicLoad("dependee.hpi"); + } + + // Load mandatory-depender 0.0.2, depending on dependee 0.0.2 + try { + dynamicLoad("mandatory-depender-0.0.2.hpi"); + fail("Should not have worked"); + } catch (IOException e) { + // Expected + } + } + + @Issue("JENKINS-21486") + @Test public void installPluginWithDisabledOptionalDependencySucceeds() throws Exception { + // Load dependee 0.0.2. + { + dynamicLoadAndDisable("dependee-0.0.2.hpi"); + } + + // Load depender 0.0.2, depending optionally on dependee 0.0.2 + { + dynamicLoad("depender-0.0.2.hpi"); + } + + // dependee is not loaded so we cannot list any extension for it. + try { + r.jenkins.getExtensionList("org.jenkinsci.plugins.dependencytest.dependee.DependeeExtensionPoint"); + fail(); + } catch( ClassNotFoundException _ ){ + } + } + + @Issue("JENKINS-21486") + @Test public void installPluginWithDisabledDependencyFails() throws Exception { + // Load dependee 0.0.2. + { + dynamicLoadAndDisable("dependee-0.0.2.hpi"); + } + + // Load mandatory-depender 0.0.2, depending on dependee 0.0.2 + try { + dynamicLoad("mandatory-depender-0.0.2.hpi"); + fail("Should not have worked"); + } catch (IOException e) { + // Expected + } + } + + + @Issue("JENKINS-21486") + @Test public void installPluginWithObsoleteOptionalDependencyFails() throws Exception { + // Load dependee 0.0.1. + { + dynamicLoad("dependee.hpi"); + } + + // Load depender 0.0.2, depending optionally on dependee 0.0.2 + try { + dynamicLoad("depender-0.0.2.hpi"); + fail("Should not have worked"); + } catch (IOException e) { + // Expected + } + } + @Issue("JENKINS-12753") @WithPlugin("tasks.jpi") @Test public void dynamicLoadRestartRequiredException() throws Exception { @@ -378,6 +448,10 @@ private void dynamicLoad(String plugin) throws IOException, InterruptedException PluginManagerUtil.dynamicLoad(plugin, r.jenkins); } + private void dynamicLoadAndDisable(String plugin) throws IOException, InterruptedException, RestartRequiredException { + PluginManagerUtil.dynamicLoad(plugin, r.jenkins, true); + } + @Test public void uploadDependencyResolution() throws Exception { PersistedList sites = r.jenkins.getUpdateCenter().getSites(); sites.clear(); diff --git a/test/src/test/java/hudson/PluginManagerUtil.java b/test/src/test/java/hudson/PluginManagerUtil.java index e67b9b31f9f3..2263622b4c44 100644 --- a/test/src/test/java/hudson/PluginManagerUtil.java +++ b/test/src/test/java/hudson/PluginManagerUtil.java @@ -48,9 +48,16 @@ public void before() throws Throwable { } public static void dynamicLoad(String plugin, Jenkins jenkins) throws IOException, InterruptedException, RestartRequiredException { + dynamicLoad(plugin, jenkins, false); + } + + public static void dynamicLoad(String plugin, Jenkins jenkins, boolean disable) throws IOException, InterruptedException, RestartRequiredException { URL src = PluginManagerTest.class.getClassLoader().getResource("plugins/" + plugin); File dest = new File(jenkins.getRootDir(), "plugins/" + plugin); FileUtils.copyURLToFile(src, dest); + if (disable) { + new File(dest.getPath() + ".disabled").createNewFile(); + } jenkins.pluginManager.dynamicLoad(dest); } } diff --git a/test/src/test/java/hudson/PluginWrapperTest.java b/test/src/test/java/hudson/PluginWrapperTest.java new file mode 100644 index 000000000000..7529c5319495 --- /dev/null +++ b/test/src/test/java/hudson/PluginWrapperTest.java @@ -0,0 +1,26 @@ +package hudson; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class PluginWrapperTest { + + @Test + public void dependencyTest() { + String version = "plugin:0.0.2"; + PluginWrapper.Dependency dependency = new PluginWrapper.Dependency(version); + assertEquals("plugin", dependency.shortName); + assertEquals("0.0.2", dependency.version); + assertEquals(false, dependency.optional); + } + + @Test + public void optionalDependencyTest() { + String version = "plugin:0.0.2;resolution:=optional"; + PluginWrapper.Dependency dependency = new PluginWrapper.Dependency(version); + assertEquals("plugin", dependency.shortName); + assertEquals("0.0.2", dependency.version); + assertEquals(true, dependency.optional); + } +} diff --git a/test/src/test/resources/plugins/dependee-0.0.2.hpi b/test/src/test/resources/plugins/dependee-0.0.2.hpi new file mode 100644 index 0000000000000000000000000000000000000000..79525b08464fdfe9ef41c5e7f18b4ab3afb78705 GIT binary patch literal 4671 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 literal 0 HcmV?d00001 From 646969b690797bb289a558104109ec1fc92b38cf Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 11:09:04 +0200 Subject: [PATCH 2/9] [JENKINS-21486] Refactoring --- core/src/main/java/hudson/PluginWrapper.java | 33 ++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 31feb91c66c2..b81dbd92a5e5 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -28,7 +28,6 @@ import hudson.PluginManager.PluginInstanceStore; import hudson.model.Api; import hudson.model.ModelObject; -import jenkins.MissingDependencyException; import jenkins.YesNoMaybe; import jenkins.model.Jenkins; import hudson.model.UpdateCenter; @@ -550,26 +549,24 @@ public boolean hasLicensesXml() { if (requiredCoreVersion == null) { LOGGER.warning(shortName + " doesn't declare required core version."); } else { - if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { - throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); - } + checkRequiredCoreVersion(requiredCoreVersion); } } - List missingDependencies = new ArrayList(); - List obsoleteDependencies = new ArrayList(); - List disabledDependencies = new ArrayList(); + List missingDependencies = new ArrayList<>(); + List obsoleteDependencies = new ArrayList<>(); + List disabledDependencies = new ArrayList<>(); // make sure dependencies exist for (Dependency d : dependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { - missingDependencies.add(d.toString()); + missingDependencies.add(d); } else { if (dependency.isActive()) { - if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { - obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + if (isDependencyObsolete(d, dependency)) { + obsoleteDependencies.add(d); } } else { - disabledDependencies.add(d.toString()); + disabledDependencies.add(d); } } @@ -578,8 +575,8 @@ public boolean hasLicensesXml() { for (Dependency d : optionalDependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency != null && dependency.isActive()) { - if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) { - obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")"); + if (isDependencyObsolete(d, dependency)) { + obsoleteDependencies.add(d); } else { dependencies.add(d); } @@ -613,6 +610,16 @@ public boolean hasLicensesXml() { } } + private void checkRequiredCoreVersion(String requiredCoreVersion) throws IOException { + if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { + throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); + } + } + + private boolean isDependencyObsolete(Dependency d, PluginWrapper dependency) { + return ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version)); + } + /** * If the plugin has {@link #getUpdateInfo() an update}, * returns the {@link hudson.model.UpdateSite.Plugin} object. From aeb6ad951ee0305e390752eff4a36609b0dcee29 Mon Sep 17 00:00:00 2001 From: Felix Belzunce Arcos Date: Wed, 6 Apr 2016 21:39:01 +0200 Subject: [PATCH 3/9] [JENKINS-21485] AdministrativeMonitor for PluginWrapper --- core/src/main/java/hudson/PluginWrapper.java | 40 +++++++++++++++++++ .../main/resources/hudson/Messages.properties | 4 ++ .../message.jelly | 15 +++++++ 3 files changed, 59 insertions(+) create mode 100644 core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index b81dbd92a5e5..dfbc1b1806d1 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableSet; import hudson.PluginManager.PluginInstanceStore; +import hudson.model.AdministrativeMonitor; import hudson.model.Api; import hudson.model.ModelObject; import jenkins.YesNoMaybe; @@ -53,6 +54,8 @@ import org.apache.commons.logging.LogFactory; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; @@ -560,13 +563,16 @@ public boolean hasLicensesXml() { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { missingDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_MissingDependency(getLongName(), d.shortName)); } else { if (dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { obsoleteDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); } } else { disabledDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_DisabledDependency(getLongName(), dependency.getLongName())); } } @@ -577,6 +583,7 @@ public boolean hasLicensesXml() { if (dependency != null && dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { obsoleteDependencies.add(d); + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); } else { dependencies.add(d); } @@ -612,6 +619,7 @@ public boolean hasLicensesXml() { private void checkRequiredCoreVersion(String requiredCoreVersion) throws IOException { if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { + NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_OutdatedCoreVersion(getLongName(), requiredCoreVersion)); throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); } } @@ -725,6 +733,38 @@ public boolean isPinningForcingOldVersion() { return false; } + @Extension + public final static PluginWrapperAdministrativeMonitor NOTICE = new PluginWrapperAdministrativeMonitor(); + + /** + * Administrative Monitor for failed plugins + */ + public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor { + public final List pluginError = new ArrayList<>(); + + void addErrorMessage(String error) { + pluginError.add(error); + } + + public boolean isActivated() { + return !pluginError.isEmpty(); + } + + /** + * Depending on whether the user said "dismiss" or "correct", send him to the right place. + */ + public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException { + if(req.hasParameter("correct")) { + rsp.sendRedirect(req.getContextPath()+"/pluginManager"); + + } + } + + public static PluginWrapperAdministrativeMonitor get() { + return AdministrativeMonitor.all().get(PluginWrapperAdministrativeMonitor.class); + } + } + // // // Action methods diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 038ecc0cd630..8bdde0139c27 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -73,4 +73,8 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details +PluginWrapper.admonitor.OutdatedCoreVersion=Plugin {0} requires Jenkins {1} or later +PluginWrapper.admonitor.MissingDependency=Plugin {0} requires the missing plugin {1} +PluginWrapper.admonitor.DisabledDependency=Plugin {0} depends on the disabled {1} +PluginWrapper.admonitor.ObsoleteDependency=Plugin {0} requires {1} {2} or later TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly new file mode 100644 index 000000000000..91be4632b206 --- /dev/null +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -0,0 +1,15 @@ + + +
+
+
+ +
+
+
    + +
  • ${pluginError}
  • +
    +
+
+
\ No newline at end of file From b8f26b34a29ab3e8d80e8c7be4df2232cee0169e Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 15:50:37 +0200 Subject: [PATCH 4/9] [JENKINS-21486] Simplify dependency errors model and serve consistent messages between console and administrative monitor --- core/src/main/java/hudson/PluginWrapper.java | 122 +++++++++--------- .../main/resources/hudson/Messages.properties | 9 +- .../message.jelly | 13 +- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index dfbc1b1806d1..45b28f761455 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -34,37 +34,41 @@ import hudson.model.UpdateCenter; import hudson.model.UpdateSite; import hudson.util.VersionNumber; +import org.jvnet.localizer.ResourceBundleHolder; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.HttpResponses; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; +import org.kohsuke.stapler.interceptor.RequirePOST; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.LogFactory; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.Closeable; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.jar.JarFile; import java.util.jar.Manifest; +import java.util.logging.Level; import java.util.logging.Logger; + import static java.util.logging.Level.WARNING; import static org.apache.commons.io.FilenameUtils.getBaseName; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.LogFactory; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.HttpResponses; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.kohsuke.stapler.export.Exported; -import org.kohsuke.stapler.export.ExportedBean; -import org.kohsuke.stapler.interceptor.RequirePOST; - -import java.util.Enumeration; -import java.util.jar.JarFile; -import java.util.logging.Level; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; /** * Represents a Jenkins plug-in and associated control information @@ -150,6 +154,12 @@ public class PluginWrapper implements Comparable, ModelObject { private final List dependencies; private final List optionalDependencies; + public List getDependencyErrors() { + return Collections.unmodifiableList(dependencyErrors); + } + + private final transient List dependencyErrors = new ArrayList<>(); + /** * Is this plugin bundled in jenkins.war? */ @@ -552,27 +562,28 @@ public boolean hasLicensesXml() { if (requiredCoreVersion == null) { LOGGER.warning(shortName + " doesn't declare required core version."); } else { - checkRequiredCoreVersion(requiredCoreVersion); + VersionNumber actualVersion = Jenkins.getVersion(); + if (actualVersion.isOlderThan(new VersionNumber(requiredCoreVersion))) { + dependencyErrors.add(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion)); + } } } - List missingDependencies = new ArrayList<>(); - List obsoleteDependencies = new ArrayList<>(); - List disabledDependencies = new ArrayList<>(); // make sure dependencies exist for (Dependency d : dependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { - missingDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_MissingDependency(getLongName(), d.shortName)); + dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); } else { if (dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { - obsoleteDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); + dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version)); } } else { - disabledDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_DisabledDependency(getLongName(), dependency.getLongName())); + if (isDependencyObsolete(d, dependency)) { + dependencyErrors.add(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version)); + } else { + dependencyErrors.add(Messages.PluginWrapper_disabled(dependency.getLongName())); + } } } @@ -582,45 +593,24 @@ public boolean hasLicensesXml() { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency != null && dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { - obsoleteDependencies.add(d); - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_ObsoleteDependency(getLongName(), dependency.getLongName(), d.version)); + dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version)); } else { dependencies.add(d); } } } - StringBuilder messageBuilder = new StringBuilder(); - if (!missingDependencies.isEmpty()) { - boolean plural = missingDependencies.size() > 1; - messageBuilder.append(plural ? "Dependencies " : "Dependency ") - .append(Util.join(missingDependencies, ", ")) - .append(" ").append(plural ? "don't" : "doesn't") - .append(" exist. "); - } - if (!disabledDependencies.isEmpty()) { - boolean plural = disabledDependencies.size() > 1; - messageBuilder.append(plural ? "Dependencies " : "Dependency ") - .append(Util.join(missingDependencies, ", ")) - .append(" ").append(plural ? "are" : "is") - .append(" disabled. "); - } - if (!obsoleteDependencies.isEmpty()) { - boolean plural = obsoleteDependencies.size() > 1; - messageBuilder.append(plural ? "Dependencies " : "Dependency ") - .append(Util.join(obsoleteDependencies, ", ")) - .append(" ").append(plural ? "are" : "is") - .append(" older than required."); - } - String message = messageBuilder.toString(); - if (!message.isEmpty()) { - throw new IOException(message); - } - } - - private void checkRequiredCoreVersion(String requiredCoreVersion) throws IOException { - if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) { - NOTICE.addErrorMessage(Messages.PluginWrapper_admonitor_OutdatedCoreVersion(getLongName(), requiredCoreVersion)); - throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ")."); + if (!dependencyErrors.isEmpty()) { + NOTICE.addPlugin(this); + StringBuilder messageBuilder = new StringBuilder(); + messageBuilder.append("Failed to load ").append(getLongName()).append(System.lineSeparator()); + for (Iterator iterator = dependencyErrors.iterator(); iterator.hasNext(); ) { + String dependencyError = iterator.next(); + messageBuilder.append(" - ").append(dependencyError); + if (iterator.hasNext()) { + messageBuilder.append(System.lineSeparator()); + } + } + throw new IOException(messageBuilder.toString()); } } @@ -740,14 +730,18 @@ public boolean isPinningForcingOldVersion() { * Administrative Monitor for failed plugins */ public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor { - public final List pluginError = new ArrayList<>(); + private final Set plugins = new HashSet<>(); - void addErrorMessage(String error) { - pluginError.add(error); + void addPlugin(PluginWrapper plugin) { + plugins.add(plugin); } public boolean isActivated() { - return !pluginError.isEmpty(); + return !plugins.isEmpty(); + } + + public Collection getPlugins() { + return plugins; } /** diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 8bdde0139c27..afbd50ab0a8e 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -73,8 +73,9 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details -PluginWrapper.admonitor.OutdatedCoreVersion=Plugin {0} requires Jenkins {1} or later -PluginWrapper.admonitor.MissingDependency=Plugin {0} requires the missing plugin {1} -PluginWrapper.admonitor.DisabledDependency=Plugin {0} depends on the disabled {1} -PluginWrapper.admonitor.ObsoleteDependency=Plugin {0} requires {1} {2} or later +PluginWrapper.missing=Plugin "{0}" ({1}) is missing. To fix, install version {1} or later. +PluginWrapper.disabledAndObsolete=Plugin "{0}" ({1}) is disabled and older than required. To fix, install version {2} or later and enable it. +PluginWrapper.disabled=Plugin "{0}" is disabled. To fix, enable it. +PluginWrapper.obsolete=Plugin "{0}" ({1}) is older than required. To fix, install version {2} or later. +PluginWrapper.obsoleteCore=You must update Jenkins from {0} to version {1} or later to run this plugin. TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly index 91be4632b206..d5855735cf57 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -6,10 +6,17 @@ + There are dependency errors loading some plugins:
    - -
  • ${pluginError}
  • + +
  • ${plugin.longName} +
      + +
    • ${d}
    • +
      +
    +
- \ No newline at end of file + From 99fa5f7a49ae06063c5306de877e1d5af3a56464 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 19:07:44 +0200 Subject: [PATCH 5/9] [JENKINS-21486] Fix :ant from daniel and separate failed plugin from missing --- core/src/main/java/hudson/PluginWrapper.java | 22 ++++++++++++++----- .../main/resources/hudson/Messages.properties | 1 + 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 45b28f761455..7b1e938daa22 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -58,9 +58,11 @@ import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -247,7 +249,7 @@ public Dependency(String s) { @Override public String toString() { - return shortName + " (" + version + ") " + (optional ? "optional" : ""); + return shortName + " (" + version + ")" + (optional ? " optional" : ""); } } @@ -572,7 +574,13 @@ public boolean hasLicensesXml() { for (Dependency d : dependencies) { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency == null) { - dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); + PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName); + if (failedDependency != null) { + dependencyErrors.add(Messages.PluginWrapper_failed_to_load(failedDependency.getLongName(), d.version)); + break; + } else { + dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); + } } else { if (dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { @@ -730,10 +738,10 @@ public boolean isPinningForcingOldVersion() { * Administrative Monitor for failed plugins */ public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor { - private final Set plugins = new HashSet<>(); + private final Map plugins = new HashMap<>(); void addPlugin(PluginWrapper plugin) { - plugins.add(plugin); + plugins.put(plugin.shortName, plugin); } public boolean isActivated() { @@ -741,7 +749,11 @@ public boolean isActivated() { } public Collection getPlugins() { - return plugins; + return plugins.values(); + } + + public PluginWrapper getPlugin(String shortName) { + return plugins.get(shortName); } /** diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index afbd50ab0a8e..448482389253 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -74,6 +74,7 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details PluginWrapper.missing=Plugin "{0}" ({1}) is missing. To fix, install version {1} or later. +PluginWrapper.failed_to_load=Plugin "{0}" ({1}) failed to load. Fix this plugin first. PluginWrapper.disabledAndObsolete=Plugin "{0}" ({1}) is disabled and older than required. To fix, install version {2} or later and enable it. PluginWrapper.disabled=Plugin "{0}" is disabled. To fix, enable it. PluginWrapper.obsolete=Plugin "{0}" ({1}) is older than required. To fix, install version {2} or later. From 6bf02d514d80e806f108290fbea06ac6f6064798 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 19:19:03 +0200 Subject: [PATCH 6/9] [JENKINS-21486] Fix up messages and show plugin version --- core/src/main/java/hudson/PluginWrapper.java | 4 ++-- core/src/main/resources/hudson/Messages.properties | 13 +++++++------ .../message.jelly | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 7b1e938daa22..b97e977ede35 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -576,7 +576,7 @@ public boolean hasLicensesXml() { if (dependency == null) { PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName); if (failedDependency != null) { - dependencyErrors.add(Messages.PluginWrapper_failed_to_load(failedDependency.getLongName(), d.version)); + dependencyErrors.add(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), d.version)); break; } else { dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); @@ -610,7 +610,7 @@ public boolean hasLicensesXml() { if (!dependencyErrors.isEmpty()) { NOTICE.addPlugin(this); StringBuilder messageBuilder = new StringBuilder(); - messageBuilder.append("Failed to load ").append(getLongName()).append(System.lineSeparator()); + messageBuilder.append(Messages.PluginWrapper_failed_to_load_plugin(getLongName(), getVersion())).append(System.lineSeparator()); for (Iterator iterator = dependencyErrors.iterator(); iterator.hasNext(); ) { String dependencyError = iterator.next(); messageBuilder.append(" - ").append(dependencyError); diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties index 448482389253..ec7f6002b40a 100644 --- a/core/src/main/resources/hudson/Messages.properties +++ b/core/src/main/resources/hudson/Messages.properties @@ -73,10 +73,11 @@ ProxyConfiguration.Success=Success Functions.NoExceptionDetails=No Exception details -PluginWrapper.missing=Plugin "{0}" ({1}) is missing. To fix, install version {1} or later. -PluginWrapper.failed_to_load=Plugin "{0}" ({1}) failed to load. Fix this plugin first. -PluginWrapper.disabledAndObsolete=Plugin "{0}" ({1}) is disabled and older than required. To fix, install version {2} or later and enable it. -PluginWrapper.disabled=Plugin "{0}" is disabled. To fix, enable it. -PluginWrapper.obsolete=Plugin "{0}" ({1}) is older than required. To fix, install version {2} or later. -PluginWrapper.obsoleteCore=You must update Jenkins from {0} to version {1} or later to run this plugin. +PluginWrapper.missing={0} v{1} is missing. To fix, install v{1} or later. +PluginWrapper.failed_to_load_plugin={0} v{1} failed to load. +PluginWrapper.failed_to_load_dependency={0} v{1} failed to load. Fix this plugin first. +PluginWrapper.disabledAndObsolete={0} v{1} is disabled and older than required. To fix, install v{2} or later and enable it. +PluginWrapper.disabled={0} is disabled. To fix, enable it. +PluginWrapper.obsolete={0} v{1} is older than required. To fix, install v{2} or later. +PluginWrapper.obsoleteCore=You must update Jenkins from v{0} to v{1} or later to run this plugin. TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly index d5855735cf57..54fe07266086 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -9,7 +9,7 @@ There are dependency errors loading some plugins:
    -
  • ${plugin.longName} +
  • ${plugin.longName} v${plugin.version}
    • ${d}
    • From d8f3bfc8ed0f88e5bff757c0eec921d6c135d5b4 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 21:30:31 +0200 Subject: [PATCH 7/9] [JENKINS-21486] Cycle monitor should display plugin long name and version --- core/src/main/java/hudson/PluginManager.java | 8 ++++---- .../PluginCycleDependenciesMonitor/message.jelly | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 941dc0cdc26f..b25bbb63ae06 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1868,14 +1868,14 @@ public static final class PluginCycleDependenciesMonitor extends AdministrativeM private transient volatile boolean isActive = false; - private transient volatile List pluginsWithCycle; + private transient volatile List pluginsWithCycle; public boolean isActivated() { if(pluginsWithCycle == null){ - pluginsWithCycle = new ArrayList(); + pluginsWithCycle = new ArrayList<>(); for (PluginWrapper p : Jenkins.getInstance().getPluginManager().getPlugins()) { if(p.hasCycleDependency()){ - pluginsWithCycle.add(p.getShortName()); + pluginsWithCycle.add(p); isActive = true; } } @@ -1883,7 +1883,7 @@ public boolean isActivated() { return isActive; } - public List getPluginsWithCycle() { + public List getPluginsWithCycle() { return pluginsWithCycle; } } diff --git a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly index 3a7110fdca57..1c80b2d66a3c 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly @@ -28,7 +28,7 @@ THE SOFTWARE. ${%PluginCycles}
        -
      • +
      From 8e3c9dadc1b09406ab6e77af33518ee3168470d4 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Thu, 28 Jul 2016 21:35:54 +0200 Subject: [PATCH 8/9] [JENKINS-21486] Mention both long name, short name and version in console --- core/src/main/java/hudson/PluginManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index b25bbb63ae06..910d560a8bfd 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -505,7 +505,7 @@ public void run(Reactor session) throws Exception { // schedule execution of loading plugins for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) { - g.followedBy().notFatal().attains(PLUGINS_PREPARED).add("Loading plugin " + p.getShortName(), new Executable() { + g.followedBy().notFatal().attains(PLUGINS_PREPARED).add(String.format("Loading plugin %s v%s (%s)", p.getLongName(), p.getVersion(), p.getShortName()), new Executable() { public void run(Reactor session) throws Exception { try { p.resolvePluginDependencies(); From dff8e806b9466652a351dd7fb7f742b6698140db Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Fri, 29 Jul 2016 07:04:35 +0200 Subject: [PATCH 9/9] [JENKINS-21486] Only consider plugins detached before the current version --- test/src/test/java/hudson/model/UsageStatisticsTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/model/UsageStatisticsTest.java b/test/src/test/java/hudson/model/UsageStatisticsTest.java index 63b6a14279de..a02ee18d7844 100644 --- a/test/src/test/java/hudson/model/UsageStatisticsTest.java +++ b/test/src/test/java/hudson/model/UsageStatisticsTest.java @@ -105,7 +105,9 @@ public void roundtrip() throws Exception { List plugins = sortPlugins((List) o.get("plugins")); Set detached = new TreeSet<>(); for (ClassicPluginStrategy.DetachedPlugin p: ClassicPluginStrategy.getDetachedPlugins()) { - detached.add(p.getShortName()); + if (p.getSplitWhen().isOlderThan(Jenkins.getVersion())) { + detached.add(p.getShortName()); + } } Set keys = new TreeSet<>(); keys.add("name");