mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-13 20:12:14 +00:00
Use detached plugins as a cache for the Update Center (#9476)
This commit is contained in:
1 parent
6f23dea734
commit
ca241b4bf6
1 file changed
+156
-1
@@ -58,14 +58,18 @@ import hudson.util.NamingThreadFactory;
|
||||
import hudson.util.PersistedList;
|
||||
import hudson.util.VersionNumber;
|
||||
import hudson.util.XStream2;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.HttpRetryException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -86,6 +90,7 @@ import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
@@ -1809,6 +1814,83 @@ public class UpdateCenter extends AbstractModelObject implements Loadable, Savea
|
||||
String getComputedSHA512();
|
||||
}
|
||||
|
||||
@SuppressFBWarnings(value = "WEAK_MESSAGE_DIGEST_SHA1", justification = "SHA-1 is only used as a fallback if SHA-256/SHA-512 are not available")
|
||||
private static class FileWithComputedChecksums implements WithComputedChecksums {
|
||||
|
||||
private final File file;
|
||||
|
||||
private String computedSHA1;
|
||||
private String computedSHA256;
|
||||
private String computedSHA512;
|
||||
|
||||
FileWithComputedChecksums(File file) {
|
||||
this.file = Objects.requireNonNull(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String getComputedSHA1() {
|
||||
if (computedSHA1 != null) {
|
||||
return computedSHA1;
|
||||
}
|
||||
|
||||
MessageDigest messageDigest;
|
||||
try {
|
||||
messageDigest = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
computedSHA1 = computeDigest(messageDigest);
|
||||
return computedSHA1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String getComputedSHA256() {
|
||||
if (computedSHA256 != null) {
|
||||
return computedSHA256;
|
||||
}
|
||||
|
||||
MessageDigest messageDigest;
|
||||
try {
|
||||
messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
computedSHA256 = computeDigest(messageDigest);
|
||||
return computedSHA256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String getComputedSHA512() {
|
||||
if (computedSHA512 != null) {
|
||||
return computedSHA512;
|
||||
}
|
||||
|
||||
MessageDigest messageDigest;
|
||||
try {
|
||||
messageDigest = MessageDigest.getInstance("SHA-512");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
computedSHA512 = computeDigest(messageDigest);
|
||||
return computedSHA512;
|
||||
}
|
||||
|
||||
private String computeDigest(MessageDigest digest) {
|
||||
try (InputStream is = new FileInputStream(file);
|
||||
BufferedInputStream bis = new BufferedInputStream(is)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int read = bis.read(buffer, 0, buffer.length);
|
||||
while (read > -1) {
|
||||
digest.update(buffer, 0, read);
|
||||
read = bis.read(buffer, 0, buffer.length);
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(digest.digest());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for a job that downloads a file from the Jenkins project.
|
||||
*/
|
||||
@@ -2245,7 +2327,24 @@ public class UpdateCenter extends AbstractModelObject implements Loadable, Savea
|
||||
return;
|
||||
}
|
||||
try {
|
||||
super._run();
|
||||
File cached = getCached(this);
|
||||
if (cached != null) {
|
||||
File dst = getDestination();
|
||||
|
||||
// A bit naive, but following the corresponding logic in UpdateCenterConfiguration#download...
|
||||
File tmp = new File(dst.getPath() + ".tmp");
|
||||
Files.copy(cached.toPath(), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
config.postValidate(this, tmp);
|
||||
|
||||
/*
|
||||
* Will unfortunately validate the checksum a second time, but this should still be faster than
|
||||
* network I/O and at least allows us to reuse code...
|
||||
*/
|
||||
config.install(this, tmp, dst);
|
||||
} else {
|
||||
super._run();
|
||||
}
|
||||
|
||||
// if this is a bundled plugin, make sure it won't get overwritten
|
||||
PluginWrapper pw = plugin.getInstalled();
|
||||
@@ -2278,6 +2377,62 @@ public class UpdateCenter extends AbstractModelObject implements Loadable, Savea
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we happen to have the file already in the {@coode WEB-INF/detached-plugins} directory and it happens to
|
||||
* match the checksum we were expecting, then save ourselves a trip to the download site. This method is
|
||||
* best-effort, and if anything goes wrong we simply fall back to the standard download path.
|
||||
*
|
||||
* @return The cached file, or null for a cache miss
|
||||
*/
|
||||
@CheckForNull
|
||||
private File getCached(DownloadJob job) {
|
||||
URL src;
|
||||
try {
|
||||
/*
|
||||
* Could make PluginManager#getDetachedLocation public and consume it here, but this method is
|
||||
* best-effort anyway.
|
||||
*/
|
||||
src = Jenkins.get().servletContext.getResource(String.format("/WEB-INF/detached-plugins/%s.hpi", plugin.name));
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (src == null || !"file".equals(src.getProtocol())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
config.preValidate(this, src);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File cached;
|
||||
try {
|
||||
cached = new File(src.toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!cached.isFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WithComputedChecksums withComputedChecksums = new FileWithComputedChecksums(cached);
|
||||
try {
|
||||
verifyChecksums(withComputedChecksums, plugin, cached);
|
||||
} catch (IOException | UncheckedIOException | UnsupportedOperationException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Allow us to reuse UpdateCenter.InstallationJob#replace.
|
||||
job.computedSHA1 = withComputedChecksums.getComputedSHA1();
|
||||
job.computedSHA256 = withComputedChecksums.getComputedSHA256();
|
||||
job.computedSHA512 = withComputedChecksums.getComputedSHA512();
|
||||
|
||||
return cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates there is another installation job for this plugin
|
||||
* @since 2.1
|
||||
|
||||
Reference in new issue
Block a user