[JENKINS-59136] Enable labels/categories with deprecated to show a warning for plugins (#4073)

* Enable labels/categories with `deprecated` to show a warning for plugins

* fix doc

* Wording

Co-Authored-By: Daniel Beck <1831569+daniel-beck@users.noreply.github.com>

* Add Restricted no external use

* add since todo

* spelling

Co-Authored-By: Tim Jacomb <t.jacomb@kainos.com>

* use stream api to collect deprecated plugins

* link to plugin url

* add break lines to deprecation message

* fix regex

* wording and separation of description

* Hide deprecated plugins inside the available section on update center

* Add a warning inside the installed section on plugin manager

* Add support for top-level 'deprecations' element in update site

* Add test

* Make this a list to allow direct element access

* This isn't a critical problem so go with warning style, rephrase label

* Add an intermediate JSON object in the UC data for extensibility

* Make 'deprecated' a meta label (not shown in the regular label list)

* Make parsing this safer

* Null safety with labels array

* Link to the deprecation URL, not the plugin URL

* Add issue reference

* Add Javadoc

Co-authored-by: Daniel Beck <1831569+daniel-beck@users.noreply.github.com>
Co-authored-by: Tim Jacomb <t.jacomb@kainos.com>
Co-authored-by: Daniel Beck <daniel-beck@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2020-07-15 20:50:20 +01:00
commit 55a523e34c
12 files changed
+367 -1

No files matched your search

+28 -1
View File
@@ -145,6 +145,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -2238,6 +2239,32 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
/**
* {@link AdministrativeMonitor} that checks if there are any plugins that are deprecated.
*
* @since TODO
*/
@Restricted(NoExternalUse.class)
@Symbol("pluginDeprecation")
@Extension
public static final class PluginDeprecationMonitor extends AdministrativeMonitor {
@Override
public String getDisplayName() {
return Messages.PluginManager_PluginDeprecationMonitor_DisplayName();
}
public boolean isActivated() {
return !getDeprecatedPlugins().isEmpty();
}
public Map<PluginWrapper, String> getDeprecatedPlugins() {
return Jenkins.get().getPluginManager().getPlugins().stream()
.filter(PluginWrapper::isDeprecated)
.collect(Collectors.toMap(Function.identity(), it -> it.getDeprecations().get(0).url));
}
}
@Restricted(DoNotUse.class)
public String unscientific(double d) {
return String.format(Locale.US, "%15.4f", d);
@@ -2254,7 +2281,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
@Restricted(DoNotUse.class) // Used from table.jelly
public boolean isMetaLabel(String label) {
return "adopt-this-plugin".equals(label);
return "adopt-this-plugin".equals(label) || "deprecated".equals(label);
}
@Restricted(DoNotUse.class) // Used from table.jelly
@@ -364,6 +364,21 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
return dependencies.stream().anyMatch(d -> !d.optional);
}
/**
* Is this plugin deprecated?
*
* @return {@code true} if and only if an update site reports deprecations for this plugin.
* @since TODO
*/
@Restricted(NoExternalUse.class)
public boolean isDeprecated() {
/*
While better handled in UpdateSite.Plugin that only exists for plugins actively being published.
We have no good model for plugin metadata from update sites for plugins not being published.
*/
return !getDeprecations().isEmpty();
}
@ExportedBean
public static final class Dependency {
@Exported
@@ -553,8 +568,11 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
/**
* Returns a one-line descriptive name of this plugin.
*
* @deprecated For most purposes, use {@link #getDisplayName()}.
*/
@Exported
@Deprecated
public String getLongName() {
String name = manifest.getMainAttributes().getValue("Long-Name");
if(name!=null) return name;
@@ -1284,6 +1302,20 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
return ExtensionList.lookupSingleton(UpdateSiteWarningsMonitor.class).getActivePluginWarningsByPlugin().getOrDefault(this, Collections.emptyList());
}
@Restricted(NoExternalUse.class)
public List<UpdateSite.Deprecation> getDeprecations() {
/* Would be much nicer to go through getInfoFromAllSites but that only works for currently published plugins */
List<UpdateSite.Deprecation> deprecations = new ArrayList<>();
for (UpdateSite site : Jenkins.get().getUpdateCenter().getSites()) {
for (Map.Entry<String, UpdateSite.Deprecation> entry : site.getData().getDeprecations().entrySet()) {
if (entry.getKey().equals(this.shortName)) {
deprecations.add(entry.getValue());
}
}
}
return deprecations;
}
private static final Logger LOGGER = Logger.getLogger(PluginWrapper.class.getName());
/**
@@ -45,13 +45,17 @@ import java.net.URL;
import java.net.URLEncoder;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
@@ -555,6 +559,13 @@ public class UpdateSite {
*/
private final Set<Warning> warnings = new HashSet<>();
/**
* Mapping of plugin IDs to deprecation notices
*
* @since TODO
*/
private final Map<String, Deprecation> deprecations = new HashMap<>();
/**
* If this is non-null, Jenkins is going to check the connectivity to this URL to make sure
* the network connection is up. Null to skip the check.
@@ -581,6 +592,24 @@ public class UpdateSite {
}
}
JSONObject deprecations = o.optJSONObject("deprecations");
if (deprecations != null) {
for (Iterator it = deprecations.keys(); it.hasNext(); ) {
try {
String pluginId = it.next().toString();
JSONObject entry = deprecations.getJSONObject(pluginId); // additional level of indirection to support future extensibility
if (entry != null) {
String referenceUrl = entry.getString("url");
if (referenceUrl != null) {
this.deprecations.put(pluginId, new Deprecation(referenceUrl));
}
}
} catch (RuntimeException ex) {
LOGGER.log(Level.WARNING, "Failed to parse JSON for deprecation", ex);
}
}
}
for(Map.Entry<String,JSONObject> e : (Set<Map.Entry<String,JSONObject>>)o.getJSONObject("plugins").entrySet()) {
Plugin p = new Plugin(sourceId, e.getValue());
// JENKINS-33308 - include implied dependencies for older plugins that may need them
@@ -593,6 +622,16 @@ public class UpdateSite {
}
}
plugins.put(Util.intern(e.getKey()), p);
// compatibility with update sites that have no separate 'deprecated' top-level entry.
// Also do this even if there are deprecations to potentially allow limiting the top-level entry to overridden URLs.
if (p.categories != null) {
if (Arrays.asList(p.categories).contains("deprecated")) {
if (!this.deprecations.containsKey(p.name)) {
this.deprecations.put(p.name, new Deprecation(p.wiki));
}
}
}
}
connectionCheckUrl = (String)o.get("connectionCheckUrl");
@@ -608,6 +647,16 @@ public class UpdateSite {
return this.warnings;
}
/**
* Returns the deprecations provided by the update site
* @return the deprecations provided by the update site
* @since TODO
*/
@Restricted(NoExternalUse.class)
public Map<String, Deprecation> getDeprecations() {
return this.deprecations;
}
/**
* Is there a new version of the core?
*/
@@ -790,6 +839,37 @@ public class UpdateSite {
}
}
/**
* Represents a deprecation of a certain component. Jenkins project policy determines exactly what it means.
*
* @since TODO
*/
@Restricted(NoExternalUse.class)
public static final class Deprecation {
/**
* URL for this deprecation.
*
* Jenkins will show a link to this URL when displaying the deprecation message.
*/
public final String url;
public Deprecation(String url) {
this.url = url;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Deprecation that = (Deprecation) o;
return Objects.equals(url, that.url);
}
@Override
public int hashCode() {
return Objects.hash(url);
}
}
/**
* Represents a warning about a certain component, mostly related to known security issues.
*
@@ -1106,7 +1186,15 @@ public class UpdateSite {
}
@Restricted(NoExternalUse.class)
public boolean isDeprecated() {
return getDeprecation() != null;
}
@Restricted(NoExternalUse.class)
public UpdateSite.Deprecation getDeprecation() {
return Jenkins.get().getUpdateCenter().getSite(sourceId).getData().getDeprecations().get(this.name);
}
public String getDisplayName() {
String displayName;