Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-24046]
Warn users if the pinning is preventing Jenkins from overwriting older versions with bundled versions.
  • Loading branch information
kohsuke committed Oct 17, 2014
1 parent 65e2a75 commit 19f9b63
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 17 deletions.
6 changes: 4 additions & 2 deletions core/src/main/java/hudson/ClassicPluginStrategy.java
Expand Up @@ -72,6 +72,8 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.apache.commons.io.FilenameUtils.getBaseName;

public class ClassicPluginStrategy implements PluginStrategy {
private final ClassLoaderReflectionToolkit clt = new ClassLoaderReflectionToolkit();

Expand Down Expand Up @@ -125,11 +127,11 @@ public PluginWrapper createPluginWrapper(File archive) throws IOException {
if (archive.isDirectory()) {// already expanded
expandDir = archive;
} else {
expandDir = new File(archive.getParentFile(), PluginWrapper.getBaseName(archive));
expandDir = new File(archive.getParentFile(), getBaseName(archive.getName()));
explode(archive, expandDir);
}

File manifestFile = new File(expandDir, "META-INF/MANIFEST.MF");
File manifestFile = new File(expandDir, PluginWrapper.MANIFEST_FILENAME);
if (!manifestFile.exists()) {
throw new IOException(
"Plugin installation failed. No manifest at "
Expand Down
45 changes: 45 additions & 0 deletions core/src/main/java/hudson/PluginManager.java
Expand Up @@ -41,6 +41,7 @@
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
import hudson.util.IOException2;
import hudson.util.IOUtils;
import hudson.util.PersistedList;
import hudson.util.Service;
import hudson.util.VersionNumber;
Expand Down Expand Up @@ -75,17 +76,20 @@
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;

import javax.annotation.CheckForNull;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -103,6 +107,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.Attributes;
Expand Down Expand Up @@ -174,6 +179,11 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
*/
private final PluginStrategy strategy;

/**
* Manifest of the plugin binaries that are bundled with core.
*/
private final Map<String,Manifest> bundledPluginManifests = new HashMap<String, Manifest>();

public PluginManager(ServletContext context, File rootDir) {
this.context = context;

Expand Down Expand Up @@ -405,6 +415,13 @@ private boolean containsHpiJpi(Collection<String> bundledPlugins, String name) {
|| bundledPlugins.contains(name.replaceAll("\\.jpi",".hpi"));
}

/**
* Returns the manifest of a bundled but not-extracted plugin.
*/
public @CheckForNull Manifest getBundledPluginManifest(String shortName) {
return bundledPluginManifests.get(shortName);
}

/**
* TODO: revisit where/how to expose this. This is an experiment.
*/
Expand Down Expand Up @@ -490,6 +507,34 @@ protected void copyBundledPlugin(URL src, String fileName) throws IOException {
// - to make sure the value is not changed after each restart, so we can avoid
// unpacking the plugin itself in ClassicPluginStrategy.explode
}
if (pinFile.exists())
parsePinnedBundledPluginManifest(src);
}

/**
* When a pin file prevented a bundled plugin from getting extracted, check if the one we currently have
* is older than we bundled.
*/
private void parsePinnedBundledPluginManifest(URL bundledJpi) {
try {
URLClassLoader cl = new URLClassLoader(new URL[]{bundledJpi});
InputStream in=null;
try {
URL res = cl.findResource(PluginWrapper.MANIFEST_FILENAME);
if (res!=null) {
in = res.openStream();
Manifest manifest = new Manifest(in);
String shortName = PluginWrapper.computeShortName(manifest, FilenameUtils.getName(bundledJpi.getPath()));
bundledPluginManifests.put(shortName, manifest);
}
} finally {
IOUtils.closeQuietly(in);
if (cl instanceof Closeable)
((Closeable)cl).close();
}
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to parse manifest of "+bundledJpi, e);
}
}

/**
Expand Down
44 changes: 29 additions & 15 deletions core/src/main/java/hudson/PluginWrapper.java
Expand Up @@ -44,7 +44,9 @@
import java.util.jar.Manifest;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.apache.commons.io.FilenameUtils.getBaseName;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.LogFactory;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
Expand Down Expand Up @@ -199,7 +201,7 @@ public PluginWrapper(PluginManager parent, File archive, Manifest manifest, URL
List<Dependency> dependencies, List<Dependency> optionalDependencies) {
this.parent = parent;
this.manifest = manifest;
this.shortName = computeShortName(manifest, archive);
this.shortName = computeShortName(manifest, archive.getName());
this.baseResourceURL = baseResourceURL;
this.classLoader = classLoader;
this.disableFile = disableFile;
Expand Down Expand Up @@ -235,7 +237,7 @@ public URL getIndexPage() {
return idx != null && idx.toString().contains(shortName) ? idx : null;
}

private String computeShortName(Manifest manifest, File archive) {
static String computeShortName(Manifest manifest, String fileName) {
// use the name captured in the manifest, as often plugins
// depend on the specific short name in its URLs.
String n = manifest.getMainAttributes().getValue("Short-Name");
Expand All @@ -247,19 +249,7 @@ private String computeShortName(Manifest manifest, File archive) {

// otherwise infer from the file name, since older plugins don't have
// this entry.
return getBaseName(archive);
}


/**
* Gets the "abc" portion from "abc.ext".
*/
static String getBaseName(File archive) {
String n = archive.getName();
int idx = n.lastIndexOf('.');
if(idx>=0)
n = n.substring(0,idx);
return n;
return getBaseName(fileName);
}

@Exported
Expand Down Expand Up @@ -338,6 +328,10 @@ public YesNoMaybe supportsDynamicLoad() {
*/
@Exported
public String getVersion() {
return getVersionOf(manifest);
}

private String getVersionOf(Manifest manifest) {
String v = manifest.getMainAttributes().getValue("Plugin-Version");
if(v!=null) return v;

Expand Down Expand Up @@ -580,6 +574,22 @@ public String getBackupVersion() {
return null;
}
}

/**
* Checks if this plugin is pinned and that's forcing us to use an older version than the bundled one.
*/
public boolean isPinningForcingOldVersion() {
if (!isPinned()) return false;

Manifest bundled = Jenkins.getInstance().pluginManager.getBundledPluginManifest(getShortName());
if (bundled==null) return false;

VersionNumber you = new VersionNumber(getVersionOf(bundled));
VersionNumber me = getVersionNumber();

return me.isOlderThan(you);
}

//
//
// Action methods
Expand Down Expand Up @@ -623,4 +633,8 @@ public HttpResponse doDoUninstall() throws IOException {

private static final Logger LOGGER = Logger.getLogger(PluginWrapper.class.getName());

/**
* Name of the plugin manifest file (to help find where we parse them.)
*/
public static final String MANIFEST_FILENAME = "META-INF/MANIFEST.MF";
}
@@ -0,0 +1,44 @@
package jenkins.diagnostics;

import com.google.common.collect.ImmutableList;
import hudson.Extension;
import hudson.PluginWrapper;
import hudson.model.AdministrativeMonitor;
import jenkins.model.Jenkins;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

/**
* Fires off when we have any pinned plugins that's blocking upgrade from the bundled version.
*
* @author Kohsuke Kawaguchi
*/
@Extension
public class PinningIsBlockingBundledPluginMonitor extends AdministrativeMonitor {
@Inject
Jenkins jenkins;

private List<PluginWrapper> offenders;

@Override
public boolean isActivated() {
return !getOffenders().isEmpty();
}

private void compute() {
List<PluginWrapper> offenders = new ArrayList<PluginWrapper>();
for (PluginWrapper p : jenkins.pluginManager.getPlugins()) {
if (p.isPinningForcingOldVersion())
offenders.add(p);
}
this.offenders = ImmutableList.copyOf(offenders);
}

public List<PluginWrapper> getOffenders() {
if (offenders==null)
compute();
return offenders;
}
}
@@ -0,0 +1,13 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<div class="warning">
<p>
${%blurb}
</p>
<ul>
<j:forEach var="p" items="${it.offenders}">
<li>${p.longName}</li>
</j:forEach>
</ul>
</div>
</j:jelly>
@@ -0,0 +1,3 @@
blurb=The following plugins are currently pinned, and \
this prevents Jenkins from using a newer version that''s bundled in the core. \
We recommend you to <a href="pluginManager/">unpin these plugins</a>:

0 comments on commit 19f9b63

Please sign in to comment.