Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use detached plugins as a cache for the Update Center #9476

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 156 additions & 1 deletion core/src/main/java/hudson/model/UpdateCenter.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@
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;
Expand All @@ -86,6 +90,7 @@
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;
Expand Down Expand Up @@ -1809,6 +1814,83 @@
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);

Check warning on line 1889 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 1826-1889 are not covered by tests
}
}
}

/**
* Base class for a job that downloads a file from the Jenkins project.
*/
Expand Down Expand Up @@ -2245,7 +2327,24 @@
return;
}
try {
super._run();
File cached = getCached(this);
if (cached != null) {

Check warning on line 2331 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 2331 is only partially covered, one branch is missing
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 {

Check warning on line 2345 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 2332-2345 are not covered by tests
super._run();
}

// if this is a bundled plugin, make sure it won't get overwritten
PluginWrapper pw = plugin.getInstalled();
Expand Down Expand Up @@ -2278,6 +2377,62 @@
}
}

/**
* 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));

This comment was marked as off-topic.

This comment was marked as off-topic.

} catch (MalformedURLException e) {
return null;

Check warning on line 2397 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 2396-2397 are not covered by tests
}

if (src == null || !"file".equals(src.getProtocol())) {

Check warning on line 2400 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 2400 is only partially covered, 3 branches are missing
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;

Check warning on line 2433 in core/src/main/java/hudson/model/UpdateCenter.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 2405-2433 are not covered by tests
}

/**
* Indicates there is another installation job for this plugin
* @since 2.1
Expand Down
Loading