Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #1847 from tfennelly/plugin-manager-dependants
[FIXED JENKINS-23150] Plugin Manager - Only allow enable/disable/uninstall when all dependants or dependencies are disabled/enabled
  • Loading branch information
tfennelly committed Oct 6, 2015
2 parents 6af3df9 + 95ca3da commit 3611360
Show file tree
Hide file tree
Showing 8 changed files with 843 additions and 14 deletions.
56 changes: 55 additions & 1 deletion core/src/main/java/hudson/PluginManager.java
Expand Up @@ -119,6 +119,8 @@
import static hudson.init.InitMilestone.*;
import hudson.model.DownloadService;
import hudson.util.FormValidation;

import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand Down Expand Up @@ -520,9 +522,33 @@ protected boolean filter(Method e) {
}
}

// Redo who depends on who.
resolveDependantPlugins();

LOGGER.info("Plugin " + p.getShortName()+":"+p.getVersion() + " dynamically installed");
}

@Restricted(NoExternalUse.class)
public synchronized void resolveDependantPlugins() {
for (PluginWrapper plugin : plugins) {
Set<String> dependants = new HashSet<>();
for (PluginWrapper possibleDependant : plugins) {
// The plugin could have just been deleted. If so, it doesn't
// count as a dependant.
if (possibleDependant.isDeleted()) {
continue;
}
List<Dependency> dependencies = possibleDependant.getDependencies();
for (Dependency dependency : dependencies) {
if (dependency.shortName.equals(plugin.getShortName())) {
dependants.add(possibleDependant.getShortName());
}
}
}
plugin.setDependants(dependants);
}
}

/**
* If the war file has any "/WEB-INF/plugins/[*.jpi | *.hpi]", extract them into the plugin directory.
*
Expand Down Expand Up @@ -779,6 +805,7 @@ public HttpResponse doUpdateSources(StaplerRequest req) throws IOException {
*/
public void doInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
boolean dynamicLoad = req.getParameter("dynamicLoad")!=null;
final List<Future<UpdateCenter.UpdateCenterJob>> deployJobs = new ArrayList<>();

Enumeration<String> en = req.getParameterNames();
while (en.hasMoreElements()) {
Expand Down Expand Up @@ -811,9 +838,36 @@ public void doInstall(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
if (p == null) {
throw new Failure("No such plugin: " + n);
}
p.deploy(dynamicLoad);

deployJobs.add(p.deploy(dynamicLoad));
}
}

// Fire a one-off thread to wait for the plugins to be deployed and then
// refresh the dependant plugins list.
new Thread() {
@Override
public void run() {
INSTALLING: while (true) {
for (Future<UpdateCenter.UpdateCenterJob> deployJob : deployJobs) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
LOGGER.log(SEVERE, "Unexpected error while waiting for some plugins to install. Plugin Manager state may be invalid. Please restart Jenkins ASAP.", e);
}
if (!deployJob.isCancelled() && !deployJob.isDone()) {
// One of the plugins is not installing/canceled, so
// go back to sleep and try again in a while.
continue INSTALLING;
}
}
// All the plugins are installed. It's now safe to refresh.
resolveDependantPlugins();
break;
}
}
}.start();

rsp.sendRedirect("../updateCenter/");
}

Expand Down
61 changes: 60 additions & 1 deletion core/src/main/java/hudson/PluginWrapper.java
Expand Up @@ -24,6 +24,7 @@
*/
package hudson;

import com.google.common.collect.ImmutableSet;
import hudson.PluginManager.PluginInstanceStore;
import hudson.model.Api;
import hudson.model.ModelObject;
Expand All @@ -40,7 +41,10 @@
import java.io.Closeable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.jar.Manifest;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
Expand All @@ -57,6 +61,7 @@
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
Expand Down Expand Up @@ -150,6 +155,54 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
*/
/*package*/ boolean isBundled;

/**
* List of plugins that depend on this plugin.
*/
private Set<String> dependants = Collections.emptySet();

/**
* The core can depend on a plugin if it is bundled. Sometimes it's the only thing that
* depends on the plugin e.g. UI support library bundle plugin.
*/
private static Set<String> CORE_ONLY_DEPENDANT = ImmutableSet.copyOf(Arrays.asList("jenkins-core"));

/**
* Set the list of components that depend on this plugin.
* @param dependants The list of components that depend on this plugin.
*/
public void setDependants(@Nonnull Set<String> dependants) {
this.dependants = dependants;
}

/**
* Get the list of components that depend on this plugin.
* @return The list of components that depend on this plugin.
*/
public @Nonnull Set<String> getDependants() {
if (isBundled && dependants.isEmpty()) {
return CORE_ONLY_DEPENDANT;
} else {
return dependants;
}
}

/**
* Does this plugin have anything that depends on it.
* @return {@code true} if something (Jenkins core, or another plugin) depends on this
* plugin, otherwise {@code false}.
*/
public boolean hasDependants() {
return (isBundled || !dependants.isEmpty());
}

/**
* Does this plugin depend on any other plugins.
* @return {@code true} if this plugin depends on other plugins, otherwise {@code false}.
*/
public boolean hasDependencies() {
return (dependencies != null && !dependencies.isEmpty());
}

@ExportedBean
public static final class Dependency {
@Exported
Expand Down Expand Up @@ -632,8 +685,14 @@ public HttpResponse doUnpin() throws IOException {

@RequirePOST
public HttpResponse doDoUninstall() throws IOException {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
Jenkins jenkins = Jenkins.getActiveInstance();

jenkins.checkPermission(Jenkins.ADMINISTER);
archive.delete();

// Redo who depends on who.
jenkins.getPluginManager().resolveDependantPlugins();

return HttpResponses.redirectViaContextPath("/pluginManager/installed"); // send back to plugin manager
}

Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/jenkins/model/Jenkins.java
Expand Up @@ -864,6 +864,9 @@ protected void doRun() throws Exception {
System.currentTimeMillis()-itemListenerStart,l.getClass().getName()));
}

// All plugins are loaded. Now we can figure out who depends on who.
resolveDependantPlugins();

if (LOG_STARTUP_PERFORMANCE)
LOGGER.info(String.format("Took %dms for complete Jenkins startup",
System.currentTimeMillis()-start));
Expand All @@ -872,6 +875,19 @@ protected void doRun() throws Exception {
}
}

private void resolveDependantPlugins() throws InterruptedException, ReactorException, IOException {
TaskGraphBuilder graphBuilder = new TaskGraphBuilder();

graphBuilder.add("Resolving Dependant Plugins Graph", new Executable() {
@Override
public void run(Reactor reactor) throws Exception {
pluginManager.resolveDependantPlugins();
}
});

executeReactor(null, graphBuilder);
}

/**
* Executes a reactor.
*
Expand Down

0 comments on commit 3611360

Please sign in to comment.