mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-02 22:55:53 +00:00
[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
This commit is contained in:
8 files changed
+191
-10
No files matched your search
@@ -88,6 +88,12 @@ import javax.annotation.Nonnull;
|
||||
*/
|
||||
@ExportedBean
|
||||
public class PluginWrapper implements Comparable<PluginWrapper>, 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 class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return shortName + " (" + version + ")";
|
||||
return shortName + " (" + version + ") " + (optional ? "optional" : "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,6 +400,21 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
|
||||
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 class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
|
||||
* thrown if one or several mandatory dependencies doesn't exists.
|
||||
*/
|
||||
/*package*/ void resolvePluginDependencies() throws IOException {
|
||||
List<Dependency> 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<String> missingDependencies = new ArrayList<String>();
|
||||
List<String> obsoleteDependencies = new ArrayList<String>();
|
||||
List<String> disabledDependencies = new ArrayList<String>();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user