diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index c781ceef06d7..70391d2e42dd 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -51,7 +51,7 @@ jobs:
private_key: ${{ secrets.JENKINS_CHANGELOG_UPDATER_PRIVATE_KEY }}
repository: jenkins-infra/jenkins.io
- name: Check out
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Publish jenkins.io changelog draft
diff --git a/.github/workflows/publish-release-artifact.yml b/.github/workflows/publish-release-artifact.yml
index 7333a12285e8..222b2b5d2ffb 100644
--- a/.github/workflows/publish-release-artifact.yml
+++ b/.github/workflows/publish-release-artifact.yml
@@ -14,7 +14,7 @@ jobs:
project-version: ${{ steps.set-version.outputs.project-version }}
is-lts: ${{ steps.set-version.outputs.is-lts }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
diff --git a/bom/pom.xml b/bom/pom.xml
index 537d08f11a36..ea33fc828776 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -39,7 +39,7 @@ THE SOFTWARE.
9.5
- 2.0.7
+ 2.0.91802.v9e2750160d012.4.21
@@ -64,7 +64,7 @@ THE SOFTWARE.
org.springframework.securityspring-security-bom
- 5.8.5
+ 5.8.6pomimport
@@ -189,12 +189,12 @@ THE SOFTWARE.
org.apache.antant
- 1.10.13
+ 1.10.14org.apache.commonscommons-compress
- 1.23.0
+ 1.24.0org.codehaus.groovy
diff --git a/core/src/main/java/hudson/init/InitStrategy.java b/core/src/main/java/hudson/init/InitStrategy.java
index be3312a1ff8a..95c31b94bf53 100644
--- a/core/src/main/java/hudson/init/InitStrategy.java
+++ b/core/src/main/java/hudson/init/InitStrategy.java
@@ -9,6 +9,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
@@ -64,7 +65,11 @@ private void listPluginFiles(PluginManager pm, String extension, Collection pluginFiles = new ArrayList<>();
+ pluginFiles.addAll(List.of(files));
+ pluginFiles.sort(Comparator.comparing(File::getName));
+
+ all.addAll(pluginFiles);
}
/**
@@ -76,15 +81,16 @@ private void listPluginFiles(PluginManager pm, String extension, Collection r) {
String hplProperty = SystemProperties.getString("hudson.bundled.plugins");
if (hplProperty != null) {
+ List pluginFiles = new ArrayList<>();
for (String hplLocation : hplProperty.split(",")) {
File hpl = new File(hplLocation.trim());
if (hpl.exists()) {
- r.add(hpl);
+ pluginFiles.add(hpl);
} else if (hpl.getName().contains("*")) {
try {
new DirScanner.Glob(hpl.getName(), null).scan(hpl.getParentFile(), new FileVisitor() {
@Override public void visit(File f, String relativePath) throws IOException {
- r.add(f);
+ pluginFiles.add(f);
}
});
} catch (IOException x) {
@@ -94,6 +100,8 @@ protected void getBundledPluginsFromProperty(final List r) {
LOGGER.warning("bundled plugin " + hplLocation + " does not exist");
}
}
+ pluginFiles.sort(Comparator.comparing(File::getName));
+ r.addAll(pluginFiles);
}
}
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index 43604d408aac..b0c32f212a09 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -592,10 +592,16 @@ public static Set parse(@CheckForNull String labels) {
final Set r = new TreeSet<>();
labels = fixNull(labels);
if (labels.length() > 0) {
- final QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(labels);
- while (tokenizer.hasMoreTokens())
- r.add(Jenkins.get().getLabelAtom(tokenizer.nextToken()));
+ Jenkins j = Jenkins.get();
+ LabelAtom labelAtom = j.tryGetLabelAtom(labels);
+ if (labelAtom == null) {
+ final QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(labels);
+ while (tokenizer.hasMoreTokens())
+ r.add(j.getLabelAtom(tokenizer.nextToken()));
+ } else {
+ r.add(labelAtom);
}
+ }
return r;
}
diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java
index 519d89c10970..2d34e6e90ade 100644
--- a/core/src/main/java/hudson/model/Node.java
+++ b/core/src/main/java/hudson/model/Node.java
@@ -68,6 +68,7 @@
import net.sf.json.JSONObject;
import org.jvnet.localizer.Localizable;
import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.accmod.restrictions.ProtectedExternally;
import org.kohsuke.stapler.BindInterceptor;
import org.kohsuke.stapler.Stapler;
@@ -298,6 +299,17 @@ public OfflineCause getTemporaryOfflineCause() {
public TagCloud getLabelCloud() {
return new TagCloud<>(getAssignedLabels(), Label::getTiedJobCount);
}
+
+ /**
+ * @return An immutable set of LabelAtom associated with the current node label.
+ */
+ @NonNull
+ @Restricted(NoExternalUse.class)
+ protected Set getLabelAtomSet() {
+ // Default implementation doesn't cache, since we can't hook on label updates.
+ return Collections.unmodifiableSet(Label.parse(getLabelString()));
+ }
+
/**
* Returns the possibly empty set of labels that are assigned to this node,
* including the automatic {@link #getSelfLabel() self label}, manually
@@ -305,13 +317,13 @@ public TagCloud getLabelCloud() {
* {@link LabelFinder} extension point.
*
* This method has a side effect of updating the hudson-wide set of labels
- * and should be called after events that will change that - e.g. a agent
+ * and should be called after events that will change that - e.g. an agent
* connecting.
*/
@Exported
public Set getAssignedLabels() {
- Set r = Label.parse(getLabelString());
+ Set r = new HashSet<>(getLabelAtomSet());
r.add(getSelfLabel());
r.addAll(getDynamicLabels());
return Collections.unmodifiableSet(r);
diff --git a/core/src/main/java/hudson/model/Slave.java b/core/src/main/java/hudson/model/Slave.java
index 22eb63cd2fc6..fe69bac0af93 100644
--- a/core/src/main/java/hudson/model/Slave.java
+++ b/core/src/main/java/hudson/model/Slave.java
@@ -36,6 +36,7 @@
import hudson.Util;
import hudson.cli.CLI;
import hudson.model.Descriptor.FormException;
+import hudson.model.labels.LabelAtom;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.Which;
@@ -60,6 +61,7 @@
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
@@ -179,6 +181,7 @@ protected Slave(@NonNull String name, String remoteFS, ComputerLauncher launcher
this.name = name;
this.remoteFS = remoteFS;
this.launcher = launcher;
+ this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
}
/**
@@ -193,7 +196,7 @@ protected Slave(@NonNull String name, String nodeDescription, String remoteFS, i
this.numExecutors = numExecutors;
this.mode = mode;
this.remoteFS = Util.fixNull(remoteFS).trim();
- this.label = Util.fixNull(labelString).trim();
+ this.labelAtomSet = Collections.unmodifiableSet(Label.parse(labelString));
this.launcher = launcher;
this.retentionStrategy = retentionStrategy;
getAssignedLabels(); // compute labels now
@@ -308,6 +311,10 @@ public DescribableList, NodePropertyDescriptor> getNodePropertie
@DataBoundSetter
public void setNodeProperties(List extends NodeProperty>> properties) throws IOException {
+ if (nodeProperties == null) {
+ warnPlugin();
+ nodeProperties = new DescribableList<>(this);
+ }
nodeProperties.replaceBy(properties);
}
@@ -328,11 +335,33 @@ public String getLabelString() {
@Override
@DataBoundSetter
public void setLabelString(String labelString) throws IOException {
- this.label = Util.fixNull(labelString).trim();
+ _setLabelString(labelString);
// Compute labels now.
getAssignedLabels();
}
+ private void _setLabelString(String labelString) {
+ this.label = Util.fixNull(labelString).trim();
+ this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
+ }
+
+ @CheckForNull // should be @NonNull, but we've seen plugins overriding readResolve() without calling super.
+ private transient Set labelAtomSet;
+
+ @Override
+ protected Set getLabelAtomSet() {
+ if (labelAtomSet == null) {
+ warnPlugin();
+ this.labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
+ }
+ return labelAtomSet;
+ }
+
+ private void warnPlugin() {
+ LOGGER.log(Level.WARNING, () -> getClass().getName() + " or one of its superclass overrides readResolve() without calling super implementation." +
+ "Please file an issue against the plugin implementing it: " + Jenkins.get().getPluginManager().whichPlugin(getClass()));
+ }
+
@Override
public Callable getClockDifferenceCallable() {
return new GetClockDifference1();
@@ -574,6 +603,7 @@ public int hashCode() {
protected Object readResolve() {
if (nodeProperties == null)
nodeProperties = new DescribableList<>(this);
+ _setLabelString(label);
return this;
}
diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java
index ca62a4d3fe0e..a134755fe95e 100644
--- a/core/src/main/java/hudson/security/SecurityRealm.java
+++ b/core/src/main/java/hudson/security/SecurityRealm.java
@@ -647,15 +647,27 @@ public static String getFrom() {
from = request.getParameter("from");
}
+ // On the 404 error page, use the session attribute it sets
+ if (request != null && request.getRequestURI().equals(request.getContextPath() + "/404")) {
+ final HttpSession session = request.getSession(false);
+ if (session != null) {
+ final Object attribute = session.getAttribute("from");
+ if (attribute != null) {
+ from = attribute.toString();
+ }
+ }
+ }
+
// If entry point was not found, try to deduce it from the request URI
- // except pages related to login process
+ // except pages related to login process and the 404 error page
if (from == null
&& request != null
&& request.getRequestURI() != null
- && !request.getRequestURI().equals("/loginError")
- && !request.getRequestURI().equals("/login")) {
-
- from = request.getRequestURI();
+ // The custom login page makes the next two lines obsolete, but safer to have them.
+ && !request.getRequestURI().equals(request.getContextPath() + "/loginError")
+ && !request.getRequestURI().equals(request.getContextPath() + "/login")
+ && !request.getRequestURI().equals(request.getContextPath() + "/404")) {
+ from = request.getRequestURI();
}
// If deduced entry point isn't deduced yet or the content is a blank value
diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java
index 8f995738683b..4776d8a02d26 100644
--- a/core/src/main/java/hudson/util/ProcessTree.java
+++ b/core/src/main/java/hudson/util/ProcessTree.java
@@ -29,7 +29,6 @@
import static java.util.logging.Level.FINER;
import static java.util.logging.Level.FINEST;
-import com.google.common.primitives.Ints;
import com.sun.jna.LastErrorException;
import com.sun.jna.Memory;
import com.sun.jna.Native;
@@ -750,7 +749,7 @@ abstract static class Unix extends Local {
@CheckForNull
@Override
public OSProcess get(@NonNull Process proc) {
- return get(Ints.checkedCast(proc.pid()));
+ return get(Math.toIntExact(proc.pid()));
}
@Override
diff --git a/core/src/main/java/jenkins/ErrorAttributeFilter.java b/core/src/main/java/jenkins/ErrorAttributeFilter.java
new file mode 100644
index 000000000000..be67bdab7588
--- /dev/null
+++ b/core/src/main/java/jenkins/ErrorAttributeFilter.java
@@ -0,0 +1,41 @@
+package jenkins;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import jenkins.model.Jenkins;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.springframework.security.core.Authentication;
+
+/**
+ * Record the current user authentication for later impersonation if the response is 404 Not Found.
+ *
+ * @see Jenkins#generateNotFoundResponse(org.kohsuke.stapler.StaplerRequest, org.kohsuke.stapler.StaplerResponse)
+ */
+@Restricted(NoExternalUse.class)
+public class ErrorAttributeFilter implements Filter {
+
+ public static final String USER_ATTRIBUTE = "jenkins.ErrorAttributeFilter.user";
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ final Authentication authentication = Jenkins.getAuthentication2();
+ servletRequest.setAttribute(USER_ATTRIBUTE, authentication);
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ @Override
+ public void destroy() {
+ // Otherwise the PCT fails
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // Otherwise the PCT fails
+ }
+}
diff --git a/core/src/main/java/jenkins/appearance/AppearanceCategory.java b/core/src/main/java/jenkins/appearance/AppearanceCategory.java
new file mode 100644
index 000000000000..529c68cdebb5
--- /dev/null
+++ b/core/src/main/java/jenkins/appearance/AppearanceCategory.java
@@ -0,0 +1,53 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2023, Tim Jacomb
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.appearance;
+
+import hudson.Extension;
+import jenkins.model.GlobalConfigurationCategory;
+
+/**
+ *
Global configuration of appearance configuration.
+ *
+ *
This should be used for Plugins that contribute to the look and feel of Jenkins.
+ * Theming, header and footer changes, information density are all good examples.
+ * API plugins for UI components that are used by other plugins also fit into that, e.g. source code display.
+ *
+ *
Configuration specific to a single plugin that is not related to the overall look and feel of Jenkins may not belong here.
+ *
+ *
If a plugin has a single global configuration it should separate appearance and general configuration to different classes.
+ *
+ */
+@Extension
+public class AppearanceCategory extends GlobalConfigurationCategory {
+ @Override
+ public String getShortDescription() {
+ return Messages.AppearanceCategory_DisplayName();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.AppearanceCategory_Description();
+ }
+}
diff --git a/core/src/main/java/jenkins/appearance/AppearanceGlobalConfiguration.java b/core/src/main/java/jenkins/appearance/AppearanceGlobalConfiguration.java
new file mode 100644
index 000000000000..35075425b90f
--- /dev/null
+++ b/core/src/main/java/jenkins/appearance/AppearanceGlobalConfiguration.java
@@ -0,0 +1,110 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2023, Tim Jacomb
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.appearance;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.Functions;
+import hudson.model.Descriptor;
+import hudson.model.ManagementLink;
+import hudson.util.FormApply;
+import java.io.IOException;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.servlet.ServletException;
+import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.verb.POST;
+
+@Extension
+public class AppearanceGlobalConfiguration extends ManagementLink {
+
+ private static final Logger LOGGER = Logger.getLogger(AppearanceGlobalConfiguration.class.getName());
+
+ @Restricted(NoExternalUse.class)
+ public static final Predicate FILTER = input -> input.getCategory() instanceof AppearanceCategory;
+
+ @Override
+ public String getIconFileName() {
+ if (Functions.getSortedDescriptorsForGlobalConfigByDescriptor(FILTER).isEmpty()) {
+ return null;
+ }
+
+ return "symbol-brush-outline";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.AppearanceGlobalConfiguration_DisplayName();
+ }
+
+ @Override
+ public String getDescription() {
+ return Messages.AppearanceGlobalConfiguration_Description();
+ }
+
+ @Override
+ public String getUrlName() {
+ return "appearance";
+ }
+
+ @NonNull
+ @Override
+ public Category getCategory() {
+ return Category.CONFIGURATION;
+ }
+
+ @POST
+ public synchronized void doConfigure(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException {
+ boolean result = configure(req, req.getSubmittedForm());
+ LOGGER.log(Level.FINE, "appearance saved: " + result);
+ FormApply.success(req.getContextPath() + "/manage").generateResponse(req, rsp, null);
+ }
+
+ private boolean configure(StaplerRequest req, JSONObject json) throws Descriptor.FormException, IOException {
+ Jenkins j = Jenkins.get();
+ j.checkPermission(Jenkins.MANAGE);
+
+ boolean result = true;
+ for (Descriptor> d : Functions.getSortedDescriptorsForGlobalConfigByDescriptor(FILTER)) {
+ result &= configureDescriptor(req, json, d);
+ }
+ j.save();
+
+ return result;
+ }
+
+ private boolean configureDescriptor(StaplerRequest req, JSONObject json, Descriptor> d) throws Descriptor.FormException {
+ String name = d.getJsonSafeClassName();
+ JSONObject js = json.has(name) ? json.getJSONObject(name) : new JSONObject(); // if it doesn't have the property, the method returns invalid null object.
+ json.putAll(js);
+ return d.configure(req, js);
+ }
+}
diff --git a/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java b/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
index eb9685f54ba0..cc1d5214e0ae 100644
--- a/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
+++ b/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
@@ -28,6 +28,7 @@
import hudson.Extension;
import hudson.diagnosis.ReverseProxySetupMonitor;
import hudson.model.AdministrativeMonitor;
+import hudson.model.ManageJenkinsAction;
import hudson.model.PageDecorator;
import hudson.util.HudsonIsLoading;
import hudson.util.HudsonIsRestarting;
@@ -53,9 +54,6 @@ public class AdministrativeMonitorsDecorator extends PageDecorator {
private final Collection ignoredJenkinsRestOfUrls = new ArrayList<>();
public AdministrativeMonitorsDecorator() {
- // redundant
- ignoredJenkinsRestOfUrls.add("manage");
-
// otherwise this would be added to every internal context menu building request
ignoredJenkinsRestOfUrls.add("contextMenu");
@@ -165,6 +163,11 @@ public Collection getMonitorsToDisplay() {
return null;
}
+ // Don't show on Manage Jenkins
+ if (o instanceof ManageJenkinsAction) {
+ return null;
+ }
+
// don't show for some URLs served directly by Jenkins
if (o instanceof Jenkins) {
String url = a.getRestOfUrl();
diff --git a/core/src/main/java/jenkins/management/Badge.java b/core/src/main/java/jenkins/management/Badge.java
index 67528ad56b32..6ee8fb25c493 100644
--- a/core/src/main/java/jenkins/management/Badge.java
+++ b/core/src/main/java/jenkins/management/Badge.java
@@ -48,6 +48,10 @@
* A badge might display the same information as an {@link AdministrativeMonitor}. While an {@link AdministrativeMonitor}
* can be disabled, a badge will always be shown. E.g. the badge of {@link OldDataMonitor.ManagementLinkImpl} always shows the number of old data entries.
*
+ *
+ * A badge can also be used in a {@code } to show information on the right of the link in the sidepanel,
+ * e.g. to show number of available plugin updates.
+ *
* @since 2.385
*/
@ExportedBean
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 9bf94cf9389a..d021e5975ec1 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -262,6 +262,7 @@
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import jenkins.AgentProtocol;
+import jenkins.ErrorAttributeFilter;
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner;
@@ -275,6 +276,7 @@
import jenkins.security.ConfidentialStore;
import jenkins.security.MasterToSlaveCallable;
import jenkins.security.RedactSecretJsonInErrorMessageSanitizer;
+import jenkins.security.ResourceDomainConfiguration;
import jenkins.security.SecurityListener;
import jenkins.security.stapler.DoActionFilter;
import jenkins.security.stapler.StaplerDispatchValidator;
@@ -930,7 +932,7 @@ protected Jenkins(File root, ServletContext context, PluginManager pluginManager
Trigger.timer = new java.util.Timer("Jenkins cron thread");
queue = new Queue(LoadBalancer.CONSISTENT_HASH);
-
+ labelAtomSet = Collections.unmodifiableSet(Label.parse(label));
try {
dependencyGraph = DependencyGraph.EMPTY;
} catch (InternalError e) {
@@ -1088,6 +1090,7 @@ protected Object readResolve() {
/* deserializing without a value set means we need to migrate */
nodeRenameMigrationNeeded = true;
}
+ _setLabelString(label);
return this;
}
@@ -1468,6 +1471,14 @@ public boolean hasPeople() {
}
public Api getApi() {
+ /* Do not show "REST API" link in footer when on 404 error page */
+ final StaplerRequest req = Stapler.getCurrentRequest();
+ if (req != null) {
+ final Object attribute = req.getAttribute("javax.servlet.error.message");
+ if (attribute != null) {
+ return null;
+ }
+ }
return new Api(this);
}
@@ -2114,6 +2125,20 @@ public Label getLabel(String expr) {
}
}
+ /**
+ * Returns the label atom of the given name, only if it already exists.
+ * @return non-null if the label atom already exists.
+ */
+ @Restricted(NoExternalUse.class)
+ public @Nullable LabelAtom tryGetLabelAtom(@NonNull String name) {
+ Label label = labels.get(name);
+ if (label instanceof LabelAtom) {
+ return (LabelAtom) label;
+ }
+ return null;
+ }
+
+
/**
* Gets all the active labels in the current system.
*/
@@ -2126,6 +2151,14 @@ public Set