getDevelopers() {
175 | return MaintainersSource.getInstance().getMaintainers(this.latestOffered.artifact);
176 | }
177 |
178 | public String getExcerpt() throws IOException {
179 | return latestOffered.getDescription();
180 | }
181 |
182 | public String getReleaseTimestamp() throws IOException {
183 | return TIMESTAMP_FORMATTER.format(latestOffered.getTimestamp());
184 | }
185 |
186 | public String getPreviousTimestamp() throws IOException {
187 | return previousOffered == null ? null : TIMESTAMP_FORMATTER.format(previousOffered.getTimestamp());
188 | }
189 |
190 | public int getPopularity() throws IOException {
191 | return Popularities.getInstance().getPopularity(artifactId);
192 | }
193 |
194 | public Integer getHealth() {
195 | return HealthScores.getInstance().getHealthScore(artifactId);
196 | }
197 |
198 | public String getLatest() {
199 | final LatestPluginVersions instance = LatestPluginVersions.getInstance();
200 | final VersionNumber latestPublishedVersion = instance.getLatestVersion(artifactId);
201 | if (latestPublishedVersion == null || latestPublishedVersion.equals(latestOffered.getVersion())) {
202 | // only include latest version information if the currently published version isn't the latest
203 | return null;
204 | }
205 | return latestPublishedVersion.toString();
206 | }
207 |
208 | private static final SimpleDateFormat TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.00Z'", Locale.US);
209 |
210 | private static final Logger LOGGER = Logger.getLogger(PluginUpdateCenterEntry.class.getName());
211 | }
212 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/Popularities.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import com.alibaba.fastjson.JSON;
4 |
5 | import java.io.IOException;
6 | import java.net.URI;
7 | import java.net.http.HttpClient;
8 | import java.net.http.HttpRequest;
9 | import java.net.http.HttpResponse;
10 | import java.util.Map;
11 | import java.util.function.Function;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * Plugin popularity is a unit-less integer value. A larger value means a plugin is more popular.
16 | * The data underlying this definition is undefined, whatever makes sense in context can be used.
17 | *
18 | * This implementation just returns the number of installations at the moment, but that may change at any time.
19 | *
20 | * The first iteration of this class returns decimal (float/double) values, but those caused problems for signature
21 | * validation in Jenkins due to the JSON normalization involved.
22 | */
23 | public class Popularities {
24 |
25 | private static final String JSON_URL = "https://raw.githubusercontent.com/jenkins-infra/infra-statistics/gh-pages/plugin-installation-trend/latestNumbers.json";
26 | // or https://stats.jenkins.io/plugin-installation-trend/latestNumbers.json
27 |
28 | private static Popularities instance;
29 |
30 | private final Map popularities;
31 |
32 | private Popularities(Map popularities) {
33 | this.popularities = popularities;
34 | }
35 |
36 | private static void initialize() throws IOException {
37 | HttpRequest request = HttpRequest.newBuilder()
38 | .uri(URI.create(JSON_URL))
39 | .GET()
40 | .build();
41 | try (final HttpClient client = HttpClient.newHttpClient()) {
42 | final HttpResponse httpResp = client.send(request, HttpResponse.BodyHandlers.ofString());
43 | final JsonResponse response = JSON.parseObject(httpResp.body(), JsonResponse.class);
44 |
45 | if (response.plugins == null) {
46 | throw new IllegalArgumentException("Specified popularity URL '" + JSON_URL + "' does not contain a JSON object 'plugins'");
47 | }
48 | Map popularities = response.plugins.keySet().stream()
49 | .collect(Collectors.toMap(Function.identity(), value -> Integer.valueOf(response.plugins.get(value))));
50 | instance = new Popularities(popularities);
51 | } catch(InterruptedException e) {
52 | throw new IOException(e);
53 | }
54 | }
55 |
56 | private static class JsonResponse {
57 | public Map plugins;
58 | }
59 |
60 | public static synchronized Popularities getInstance() throws IOException {
61 | if (instance == null) {
62 | initialize();
63 | }
64 | return instance;
65 | }
66 |
67 | public int getPopularity(String pluginId) {
68 | return this.popularities.getOrDefault(pluginId, 0);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/XmlCache.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import java.io.File;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | public class XmlCache {
8 | public static class CachedValue {
9 | public final String value;
10 | private CachedValue(String value) {
11 | this.value = value;
12 | }
13 | }
14 |
15 | private static Map cache = new HashMap<>();
16 |
17 | public static CachedValue readCache(File file, String xpath) {
18 | return cache.getOrDefault(file + ":" + xpath, null);
19 | }
20 |
21 | public static void writeCache(File file, String xpath, String value) {
22 | cache.put(file + ":" + xpath, new CachedValue(value));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/args4j/Default.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.args4j;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.RetentionPolicy;
5 |
6 | /**
7 | * Provide a way to define default values for {@link org.kohsuke.args4j.Option}s
8 | * that cannot be reset in {@link io.jenkins.update_center.Main#run} when taking
9 | * an arguments file.
10 | */
11 | @Retention(RetentionPolicy.RUNTIME)
12 | public @interface Default {
13 | String value();
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/args4j/LevelOptionHandler.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.args4j;
2 |
3 | import org.kohsuke.args4j.CmdLineException;
4 | import org.kohsuke.args4j.CmdLineParser;
5 | import org.kohsuke.args4j.OptionDef;
6 | import org.kohsuke.args4j.spi.OneArgumentOptionHandler;
7 | import org.kohsuke.args4j.spi.Setter;
8 |
9 | import java.util.logging.Level;
10 |
11 | public class LevelOptionHandler extends OneArgumentOptionHandler {
12 | public LevelOptionHandler(CmdLineParser parser, OptionDef option, Setter super Level> setter) {
13 | super(parser, option, setter);
14 | }
15 |
16 | @Override
17 | public String getDefaultMetaVariable() {
18 | return "LEVEL";
19 | }
20 |
21 | @Override
22 | protected Level parse(String s) throws NumberFormatException, CmdLineException {
23 | return Level.parse(s);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/JsonSignature.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | import java.util.List;
6 |
7 | public class JsonSignature {
8 | private List certificates;
9 |
10 | private String digest;
11 | private String signature;
12 |
13 | private String digest512;
14 | private String signature512;
15 |
16 | public void setCertificates(List certificates) {
17 | this.certificates = certificates;
18 | }
19 |
20 | public void setDigest(String digest) {
21 | this.digest = digest;
22 | }
23 |
24 | public void setSignature(String signature) {
25 | this.signature = signature;
26 | }
27 |
28 | public void setDigest512(String digest512) {
29 | this.digest512 = digest512;
30 | }
31 |
32 | public void setSignature512(String signature512) {
33 | this.signature512 = signature512;
34 | }
35 |
36 | public List getCertificates() {
37 | return certificates;
38 | }
39 |
40 | @JSONField(name = "correct_digest")
41 | public String getDigest() {
42 | return digest;
43 | }
44 |
45 | @JSONField(name = "correct_signature")
46 | public String getSignature() {
47 | return signature;
48 | }
49 |
50 | @JSONField(name = "correct_digest512")
51 | public String getDigest512() {
52 | return digest512;
53 | }
54 |
55 | @JSONField(name = "correct_signature512")
56 | public String getSignature512() {
57 | return signature512;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/PlatformCategory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | import java.util.List;
6 |
7 | public class PlatformCategory {
8 | @JSONField
9 | public String category;
10 |
11 | @JSONField
12 | public List plugins;
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/PlatformPlugin.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | public class PlatformPlugin {
6 |
7 | /**
8 | * The plugin ID.
9 | */
10 | @JSONField
11 | public String name;
12 |
13 | /**
14 | * In which version of Jenkins was this suggestion added?
15 | * Used for upgrade wizard / similar functionality.
16 | */
17 | @JSONField
18 | public String added;
19 |
20 | /**
21 | * Whether this plugin should be installed by default.
22 | */
23 | @JSONField
24 | public boolean suggested;
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/PlatformPluginsRoot.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.annotation.JSONField;
5 | import java.nio.file.Files;
6 | import org.apache.commons.io.IOUtils;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 | import java.io.Reader;
11 | import java.nio.charset.StandardCharsets;
12 | import java.util.Arrays;
13 | import java.util.List;
14 |
15 | public class PlatformPluginsRoot extends WithSignature {
16 |
17 | @JSONField
18 | public List categories;
19 |
20 | public PlatformPluginsRoot(File referenceFile) throws IOException {
21 | try (Reader r = Files.newBufferedReader(referenceFile.toPath(), StandardCharsets.UTF_8)) {
22 | categories = Arrays.asList(JSON.parseObject(IOUtils.toString(r), PlatformCategory[].class));
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/PluginDocumentationUrlsRoot.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import io.jenkins.update_center.MavenRepository;
5 | import io.jenkins.update_center.Plugin;
6 |
7 | import java.io.IOException;
8 | import java.util.Map;
9 | import java.util.TreeMap;
10 |
11 | public class PluginDocumentationUrlsRoot extends WithoutSignature {
12 |
13 | @JSONField(unwrapped = true)
14 | public final Map pluginToEntry = new TreeMap<>();
15 |
16 | public PluginDocumentationUrlsRoot(MavenRepository repo) throws IOException {
17 | for (Plugin plugin : repo.listJenkinsPlugins()) {
18 | pluginToEntry.put(plugin.getArtifactId(), new Entry(plugin.getLatest().getPluginUrl()));
19 | }
20 | }
21 |
22 | public static class Entry {
23 | private final String url;
24 |
25 | private Entry(String url) {
26 | this.url = url;
27 | }
28 |
29 | public String getUrl() {
30 | return url;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/PluginVersions.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import hudson.util.VersionNumber;
5 | import io.jenkins.update_center.HPI;
6 |
7 | import java.io.IOException;
8 | import java.util.Comparator;
9 | import java.util.LinkedHashMap;
10 | import java.util.Map;
11 | import java.util.logging.Logger;
12 | import java.util.stream.Collectors;
13 |
14 | import static java.util.logging.Level.INFO;
15 |
16 | public class PluginVersions {
17 | private static final Logger LOGGER = Logger.getLogger(PluginVersions.class.getName());
18 |
19 | @JSONField(unwrapped = true)
20 | public Map releases = new LinkedHashMap<>();
21 |
22 | PluginVersions(Map artifacts) {
23 | for (VersionNumber versionNumber : artifacts.keySet().stream().sorted().collect(Collectors.toList())) {
24 | try {
25 | if (releases.put(versionNumber.toString(), new PluginVersionsEntry(artifacts.get(versionNumber))) != null) {
26 | throw new IllegalStateException("Duplicate key");
27 | }
28 | } catch (IOException ex) {
29 | LOGGER.log(INFO, "Failed to add " + artifacts.get(versionNumber).artifact.getGav() + " to plugin versions", ex);
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/PluginVersionsEntry.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import io.jenkins.update_center.MavenRepository;
5 | import io.jenkins.update_center.HPI;
6 |
7 | import java.io.IOException;
8 | import java.text.SimpleDateFormat;
9 | import java.util.List;
10 | import java.util.Locale;
11 |
12 | public class PluginVersionsEntry {
13 | @JSONField
14 | public final String buildDate;
15 | @JSONField
16 | public final String name;
17 | @JSONField
18 | public final String requiredCore;
19 | @JSONField
20 | public final String sha1;
21 | @JSONField
22 | public final String sha256;
23 | @JSONField
24 | public final String url;
25 | @JSONField
26 | public final String version;
27 | @JSONField
28 | public final String compatibleSinceVersion;
29 | @JSONField
30 | public final String releaseTimestamp;
31 |
32 | @JSONField
33 | public final List dependencies;
34 |
35 | PluginVersionsEntry(HPI hpi) throws IOException {
36 | final MavenRepository.ArtifactMetadata artifactMetadata = hpi.getMetadata();
37 | name = hpi.artifact.artifactId;
38 | requiredCore = hpi.getRequiredJenkinsVersion();
39 | sha1 = artifactMetadata.sha1;
40 | sha256 = artifactMetadata.sha256;
41 | url = hpi.getDownloadUrl().toString();
42 | version = hpi.version;
43 | buildDate = hpi.getTimestampAsString();
44 | dependencies = hpi.getDependencies();
45 | compatibleSinceVersion = hpi.getCompatibleSinceVersion();
46 | releaseTimestamp = TIMESTAMP_FORMATTER.format(hpi.getTimestamp());
47 | }
48 |
49 | private static final SimpleDateFormat TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.00Z'", Locale.US);
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/PluginVersionsRoot.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import io.jenkins.update_center.MavenRepository;
5 | import io.jenkins.update_center.Plugin;
6 |
7 | import java.io.IOException;
8 | import java.util.Map;
9 | import java.util.TreeMap;
10 | import java.util.stream.Collectors;
11 |
12 | public class PluginVersionsRoot extends WithSignature {
13 | @JSONField
14 | public final String updateCenterVersion;
15 | private final MavenRepository repository;
16 |
17 | private Map plugins;
18 |
19 | public PluginVersionsRoot(String updateCenterVersion, MavenRepository repository) {
20 | this.updateCenterVersion = updateCenterVersion;
21 | this.repository = repository;
22 | }
23 |
24 | @JSONField
25 | public Map getPlugins() throws IOException {
26 | if (plugins == null) {
27 | plugins = new TreeMap<>(repository.listJenkinsPlugins().stream().collect(Collectors.toMap(Plugin::getArtifactId, plugin -> new PluginVersions(plugin.getArtifacts()))));
28 | }
29 | plugins.entrySet().removeIf(e -> e.getValue().releases.isEmpty());
30 | return plugins;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/RecentReleasesEntry.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import io.jenkins.update_center.HPI;
4 |
5 | public class RecentReleasesEntry {
6 | private HPI hpi;
7 | public RecentReleasesEntry(HPI hpi) {
8 | this.hpi = hpi;
9 | }
10 |
11 | public String getName() {
12 | return hpi.artifact.artifactId;
13 | }
14 |
15 | public String getVersion() {
16 | return hpi.version;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/RecentReleasesRoot.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import io.jenkins.update_center.HPI;
5 | import io.jenkins.update_center.MavenRepository;
6 | import io.jenkins.update_center.Plugin;
7 | import io.jenkins.update_center.util.Environment;
8 |
9 | import java.io.IOException;
10 | import java.time.Duration;
11 | import java.time.Instant;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class RecentReleasesRoot extends WithoutSignature {
16 | @JSONField
17 | public List releases = new ArrayList<>();
18 |
19 | public RecentReleasesRoot(MavenRepository repository) throws IOException {
20 | for (Plugin plugin : repository.listJenkinsPlugins()) {
21 | for (HPI release : plugin.getArtifacts().values()) {
22 | if (Instant.ofEpochMilli(release.getTimestamp()).isBefore(Instant.now().minus(MAX_AGE))) {
23 | // too old, ignore
24 | continue;
25 | }
26 | releases.add(new RecentReleasesEntry(release));
27 | }
28 | }
29 | }
30 |
31 | private static final Duration MAX_AGE = Duration.ofHours(Environment.getInteger("RECENT_RELEASES_MAX_AGE_HOURS", 3));
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/ReleaseHistoryDate.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import io.jenkins.update_center.HPI;
5 | import io.jenkins.update_center.MavenArtifact;
6 |
7 | import java.text.SimpleDateFormat;
8 | import java.util.ArrayList;
9 | import java.util.Date;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.logging.Level;
13 | import java.util.logging.Logger;
14 |
15 | class ReleaseHistoryDate {
16 | private static final Logger LOGGER = Logger.getLogger(ReleaseHistoryDate.class.getName());
17 |
18 | @JSONField
19 | public final String date;
20 |
21 | @JSONField
22 | public final List releases;
23 |
24 | ReleaseHistoryDate(Date date, Map pluginsById) {
25 | SimpleDateFormat dateFormat = MavenArtifact.getDateFormat();
26 | this.date = dateFormat.format(date);
27 | List list = new ArrayList<>();
28 | for (HPI hpi : pluginsById.values()) {
29 | try {
30 | ReleaseHistoryEntry releaseHistoryEntry = new ReleaseHistoryEntry(hpi);
31 | list.add(releaseHistoryEntry);
32 | } catch (Exception ex) {
33 | LOGGER.log(Level.INFO, "Failed to retrieve plugin info for " + hpi.artifact.artifactId, ex);
34 | }
35 | }
36 | this.releases = list;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/ReleaseHistoryEntry.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import io.jenkins.update_center.HPI;
5 |
6 | import java.io.IOException;
7 | import java.util.Calendar;
8 | import java.util.GregorianCalendar;
9 |
10 | class ReleaseHistoryEntry {
11 | @JSONField
12 | public final String title;
13 | @JSONField
14 | public final String wiki; // historical name for the plugin documentation URL field
15 | @JSONField
16 | public final String gav;
17 | @JSONField
18 | public final String version;
19 | @JSONField
20 | public final long timestamp;
21 | @JSONField
22 | public final String url;
23 | @JSONField
24 | public Boolean latestRelease;
25 | @JSONField
26 | public Boolean firstRelease;
27 |
28 | private static final Calendar DATE_CUTOFF = new GregorianCalendar();
29 |
30 | static {
31 | DATE_CUTOFF.add(Calendar.DAY_OF_MONTH, -31);
32 | }
33 |
34 | ReleaseHistoryEntry(HPI hpi) throws IOException {
35 | if (hpi.getTimestampAsDate().after(DATE_CUTOFF.getTime())) {
36 | title = hpi.getName();
37 | wiki = hpi.getPluginUrl();
38 | } else {
39 | title = null;
40 | wiki = null;
41 | }
42 | if (hpi.getPlugin().getLatest() == hpi) {
43 | latestRelease = true;
44 | }
45 | if (hpi.getPlugin().getFirst() == hpi) {
46 | firstRelease = true;
47 | }
48 | version = hpi.version;
49 | this.gav = hpi.artifact.getGav();
50 | timestamp = hpi.repository.getMetadata(hpi).timestamp;
51 | url = "https://plugins.jenkins.io/" + hpi.artifact.artifactId;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/ReleaseHistoryRoot.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import io.jenkins.update_center.HPI;
5 | import io.jenkins.update_center.MavenRepository;
6 |
7 | import java.io.IOException;
8 | import java.util.ArrayList;
9 | import java.util.Date;
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | public class ReleaseHistoryRoot extends WithoutSignature {
14 | @JSONField
15 | public final List releaseHistory;
16 |
17 | public ReleaseHistoryRoot(MavenRepository repository) throws IOException {
18 | List list = new ArrayList<>();
19 | for (Map.Entry> entry : repository.listPluginsByReleaseDate().entrySet()) {
20 | ReleaseHistoryDate releaseHistoryDate = new ReleaseHistoryDate(entry.getKey(), entry.getValue());
21 | list.add(releaseHistoryDate);
22 | }
23 | this.releaseHistory = list;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/TieredUpdateSitesGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2020, Daniel Beck
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package io.jenkins.update_center.json;
25 |
26 | import com.alibaba.fastjson.annotation.JSONField;
27 | import hudson.util.VersionNumber;
28 | import io.jenkins.update_center.HPI;
29 | import io.jenkins.update_center.JenkinsWar;
30 | import io.jenkins.update_center.MavenRepository;
31 |
32 | import java.io.IOException;
33 | import java.time.Instant;
34 | import java.time.temporal.ChronoUnit;
35 | import java.util.Collection;
36 | import java.util.Comparator;
37 | import java.util.HashSet;
38 | import java.util.List;
39 | import java.util.Objects;
40 | import java.util.Optional;
41 | import java.util.Set;
42 | import java.util.TreeMap;
43 | import java.util.logging.Level;
44 | import java.util.logging.Logger;
45 | import java.util.stream.Collectors;
46 |
47 | public class TieredUpdateSitesGenerator extends WithoutSignature {
48 |
49 | private MavenRepository repository;
50 |
51 | @JSONField
52 | public List weeklyCores;
53 |
54 | @JSONField
55 | public List stableCores;
56 |
57 | public TieredUpdateSitesGenerator withRepository(MavenRepository repository) throws IOException {
58 | this.repository = repository;
59 | update();
60 | return this;
61 | }
62 |
63 | private static boolean isStableVersion(VersionNumber version) {
64 | return version.getDigitAt(2) != -1;
65 | }
66 |
67 | private static VersionNumber nextWeeklyReleaseAfterStableBaseline(VersionNumber version) {
68 | if (!version.toString().matches("[0-9][.][0-9]+[.][1-9]")) {
69 | throw new IllegalArgumentException("Unexpected LTS version: " + version.toString());
70 | }
71 | return new VersionNumber(version.getDigitAt(0) + "." + (version.getDigitAt(1) + 1));
72 | }
73 |
74 | private VersionNumber nextLtsReleaseAfterWeekly(VersionNumber dependencyVersion, Set keySet) {
75 | return keySet.stream().filter(TieredUpdateSitesGenerator::isStableVersion).sorted().filter(v -> v.isNewerThan(dependencyVersion)).findFirst().orElse(null);
76 | }
77 |
78 | private static boolean isReleaseRecentEnough(JenkinsWar war) throws IOException {
79 | Objects.requireNonNull(war, "war");
80 | return war.getTimestampAsDate().toInstant().isAfter(Instant.now().minus(CORE_AGE_DAYS, ChronoUnit.DAYS));
81 | }
82 |
83 | public void update() throws IOException {
84 | Collection allPluginReleases = this.repository.listJenkinsPlugins().stream()
85 | .map(plugin -> plugin.getArtifacts().values())
86 | .reduce(new HashSet<>(), (acc, els) -> { acc.addAll(els); return acc; });
87 |
88 | final List coreDependencyVersions = allPluginReleases.stream().map(v -> {
89 | try {
90 | return v.getRequiredJenkinsVersion();
91 | } catch (IOException e) {
92 | LOGGER.log(Level.WARNING, "Failed to determine required Jenkins version for " + v.getGavId(), e);
93 | return null;
94 | }
95 | }).filter(Objects::nonNull).collect(Collectors.toSet()).stream().map(VersionNumber::new).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
96 |
97 | final TreeMap allJenkinsWarsByVersionNumber = this.repository.getJenkinsWarsByVersionNumber();
98 | final Set weeklyCores = new HashSet<>();
99 | final Set stableCores = new HashSet<>();
100 |
101 | boolean stableDone = false;
102 | boolean weeklyDone = false;
103 |
104 | for (VersionNumber dependencyVersion : coreDependencyVersions) {
105 | final JenkinsWar war = allJenkinsWarsByVersionNumber.get(dependencyVersion);
106 | if (war == null) {
107 | LOGGER.log(Level.INFO, "Did not find declared core dependency version among all core releases: " + dependencyVersion.toString() + ". It is used by " + allPluginReleases.stream().filter( p -> {
108 | try {
109 | return p.getRequiredJenkinsVersion().equals(dependencyVersion.toString());
110 | } catch (IOException e) {
111 | // ignore
112 | return false;
113 | }
114 | }).map(HPI::getGavId).collect(Collectors.joining(", ")));
115 | continue;
116 | }
117 | final boolean releaseRecentEnough = isReleaseRecentEnough(war);
118 | if (isStableVersion(dependencyVersion)) {
119 | if (!stableDone) {
120 | if (!releaseRecentEnough) {
121 | stableDone = true;
122 | }
123 | stableCores.add(dependencyVersion);
124 | if (!weeklyDone) {
125 | weeklyCores.add(nextWeeklyReleaseAfterStableBaseline(dependencyVersion));
126 | }
127 | }
128 | } else {
129 | if (!weeklyDone) {
130 | if (!releaseRecentEnough) {
131 | weeklyDone = true;
132 | }
133 | weeklyCores.add(dependencyVersion);
134 | }
135 | // Plugin depends on a weekly version, make sure the next higher LTS release is also included
136 | if (!stableDone) {
137 | final VersionNumber v = nextLtsReleaseAfterWeekly(dependencyVersion, allJenkinsWarsByVersionNumber.keySet());
138 | if (v != null) {
139 | stableCores.add(v);
140 | }
141 | }
142 | }
143 | if (stableDone && weeklyDone) {
144 | break;
145 | }
146 | }
147 |
148 | this.stableCores = stableCores.stream().map(VersionNumber::toString).sorted().collect(Collectors.toList());
149 | this.weeklyCores = weeklyCores.stream().map(VersionNumber::toString).sorted().collect(Collectors.toList());
150 | }
151 |
152 | public static final Logger LOGGER = Logger.getLogger(TieredUpdateSitesGenerator.class.getName());
153 |
154 | private static final int CORE_AGE_DAYS = 400;
155 | }
156 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/UpdateCenterCore.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import hudson.util.VersionNumber;
5 | import io.jenkins.update_center.JenkinsWar;
6 | import io.jenkins.update_center.MavenRepository;
7 |
8 | import java.io.IOException;
9 | import java.util.TreeMap;
10 |
11 | public class UpdateCenterCore {
12 |
13 | @JSONField
14 | public String buildDate;
15 |
16 | @JSONField
17 | public String name = "core";
18 |
19 | @JSONField
20 | public String sha1;
21 |
22 | @JSONField
23 | public String sha256;
24 |
25 | @JSONField
26 | public String url;
27 |
28 | @JSONField
29 | public String version;
30 |
31 | @JSONField
32 | public long size;
33 |
34 | UpdateCenterCore(TreeMap jenkinsWarsByVersionNumber) throws IOException {
35 | if (jenkinsWarsByVersionNumber.isEmpty()) {
36 | return;
37 | }
38 |
39 | JenkinsWar war = jenkinsWarsByVersionNumber.get(jenkinsWarsByVersionNumber.firstKey());
40 |
41 | version = war.version;
42 | url = war.getDownloadUrl().toString();
43 | final MavenRepository.ArtifactMetadata artifactMetadata = war.getMetadata();
44 | sha1 = artifactMetadata.sha1;
45 | sha256 = artifactMetadata.sha256;
46 | buildDate = war.getTimestampAsString();
47 | size = artifactMetadata.size;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/UpdateCenterDeprecation.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | public class UpdateCenterDeprecation {
6 |
7 | @JSONField
8 | public final String url;
9 |
10 | public UpdateCenterDeprecation(String url) {
11 | this.url = url;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/UpdateCenterRoot.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.annotation.JSONField;
5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6 | import io.jenkins.update_center.BaseMavenRepository;
7 | import io.jenkins.update_center.Deprecations;
8 | import io.jenkins.update_center.MavenRepository;
9 | import io.jenkins.update_center.Plugin;
10 | import io.jenkins.update_center.PluginUpdateCenterEntry;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.nio.charset.StandardCharsets;
15 | import java.nio.file.Files;
16 | import java.util.Arrays;
17 | import java.util.List;
18 | import java.util.Map;
19 | import java.util.TreeMap;
20 | import java.util.function.Function;
21 | import java.util.logging.Level;
22 | import java.util.logging.Logger;
23 | import java.util.stream.Collectors;
24 | import org.apache.commons.lang3.StringUtils;
25 |
26 | public class UpdateCenterRoot extends WithSignature {
27 | private static final Logger LOGGER = Logger.getLogger(UpdateCenterRoot.class.getName());
28 |
29 | @JSONField
30 | @SuppressFBWarnings(value = "SS_SHOULD_BE_STATIC", justification = "Accessed by JSON serializer")
31 | public final String updateCenterVersion = "1";
32 |
33 | @JSONField
34 | public final String connectionCheckUrl;
35 |
36 | @JSONField
37 | public final String id;
38 |
39 | @JSONField
40 | public UpdateCenterCore core;
41 |
42 | @JSONField
43 | public Map plugins = new TreeMap<>();
44 |
45 | @JSONField
46 | public List warnings;
47 |
48 | @JSONField
49 | public Map deprecations;
50 |
51 | public UpdateCenterRoot(String id, String connectionCheckUrl, MavenRepository repo, File warningsJsonFile) throws IOException {
52 | if (StringUtils.isEmpty(id)) {
53 | throw new IllegalArgumentException("'id' is required");
54 | }
55 | this.id = id;
56 | if (StringUtils.isEmpty(connectionCheckUrl)) {
57 | throw new IllegalArgumentException("'connectionCheckUrl' is required");
58 | }
59 | this.connectionCheckUrl = connectionCheckUrl;
60 |
61 | // load warnings
62 | final String warningsJsonText = String.join("", Files.readAllLines(warningsJsonFile.toPath(), StandardCharsets.UTF_8));
63 | warnings = Arrays.asList(JSON.parseObject(warningsJsonText, UpdateCenterWarning[].class));
64 |
65 | // load deprecations
66 | deprecations = new TreeMap<>(Deprecations.getDeprecatedPlugins().collect(Collectors.toMap(Function.identity(), UpdateCenterRoot::deprecationForPlugin)));
67 |
68 | for (Plugin plugin : repo.listJenkinsPlugins()) {
69 | try {
70 | PluginUpdateCenterEntry entry = new PluginUpdateCenterEntry(plugin);
71 | plugins.put(plugin.getArtifactId(), entry);
72 | } catch (IOException ex) {
73 | LOGGER.log(Level.INFO, "Failed to add update center entry for: " + plugin, ex);
74 | }
75 | }
76 |
77 | core = new UpdateCenterCore(repo.getJenkinsWarsByVersionNumber());
78 | }
79 |
80 | private static UpdateCenterDeprecation deprecationForPlugin(String artifactId) {
81 | String deprecationUrl = Deprecations.getCustomDeprecationUri(artifactId);
82 | String noticeUrl = deprecationUrl != null ? deprecationUrl : BaseMavenRepository.getIgnoreNoticeUrl(artifactId);
83 | return new UpdateCenterDeprecation(noticeUrl);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/UpdateCenterWarning.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | public class UpdateCenterWarning {
9 | @JSONField
10 | public String id;
11 |
12 | @JSONField
13 | public String message;
14 |
15 | @JSONField
16 | public String name;
17 |
18 | @JSONField
19 | public String type;
20 |
21 | @JSONField
22 | public String url;
23 |
24 | @JSONField
25 | public List versions = new ArrayList<>();
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/UpdateCenterWarningVersionRange.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | public class UpdateCenterWarningVersionRange {
6 | @JSONField
7 | public String firstVersion;
8 |
9 | @JSONField
10 | public String lastVersion;
11 |
12 | @JSONField
13 | public String pattern;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/WithSignature.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.annotation.JSONField;
5 | import com.alibaba.fastjson.serializer.SerializerFeature;
6 | import io.jenkins.update_center.Signer;
7 |
8 | import io.jenkins.update_center.util.Timestamp;
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.io.OutputStream;
12 | import java.io.OutputStreamWriter;
13 | import java.io.StringWriter;
14 | import java.io.Writer;
15 | import java.nio.charset.StandardCharsets;
16 | import java.nio.file.Files;
17 | import java.security.GeneralSecurityException;
18 |
19 | /**
20 | * Support generation of JSON output with included checksum + signatures block for the same JSON output.
21 | */
22 | public abstract class WithSignature {
23 | private JsonSignature signature;
24 |
25 | @JSONField
26 | public JsonSignature getSignature() {
27 | return signature;
28 | }
29 |
30 | /**
31 | * Returns a string with the current date and time in ISO-8601 format.
32 | * It doesn't have fractional seconds and the timezone is always UTC ('Z').
33 | *
34 | * @return a string with the current date and time in the format YYYY-MM-DD'T'HH:mm:ss'Z'
35 | */
36 | public String getGenerationTimestamp() {
37 | return Timestamp.TIMESTAMP;
38 | }
39 |
40 | /**
41 | * Generate JSON checksums and add a signature block to the JSON written to the specified {@link Writer}.
42 | *
43 | * This will run JSON generation twice: Once without the signature block to compute checksums, and a second time to
44 | * include the signature block and write it to the output file.
45 | *
46 | * Because of this, it is important that (with the exception of {@link #getSignature()} all getters etc. of subtypes
47 | * and any types reachable through the object graph for JSON generation return the same content on subsequent calls.
48 | *
49 | * Additionally, implementations of this class, and all types reachable via fields and getters used during JSON
50 | * generation should employ some sort of caching to prevent expensive computations from being invoked twice.
51 | *
52 | * @param writer the writer to write to
53 | * @param signer the signer
54 | * @param pretty whether to pretty-print format the JSON output
55 | * @throws IOException when any IO error occurs
56 | * @throws GeneralSecurityException when an issue during signing occurs
57 | */
58 | private void writeWithSignature(Writer writer, Signer signer, boolean pretty) throws IOException, GeneralSecurityException {
59 | signature = null;
60 |
61 | final String unsignedJson = JSON.toJSONString(this, SerializerFeature.DisableCircularReferenceDetect);
62 | signature = signer.sign(unsignedJson);
63 |
64 | if (pretty) {
65 | JSON.writeJSONString(writer, this, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.PrettyFormat);
66 | } else {
67 | JSON.writeJSONString(writer, this, SerializerFeature.DisableCircularReferenceDetect);
68 | }
69 | writer.flush();
70 | }
71 |
72 | /**
73 | * Convenience wrapper for {@link #writeWithSignature(Writer, Signer, boolean)} writing to a file.
74 | *
75 | * @param outputFile the file to write to
76 | * @param signer the signer
77 | * @param pretty whether to pretty-print format the JSON output
78 | * @throws IOException when any IO error occurs
79 | * @throws GeneralSecurityException when an issue during signing occurs
80 | */
81 | public void writeWithSignature(File outputFile, Signer signer, boolean pretty) throws IOException, GeneralSecurityException {
82 | try (OutputStream os = Files.newOutputStream(outputFile.toPath()); OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
83 | writeWithSignature(writer, signer, pretty);
84 | }
85 | }
86 |
87 | /**
88 | * Like {@link #writeWithSignature(File, Signer, boolean)} but the output is returned as a String.
89 | * @param signer the signer
90 | * @param pretty whether to pretty-print format the JSON output
91 | * @return the JSON output
92 | * @throws IOException when any IO error occurs
93 | * @throws GeneralSecurityException when an issue during signing occurs
94 | */
95 | public String encodeWithSignature(Signer signer, boolean pretty) throws IOException, GeneralSecurityException {
96 | StringWriter writer = new StringWriter();
97 | writeWithSignature(writer, signer, pretty);
98 | return writer.getBuffer().toString();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/json/WithoutSignature.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.json;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.serializer.SerializerFeature;
5 |
6 | import java.io.BufferedWriter;
7 | import java.io.File;
8 | import java.io.IOException;
9 | import java.nio.charset.StandardCharsets;
10 | import java.nio.file.Files;
11 |
12 | public class WithoutSignature {
13 | public void write(File file, boolean pretty) throws IOException {
14 | final File parent = file.getParentFile();
15 | if (!parent.mkdirs() && !parent.isDirectory()) {
16 | throw new IOException("Failed to create " + parent);
17 | }
18 | try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
19 | if (pretty) {
20 | JSON.writeJSONString(writer, this, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.PrettyFormat);
21 | } else {
22 | JSON.writeJSONString(writer, this, SerializerFeature.DisableCircularReferenceDetect);
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/util/Environment.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.util;
2 |
3 | import javax.annotation.CheckForNull;
4 | import javax.annotation.Nonnull;
5 | import java.util.logging.Level;
6 | import java.util.logging.Logger;
7 |
8 | public final class Environment {
9 | private Environment() {}
10 |
11 | public static String getString(@Nonnull String key, @CheckForNull String defaultValue) {
12 | final String property = System.getProperty(key);
13 | if (property != null) {
14 | LOGGER.log(Level.CONFIG, "Found key: " + key + " in system properties: " + property);
15 | return property;
16 | }
17 |
18 | final String env = System.getenv(key);
19 | if (env != null) {
20 | LOGGER.log(Level.CONFIG, "Found key: " + key + " in process environment: " + env);
21 | return env;
22 | }
23 |
24 | LOGGER.log(Level.CONFIG, "Failed to find key: " + key + " so using default: " + defaultValue);
25 | return defaultValue;
26 | }
27 |
28 | public static String getString(@Nonnull String key) {
29 | return getString(key, null);
30 | }
31 |
32 | public static int getInteger(@Nonnull String key) {
33 | return getInteger(key, 0);
34 | }
35 |
36 | public static int getInteger(@Nonnull String key, int defaultValue) {
37 | try {
38 | return Integer.parseInt(getString(key, Integer.toString(defaultValue)));
39 | } catch (NumberFormatException nfe) {
40 | LOGGER.log(Level.WARNING, nfe.getMessage(), nfe);
41 | return defaultValue;
42 | }
43 | }
44 |
45 | private static final Logger LOGGER = Logger.getLogger(Environment.class.getName());
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/util/Timestamp.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.util;
2 |
3 | import java.time.Instant;
4 | import java.time.ZoneOffset;
5 | import java.time.format.DateTimeFormatter;
6 |
7 | public class Timestamp {
8 | public static final String TIMESTAMP = DateTimeFormatter.ISO_DATE_TIME.format(Instant.now().atOffset(ZoneOffset.UTC).withNano(0));
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/wrappers/AllowedArtifactsListMavenRepository.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.wrappers;
2 |
3 | import hudson.util.VersionNumber;
4 | import io.jenkins.update_center.JenkinsWar;
5 | import io.jenkins.update_center.HPI;
6 | import io.jenkins.update_center.Plugin;
7 |
8 | import java.io.IOException;
9 | import java.util.Arrays;
10 | import java.util.Collection;
11 | import java.util.Iterator;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.Properties;
15 | import java.util.TreeMap;
16 | import java.util.logging.Level;
17 | import java.util.logging.Logger;
18 | import java.util.stream.Collectors;
19 |
20 | public class AllowedArtifactsListMavenRepository extends MavenRepositoryWrapper {
21 | private static final Logger LOGGER = Logger.getLogger(AllowedArtifactsListMavenRepository.class.getName());
22 |
23 | private final Properties allowedArtifactsList;
24 |
25 | public AllowedArtifactsListMavenRepository(Properties allowedArtifactsList) {
26 | this.allowedArtifactsList = allowedArtifactsList;
27 | }
28 |
29 | @Override
30 | public Collection listJenkinsPlugins() throws IOException {
31 | final Collection plugins = base.listJenkinsPlugins();
32 | for (Iterator pluginIterator = plugins.iterator(); pluginIterator.hasNext(); ) {
33 | Plugin plugin = pluginIterator.next();
34 | final String listEntry = allowedArtifactsList.getProperty(plugin.getArtifactId());
35 |
36 | if (listEntry == null) {
37 | pluginIterator.remove();
38 | continue;
39 | }
40 |
41 | if (listEntry.equals("*")) {
42 | continue; // entire artifactId allowed
43 | }
44 |
45 | final List allowedVersions = Arrays.stream(listEntry.split("\\s+")).map(String::trim).collect(Collectors.toList());
46 |
47 | for (Iterator> versionIterator = plugin.getArtifacts().entrySet().iterator(); versionIterator.hasNext(); ) {
48 | Map.Entry entry = versionIterator.next();
49 | HPI hpi = entry.getValue();
50 | if (!allowedVersions.contains(hpi.version)) {
51 | versionIterator.remove();
52 | }
53 | }
54 | if (plugin.getArtifacts().isEmpty()) {
55 | LOGGER.log(Level.WARNING, "Individual versions of a plugin are allowed, but none of them matched: " + plugin.getArtifactId() + " versions: " + listEntry);
56 | pluginIterator.remove();
57 | }
58 | }
59 | return plugins;
60 | }
61 |
62 | @Override
63 | public TreeMap getJenkinsWarsByVersionNumber() throws IOException {
64 | final String listEntry = allowedArtifactsList.getProperty("jenkins-core");
65 |
66 | if (listEntry == null) {
67 | return new TreeMap<>(); // TODO fix return type so it's only a Map
68 | }
69 |
70 | TreeMap releases = base.getJenkinsWarsByVersionNumber();
71 |
72 | if (listEntry.equals("*")) {
73 | return releases;
74 | }
75 |
76 | final List allowedVersions = Arrays.stream(listEntry.split("\\s+")).map(String::trim).collect(Collectors.toList());
77 |
78 | releases.keySet().retainAll(releases.keySet().stream().filter(it -> allowedVersions.contains(it.toString())).collect(Collectors.toSet()));
79 |
80 | return releases;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/wrappers/AlphaBetaOnlyRepository.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.wrappers;
2 |
3 | import hudson.util.VersionNumber;
4 | import io.jenkins.update_center.HPI;
5 | import io.jenkins.update_center.Plugin;
6 |
7 | import java.io.IOException;
8 | import java.util.Collection;
9 | import java.util.Iterator;
10 | import java.util.Map.Entry;
11 |
12 | /**
13 | * Filter down to alpha/beta releases of plugins (or the negation of it.)
14 | *
15 | * @author Kohsuke Kawaguchi
16 | */
17 | public class AlphaBetaOnlyRepository extends MavenRepositoryWrapper {
18 |
19 | /**
20 | * If true, negate the logic and only find non-alpha/beta releases.
21 | */
22 | private boolean negative;
23 |
24 | public AlphaBetaOnlyRepository(boolean negative) {
25 | this.negative = negative;
26 | }
27 |
28 | @Override
29 | public Collection listJenkinsPlugins() throws IOException {
30 | Collection r = base.listJenkinsPlugins();
31 | for (Iterator jtr = r.iterator(); jtr.hasNext();) {
32 | Plugin h = jtr.next();
33 |
34 | for (Iterator> itr = h.getArtifacts().entrySet().iterator(); itr.hasNext();) {
35 | Entry e = itr.next();
36 | if (e.getValue().isAlphaOrBeta()^negative)
37 | continue;
38 | itr.remove();
39 | }
40 |
41 | if (h.getArtifacts().isEmpty())
42 | jtr.remove();
43 | }
44 |
45 | return r;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/wrappers/FilteringRepository.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.wrappers;
2 |
3 | import hudson.util.VersionNumber;
4 | import io.jenkins.update_center.PluginFilter;
5 | import io.jenkins.update_center.HPI;
6 | import io.jenkins.update_center.Plugin;
7 |
8 | import javax.annotation.Nonnull;
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.Collection;
12 | import java.util.Iterator;
13 | import java.util.List;
14 | import java.util.Map;
15 |
16 | public class FilteringRepository extends MavenRepositoryWrapper {
17 |
18 | /**
19 | * Adds a plugin filter.
20 | * @param filter Filter to be added.
21 | */
22 | private void addPluginFilter(@Nonnull PluginFilter filter) {
23 | pluginFilters.add(filter);
24 | }
25 |
26 | private List pluginFilters = new ArrayList<>();
27 |
28 | @Override
29 | public Collection listJenkinsPlugins() throws IOException {
30 | Collection r = base.listJenkinsPlugins();
31 | for (Iterator jtr = r.iterator(); jtr.hasNext();) {
32 | Plugin h = jtr.next();
33 |
34 | for (Iterator> itr = h.getArtifacts().entrySet().iterator(); itr.hasNext();) {
35 | Map.Entry e = itr.next();
36 | for (PluginFilter filter : pluginFilters) {
37 | if (filter.shouldIgnore(e.getValue())) {
38 | itr.remove();
39 | }
40 | }
41 | }
42 |
43 | if (h.getArtifacts().isEmpty())
44 | jtr.remove();
45 | }
46 |
47 | return r;
48 | }
49 |
50 | public FilteringRepository withPluginFilter(PluginFilter pluginFilter) {
51 | addPluginFilter(pluginFilter);
52 | return this;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/wrappers/MavenRepositoryWrapper.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.wrappers;
2 |
3 | import hudson.util.VersionNumber;
4 | import io.jenkins.update_center.MavenRepository;
5 | import io.jenkins.update_center.ArtifactCoordinates;
6 | import io.jenkins.update_center.JenkinsWar;
7 | import io.jenkins.update_center.MavenArtifact;
8 | import io.jenkins.update_center.Plugin;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.util.Collection;
14 | import java.util.Map;
15 | import java.util.TreeMap;
16 | import java.util.jar.Manifest;
17 |
18 | public class MavenRepositoryWrapper implements MavenRepository {
19 |
20 | MavenRepository base;
21 |
22 | public MavenRepositoryWrapper withBaseRepository(MavenRepository base) {
23 | this.base = base;
24 | return this;
25 | }
26 |
27 | @Override
28 | public TreeMap getJenkinsWarsByVersionNumber() throws IOException {
29 | return base.getJenkinsWarsByVersionNumber();
30 | }
31 |
32 | @Override
33 | public void addWarsInGroupIdToMap(Map r, String groupId, VersionNumber cap) throws IOException {
34 | base.addWarsInGroupIdToMap(r, groupId, cap);
35 | }
36 |
37 | @Override
38 | public Collection listAllPlugins() throws IOException {
39 | return base.listAllPlugins();
40 | }
41 |
42 | @Override
43 | public ArtifactMetadata getMetadata(MavenArtifact artifact) throws IOException {
44 | return base.getMetadata(artifact);
45 | }
46 |
47 | @Override
48 | public Manifest getManifest(MavenArtifact artifact) throws IOException {
49 | return base.getManifest(artifact);
50 | }
51 |
52 | @Override
53 | public InputStream getZipFileEntry(MavenArtifact artifact, String path) throws IOException {
54 | return base.getZipFileEntry(artifact, path);
55 | }
56 |
57 | @Override
58 | public File resolve(ArtifactCoordinates artifact) throws IOException {
59 | return base.resolve(artifact);
60 | }
61 |
62 | @Override
63 | public Collection listJenkinsPlugins() throws IOException {
64 | return base.listJenkinsPlugins();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/wrappers/StableWarMavenRepository.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.wrappers;
2 |
3 | import hudson.util.VersionNumber;
4 | import io.jenkins.update_center.JenkinsWar;
5 |
6 | import java.io.IOException;
7 | import java.util.TreeMap;
8 | import java.util.stream.Collectors;
9 |
10 | /**
11 | * Delegating {@link MavenRepositoryWrapper} to limit core releases to those with LTS version numbers.
12 | */
13 | public class StableWarMavenRepository extends MavenRepositoryWrapper {
14 |
15 | @Override
16 | public TreeMap getJenkinsWarsByVersionNumber() throws IOException {
17 | TreeMap releases = base.getJenkinsWarsByVersionNumber();
18 |
19 | releases.keySet().retainAll(releases.keySet().stream().filter(it -> it.getDigitAt(2) != -1).collect(Collectors.toSet()));
20 |
21 | return releases;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/wrappers/TruncatedMavenRepository.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.wrappers;
2 |
3 | import io.jenkins.update_center.MavenRepository;
4 | import io.jenkins.update_center.Plugin;
5 |
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.Collection;
9 | import java.util.List;
10 |
11 | /**
12 | * {@link MavenRepository} that limits the # of plugins that it reports.
13 | *
14 | * This is primary for monkey-testing with subset of data.
15 | */
16 | public class TruncatedMavenRepository extends MavenRepositoryWrapper {
17 | private final int cap;
18 |
19 | public TruncatedMavenRepository(int cap) {
20 | this.cap = cap;
21 | }
22 |
23 | @Override
24 | public Collection listJenkinsPlugins() throws IOException {
25 | List result = new ArrayList<>(base.listJenkinsPlugins());
26 | return result.subList(0, Math.min(cap,result.size()));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/update_center/wrappers/VersionCappedMavenRepository.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center.wrappers;
2 |
3 | import hudson.util.VersionNumber;
4 | import io.jenkins.update_center.JenkinsWar;
5 | import io.jenkins.update_center.BaseMavenRepository;
6 | import io.jenkins.update_center.HPI;
7 | import io.jenkins.update_center.Plugin;
8 |
9 | import javax.annotation.CheckForNull;
10 | import java.io.IOException;
11 | import java.util.Collection;
12 | import java.util.Iterator;
13 | import java.util.Map;
14 | import java.util.Map.Entry;
15 | import java.util.TreeMap;
16 | import java.util.logging.Level;
17 |
18 | /**
19 | * Delegating {@link BaseMavenRepository} to limit the data to the subset compatible with the specific version.
20 | */
21 | public class VersionCappedMavenRepository extends MavenRepositoryWrapper {
22 |
23 | /**
24 | * Version number to cap. We only report plugins that are compatible with this core version.
25 | */
26 | @CheckForNull
27 | private final VersionNumber capPlugin;
28 |
29 | /**
30 | * Version number to cap core. We only report core versions as high as this.
31 | */
32 | @CheckForNull
33 | private final VersionNumber capCore;
34 |
35 | public VersionCappedMavenRepository(@CheckForNull VersionNumber capPlugin, @CheckForNull VersionNumber capCore) {
36 | this.capPlugin = capPlugin;
37 | this.capCore = capCore;
38 | }
39 |
40 | @Override
41 | public TreeMap getJenkinsWarsByVersionNumber() throws IOException {
42 | final TreeMap allWars = base.getJenkinsWarsByVersionNumber();
43 | if (capCore == null) {
44 | return allWars;
45 | }
46 | return new TreeMap<>(allWars.tailMap(capCore,true));
47 | }
48 |
49 | @Override
50 | public Collection listJenkinsPlugins() throws IOException {
51 | Collection r = base.listJenkinsPlugins();
52 |
53 | for (Iterator jtr = r.iterator(); jtr.hasNext();) {
54 | Plugin h = jtr.next();
55 |
56 |
57 | Map versionNumberHPIMap = new TreeMap<>(VersionNumber.DESCENDING);
58 |
59 | for (Entry e : h.getArtifacts().entrySet()) {
60 | if (capPlugin == null) {
61 | // no cap
62 | versionNumberHPIMap.put(e.getKey(), e.getValue());
63 | if (versionNumberHPIMap.size() >= 2) {
64 | break;
65 | }
66 | continue;
67 | }
68 | try {
69 | VersionNumber v = new VersionNumber(e.getValue().getRequiredJenkinsVersion());
70 | if (v.compareTo(capPlugin) <= 0) {
71 | versionNumberHPIMap.put(e.getKey(), e.getValue());
72 | if (versionNumberHPIMap.size() >= 2) {
73 | break;
74 | }
75 | }
76 | } catch (IOException x) {
77 | LOGGER.log(Level.WARNING, "Failed to filter version " + e.getKey() + " by core dependency for plugin: " + h.getArtifactId(), x);
78 | }
79 | }
80 |
81 | h.getArtifacts().entrySet().retainAll(versionNumberHPIMap.entrySet());
82 |
83 | if (h.getArtifacts().isEmpty())
84 | jtr.remove();
85 | }
86 |
87 | return r;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/test/java/DummyTest.java:
--------------------------------------------------------------------------------
1 | import hudson.util.VersionNumber;
2 | import junit.framework.TestCase;
3 |
4 | /**
5 | * @author Kohsuke Kawaguchi
6 | */
7 | public class DummyTest extends TestCase {
8 | public void test1() {} // work around a bug in surefire plugin
9 |
10 | public void test2() {
11 | assertTrue(new VersionNumber("1.0-alpha-1").compareTo(new VersionNumber("1.0"))<0);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/ListExisting.java:
--------------------------------------------------------------------------------
1 | import io.jenkins.update_center.DefaultMavenRepositoryBuilder;
2 | import io.jenkins.update_center.HPI;
3 | import io.jenkins.update_center.MavenRepository;
4 | import io.jenkins.update_center.Plugin;
5 |
6 | import java.util.Collection;
7 | import java.util.Set;
8 | import java.util.TreeSet;
9 |
10 | /**
11 | * List up existing groupIds used by plugins.
12 | *
13 | * @author Kohsuke Kawaguchi
14 | */
15 | public class ListExisting {
16 | public static void main(String[] args) throws Exception{
17 | MavenRepository r = DefaultMavenRepositoryBuilder.getInstance();
18 |
19 | Set groupIds = new TreeSet();
20 | Collection all = r.listJenkinsPlugins();
21 | for (Plugin p : all) {
22 | HPI hpi = p.getLatest();
23 | groupIds.add(hpi.artifact.groupId);
24 | }
25 |
26 | for (String groupId : groupIds) {
27 | System.out.println(groupId);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/ListPluginsAndVersions.java:
--------------------------------------------------------------------------------
1 | import io.jenkins.update_center.DefaultMavenRepositoryBuilder;
2 | import io.jenkins.update_center.HPI;
3 | import io.jenkins.update_center.MavenRepository;
4 | import io.jenkins.update_center.Plugin;
5 |
6 | import java.util.Collection;
7 |
8 | /**
9 | * Test program that lists all the plugin names and their versions.
10 | *
11 | * @author Kohsuke Kawaguchi
12 | */
13 | public class ListPluginsAndVersions {
14 | public static void main(String[] args) throws Exception{
15 | MavenRepository r = DefaultMavenRepositoryBuilder.getInstance();
16 |
17 | System.out.println(r.getJenkinsWarsByVersionNumber().firstKey());
18 |
19 | Collection all = r.listJenkinsPlugins();
20 | for (Plugin p : all) {
21 | HPI hpi = p.getLatest();
22 | System.out.printf("%s\t%s\n", p.getArtifactId(), hpi.toString());
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/ArtifactCoordinatesTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | public class ArtifactCoordinatesTest {
8 |
9 | @Test
10 | public void testVersionValid() {
11 | assertVersionIsValid("2", true);
12 | assertVersionIsValid("3.1-rc4", true);
13 | assertVersionIsValid("4-beta", true);
14 | assertVersionIsValid("2g", false);
15 | assertVersionIsValid("g2", false);
16 | assertVersionIsValid("-2", false);
17 | }
18 |
19 | private void assertVersionIsValid(String version, boolean valid) {
20 | ArtifactCoordinates coordinates = new ArtifactCoordinates("", "", version, "");
21 | assertEquals(valid, coordinates.isVersionValid());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/DeprecationTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.nio.file.Files;
9 | import java.util.Properties;
10 |
11 | import static org.junit.Assert.assertFalse;
12 |
13 | public class DeprecationTest {
14 |
15 | @Test
16 | public void noOverlap() throws IOException {
17 | Properties ignore = new Properties();
18 | InputStream stream = Files.newInputStream(new File(Main.resourcesDir,
19 | "artifact-ignores.properties").toPath());
20 | ignore.load(stream);
21 | Properties deprecations = new Properties();
22 | stream = Files.newInputStream(new File(Main.resourcesDir,
23 | "deprecations.properties").toPath());
24 | deprecations.load(stream);
25 | for (String key: deprecations.stringPropertyNames()) {
26 | assertFalse(key, ignore.containsKey(key));
27 | }
28 | for (String key: ignore.stringPropertyNames()) {
29 | assertFalse(key, deprecations.containsKey(key));
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/GitHubSourceTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import okhttp3.mockwebserver.MockResponse;
4 | import okhttp3.mockwebserver.MockWebServer;
5 | import org.apache.commons.io.IOUtils;
6 | import org.junit.Test;
7 |
8 | import java.io.IOException;
9 | import java.util.Arrays;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | public class GitHubSourceTest {
14 |
15 | @Test
16 | public void testCodeQL() throws Exception {
17 | MockWebServer server = new MockWebServer();
18 |
19 | server.enqueue(new MockResponse().setBody(
20 | IOUtils.toString(
21 | this.getClass().getClassLoader().getResourceAsStream("github_graphql_null.txt"),
22 | "UTF-8"
23 | )
24 | ));
25 | server.enqueue(new MockResponse().setBody(
26 | IOUtils.toString(
27 | this.getClass().getClassLoader().getResourceAsStream("github_graphql_Y3Vyc29yOnYyOpHOA0oRaA==.txt"),
28 | "UTF-8"
29 | )
30 | ));
31 | // Start the server.
32 | server.start();
33 |
34 | GitHubSource gh = new MockWebServerGitHubSource(server);
35 | assertEquals(Arrays.asList("cmake","jenkins-plugin", "jenkins-builder", "pipeline"), gh.getRepositoryTopics("jenkinsci", "cmakebuilder-plugin"));
36 | assertEquals("incoming", gh.getDefaultBranch("jenkinsci", "jmdns"));
37 | // Shut down the server. Instances cannot be reused.
38 | server.shutdown();
39 | }
40 |
41 | private static class MockWebServerGitHubSource extends GitHubSource {
42 | private final MockWebServer server;
43 |
44 | private MockWebServerGitHubSource(MockWebServer server) throws IOException {
45 | this.server = server;
46 | initializeOrganizationData("jenkinsci");
47 | }
48 |
49 | @Override
50 | protected String getGraphqlUrl() {
51 | return server.url("/graphql").toString();
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/HPITest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertFalse;
6 | import static org.junit.Assert.assertTrue;
7 |
8 | public class HPITest {
9 | @Test
10 | public void isValidCoreDependencyTest() {
11 | assertTrue(HPI.isValidCoreDependency("1.0"));
12 | assertTrue(HPI.isValidCoreDependency("1.654"));
13 | assertTrue(HPI.isValidCoreDependency("2.0"));
14 | assertTrue(HPI.isValidCoreDependency("2.1"));
15 | assertTrue(HPI.isValidCoreDependency("2.1000"));
16 | assertFalse(HPI.isValidCoreDependency("2.00"));
17 | assertFalse(HPI.isValidCoreDependency("2.01"));
18 | assertFalse(HPI.isValidCoreDependency("2.100-SNAPSHOT"));
19 | assertFalse(HPI.isValidCoreDependency("2.0-rc-1"));
20 | assertFalse(HPI.isValidCoreDependency("2.0-rc-1.vabcd1234"));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/JsonChecksumTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.annotation.JSONField;
5 | import com.alibaba.fastjson.serializer.SerializerFeature;
6 | import net.sf.json.JSONArray;
7 | import net.sf.json.JSONObject;
8 | import org.junit.Assert;
9 | import org.junit.Test;
10 |
11 | import java.io.StringWriter;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class JsonChecksumTest {
16 | @Test
17 | public void testJsonChecksum() throws Exception {
18 |
19 | // step 1: Generate JSON using fastjson
20 | Root root = new Root();
21 |
22 | StringWriter fastJsonWriter = new StringWriter();
23 | JSON.writeJSONString(fastJsonWriter, root, SerializerFeature.DisableCircularReferenceDetect);
24 | String fastJsonOutput = fastJsonWriter.getBuffer().toString();
25 |
26 | // step 2: Load generated JSON with json-lib, re-write it canonically
27 | JSONObject o = JSONObject.fromObject(fastJsonOutput);
28 |
29 | StringWriter jsonLibWriter = new StringWriter();
30 | o.writeCanonical(jsonLibWriter);
31 | String jsonLibOutput = jsonLibWriter.getBuffer().toString();
32 |
33 | Assert.assertEquals("JSONlib and fastjson should generate same output", jsonLibOutput, fastJsonOutput);
34 |
35 | // step 3: Generate equivalent JSON with json-lib, compare
36 | StringWriter jsonLibWriter2 = new StringWriter();
37 | root.toJSON().writeCanonical(jsonLibWriter2);
38 | String jsonLibOutput2 = jsonLibWriter2.getBuffer().toString();
39 |
40 | Assert.assertEquals("JSONlib after load and standalone should be the same", jsonLibOutput, jsonLibOutput2);
41 | }
42 |
43 | private static class Root {
44 | @JSONField
45 | public String baz;
46 |
47 | @JSONField
48 | public List entries;
49 |
50 | @JSONField
51 | public String foo;
52 |
53 | @JSONField
54 | public String bar;
55 |
56 | @JSONField
57 | public long getLong() {
58 | return Long.MAX_VALUE;
59 | };
60 |
61 | @JSONField
62 | public float random;
63 |
64 | public Root() {
65 | random = (float) Math.random();
66 | entries = new ArrayList<>();
67 | entries.add(new ListEntry("one"));
68 | entries.add(new ListEntry("two"));
69 | entries.add(new ListEntry("three"));
70 | entries.add(new ListEntry("four"));
71 |
72 | bar = "bar";
73 | baz = "qux";
74 | foo = "Quuux";
75 | }
76 |
77 | public JSONObject toJSON() {
78 | JSONObject o = new JSONObject();
79 | o.put("bar", bar);
80 | o.put("foo", foo);
81 | o.put("baz", baz);
82 | o.put("random", random);
83 | o.put("long", getLong());
84 | JSONArray a = new JSONArray();
85 | entries.forEach(e -> a.add(e.toJSON()));
86 | o.put("entries", a);
87 | return o;
88 | }
89 | }
90 |
91 | private static class ListEntry {
92 | @JSONField
93 | public String value;
94 | public ListEntry(String value) {
95 | this.value = value;
96 | }
97 |
98 | @JSONField
99 | public String getFoo() {
100 | return "\u0000";
101 | }
102 | @JSONField
103 | public String getBar() {
104 | return "\uD834\uDD1E";
105 | }
106 |
107 | public JSONObject toJSON() {
108 | JSONObject o = new JSONObject();
109 | o.put("value", value);
110 | o.put("foo", getFoo());
111 | o.put("bar", getBar());
112 | return o;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/MaintainersSourceTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.util.List;
7 |
8 | public class MaintainersSourceTest {
9 | @Test
10 | public void testContent() {
11 | final List maintainers = MaintainersSource.getInstance().getMaintainers(new ArtifactCoordinates("org.jenkins-ci.plugins", "matrix-auth", "unused", "unused"));
12 | Assert.assertEquals("matrix-auth has one maintainer", 1, maintainers.size());
13 | final MaintainersSource.Maintainer maintainer = maintainers.get(0);
14 | Assert.assertEquals("User ID expected", "danielbeck", maintainer.getDeveloperId());
15 | Assert.assertEquals("Display name expected", "Daniel Beck", maintainer.getName());
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/SanitizerTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import org.junit.Test;
4 | import org.owasp.html.HtmlSanitizer;
5 | import org.owasp.html.HtmlStreamRenderer;
6 |
7 | import java.util.logging.Level;
8 | import java.util.logging.Logger;
9 |
10 | import static org.hamcrest.Matchers.allOf;
11 | import static org.hamcrest.Matchers.containsString;
12 | import static org.hamcrest.Matchers.is;
13 | import static org.hamcrest.MatcherAssert.assertThat;
14 |
15 | public class SanitizerTest {
16 | private static final Logger LOGGER = Logger.getLogger(SanitizerTest.class.getName());
17 |
18 | @Test
19 | public void testSanitizer() {
20 | assertSanitize("strong!", "strong!");
21 | assertSanitize("foo", "foo
");
22 | assertSanitize("this is the logo:", "this is the logo:
");
23 | assertSanitize("this is the URL", "this is the URL");
24 | assertSanitize("this is the URL", "this is the URL");
25 | assertSanitize("this is the URL", "this is the URL");
26 | assertSanitize("this is the URL", "this is the URL");
27 | }
28 |
29 | private void assertSanitize(String expected, String input) {
30 | StringBuilder b = new StringBuilder();
31 | HtmlStreamRenderer renderer = HtmlStreamRenderer.create(b, Throwable::printStackTrace, html -> LOGGER.log(Level.INFO, "Bad HTML: '" + html + "'"));
32 | HtmlSanitizer.sanitize(input, HPI.HTML_POLICY.apply(renderer), HPI.PRE_PROCESSOR);
33 | assertSanitizer336Workaround(expected, b.toString());
34 | }
35 |
36 | // Workaround for https://github.com/OWASP/java-html-sanitizer/issues/336
37 | private void assertSanitizer336Workaround(String expected, String actual) {
38 | if (expected.contains("rel")) {
39 | assertThat(expected, allOf(containsString("nofollow"), containsString("noopener"), containsString("noreferrer")));
40 | assertThat(actual, allOf(containsString("nofollow"), containsString("noopener"), containsString("noreferrer")));
41 | }
42 | expected = expected.replace("nofollow", "*").replace("noopener", "*").replace("noreferrer", "*");
43 | actual = actual.replace("nofollow", "*").replace("noopener", "*").replace("noreferrer", "*");
44 | assertThat(expected, is(actual));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/SignerTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import io.jenkins.update_center.json.JsonSignature;
4 | import java.nio.file.StandardCopyOption;
5 | import java.util.Collections;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.nio.file.Files;
9 | import java.nio.file.Path;
10 | import java.security.GeneralSecurityException;
11 | import org.junit.Test;
12 |
13 | import static org.hamcrest.CoreMatchers.is;
14 | import static org.hamcrest.CoreMatchers.notNullValue;
15 | import static org.hamcrest.MatcherAssert.assertThat;
16 |
17 | // The resources used in these tests expire in 2034.
18 | // Generated with:
19 | // OpenSSL 3.3.2 3 Sep 2024 (Library: OpenSSL 3.3.2 3 Sep 2024)
20 | // Using:
21 | // openssl genrsa [-traditional] -out FILENAME.key 4096
22 | // openssl req -new -x509 -days 3650 -key FILENAME.key -out FILENAME.cert -subj "/C=/ST=/L=/O=SignerTest/OU=SignerTest/CN=SignerTest/emailAddress=example@example.invalid"
23 | public class SignerTest {
24 | @Test
25 | public void traditionalFormat() throws IOException, GeneralSecurityException {
26 | Signer signer = new Signer();
27 | try (InputStream is = SignerTest.class.getResourceAsStream("/traditional.key")) {
28 | final Path filePath = Files.createTempFile("update-center2-", ".key");
29 | Files.copy(is, filePath, StandardCopyOption.REPLACE_EXISTING);
30 | signer.privateKey = filePath.toFile();
31 | }
32 | try (InputStream is = SignerTest.class.getResourceAsStream("/traditional.cert")) {
33 | final Path filePath = Files.createTempFile("update-center2-", ".cert");
34 | Files.copy(is, filePath, StandardCopyOption.REPLACE_EXISTING);
35 | signer.certificates = Collections.singletonList(filePath.toFile());
36 | }
37 |
38 | final JsonSignature signature = signer.sign("{}");
39 | assertThat(signature.getSignature512(), notNullValue());
40 | assertThat(signature.getDigest512(), notNullValue());
41 | assertThat(signature.getSignature(), notNullValue());
42 | assertThat(signature.getCertificates().size(), is(1));
43 | }
44 |
45 | @Test
46 | public void modernFormat() throws IOException, GeneralSecurityException {
47 | Signer signer = new Signer();
48 | try (InputStream is = SignerTest.class.getResourceAsStream("/modern.key")) {
49 | final Path filePath = Files.createTempFile("update-center2-", ".key");
50 | Files.copy(is, filePath, StandardCopyOption.REPLACE_EXISTING);
51 | signer.privateKey = filePath.toFile();
52 | }
53 | try (InputStream is = SignerTest.class.getResourceAsStream("/modern.cert")) {
54 | final Path filePath = Files.createTempFile("update-center2-", ".cert");
55 | Files.copy(is, filePath, StandardCopyOption.REPLACE_EXISTING);
56 | signer.certificates = Collections.singletonList(filePath.toFile());
57 | }
58 |
59 | final JsonSignature signature = signer.sign("{}");
60 | assertThat(signature.getSignature512(), notNullValue());
61 | assertThat(signature.getDigest512(), notNullValue());
62 | assertThat(signature.getSignature(), notNullValue());
63 | assertThat(signature.getCertificates().size(), is(1));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/TimestampTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import io.jenkins.update_center.json.WithSignature;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | import java.time.Instant;
8 | import java.time.temporal.ChronoUnit;
9 |
10 | public class TimestampTest {
11 | @Test
12 | public void checkTimestamp() throws Exception {
13 | WithSignature w = new WithSignature() {
14 | };
15 |
16 | String timestamp = w.getGenerationTimestamp();
17 |
18 | Assert.assertTrue("format as expected", timestamp.matches("^202[0-9][-][0-9]{2}[-][0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$"));
19 |
20 | Instant parsed = Instant.parse(timestamp);
21 |
22 | Assert.assertTrue("before now", parsed.isBefore(Instant.now()));
23 | Assert.assertTrue("very recent", parsed.isAfter(Instant.now().minus(1, ChronoUnit.SECONDS)));
24 |
25 | String timestamp2 = w.getGenerationTimestamp();
26 | Assert.assertEquals("Doesn't change over time", timestamp, timestamp2);
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/update_center/WarningsTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.update_center;
2 |
3 | import junit.framework.Assert;
4 | import net.sf.json.JSONArray;
5 | import net.sf.json.JSONException;
6 | import net.sf.json.JSONObject;
7 | import org.apache.commons.io.IOUtils;
8 | import org.junit.Test;
9 |
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.IOException;
13 | import java.net.URI;
14 | import java.net.http.HttpClient;
15 | import java.net.http.HttpRequest;
16 | import java.net.http.HttpResponse;
17 | import java.nio.file.Files;
18 | import java.util.ArrayList;
19 | import java.util.HashMap;
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.regex.Pattern;
23 |
24 | public class WarningsTest {
25 | @Test
26 | public void testValidJsonFile() throws Exception {
27 | String warningsText = IOUtils.toString(new FileInputStream(new File("resources/warnings.json")));
28 | JSONArray warnings = JSONArray.fromObject(warningsText);
29 |
30 | for (int i = 0 ; i < warnings.size() ; i++) {
31 | JSONObject o = warnings.getJSONObject(i);
32 | assertNonEmptyString(o.getString("id"));
33 | assertNonEmptyString(o.getString("type"));
34 | assertNonEmptyString(o.getString("name"));
35 | assertNonEmptyString(o.getString("message"));
36 | assertNonEmptyString(o.getString("url"));
37 | JSONArray versions = o.getJSONArray("versions");
38 | for (int j = 0 ; j < versions.size() ; j++) {
39 | JSONObject version = versions.getJSONObject(j);
40 | String pattern = version.getString("pattern");
41 | assertNonEmptyString(pattern);
42 | Pattern p = Pattern.compile(pattern);
43 | }
44 | }
45 | }
46 |
47 | private void assertNonEmptyString(String str) {
48 | Assert.assertNotNull(str);
49 | Assert.assertFalse("".equals(str));
50 | }
51 |
52 | static class Warning {
53 | public String id;
54 | public Map versions = new HashMap<>();
55 | }
56 |
57 | private Map> loadPluginWarnings() throws IOException {
58 | Map> loadedWarnings = new HashMap<>();
59 |
60 | String warningsText = IOUtils.toString(Files.newInputStream(new File(Main.resourcesDir, "warnings.json").toPath()));
61 | JSONArray warnings = JSONArray.fromObject(warningsText);
62 |
63 | for (int i = 0 ; i < warnings.size() ; i++) {
64 | JSONObject o = warnings.getJSONObject(i);
65 |
66 | if (o.getString("type").equals("core")) {
67 | continue;
68 | }
69 |
70 | Warning warning = new Warning();
71 | warning.id = o.getString("url") + " / " + o.getString("id");
72 |
73 | JSONArray versions = o.getJSONArray("versions");
74 | for (int j = 0 ; j < versions.size() ; j++) {
75 | JSONObject version = versions.getJSONObject(j);
76 | String pattern = version.getString("pattern");
77 | assertNonEmptyString(pattern);
78 |
79 | if (pattern.contains("beta")) {
80 | // ignore for this test as these don't show in release history but we don't have an experimental release history
81 | continue;
82 | }
83 |
84 | Pattern p = Pattern.compile(pattern);
85 | warning.versions.put(p, false);
86 | }
87 |
88 | if (!loadedWarnings.containsKey(o.getString("name"))) {
89 | loadedWarnings.put(o.getString("name"), new ArrayList());
90 | }
91 | loadedWarnings.get(o.getString("name")).add(warning);
92 | }
93 | return loadedWarnings;
94 | }
95 |
96 | private static void testForWarning(String gav, Map> warnings) {
97 | String[] gavParts = gav.split(":");
98 | String pluginId = gavParts[1];
99 | String version = gavParts[2];
100 | if (warnings.containsKey(pluginId)) {
101 | for (Warning warning : warnings.get(pluginId)) {
102 | Map versions = warning.versions;
103 | for (Pattern p : versions.keySet()) {
104 | if (p.matcher(version).matches()) {
105 | versions.replace(p, true);
106 | // written to target/surefire-reports/io.jenkins.update_center.WarningsTest-output.txt
107 | System.out.println("Warning " + warning.id + " matches " + gav);
108 | } else {
109 | System.out.println("Warning " + warning.id + " does NOT match " + gav);
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
116 | @Test
117 | public void testWarningsAgainstReleaseHistory() throws IOException {
118 | Map> warnings = loadPluginWarnings();
119 |
120 | JSONObject json;
121 | try (final HttpClient client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build()) {
122 | final HttpRequest request = HttpRequest.newBuilder()
123 | .uri(URI.create("https://updates.jenkins.io/release-history.json"))
124 | .GET()
125 | .build();
126 | final String body = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
127 | json = JSONObject.fromObject(body);
128 | } catch (InterruptedException e) {
129 | throw new IOException(e);
130 | }
131 |
132 | JSONArray dates = json.getJSONArray("releaseHistory");
133 |
134 | for (int dateIndex = 0; dateIndex < dates.size(); dateIndex++) {
135 | JSONObject date = dates.getJSONObject(dateIndex);
136 |
137 | JSONArray releases = date.getJSONArray("releases");
138 | for (int releaseIndex = 0; releaseIndex < releases.size(); releaseIndex++) {
139 | JSONObject release = releases.getJSONObject(releaseIndex);
140 |
141 | try {
142 | String gav = release.getString("gav");
143 | testForWarning(gav, warnings);
144 | } catch (JSONException ex) {
145 | // TODO wtf?
146 | }
147 | }
148 | }
149 |
150 | // TODO figure out how to deal with blacklisted plugins
151 | // for (Map.Entry warningEntry : warnings.entrySet()) {
152 | // Warning warning = warningEntry.getValue();
153 | // for (Map.Entry patternBooleanEntry : warning.versions.entrySet()) {
154 | // if (!patternBooleanEntry.getValue()) {
155 | // Assert.fail("Pattern " + patternBooleanEntry.getKey().toString() + " did not match any release");
156 | // }
157 | // }
158 | // }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/test/resources/modern.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
32 | BvsbQAUNn0tnXAeAYyOhM0/Fgg==
33 | -----END CERTIFICATE-----
34 |
--------------------------------------------------------------------------------
/src/test/resources/modern.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | 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
51 | 6y3uXEUymPA0CSxXU7B7EY/M9RBtrQ==
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/src/test/resources/traditional.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
32 | B/Rl5ajepb8AoJQDiFkhxtA/bQ==
33 | -----END CERTIFICATE-----
34 |
--------------------------------------------------------------------------------
/src/test/resources/traditional.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | 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
51 | -----END RSA PRIVATE KEY-----
52 |
--------------------------------------------------------------------------------