diff --git a/changelog.html b/changelog.html
index ff2228d6f8..a3dcc675e3 100644
--- a/changelog.html
+++ b/changelog.html
@@ -40,11 +40,11 @@ Some tips:
Help other Jenkins users by letting the community know which releases you've used,
and whether they had any significant issues.
Legend:
- = I use it on my production site without major issues.
- = I don't recommend it.
- = I tried it but rolled back to a previous version.
View ratings below, and click one of the icons next to your version to provide your input.
@@ -56,15 +56,108 @@ Upcoming changes
-
- Developer API: Add a try with resources form of impersonation.
- (issue 36494)
-
- Minor optimization in calculation of recent build stability health report
- (issue 36629)
+ Fix plugin dependency resolution. Jenkins will now refuse to load plugins with unsatisfied dependencies, which resulted in difficult to diagnose problems. This may result in errors on startup if your instance has an invalid plugin configuration., check the Jenkins log for details.
+
+ Decouple bouncycastle libraries from Jenkins into bouncycastle-api plugin.
+ (issue 36923)
+
+ Upgrade to instance-identity module 2.1.
+ (issue 36922)
+
+ Hide the Java Web Start launcher when the TCP agent port is disabled.
+ (issue 36996)
+
+ Allow admins to control the enabled agent protocols on their instance from the global security settings screen.
+ (issue 37032)
+
+ Make sure that the All view is created.
+ (issue 36908)
+
+ Remove trailing space from Hudson.DisplayName in Spanish, which resulted in problems with Blue Ocean.
+ (issue 36940)
+
+ Tell browsers not to cache or try to autocomplete forms in Jenkins to prevent problems due to invalid data in form submissions.
+ From now on, only select form fields (e.g. job name) will offer autocompletion.
+ (issue 18435)
+
+ Add a cache for user information to fix performance regression due to SECURITY-243.
+ (issue 35493)
+
+ Prevent null pointer exceptions when not entering a cron spec for a trigger.
+ (issue 36748)
+
+ Defend against some fatal startup errors.
+ (issue 36666)
+
+ Use the icon specified by the computer implementation on its overview page.
+ (issue 36775)
+
+ Internal: Extract the CLI command offline-node from core.
+ (issue 34468)
+
diff --git a/cli/pom.xml b/cli/pom.xml
index 85514610ff..21ba2f46cf 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
org.jenkins-ci.mainpom
- 2.14-SNAPSHOT
+ 2.17-SNAPSHOTcli
@@ -92,6 +92,10 @@
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
diff --git a/core/pom.xml b/core/pom.xml
index 34a6bafa58..db5de4ddbe 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.mainpom
- 2.14-SNAPSHOT
+ 2.17-SNAPSHOTjenkins-core
@@ -42,6 +42,8 @@ THE SOFTWARE.
1.2432.5.6.SEC032.4.7
+
+ false
@@ -803,6 +805,10 @@ THE SOFTWARE.
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
@@ -878,20 +884,12 @@ THE SOFTWARE.
+
findbugs
-
-
-
- org.codehaus.mojo
- findbugs-maven-plugin
-
- Max
- High
- src/findbugs-filter.xml
-
-
-
-
+
+
+ true
+
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index 7164ffeef6..ff933347c3 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -57,17 +57,13 @@ import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URI;
import java.net.URL;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
@@ -277,7 +273,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
// some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(detached.splitWhen) <= 0) {
- out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requireVersion));
+ out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requiredVersion));
LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {pluginName, detached.shortName, jenkinsVersion});
}
}
@@ -378,12 +374,12 @@ public class ClassicPluginStrategy implements PluginStrategy {
* be "1.123.*" (because 1.124 will be the first version that doesn't include the removed code.)
*/
private final VersionNumber splitWhen;
- private final String requireVersion;
+ private final String requiredVersion;
- private DetachedPlugin(String shortName, String splitWhen, String requireVersion) {
+ private DetachedPlugin(String shortName, String splitWhen, String requiredVersion) {
this.shortName = shortName;
this.splitWhen = new VersionNumber(splitWhen);
- this.requireVersion = requireVersion;
+ this.requiredVersion = requiredVersion;
}
/**
@@ -401,6 +397,16 @@ public class ClassicPluginStrategy implements PluginStrategy {
public VersionNumber getSplitWhen() {
return splitWhen;
}
+
+ /**
+ * Gets the minimum required version for the current version of Jenkins.
+ *
+ * @return the minimum required version for the current version of Jenkins.
+ * @sice 2.16
+ */
+ public VersionNumber getRequiredVersion() {
+ return new VersionNumber(requiredVersion);
+ }
}
private static final List DETACHED_LIST = Collections.unmodifiableList(Arrays.asList(
@@ -417,7 +423,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
new DetachedPlugin("windows-slaves", "1.547.*", "1.0"),
new DetachedPlugin("antisamy-markup-formatter", "1.553.*", "1.0"),
new DetachedPlugin("matrix-project", "1.561.*", "1.0"),
- new DetachedPlugin("junit", "1.577.*", "1.0")
+ new DetachedPlugin("junit", "1.577.*", "1.0"),
+ new DetachedPlugin("bouncycastle-api", "2.16.*", "2.16.0")
));
/** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index e2e7e7ce69..85a148595f 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -154,6 +154,7 @@ import org.kohsuke.stapler.jelly.InternationalizedStringExpression.RawHtmlArgume
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
+import hudson.model.PasswordParameterDefinition;
import hudson.util.RunList;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.CheckForNull;
@@ -1779,7 +1780,7 @@ public class Functions {
}
return ((Secret) o).getEncryptedValue();
}
- if (getIsUnitTest()) {
+ if (getIsUnitTest() && !o.equals(PasswordParameterDefinition.DEFAULT_VALUE)) {
throw new SecurityException("attempted to render plaintext ‘" + o + "’ in password field; use a getter of type Secret instead");
}
return o.toString();
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index e8ad0b275c..f1a463a5ff 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -466,7 +466,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
if(p.isActive())
activePlugins.add(p);
}
- } catch (CycleDetectedException e) {
+ } catch (CycleDetectedException e) { // TODO this should be impossible, since we override reactOnCycle to not throw the exception
stop(); // disable all plugins since classloading from them can lead to StackOverflow
throw e; // let Hudson fail
}
@@ -506,7 +506,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// schedule execution of loading plugins
for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
- g.followedBy().notFatal().attains(PLUGINS_PREPARED).add("Loading plugin " + p.getShortName(), new Executable() {
+ g.followedBy().notFatal().attains(PLUGINS_PREPARED).add(String.format("Loading plugin %s v%s (%s)", p.getLongName(), p.getVersion(), p.getShortName()), new Executable() {
public void run(Reactor session) throws Exception {
try {
p.resolvePluginDependencies();
@@ -721,6 +721,34 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
new Object[] {lastExecVersion, Jenkins.VERSION, loadedDetached});
InstallUtil.saveLastExecVersion();
+ } else {
+ final Set forceUpgrade = new HashSet<>();
+ for (ClassicPluginStrategy.DetachedPlugin p : ClassicPluginStrategy.getDetachedPlugins()) {
+ VersionNumber installedVersion = getPluginVersion(rootDir, p.getShortName());
+ VersionNumber requiredVersion = p.getRequiredVersion();
+ if (installedVersion != null && installedVersion.isOlderThan(requiredVersion)) {
+ LOGGER.log(Level.WARNING,
+ "Detached plugin {0} found at version {1}, required minimum version is {2}",
+ new Object[]{p.getShortName(), installedVersion, requiredVersion});
+ forceUpgrade.add(p);
+ }
+ }
+ if (!forceUpgrade.isEmpty()) {
+ Set loadedDetached = loadPluginsFromWar("/WEB-INF/detached-plugins", new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ name = normalisePluginName(name);
+ for (ClassicPluginStrategy.DetachedPlugin detachedPlugin : forceUpgrade) {
+ if (detachedPlugin.getShortName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ LOGGER.log(INFO, "Upgraded detached plugins (and dependencies): {0}",
+ new Object[]{loadedDetached});
+ }
}
}
@@ -821,7 +849,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// so existing plugins can't be depending on this newly deployed one.
plugins.add(p);
- activePlugins.add(p);
+ if (p.isActive())
+ activePlugins.add(p);
synchronized (((UberClassLoader) uberClassLoader).loaded) {
((UberClassLoader) uberClassLoader).loaded.clear();
}
@@ -1275,7 +1304,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
* the plugin will only take effect after the reboot.
* See {@link UpdateCenter#isRestartRequiredForCompletion()}
* @return The install job list.
- * @since FIXME
+ * @since 2.0
*/
@Restricted(NoExternalUse.class)
public List> install(@Nonnull Collection plugins, boolean dynamicLoad) {
@@ -1844,14 +1873,14 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
private transient volatile boolean isActive = false;
- private transient volatile List pluginsWithCycle;
+ private transient volatile List pluginsWithCycle;
public boolean isActivated() {
if(pluginsWithCycle == null){
- pluginsWithCycle = new ArrayList();
+ pluginsWithCycle = new ArrayList<>();
for (PluginWrapper p : Jenkins.getInstance().getPluginManager().getPlugins()) {
if(p.hasCycleDependency()){
- pluginsWithCycle.add(p.getShortName());
+ pluginsWithCycle.add(p);
isActive = true;
}
}
@@ -1859,7 +1888,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
return isActive;
}
- public List getPluginsWithCycle() {
+ public List getPluginsWithCycle() {
return pluginsWithCycle;
}
}
diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java
index f61b96e7cf..b97e977ede 100644
--- a/core/src/main/java/hudson/PluginWrapper.java
+++ b/core/src/main/java/hudson/PluginWrapper.java
@@ -26,43 +26,51 @@ package hudson;
import com.google.common.collect.ImmutableSet;
import hudson.PluginManager.PluginInstanceStore;
+import hudson.model.AdministrativeMonitor;
import hudson.model.Api;
import hudson.model.ModelObject;
-import jenkins.MissingDependencyException;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
import hudson.util.VersionNumber;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Closeable;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.jar.Manifest;
-import java.util.logging.Logger;
-import static java.util.logging.Level.WARNING;
-import static org.apache.commons.io.FilenameUtils.getBaseName;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.logging.LogFactory;
+import org.jvnet.localizer.ResourceBundleHolder;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
-import java.util.Enumeration;
-import java.util.jar.JarFile;
-import java.util.logging.Level;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.LogFactory;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.WARNING;
+import static org.apache.commons.io.FilenameUtils.getBaseName;
/**
* Represents a Jenkins plug-in and associated control information
@@ -88,6 +96,12 @@ import javax.annotation.Nonnull;
*/
@ExportedBean
public class PluginWrapper implements Comparable, ModelObject {
+ /**
+ * A plugin won't be loaded unless his declared dependencies are present and match the required minimal version.
+ * This can be set to false to disable the version check (legacy behaviour)
+ */
+ private static final boolean ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK = Boolean.parseBoolean(System.getProperty(PluginWrapper.class.getName()+"." + "dependenciesVersionCheck.enabled", "true"));
+
/**
* {@link PluginManager} to which this belongs to.
*/
@@ -142,6 +156,12 @@ public class PluginWrapper implements Comparable, ModelObject {
private final List dependencies;
private final List optionalDependencies;
+ public List getDependencyErrors() {
+ return Collections.unmodifiableList(dependencyErrors);
+ }
+
+ private final transient List dependencyErrors = new ArrayList<>();
+
/**
* Is this plugin bundled in jenkins.war?
*/
@@ -229,7 +249,7 @@ public class PluginWrapper implements Comparable, ModelObject {
@Override
public String toString() {
- return shortName + " (" + version + ")";
+ return shortName + " (" + version + ")" + (optional ? " optional" : "");
}
}
@@ -394,6 +414,21 @@ public class PluginWrapper implements Comparable, ModelObject {
return "???";
}
+ /**
+ * Returns the required Jenkins core version of this plugin.
+ * @return the required Jenkins core version of this plugin.
+ * @since XXX
+ */
+ @Exported
+ public @CheckForNull String getRequiredCoreVersion() {
+ String v = manifest.getMainAttributes().getValue("Jenkins-Version");
+ if (v!= null) return v;
+
+ v = manifest.getMainAttributes().getValue("Hudson-Version");
+ if (v!= null) return v;
+ return null;
+ }
+
/**
* Returns the version number of this plugin
*/
@@ -524,20 +559,71 @@ public class PluginWrapper implements Comparable, ModelObject {
* thrown if one or several mandatory dependencies doesn't exists.
*/
/*package*/ void resolvePluginDependencies() throws IOException {
- List missingDependencies = new ArrayList<>();
+ if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK) {
+ String requiredCoreVersion = getRequiredCoreVersion();
+ if (requiredCoreVersion == null) {
+ LOGGER.warning(shortName + " doesn't declare required core version.");
+ } else {
+ VersionNumber actualVersion = Jenkins.getVersion();
+ if (actualVersion.isOlderThan(new VersionNumber(requiredCoreVersion))) {
+ dependencyErrors.add(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion));
+ }
+ }
+ }
// make sure dependencies exist
for (Dependency d : dependencies) {
- if (parent.getPlugin(d.shortName) == null)
- missingDependencies.add(d);
- }
- if (!missingDependencies.isEmpty())
- throw new MissingDependencyException(this.shortName, missingDependencies);
+ PluginWrapper dependency = parent.getPlugin(d.shortName);
+ if (dependency == null) {
+ PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName);
+ if (failedDependency != null) {
+ dependencyErrors.add(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), d.version));
+ break;
+ } else {
+ dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version));
+ }
+ } else {
+ if (dependency.isActive()) {
+ if (isDependencyObsolete(d, dependency)) {
+ dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version));
+ }
+ } else {
+ if (isDependencyObsolete(d, dependency)) {
+ dependencyErrors.add(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version));
+ } else {
+ dependencyErrors.add(Messages.PluginWrapper_disabled(dependency.getLongName()));
+ }
+ }
+ }
+ }
// add the optional dependencies that exists
for (Dependency d : optionalDependencies) {
- if (parent.getPlugin(d.shortName) != null)
- dependencies.add(d);
+ PluginWrapper dependency = parent.getPlugin(d.shortName);
+ if (dependency != null && dependency.isActive()) {
+ if (isDependencyObsolete(d, dependency)) {
+ dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version));
+ } else {
+ dependencies.add(d);
+ }
+ }
}
+ if (!dependencyErrors.isEmpty()) {
+ NOTICE.addPlugin(this);
+ StringBuilder messageBuilder = new StringBuilder();
+ messageBuilder.append(Messages.PluginWrapper_failed_to_load_plugin(getLongName(), getVersion())).append(System.lineSeparator());
+ for (Iterator iterator = dependencyErrors.iterator(); iterator.hasNext(); ) {
+ String dependencyError = iterator.next();
+ messageBuilder.append(" - ").append(dependencyError);
+ if (iterator.hasNext()) {
+ messageBuilder.append(System.lineSeparator());
+ }
+ }
+ throw new IOException(messageBuilder.toString());
+ }
+ }
+
+ private boolean isDependencyObsolete(Dependency d, PluginWrapper dependency) {
+ return ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version));
}
/**
@@ -645,6 +731,46 @@ public class PluginWrapper implements Comparable, ModelObject {
return false;
}
+ @Extension
+ public final static PluginWrapperAdministrativeMonitor NOTICE = new PluginWrapperAdministrativeMonitor();
+
+ /**
+ * Administrative Monitor for failed plugins
+ */
+ public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor {
+ private final Map plugins = new HashMap<>();
+
+ void addPlugin(PluginWrapper plugin) {
+ plugins.put(plugin.shortName, plugin);
+ }
+
+ public boolean isActivated() {
+ return !plugins.isEmpty();
+ }
+
+ public Collection getPlugins() {
+ return plugins.values();
+ }
+
+ public PluginWrapper getPlugin(String shortName) {
+ return plugins.get(shortName);
+ }
+
+ /**
+ * Depending on whether the user said "dismiss" or "correct", send him to the right place.
+ */
+ public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException {
+ if(req.hasParameter("correct")) {
+ rsp.sendRedirect(req.getContextPath()+"/pluginManager");
+
+ }
+ }
+
+ public static PluginWrapperAdministrativeMonitor get() {
+ return AdministrativeMonitor.all().get(PluginWrapperAdministrativeMonitor.class);
+ }
+ }
+
//
//
// Action methods
diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java
index e66778a008..c01f711406 100644
--- a/core/src/main/java/hudson/TcpSlaveAgentListener.java
+++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java
@@ -23,6 +23,11 @@
*/
package hudson;
+import java.nio.charset.Charset;
+import java.security.interfaces.RSAPublicKey;
+import javax.annotation.Nullable;
+import jenkins.model.Jenkins;
+import jenkins.model.identity.InstanceIdentityProvider;
import jenkins.util.SystemProperties;
import hudson.slaves.OfflineCause;
import java.io.DataOutputStream;
@@ -44,7 +49,9 @@ import java.net.Socket;
import java.nio.channels.ServerSocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
/**
* Listens to incoming TCP connections from JNLP agents and CLI.
@@ -103,6 +110,28 @@ public final class TcpSlaveAgentListener extends Thread {
return CLI_PORT != null ? CLI_PORT : getPort();
}
+ /**
+ * Gets the Base64 encoded public key that forms part of this instance's identity keypair.
+ * @return the Base64 encoded public key
+ * @since 2.16
+ */
+ @Nullable
+ public String getIdentityPublicKey() {
+ RSAPublicKey key = InstanceIdentityProvider.RSA.getPublicKey();
+ return key == null ? null : new String(Base64.encodeBase64(key.getEncoded()), Charset.forName("UTF-8"));
+ }
+
+ /**
+ * Returns a comma separated list of the enabled {@link AgentProtocol#getName()} implementations so that
+ * clients can avoid creating additional work for the server attempting to connect with unsupported protocols.
+ *
+ * @return a comma separated list of the enabled {@link AgentProtocol#getName()} implementations
+ * @since 2.16
+ */
+ public String getAgentProtocolNames() {
+ return StringUtils.join(Jenkins.getInstance().getAgentProtocols(), ", ");
+ }
+
@Override
public void run() {
try {
@@ -178,9 +207,13 @@ public final class TcpSlaveAgentListener extends Thread {
if(s.startsWith("Protocol:")) {
String protocol = s.substring(9);
AgentProtocol p = AgentProtocol.of(protocol);
- if (p!=null)
- p.handle(this.s);
- else
+ if (p!=null) {
+ if (Jenkins.getInstance().getAgentProtocols().contains(protocol)) {
+ p.handle(this.s);
+ } else {
+ error(out, "Disabled protocol:" + s);
+ }
+ } else
error(out, "Unknown protocol:" + s);
} else {
error(out, "Unrecognized protocol: "+s);
@@ -228,11 +261,27 @@ public final class TcpSlaveAgentListener extends Thread {
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isRequired() {
+ return true;
+ }
+
@Override
public String getName() {
return "Ping";
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return Messages.TcpSlaveAgentListener_PingAgentProtocol_displayName();
+ }
+
@Override
public void handle(Socket socket) throws IOException, InterruptedException {
try {
diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java
index aa1d031b4b..f2160fe31f 100644
--- a/core/src/main/java/hudson/cli/CliProtocol.java
+++ b/core/src/main/java/hudson/cli/CliProtocol.java
@@ -1,6 +1,7 @@
package hudson.cli;
import hudson.Extension;
+import hudson.Util;
import hudson.model.Computer;
import hudson.remoting.Channel;
import hudson.remoting.Channel.Mode;
@@ -31,11 +32,27 @@ public class CliProtocol extends AgentProtocol {
@Inject
NioChannelSelector nio;
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptIn() {
+ return OPT_IN;
+ }
+
@Override
public String getName() {
return "CLI-connect";
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return "Jenkins CLI Protocol/1";
+ }
+
@Override
public void handle(Socket socket) throws IOException, InterruptedException {
new Handler(nio.getHub(),socket).run();
@@ -85,4 +102,14 @@ public class CliProtocol extends AgentProtocol {
channel.join();
}
}
+
+ /**
+ * A/B test turning off this protocol by default.
+ */
+ private static final boolean OPT_IN;
+
+ static {
+ byte hash = Util.fromHexString(Jenkins.getInstance().getLegacyInstanceId())[0];
+ OPT_IN = (hash % 10) == 0;
+ }
}
diff --git a/core/src/main/java/hudson/cli/CliProtocol2.java b/core/src/main/java/hudson/cli/CliProtocol2.java
index a4893cfd94..cd7d8b04f6 100644
--- a/core/src/main/java/hudson/cli/CliProtocol2.java
+++ b/core/src/main/java/hudson/cli/CliProtocol2.java
@@ -28,6 +28,22 @@ public class CliProtocol2 extends CliProtocol {
return "CLI2-connect";
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptIn() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return "Jenkins CLI Protocol/2";
+ }
+
@Override
public void handle(Socket socket) throws IOException, InterruptedException {
new Handler2(nio.getHub(), socket).run();
diff --git a/core/src/main/java/hudson/cli/OfflineNodeCommand.java b/core/src/main/java/hudson/cli/OfflineNodeCommand.java
new file mode 100644
index 0000000000..b92cee59ca
--- /dev/null
+++ b/core/src/main/java/hudson/cli/OfflineNodeCommand.java
@@ -0,0 +1,95 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * 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 hudson.cli;
+
+import hudson.AbortException;
+import hudson.Extension;
+import hudson.model.Computer;
+import hudson.model.ComputerSet;
+import hudson.util.EditDistance;
+import jenkins.model.Jenkins;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * @author pjanouse
+ * @since TODO
+ */
+@Extension
+public class OfflineNodeCommand extends CLICommand {
+
+ @Argument(metaVar = "NAME", usage = "Agent name, or empty string for master", required = true, multiValued = true)
+ private List nodes;
+
+ @Option(name = "-m", usage = "Record the reason about why you are disconnecting this node")
+ public String cause;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.OfflineNodeCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ boolean errorOccurred = false;
+ final Jenkins jenkins = Jenkins.getInstance();
+ final HashSet hs = new HashSet(nodes);
+ List names = null;
+
+ for (String node_s : hs) {
+ try {
+ Computer computer = jenkins.getComputer(node_s);
+ if (computer == null) {
+ if (names == null) {
+ names = ComputerSet.getComputerNames();
+ }
+ String adv = EditDistance.findNearest(node_s, names);
+ throw new IllegalArgumentException(adv == null ?
+ hudson.model.Messages.Computer_NoSuchSlaveExistsWithoutAdvice(node_s) :
+ hudson.model.Messages.Computer_NoSuchSlaveExists(node_s, adv));
+ }
+ computer.cliOffline(cause);
+ } catch (Exception e) {
+ if (hs.size() == 1) {
+ throw e;
+ }
+
+ stderr.println(node_s + ": " + e.getMessage());
+ errorOccurred = true;
+ continue;
+ }
+ }
+
+ if (errorOccurred) {
+ throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ }
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java
new file mode 100644
index 0000000000..161fe1879d
--- /dev/null
+++ b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java
@@ -0,0 +1,50 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, Red Hat, Inc.
+ *
+ * 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 hudson.cli;
+
+import hudson.Extension;
+import hudson.model.Node;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * @author pjanouse
+ * @since TODO
+ */
+@Extension
+public class WaitNodeOfflineCommand extends CLICommand {
+
+ @Argument(metaVar="NODE", usage="Name of the node", required=true)
+ public Node node;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.WaitNodeOfflineCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ node.toComputer().waitUntilOffline();
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java
new file mode 100644
index 0000000000..fa33bdd17d
--- /dev/null
+++ b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java
@@ -0,0 +1,50 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, Red Hat, Inc.
+ *
+ * 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 hudson.cli;
+
+import hudson.Extension;
+import hudson.model.Node;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * @author pjanouse
+ * @since TODO
+ */
+@Extension
+public class WaitNodeOnlineCommand extends CLICommand {
+
+ @Argument(metaVar="NODE", usage="Name of the node", required=true)
+ public Node node;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.WaitNodeOnlineCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ node.toComputer().waitUntilOnline();
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 8dab4cb80c..2c2fe31c76 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -1105,7 +1105,7 @@ public abstract class AbstractProject
,R extends A
*/
@Deprecated
public static class BecauseOfBuildInProgress extends BlockedBecauseOfBuildInProgress {
- public BecauseOfBuildInProgress(AbstractBuild, ?> build) {
+ public BecauseOfBuildInProgress(@Nonnull AbstractBuild, ?> build) {
super(build);
}
}
@@ -1146,7 +1146,15 @@ public abstract class AbstractProject
,R extends A
public CauseOfBlockage getCauseOfBlockage() {
// Block builds until they are done with post-production
if (isLogUpdated() && !isConcurrentBuild()) {
- return new BlockedBecauseOfBuildInProgress(getLastBuild());
+ final R lastBuild = getLastBuild();
+ if (lastBuild != null) {
+ return new BlockedBecauseOfBuildInProgress(lastBuild);
+ } else {
+ // The build has been likely deleted after the isLogUpdated() call.
+ // Another cause may be an API implemetation glit�h in the implementation for AbstractProject.
+ // Anyway, we should let the code go then.
+ LOGGER.log(Level.FINE, "The last build has been deleted during the non-concurrent cause creation. The build is not blocked anymore");
+ }
}
if (blockBuildWhenDownstreamBuilding()) {
AbstractProject,?> bup = getBuildingDownstream();
diff --git a/core/src/main/java/hudson/model/BuildTimelineWidget.java b/core/src/main/java/hudson/model/BuildTimelineWidget.java
index 9b8bc849b8..31810b5f5e 100644
--- a/core/src/main/java/hudson/model/BuildTimelineWidget.java
+++ b/core/src/main/java/hudson/model/BuildTimelineWidget.java
@@ -62,8 +62,8 @@ public class BuildTimelineWidget {
TimelineEventList result = new TimelineEventList();
for (Run r : builds.byTimestamp(min,max)) {
Event e = new Event();
- e.start = r.getTime();
- e.end = new Date(r.timestamp+r.getDuration());
+ e.start = new Date(r.getStartTimeInMillis());
+ e.end = new Date(r.getStartTimeInMillis()+r.getDuration());
e.title = r.getFullDisplayName();
// what to put in the description?
// e.description = "Longish description of event "+r.getFullDisplayName();
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index 4d4d1e5fe6..00f52e197c 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -68,6 +68,8 @@ import jenkins.model.Jenkins;
import jenkins.util.ContextResettingExecutorService;
import jenkins.security.MasterToSlaveCallable;
+import org.jenkins.ui.icon.Icon;
+import org.jenkins.ui.icon.IconSet;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -521,10 +523,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
/**
- * CLI command to mark the node offline.
+ * @deprecated Implementation of CLI command "offline-node" moved to {@link hudson.cli.OfflineNodeCommand}.
+ *
+ * @param cause
+ * Record the note about why you are disconnecting this node
*/
- @CLIMethod(name="offline-node")
- public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
+ @Deprecated
+ public void cliOffline(String cause) throws ExecutionException, InterruptedException {
checkPermission(DISCONNECT);
setTemporarilyOffline(true, new ByCLI(cause));
}
@@ -718,6 +723,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
}
+ /**
+ * Returns the icon for this computer.
+ *
+ * It is both the recommended and default implementation to serve different icons based on {@link #isOffline}
+ *
+ * @see #getIconClassName()
+ */
@Exported
public String getIcon() {
if(isOffline())
@@ -726,6 +738,17 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
return "computer.png";
}
+ /**
+ * Returns the class name that will be used to lookup the icon.
+ *
+ * This class name will be added as a class tag to the html img tags where the icon should
+ * show up followed by a size specifier given by {@link Icon#toNormalizedIconSizeClass(String)}
+ * The conversion of class tag to src tag is registered through {@link IconSet#addIcon(Icon)}
+ *
+ * It is both the recommended and default implementation to serve different icons based on {@link #isOffline}
+ *
+ * @see #getIcon()
+ */
@Exported
public String getIconClassName() {
if(isOffline())
@@ -1471,7 +1494,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* Blocks until the node becomes online/offline.
*/
- @CLIMethod(name="wait-node-online")
public void waitUntilOnline() throws InterruptedException {
synchronized (statusChangeLock) {
while (!isOnline())
@@ -1479,7 +1501,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
}
- @CLIMethod(name="wait-node-offline")
public void waitUntilOffline() throws InterruptedException {
synchronized (statusChangeLock) {
while (!isOffline())
diff --git a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
index 838e3a0caf..f092a8bb10 100644
--- a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
+++ b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
@@ -35,7 +35,7 @@ public abstract class DescriptorVisibilityFilter implements ExtensionPoint {
* @return true to allow the descriptor to be visible. false to hide it.
* If any of the installed {@link DescriptorVisibilityFilter} returns false,
* the descriptor is not shown.
- * @since FIXME
+ * @since 2.12
*/
public boolean filterType(@Nonnull Class> contextClass, @Nonnull Descriptor descriptor) {
return true;
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index c2ab05a678..2e33bdf2d0 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -1468,7 +1468,7 @@ public class Fingerprint implements ModelObject, Saveable {
* Provides the XStream instance this class is using for serialization.
*
* @return the XStream instance
- * @since FIXME
+ * @since 1.655
*/
@Nonnull
public static XStream2 getXStream() {
diff --git a/core/src/main/java/hudson/model/ParametersAction.java b/core/src/main/java/hudson/model/ParametersAction.java
index d3ff1ef803..50a0b8184b 100644
--- a/core/src/main/java/hudson/model/ParametersAction.java
+++ b/core/src/main/java/hudson/model/ParametersAction.java
@@ -316,7 +316,7 @@ public class ParametersAction implements RunAction2, Iterable, Q
if (this.parameterDefinitionNames.contains(v.getName()) || isSafeParameter(v.getName())) {
filteredParameters.add(v);
} else {
- LOGGER.log(Level.WARNING, "Skipped parameter `{0}` as it is undefined on `{1}`. Set `-D{2}`=true to allow "
+ LOGGER.log(Level.WARNING, "Skipped parameter `{0}` as it is undefined on `{1}`. Set `-D{2}=true` to allow "
+ "undefined parameters to be injected as environment variables or `-D{3}=[comma-separated list]` to whitelist specific parameter names, "
+ "even though it represents a security breach",
new Object [] { v.getName(), run.getParent().getFullName(), KEEP_UNDEFINED_PARAMETERS_SYSTEM_PROPERTY_NAME, SAFE_PARAMETERS_SYSTEM_PROPERTY_NAME });
diff --git a/core/src/main/java/hudson/model/PasswordParameterDefinition.java b/core/src/main/java/hudson/model/PasswordParameterDefinition.java
index f4cad5a8c3..f3dfe79356 100644
--- a/core/src/main/java/hudson/model/PasswordParameterDefinition.java
+++ b/core/src/main/java/hudson/model/PasswordParameterDefinition.java
@@ -31,6 +31,7 @@ import hudson.Extension;
import hudson.util.Secret;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Parameter whose value is a {@link Secret} and is hidden from the UI.
@@ -40,6 +41,9 @@ import org.kohsuke.accmod.restrictions.DoNotUse;
*/
public class PasswordParameterDefinition extends SimpleParameterDefinition {
+ @Restricted(NoExternalUse.class)
+ public static final String DEFAULT_VALUE = "";
+
private Secret defaultValue;
@DataBoundConstructor
@@ -66,6 +70,9 @@ public class PasswordParameterDefinition extends SimpleParameterDefinition {
@Override
public PasswordParameterValue createValue(StaplerRequest req, JSONObject jo) {
PasswordParameterValue value = req.bindJSON(PasswordParameterValue.class, jo);
+ if (value.getValue().getPlainText().equals(DEFAULT_VALUE)) {
+ value = new PasswordParameterValue(getName(), getDefaultValue());
+ }
value.setDescription(getDescription());
return value;
}
diff --git a/core/src/main/java/hudson/model/PasswordParameterValue.java b/core/src/main/java/hudson/model/PasswordParameterValue.java
index d7d917ee48..9f384fbb22 100644
--- a/core/src/main/java/hudson/model/PasswordParameterValue.java
+++ b/core/src/main/java/hudson/model/PasswordParameterValue.java
@@ -29,12 +29,14 @@ import hudson.util.VariableResolver;
import org.kohsuke.stapler.DataBoundConstructor;
import java.util.Locale;
+import javax.annotation.Nonnull;
/**
* @author Kohsuke Kawaguchi
*/
public class PasswordParameterValue extends ParameterValue {
+ @Nonnull
private final Secret value;
// kept for backward compatibility
@@ -68,7 +70,8 @@ public class PasswordParameterValue extends ParameterValue {
public boolean isSensitive() {
return true;
}
-
+
+ @Nonnull
public Secret getValue() {
return value;
}
diff --git a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
index aa6a2835b1..f53306d1b2 100644
--- a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
+++ b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
@@ -139,7 +139,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor {
*
* @return A string, by default the value from newInstanceDetail view is taken.
*
- * @since TODO
+ * @since 2.0
*/
@Nonnull
public String getDescription() {
@@ -171,7 +171,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor {
*
* @return A string with the category identifier, {@link ItemCategory.UncategorizedCategory#getId()} by default.
*
- * @since TODO
+ * @since 2.0
*/
@Nonnull
public String getCategoryId() {
@@ -188,7 +188,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor {
*
* @return A string or null if it is not defined.
*
- * @since TODO
+ * @since 2.0
*/
@CheckForNull
public String getIconFilePathPattern() {
@@ -202,7 +202,7 @@ public abstract class TopLevelItemDescriptor extends Descriptor {
*
* @return A string or null if it is not defined.
*
- * @since TODO
+ * @since 2.0
*/
@CheckForNull
public String getIconFilePath(String size) {
diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java
index 9dfe633f04..1cf1ffdf8b 100644
--- a/core/src/main/java/hudson/model/User.java
+++ b/core/src/main/java/hudson/model/User.java
@@ -24,6 +24,7 @@
*/
package hudson.model;
+import jenkins.security.UserDetailsCache;
import jenkins.util.SystemProperties;
import com.google.common.base.Predicate;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
@@ -81,6 +82,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
@@ -579,6 +581,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
}
} finally {
byNameLock.readLock().unlock();
+ UserDetailsCache.get().invalidateAll();
}
}
@@ -612,6 +615,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
}
} finally {
byNameLock.writeLock().unlock();
+ UserDetailsCache.get().invalidateAll();
}
}
@@ -750,6 +754,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
byNameLock.readLock().unlock();
}
Util.deleteRecursive(new File(getRootDir(), strategy.filenameOf(id)));
+ UserDetailsCache.get().invalidate(strategy.keyFor(id));
}
/**
@@ -767,7 +772,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
checkPermission(Jenkins.ADMINISTER);
JSONObject json = req.getSubmittedForm();
-
+ String oldFullName = this.fullName;
fullName = json.getString("fullName");
description = json.getString("description");
@@ -793,6 +798,10 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
save();
+ if (oldFullName != null && !oldFullName.equals(this.fullName)) {
+ UserDetailsCache.get().invalidate(oldFullName);
+ }
+
FormApply.success(".").generateResponse(req,rsp,this);
}
@@ -1049,24 +1058,17 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
return existing.getId();
}
if (SECURITY_243_FULL_DEFENSE) {
- Jenkins j = Jenkins.getInstance();
- if (j != null) {
- if (!resolving.get()) {
- resolving.set(true);
- try {
- UserDetails userDetails = j.getSecurityRealm().loadUserByUsername(idOrFullName);
- if (userDetails == null) {
- throw new NullPointerException("hudson.security.SecurityRealm should never return null. "
- + j.getSecurityRealm() + " returned null for idOrFullName='" + idOrFullName + "'");
- }
- return userDetails.getUsername();
- } catch (UsernameNotFoundException x) {
- LOGGER.log(Level.FINE, "not sure whether " + idOrFullName + " is a valid username or not", x);
- } catch (DataAccessException x) {
- LOGGER.log(Level.FINE, "could not look up " + idOrFullName, x);
- } finally {
- resolving.set(false);
- }
+ if (!resolving.get()) {
+ resolving.set(true);
+ try {
+ UserDetails userDetails = UserDetailsCache.get().loadUserByUsername(idOrFullName);
+ return userDetails.getUsername();
+ } catch (UsernameNotFoundException x) {
+ LOGGER.log(Level.FINE, "not sure whether " + idOrFullName + " is a valid username or not", x);
+ } catch (DataAccessException | ExecutionException x) {
+ LOGGER.log(Level.FINE, "could not look up " + idOrFullName, x);
+ } finally {
+ resolving.set(false);
}
}
}
diff --git a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java
index 36edde3f4e..5fa446fef7 100644
--- a/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java
+++ b/core/src/main/java/hudson/security/GlobalSecurityConfiguration.java
@@ -35,6 +35,8 @@ import hudson.model.ManagementLink;
import hudson.util.FormApply;
import java.io.IOException;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -43,6 +45,7 @@ import javax.servlet.ServletException;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
import jenkins.util.ServerTcpPort;
+import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
@@ -69,6 +72,10 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr
return Jenkins.getInstance().getSlaveAgentPort();
}
+ public Set getAgentProtocols() {
+ return Jenkins.getInstance().getAgentProtocols();
+ }
+
public boolean isDisableRememberMe() {
return Jenkins.getInstance().isDisableRememberMe();
}
@@ -100,6 +107,18 @@ public class GlobalSecurityConfiguration extends ManagementLink implements Descr
} catch (IOException e) {
throw new hudson.model.Descriptor.FormException(e, "slaveAgentPortType");
}
+ Set agentProtocols = new TreeSet<>();
+ if (security.has("agentProtocol")) {
+ Object protocols = security.get("agentProtocol");
+ if (protocols instanceof JSONArray) {
+ for (int i = 0; i < ((JSONArray) protocols).size(); i++) {
+ agentProtocols.add(((JSONArray) protocols).getString(i));
+ }
+ } else {
+ agentProtocols.add(protocols.toString());
+ }
+ }
+ j.setAgentProtocols(agentProtocols);
} else {
j.disableSecurity();
}
diff --git a/core/src/main/java/hudson/slaves/JNLPLauncher.java b/core/src/main/java/hudson/slaves/JNLPLauncher.java
index 37218e4fac..c83382f796 100644
--- a/core/src/main/java/hudson/slaves/JNLPLauncher.java
+++ b/core/src/main/java/hudson/slaves/JNLPLauncher.java
@@ -23,10 +23,13 @@
*/
package hudson.slaves;
-import hudson.model.Descriptor;
-import hudson.model.TaskListener;
-import hudson.Util;
import hudson.Extension;
+import hudson.Util;
+import hudson.model.Descriptor;
+import hudson.model.DescriptorVisibilityFilter;
+import hudson.model.TaskListener;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -94,4 +97,29 @@ public class JNLPLauncher extends ComputerLauncher {
}
};
+ /**
+ * Hides the JNLP launcher when the JNLP agent port is not enabled.
+ *
+ * @since 2.16
+ */
+ @Extension
+ public static class DescriptorVisibilityFilterImpl extends DescriptorVisibilityFilter {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean filter(@CheckForNull Object context, @Nonnull Descriptor descriptor) {
+ return descriptor.clazz != JNLPLauncher.class || Jenkins.getInstance().getTcpSlaveAgentListener() != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean filterType(@Nonnull Class> contextClass, @Nonnull Descriptor descriptor) {
+ return descriptor.clazz != JNLPLauncher.class || Jenkins.getInstance().getTcpSlaveAgentListener() != null;
+ }
+ }
+
}
diff --git a/core/src/main/java/hudson/tasks/ArtifactArchiver.java b/core/src/main/java/hudson/tasks/ArtifactArchiver.java
index 7ebc1a4a9a..ca1f154f15 100644
--- a/core/src/main/java/hudson/tasks/ArtifactArchiver.java
+++ b/core/src/main/java/hudson/tasks/ArtifactArchiver.java
@@ -50,6 +50,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
import net.sf.json.JSONObject;
import javax.annotation.Nonnull;
@@ -76,7 +77,7 @@ public class ArtifactArchiver extends Recorder implements SimpleBuildStep {
/**
* Possibly null 'excludes' pattern as in Ant.
*/
- private String excludes = "";
+ private String excludes;
@Deprecated
private Boolean latestOnly;
@@ -154,11 +155,11 @@ public class ArtifactArchiver extends Recorder implements SimpleBuildStep {
return artifacts;
}
- public String getExcludes() {
+ public @CheckForNull String getExcludes() {
return excludes;
}
- @DataBoundSetter public final void setExcludes(String excludes) {
+ @DataBoundSetter public final void setExcludes(@CheckForNull String excludes) {
this.excludes = Util.fixEmptyAndTrim(excludes);
}
diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java
index c528983085..c05a4490ce 100644
--- a/core/src/main/java/hudson/triggers/Trigger.java
+++ b/core/src/main/java/hudson/triggers/Trigger.java
@@ -94,7 +94,7 @@ public abstract class Trigger implements Describable>
} catch (ANTLRException e) {
// this shouldn't fail because we've already parsed stuff in the constructor,
// so if it fails, use whatever 'tabs' that we already have.
- LOGGER.log(Level.FINE, "Failed to parse crontab spec: "+spec,e);
+ LOGGER.log(Level.WARNING, String.format("Failed to parse crontab spec %s in job %s", spec, project.getFullName()), e);
}
}
@@ -264,20 +264,24 @@ public abstract class Trigger implements Describable>
// Process all triggers, except SCMTriggers when synchronousPolling is set
for (ParameterizedJobMixIn.ParameterizedJob p : inst.getAllItems(ParameterizedJobMixIn.ParameterizedJob.class)) {
for (Trigger t : p.getTriggers().values()) {
- if (! (t instanceof SCMTrigger && scmd.synchronousPolling)) {
- LOGGER.log(Level.FINE, "cron checking {0} with spec ‘{1}’", new Object[] {p, t.spec.trim()});
+ if (!(t instanceof SCMTrigger && scmd.synchronousPolling)) {
+ if (t !=null && t.spec != null && t.tabs != null) {
+ LOGGER.log(Level.FINE, "cron checking {0} with spec ‘{1}’", new Object[]{p, t.spec.trim()});
- if (t.tabs.check(cal)) {
- LOGGER.log(Level.CONFIG, "cron triggered {0}", p);
- try {
- t.run();
- } catch (Throwable e) {
- // t.run() is a plugin, and some of them throw RuntimeException and other things.
- // don't let that cancel the polling activity. report and move on.
- LOGGER.log(Level.WARNING, t.getClass().getName() + ".run() failed for " + p, e);
+ if (t.tabs.check(cal)) {
+ LOGGER.log(Level.CONFIG, "cron triggered {0}", p);
+ try {
+ t.run();
+ } catch (Throwable e) {
+ // t.run() is a plugin, and some of them throw RuntimeException and other things.
+ // don't let that cancel the polling activity. report and move on.
+ LOGGER.log(Level.WARNING, t.getClass().getName() + ".run() failed for " + p, e);
+ }
+ } else {
+ LOGGER.log(Level.FINER, "did not trigger {0}", p);
}
} else {
- LOGGER.log(Level.FINER, "did not trigger {0}", p);
+ LOGGER.log(Level.WARNING, "The job {0} has a syntactically incorrect config and is missing the cron spec for a trigger", p.getFullName());
}
}
}
diff --git a/core/src/main/java/hudson/util/Secret.java b/core/src/main/java/hudson/util/Secret.java
index 0cfd9171a9..a2a2bcb971 100644
--- a/core/src/main/java/hudson/util/Secret.java
+++ b/core/src/main/java/hudson/util/Secret.java
@@ -42,6 +42,8 @@ import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.regex.Pattern;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -62,6 +64,7 @@ public final class Secret implements Serializable {
/**
* Unencrypted secret text.
*/
+ @Nonnull
private final String value;
private Secret(String value) {
@@ -87,6 +90,7 @@ public final class Secret implements Serializable {
* Before using this method, ask yourself if you'd be better off using {@link Secret#toString(Secret)}
* to avoid NPE.
*/
+ @Nonnull
public String getPlainText() {
return value;
}
@@ -144,7 +148,8 @@ public final class Secret implements Serializable {
* Reverse operation of {@link #getEncryptedValue()}. Returns null
* if the given cipher text was invalid.
*/
- public static Secret decrypt(String data) {
+ @CheckForNull
+ public static Secret decrypt(@CheckForNull String data) {
if(data==null) return null;
try {
byte[] in = Base64.decode(data.toCharArray());
@@ -192,10 +197,9 @@ public final class Secret implements Serializable {
*
*
* Useful for recovering a value from a form field.
- *
- * @return never null
*/
- public static Secret fromString(String data) {
+ @Nonnull
+ public static Secret fromString(@CheckForNull String data) {
data = Util.fixNull(data);
Secret s = decrypt(data);
if(s==null) s=new Secret(data);
@@ -207,7 +211,8 @@ public final class Secret implements Serializable {
* To be consistent with {@link #fromString(String)}, this method doesn't distinguish
* empty password and null password.
*/
- public static String toString(Secret s) {
+ @Nonnull
+ public static String toString(@CheckForNull Secret s) {
return s==null ? "" : s.value;
}
diff --git a/core/src/main/java/jenkins/AgentProtocol.java b/core/src/main/java/jenkins/AgentProtocol.java
index 667f881cde..93140b71c1 100644
--- a/core/src/main/java/jenkins/AgentProtocol.java
+++ b/core/src/main/java/jenkins/AgentProtocol.java
@@ -7,6 +7,8 @@ import hudson.TcpSlaveAgentListener;
import java.io.IOException;
import java.net.Socket;
+import java.util.Set;
+import jenkins.model.Jenkins;
/**
* Pluggable Jenkins TCP agent protocol handler called from {@link TcpSlaveAgentListener}.
@@ -21,6 +23,36 @@ import java.net.Socket;
* @see TcpSlaveAgentListener
*/
public abstract class AgentProtocol implements ExtensionPoint {
+ /**
+ * Allow experimental {@link AgentProtocol} implementations to declare being opt-in.
+ * Note that {@link Jenkins#setAgentProtocols(Set)} only records the protocols where the admin has made a
+ * conscious decision thus:
+ *
+ *
if a protocol is opt-in, it records the admin enabling it
+ *
if a protocol is opt-out, it records the admin disabling it
+ *
+ * Implementations should not transition rapidly from {@code opt-in -> opt-out -> opt-in}.
+ * Implementations should never flip-flop: {@code opt-in -> opt-out -> opt-in -> opt-out} as that will basically
+ * clear any preference that an admin has set. This latter restriction should be ok as we only ever will be
+ * adding new protocols and retiring old ones.
+ *
+ * @return {@code true} if the protocol requires explicit opt-in.
+ * @since 2.16
+ * @see Jenkins#setAgentProtocols(Set)
+ */
+ public boolean isOptIn() {
+ return false;
+ }
+ /**
+ * Allow essential {@link AgentProtocol} implementations (basically {@link TcpSlaveAgentListener.PingAgentProtocol})
+ * to be always enabled.
+ *
+ * @return {@code true} if the protocol can never be disabled.
+ * @since 2.16
+ */
+ public boolean isRequired() {
+ return false;
+ }
/**
* Protocol name.
*
@@ -32,6 +64,16 @@ public abstract class AgentProtocol implements ExtensionPoint {
*/
public abstract String getName();
+ /**
+ * Returns the human readable protocol display name.
+ *
+ * @return the human readable protocol display name.
+ * @since 2.16
+ */
+ public String getDisplayName() {
+ return getName();
+ }
+
/**
* Called by the connection handling thread to execute the protocol.
*/
diff --git a/core/src/main/java/jenkins/InitReactorRunner.java b/core/src/main/java/jenkins/InitReactorRunner.java
index 3c6f43988c..c5c2bfb844 100644
--- a/core/src/main/java/jenkins/InitReactorRunner.java
+++ b/core/src/main/java/jenkins/InitReactorRunner.java
@@ -25,6 +25,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.SEVERE;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Executes the {@link Reactor} for the purpose of bootup.
@@ -61,15 +63,15 @@ public class InitReactorRunner {
r.add(new ReactorListener() {
final Level level = Level.parse( Configuration.getStringConfigParameter("initLogLevel", "FINE") );
public void onTaskStarted(Task t) {
- LOGGER.log(level,"Started "+t.getDisplayName());
+ LOGGER.log(level, "Started {0}", getDisplayName(t));
}
public void onTaskCompleted(Task t) {
- LOGGER.log(level,"Completed "+t.getDisplayName());
+ LOGGER.log(level, "Completed {0}", getDisplayName(t));
}
public void onTaskFailed(Task t, Throwable err, boolean fatal) {
- LOGGER.log(SEVERE, "Failed "+t.getDisplayName(),err);
+ LOGGER.log(SEVERE, "Failed " + getDisplayName(t), err);
}
public void onAttained(Milestone milestone) {
@@ -86,6 +88,17 @@ public class InitReactorRunner {
return new ReactorListener.Aggregator(r);
}
+ /** Like {@link Task#getDisplayName} but more robust. */
+ @Restricted(NoExternalUse.class)
+ public static String getDisplayName(Task t) {
+ try {
+ return t.getDisplayName();
+ } catch (RuntimeException | Error x) {
+ LOGGER.log(Level.WARNING, "failed to find displayName of " + t, x);
+ return t.toString();
+ }
+ }
+
/**
* Called when the init milestone is attained.
*/
diff --git a/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java b/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java
index f55537c2ca..9abdd8fa88 100644
--- a/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java
+++ b/core/src/main/java/jenkins/model/BlockedBecauseOfBuildInProgress.java
@@ -28,6 +28,7 @@ import hudson.model.Executor;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.queue.CauseOfBlockage;
+import javax.annotation.Nonnull;
/**
* Indicates that a new build is blocked because the previous build is already in progress.
@@ -36,9 +37,14 @@ import hudson.model.queue.CauseOfBlockage;
*/
public class BlockedBecauseOfBuildInProgress extends CauseOfBlockage {
+ @Nonnull
private final Run, ?> build;
- public BlockedBecauseOfBuildInProgress(Run, ?> build) {
+ /**
+ * Creates a cause for the specified build.
+ * @param build Build, which is already in progress
+ */
+ public BlockedBecauseOfBuildInProgress(@Nonnull Run, ?> build) {
this.build = build;
}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 65ac9f25c0..83e3184894 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -50,6 +50,7 @@ import hudson.Plugin;
import hudson.PluginManager;
import hudson.PluginWrapper;
import hudson.ProxyConfiguration;
+import jenkins.AgentProtocol;
import jenkins.util.SystemProperties;
import hudson.TcpSlaveAgentListener;
import hudson.UDPBroadcastThread;
@@ -237,7 +238,6 @@ import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
-import org.kohsuke.args4j.Option;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
@@ -598,6 +598,34 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
private int slaveAgentPort = SystemProperties.getInteger(Jenkins.class.getName()+".slaveAgentPort",0);
+ /**
+ * The TCP agent protocols that are explicitly disabled (we store the disabled ones so that newer protocols
+ * are enabled by default). Will be {@code null} instead of empty to simplify XML format.
+ *
+ * @since 2.16
+ */
+ @CheckForNull
+ private List disabledAgentProtocols;
+
+ /**
+ * The TCP agent protocols that are {@link AgentProtocol#isOptIn()} and explicitly enabled.
+ * Will be {@code null} instead of empty to simplify XML format.
+ *
+ * @since 2.16
+ */
+ @CheckForNull
+ private List enabledAgentProtocols;
+
+ /**
+ * The TCP agent protocols that are enabled. Built from {@link #disabledAgentProtocols} and
+ * {@link #enabledAgentProtocols}.
+ *
+ * @since 2.16
+ * @see #setAgentProtocols(Set)
+ * @see #getAgentProtocols()
+ */
+ private transient Set agentProtocols;
+
/**
* Whitespace-separated labels assigned to the master as a {@link Node}.
*/
@@ -987,7 +1015,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if (is!=null && is.skipInitTask(task)) return;
ACL.impersonate(ACL.SYSTEM); // full access in the initialization thread
- String taskName = task.getDisplayName();
+ String taskName = InitReactorRunner.getDisplayName(task);
Thread t = Thread.currentThread();
String name = t.getName();
@@ -999,11 +1027,24 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if(LOG_STARTUP_PERFORMANCE)
LOGGER.info(String.format("Took %dms for %s by %s",
System.currentTimeMillis()-start, taskName, name));
+ } catch (Exception | Error x) {
+ if (containsLinkageError(x)) {
+ LOGGER.log(Level.WARNING, taskName + " failed perhaps due to plugin dependency issues", x);
+ } else {
+ throw x;
+ }
} finally {
t.setName(name);
SecurityContextHolder.clearContext();
}
}
+ private boolean containsLinkageError(Throwable x) {
+ if (x instanceof LinkageError) {
+ return true;
+ }
+ Throwable x2 = x.getCause();
+ return x2 != null && containsLinkageError(x2);
+ }
};
new InitReactorRunner() {
@@ -1047,6 +1088,84 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
launchTcpSlaveAgentListener();
}
+ /**
+ * Returns the enabled agent protocols.
+ *
+ * @return the enabled agent protocols.
+ * @since 2.16
+ */
+ public Set getAgentProtocols() {
+ if (agentProtocols == null) {
+ // idempotent, so don't care if we do this concurrently, should all get same result
+ Set result = new TreeSet<>();
+ Set disabled = new TreeSet<>();
+ for (String p : Util.fixNull(disabledAgentProtocols)) {
+ disabled.add(p.trim());
+ }
+ Set enabled = new TreeSet<>();
+ for (String p : Util.fixNull(enabledAgentProtocols)) {
+ enabled.add(p.trim());
+ }
+ for (AgentProtocol p : AgentProtocol.all()) {
+ String name = p.getName();
+ if (name != null && (p.isRequired()
+ || (!disabled.contains(name) && (!p.isOptIn() || enabled.contains(name))))) {
+ result.add(name);
+ }
+ }
+ agentProtocols = result;
+ return result;
+ }
+ return agentProtocols;
+ }
+
+ /**
+ * Sets the enabled agent protocols.
+ *
+ * @param protocols the enabled agent protocols.
+ * @since 2.16
+ */
+ public void setAgentProtocols(Set protocols) {
+ Set disabled = new TreeSet<>();
+ Set enabled = new TreeSet<>();
+ for (AgentProtocol p : AgentProtocol.all()) {
+ String name = p.getName();
+ if (name != null && !p.isRequired()) {
+ // we want to record the protocols where the admin has made a conscious decision
+ // thus, if a protocol is opt-in, we record the admin enabling it
+ // if a protocol is opt-out, we record the admin disabling it
+ // We should not transition rapidly from opt-in -> opt-out -> opt-in
+ // the scenario we want to have work is:
+ // 1. We introduce a new protocol, it starts off as opt-in. Some admins decide to test and opt-in
+ // 2. We decide that the protocol is ready for general use. It gets marked as opt-out. Any admins
+ // that took part in early testing now have their config switched to not mention the new protocol
+ // at all when they save their config as the protocol is now opt-out. Any admins that want to
+ // disable it can do so and will have their preference recorded.
+ // 3. We decide that the protocol needs to be retired. It gets switched back to opt-in. At this point
+ // the initial opt-in admins, assuming they visited an upgrade to a master with step 2, will
+ // have the protocol disabled for them. This is what we want. If they didn't upgrade to a master
+ // with step 2, well there is not much we can do to differentiate them from somebody who is upgrading
+ // from a previous step 3 master and had needed to keep the protocol turned on.
+ //
+ // What we should never do is flip-flop: opt-in -> opt-out -> opt-in -> opt-out as that will basically
+ // clear any preference that an admin has set, but this should be ok as we only ever will be
+ // adding new protocols and retiring old ones.
+ if (p.isOptIn()) {
+ if (protocols.contains(name)) {
+ enabled.add(name);
+ }
+ } else {
+ if (!protocols.contains(name)) {
+ disabled.add(name);
+ }
+ }
+ }
+ }
+ disabledAgentProtocols = disabled.isEmpty() ? null : new ArrayList<>(disabled);
+ enabledAgentProtocols = enabled.isEmpty() ? null : new ArrayList<>(enabled);
+ agentProtocols = null;
+ }
+
private void launchTcpSlaveAgentListener() throws IOException {
synchronized(tcpSlaveAgentListenerLock) {
// shutdown previous agent if the port has changed
@@ -2855,15 +2974,12 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
// initialize views by inserting the default view if necessary
// this is both for clean Jenkins and for backward compatibility.
- if(views.size()==0) {
+ if(views.size()==0 || primaryView==null) {
View v = new AllView(Messages.Hudson_ViewName());
setViewOwner(v);
views.add(0,v);
primaryView = v.getViewName();
}
- if (primaryView==null) {
- primaryView = views.get(0).getViewName();
- }
if (useSecurity!=null && !useSecurity) {
// forced reset to the unsecure mode.
@@ -3022,15 +3138,15 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
final Level level = Level.parse(Configuration.getStringConfigParameter("termLogLevel", "FINE"));
public void onTaskStarted(Task t) {
- LOGGER.log(level, "Started " + t.getDisplayName());
+ LOGGER.log(level, "Started {0}", InitReactorRunner.getDisplayName(t));
}
public void onTaskCompleted(Task t) {
- LOGGER.log(level, "Completed " + t.getDisplayName());
+ LOGGER.log(level, "Completed {0}", InitReactorRunner.getDisplayName(t));
}
public void onTaskFailed(Task t, Throwable err, boolean fatal) {
- LOGGER.log(SEVERE, "Failed " + t.getDisplayName(), err);
+ LOGGER.log(SEVERE, "Failed " + InitReactorRunner.getDisplayName(t), err);
}
public void onAttained(Milestone milestone) {
@@ -4783,7 +4899,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
"/signup",
"/tcpSlaveAgentListener",
"/federatedLoginService/",
- "/securityRealm"
+ "/securityRealm",
+ "/instance-identity"
);
/**
@@ -4807,6 +4924,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
// for backward compatibility with <1.75, recognize the tag name "view" as well.
XSTREAM.alias("view", ListView.class);
XSTREAM.alias("listView", ListView.class);
+ XSTREAM.addImplicitCollection(Jenkins.class, "disabledAgentProtocols", "disabledAgentProtocol", String.class);
+ XSTREAM.addImplicitCollection(Jenkins.class, "enabledAgentProtocols", "enabledAgentProtocol", String.class);
XSTREAM2.addCriticalField(Jenkins.class, "securityRealm");
XSTREAM2.addCriticalField(Jenkins.class, "authorizationStrategy");
// this seems to be necessary to force registration of converter early enough
diff --git a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
index 40e904eecd..40fa6503a8 100644
--- a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
+++ b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
@@ -52,7 +52,7 @@ import java.util.Map;
import javax.annotation.CheckForNull;
import javax.servlet.ServletException;
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
-import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
import jenkins.util.TimeDuration;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -178,7 +178,7 @@ public abstract class ParameterizedJobMixIn & Param
}
if (!asJob().isBuildable()) {
- throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR, new IOException(asJob().getFullName() + " is not buildable"));
+ throw HttpResponses.error(SC_CONFLICT, new IOException(asJob().getFullName() + " is not buildable"));
}
// if a build is parameterized, let that take over
@@ -215,6 +215,9 @@ public abstract class ParameterizedJobMixIn & Param
hudson.model.BuildAuthorizationToken.checkPermission(asJob(), asJob().getAuthToken(), req, rsp);
ParametersDefinitionProperty pp = asJob().getProperty(ParametersDefinitionProperty.class);
+ if (!asJob().isBuildable()) {
+ throw HttpResponses.error(SC_CONFLICT, new IOException(asJob().getFullName() + " is not buildable!"));
+ }
if (pp != null) {
pp.buildWithParameters(req, rsp, delay);
} else {
diff --git a/core/src/main/java/jenkins/model/identity/IdentityRootAction.java b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java
new file mode 100644
index 0000000000..6497c012d1
--- /dev/null
+++ b/core/src/main/java/jenkins/model/identity/IdentityRootAction.java
@@ -0,0 +1,97 @@
+package jenkins.model.identity;
+
+import hudson.Extension;
+import hudson.model.UnprotectedRootAction;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * A simple root action that exposes the public key to users so that they do not need to search for the
+ * {@code X-Instance-Identity} response header, also exposes the fingerprint of the public key so that people
+ * can verify a fingerprint of a master before connecting to it.
+ *
+ * @since 2.16
+ */
+@Extension
+public class IdentityRootAction implements UnprotectedRootAction {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getIconFileName() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getUrlName() {
+ return InstanceIdentityProvider.RSA.getKeyPair() == null ? null : "instance-identity";
+ }
+
+ /**
+ * Returns the PEM encoded public key.
+ *
+ * @return the PEM encoded public key.
+ */
+ public String getPublicKey() {
+ RSAPublicKey key = InstanceIdentityProvider.RSA.getPublicKey();
+ if (key == null) {
+ return null;
+ }
+ byte[] encoded = Base64.encodeBase64(key.getEncoded());
+ int index = 0;
+ StringBuilder buf = new StringBuilder(encoded.length + 20);
+ while (index < encoded.length) {
+ int len = Math.min(64, encoded.length - index);
+ if (index > 0) {
+ buf.append("\n");
+ }
+ buf.append(new String(encoded, index, len, Charsets.UTF_8));
+ index += len;
+ }
+ return String.format("-----BEGIN PUBLIC KEY-----%n%s%n-----END PUBLIC KEY-----%n", buf.toString());
+ }
+
+ /**
+ * Returns the fingerprint of the public key.
+ *
+ * @return the fingerprint of the public key.
+ */
+ public String getFingerprint() {
+ RSAPublicKey key = InstanceIdentityProvider.RSA.getPublicKey();
+ if (key == null) {
+ return null;
+ }
+ // TODO replace with org.jenkinsci.remoting.util.KeyUtils once JENKINS-36871 changes are merged
+ try {
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+ digest.reset();
+ byte[] bytes = digest.digest(key.getEncoded());
+ StringBuilder result = new StringBuilder(Math.max(0, bytes.length * 3 - 1));
+ for (int i = 0; i < bytes.length; i++) {
+ if (i > 0) {
+ result.append(':');
+ }
+ int b = bytes[i] & 0xFF;
+ result.append(Character.forDigit((b>>4)&0x0f, 16)).append(Character.forDigit(b&0xf, 16));
+ }
+ return result.toString();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("JLS mandates MD5 support");
+ }
+ }
+}
diff --git a/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java
new file mode 100644
index 0000000000..c65e27fa90
--- /dev/null
+++ b/core/src/main/java/jenkins/model/identity/InstanceIdentityProvider.java
@@ -0,0 +1,301 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, CloudBees Inc.
+ *
+ * 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.model.identity;
+
+import hudson.ExtensionList;
+import hudson.ExtensionPoint;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * A source of instance identity.
+ *
+ * @param the type of public key.
+ * @param the type of private key.
+ * @since 2.16
+ */
+public abstract class InstanceIdentityProvider implements
+ ExtensionPoint {
+
+ /**
+ * Our logger.
+ */
+ private static final Logger LOGGER = Logger.getLogger(InstanceIdentityProvider.class.getName());
+ /**
+ * RSA keys.
+ */
+ public static final KeyTypes RSA =
+ new KeyTypes<>(RSAPublicKey.class, RSAPrivateKey.class);
+ /**
+ * DSA keys.
+ */
+ public static final KeyTypes DSA =
+ new KeyTypes<>(DSAPublicKey.class, DSAPrivateKey.class);
+ /**
+ * EC keys
+ */
+ public static final KeyTypes EC =
+ new KeyTypes<>(ECPublicKey.class, ECPrivateKey.class);
+
+ /**
+ * Gets the {@link KeyPair} that comprises the instance identity.
+ *
+ * @return the {@link KeyPair} that comprises the instance identity. {@code null} could technically be returned in
+ * the event that a keypair could not be generated, for example if the specific key type of this provider
+ * is not permitted at the required length by the JCA policy.
+ */
+ @CheckForNull
+ protected abstract KeyPair getKeyPair();
+
+ /**
+ * Shortcut to {@link KeyPair#getPublic()}.
+ *
+ * @return the public key. {@code null} if {@link #getKeyPair()} is {@code null}.
+ */
+ @SuppressWarnings("unchecked")
+ @CheckForNull
+ protected PUB getPublicKey() {
+ KeyPair keyPair = getKeyPair();
+ return keyPair == null ? null : (PUB) keyPair.getPublic();
+ }
+
+ /**
+ * Shortcut to {@link KeyPair#getPrivate()}.
+ *
+ * @return the private key. {@code null} if {@link #getKeyPair()} is {@code null}.
+ */
+ @SuppressWarnings("unchecked")
+ @CheckForNull
+ protected PRIV getPrivateKey() {
+ KeyPair keyPair = getKeyPair();
+ return keyPair == null ? null : (PRIV) keyPair.getPrivate();
+ }
+
+ /**
+ * Gets the self-signed {@link X509Certificate} that is associated with this identity. The certificate
+ * will must be currently valid. Repeated calls to this method may result in new certificates being generated.
+ *
+ * @return the certificate. {@code null} if {@link #getKeyPair()} is {@code null}.
+ */
+ @CheckForNull
+ protected abstract X509Certificate getCertificate();
+
+ /**
+ * Holds information about the paired keytypes that can be used to form the various identity keys.
+ *
+ * @param the type of public key.
+ * @param the type of private key.
+ */
+ public static final class KeyTypes {
+ /**
+ * The interface for the public key.
+ */
+ private final Class pubKeyType;
+ /**
+ * The interface for the private key.
+ */
+ private final Class privKeyType;
+
+ /**
+ * Constructor.
+ *
+ * @param pubKeyType the interface for the public key.
+ * @param privKeyType the interface for the private key.
+ */
+ private KeyTypes(Class pubKeyType, Class privKeyType) {
+ this.pubKeyType = pubKeyType;
+ this.privKeyType = privKeyType;
+ }
+
+ /**
+ * Gets the provider of the required identity type.
+ *
+ * @param type the type of keys.
+ * @param the type of public key.
+ * @param the type of private key.
+ * @return the provider or {@code null} if no provider of the specified type is available.
+ */
+ @CheckForNull
+ @SuppressWarnings("unchecked")
+ private static InstanceIdentityProvider get(
+ @Nonnull KeyTypes type) {
+ for (InstanceIdentityProvider provider : ExtensionList.lookup(InstanceIdentityProvider.class)) {
+ try {
+ KeyPair keyPair = provider.getKeyPair();
+ if (keyPair != null
+ && type.pubKeyType.isInstance(keyPair.getPublic())
+ && type.privKeyType.isInstance(keyPair.getPrivate())) {
+ return (InstanceIdentityProvider) provider;
+ }
+ } catch (RuntimeException e) {
+ LOGGER.log(Level.WARNING,
+ "Instance identity provider " + provider + " propagated a runtime exception", e);
+ } catch (Error e) {
+ LOGGER.log(Level.INFO,
+ "Encountered an error while consulting instance identity provider " + provider, e);
+ throw e;
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE,
+ "Instance identity provider " + provider + " propagated an uncaught exception", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the interface for the public key.
+ *
+ * @return the interface for the public key.
+ */
+ public Class getPublicKeyClass() {
+ return pubKeyType;
+ }
+
+ /**
+ * Gets the interface for the private key.
+ *
+ * @return the interface for the private key.
+ */
+ public Class getPrivateKeyClass() {
+ return privKeyType;
+ }
+
+ /**
+ * Gets the {@link KeyPair} that comprises the instance identity.
+ *
+ * @return the {@link KeyPair} that comprises the instance identity. {@code null} could technically be
+ * returned in
+ * the event that a keypair could not be generated, for example if the specific key type of this provider
+ * is not permitted at the required length by the JCA policy.
+ */
+ @CheckForNull
+ public KeyPair getKeyPair() {
+ InstanceIdentityProvider provider = get(this);
+ try {
+ return provider == null ? null : provider.getKeyPair();
+ } catch (RuntimeException e) {
+ LOGGER.log(Level.WARNING,
+ "Instance identity provider " + provider + " propagated a runtime exception", e);
+ return null;
+ } catch (Error e) {
+ LOGGER.log(Level.INFO,
+ "Encountered an error while consulting instance identity provider " + provider, e);
+ throw e;
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE,
+ "Instance identity provider " + provider + " propagated an uncaught exception", e);
+ return null;
+ }
+ }
+
+ /**
+ * Shortcut to {@link KeyPair#getPublic()}.
+ *
+ * @return the public key. {@code null} if {@link #getKeyPair()} is {@code null}.
+ */
+ @CheckForNull
+ public PUB getPublicKey() {
+ InstanceIdentityProvider provider = get(this);
+ try {
+ return provider == null ? null : provider.getPublicKey();
+ } catch (RuntimeException e) {
+ LOGGER.log(Level.WARNING,
+ "Instance identity provider " + provider + " propagated a runtime exception", e);
+ return null;
+ } catch (Error e) {
+ LOGGER.log(Level.INFO,
+ "Encountered an error while consulting instance identity provider " + provider, e);
+ throw e;
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE,
+ "Instance identity provider " + provider + " propagated an uncaught exception", e);
+ return null;
+ }
+ }
+
+ /**
+ * Shortcut to {@link KeyPair#getPrivate()}.
+ *
+ * @return the private key. {@code null} if {@link #getKeyPair()} is {@code null}.
+ */
+ @CheckForNull
+ public PRIV getPrivateKey() {
+ InstanceIdentityProvider provider = get(this);
+ try {
+ return provider == null ? null : provider.getPrivateKey();
+ } catch (RuntimeException e) {
+ LOGGER.log(Level.WARNING,
+ "Instance identity provider " + provider + " propagated a runtime exception", e);
+ return null;
+ } catch (Error e) {
+ LOGGER.log(Level.INFO,
+ "Encountered an error while consulting instance identity provider " + provider, e);
+ throw e;
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE,
+ "Instance identity provider " + provider + " propagated an uncaught exception", e);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the self-signed {@link X509Certificate} that is associated with this identity. The certificate
+ * will must be currently valid. Repeated calls to this method may result in new certificates being generated.
+ *
+ * @return the certificate. {@code null} if {@link #getKeyPair()} is {@code null}.
+ */
+ @CheckForNull
+ public X509Certificate getCertificate() {
+ InstanceIdentityProvider provider = get(this);
+ try {
+ return provider == null ? null : provider.getCertificate();
+ } catch (RuntimeException e) {
+ LOGGER.log(Level.WARNING,
+ "Instance identity provider " + provider + " propagated a runtime exception", e);
+ return null;
+ } catch (Error e) {
+ LOGGER.log(Level.INFO,
+ "Encountered an error while consulting instance identity provider " + provider, e);
+ throw e;
+ } catch (Throwable e) {
+ LOGGER.log(Level.SEVERE,
+ "Instance identity provider " + provider + " propagated an uncaught exception", e);
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java b/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java
index d1e5479bff..cf6d0daeaf 100644
--- a/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java
+++ b/core/src/main/java/jenkins/model/item_category/StandaloneProjectsCategory.java
@@ -28,6 +28,8 @@ import hudson.Extension;
/**
* Designed for projects with a self-contained configuration and history.
+ *
+ * @since 2.0
*/
@Extension(ordinal = -100)
public class StandaloneProjectsCategory extends ItemCategory {
diff --git a/core/src/main/java/jenkins/security/UserDetailsCache.java b/core/src/main/java/jenkins/security/UserDetailsCache.java
new file mode 100644
index 0000000000..be6e2d20b6
--- /dev/null
+++ b/core/src/main/java/jenkins/security/UserDetailsCache.java
@@ -0,0 +1,189 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, CloudBees, Inc.
+ *
+ * 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.security;
+
+import com.google.common.cache.Cache;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.security.UserMayOrMayNotExistException;
+import jenkins.model.Jenkins;
+import jenkins.util.SystemProperties;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UsernameNotFoundException;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.springframework.dao.DataAccessException;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.cache.CacheBuilder.newBuilder;
+
+/**
+ * Cache layer for {@link org.acegisecurity.userdetails.UserDetails} lookup.
+ *
+ * @since TODO
+ */
+@Extension
+public final class UserDetailsCache {
+
+ private static final String SYS_PROP_NAME = UserDetailsCache.class.getName() + ".EXPIRE_AFTER_WRITE_SEC";
+ /**
+ * Nr of seconds before a value expires after being cached, note full GC will also clear the cache.
+ * Should be able to set this value in script and then reload from disk to change in runtime.
+ */
+ private static /*not final*/ Integer EXPIRE_AFTER_WRITE_SEC = SystemProperties.getInteger(SYS_PROP_NAME, (int)TimeUnit.MINUTES.toSeconds(2));
+ private final Cache detailsCache;
+ private final Cache existanceCache;
+
+ /**
+ * Constructor intended to be instantiated by Jenkins only.
+ */
+ @Restricted(NoExternalUse.class)
+ public UserDetailsCache() {
+ if (EXPIRE_AFTER_WRITE_SEC == null || EXPIRE_AFTER_WRITE_SEC <= 0) {
+ //just in case someone is trying to trick us
+ EXPIRE_AFTER_WRITE_SEC = SystemProperties.getInteger(SYS_PROP_NAME, (int)TimeUnit.MINUTES.toSeconds(2));
+ if (EXPIRE_AFTER_WRITE_SEC <= 0) {
+ //The property could also be set to a negative value
+ EXPIRE_AFTER_WRITE_SEC = (int)TimeUnit.MINUTES.toSeconds(2);
+ }
+ }
+ detailsCache = newBuilder().softValues().expireAfterWrite(EXPIRE_AFTER_WRITE_SEC, TimeUnit.SECONDS).build();
+ existanceCache = newBuilder().softValues().expireAfterWrite(EXPIRE_AFTER_WRITE_SEC, TimeUnit.SECONDS).build();
+ }
+
+ /**
+ * The singleton instance registered in Jenkins.
+ * @return the cache
+ */
+ public static UserDetailsCache get() {
+ return ExtensionList.lookup(UserDetailsCache.class).get(UserDetailsCache.class);
+ }
+
+ /**
+ * Gets the cached UserDetails for the given username.
+ * Similar to {@link #loadUserByUsername(String)} except it doesn't perform the actual lookup if there is a cache miss.
+ *
+ * @param idOrFullName the username
+ *
+ * @return {@code null} if the cache doesn't contain any data for the key or the user details cached for the key.
+ * @throws UsernameNotFoundException if a previous lookup resulted in the same
+ */
+ @CheckForNull
+ public UserDetails getCached(String idOrFullName) throws UsernameNotFoundException {
+ Boolean exists = existanceCache.getIfPresent(idOrFullName);
+ if (exists != null && !exists) {
+ throw new UserMayOrMayNotExistException(String.format("\"%s\" does not exist", idOrFullName));
+ } else {
+ return detailsCache.getIfPresent(idOrFullName);
+ }
+ }
+
+ /**
+ * Locates the user based on the username, by first looking in the cache and then delegate to
+ * {@link hudson.security.SecurityRealm#loadUserByUsername(String)}.
+ *
+ * @param idOrFullName the username
+ * @return the details
+ *
+ * @throws UsernameNotFoundException (normally a {@link hudson.security.UserMayOrMayNotExistException})
+ * if the user could not be found or the user has no GrantedAuthority
+ * @throws DataAccessException if user could not be found for a repository-specific reason
+ * @throws ExecutionException if anything else went wrong in the cache lookup/retrieval
+ */
+ @Nonnull
+ public UserDetails loadUserByUsername(String idOrFullName) throws UsernameNotFoundException, DataAccessException, ExecutionException {
+ Boolean exists = existanceCache.getIfPresent(idOrFullName);
+ if(exists != null && !exists) {
+ throw new UsernameNotFoundException(String.format("\"%s\" does not exist", idOrFullName));
+ } else {
+ try {
+ return detailsCache.get(idOrFullName, new Retriever(idOrFullName));
+ } catch (ExecutionException | UncheckedExecutionException e) {
+ if (e.getCause() instanceof UsernameNotFoundException) {
+ throw ((UsernameNotFoundException)e.getCause());
+ } else if (e.getCause() instanceof DataAccessException) {
+ throw ((DataAccessException)e.getCause());
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Discards all entries in the cache.
+ */
+ public void invalidateAll() {
+ existanceCache.invalidateAll();
+ detailsCache.invalidateAll();
+ }
+
+ /**
+ * Discards any cached value for key.
+ * @param idOrFullName the key
+ */
+ public void invalidate(final String idOrFullName) {
+ existanceCache.invalidate(idOrFullName);
+ detailsCache.invalidate(idOrFullName);
+ }
+
+ /**
+ * Callable that performs the actual lookup if there is a cache miss.
+ * @see #loadUserByUsername(String)
+ */
+ private class Retriever implements Callable {
+ private final String idOrFullName;
+
+ private Retriever(final String idOrFullName) {
+ this.idOrFullName = idOrFullName;
+ }
+
+ @Override
+ public UserDetails call() throws Exception {
+ try {
+ Jenkins jenkins = Jenkins.getInstance();
+ UserDetails userDetails = jenkins.getSecurityRealm().loadUserByUsername(idOrFullName);
+ if (userDetails == null) {
+ existanceCache.put(this.idOrFullName, Boolean.FALSE);
+ throw new NullPointerException("hudson.security.SecurityRealm should never return null. "
+ + jenkins.getSecurityRealm() + " returned null for idOrFullName='" + idOrFullName + "'");
+ }
+ existanceCache.put(this.idOrFullName, Boolean.TRUE);
+ return userDetails;
+ } catch (UsernameNotFoundException e) {
+ existanceCache.put(this.idOrFullName, Boolean.FALSE);
+ throw e;
+ } catch (DataAccessException e) {
+ existanceCache.invalidate(this.idOrFullName);
+ throw e;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java
index 36ebaf2969..d0759dbd6b 100644
--- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java
+++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java
@@ -2,6 +2,7 @@ package jenkins.slaves;
import hudson.AbortException;
import hudson.Extension;
+import hudson.Util;
import hudson.model.Computer;
import hudson.remoting.Channel;
import hudson.remoting.Channel.Listener;
@@ -60,11 +61,27 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol {
@Inject
NioChannelSelector hub;
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptIn() {
+ return OPT_IN;
+ }
+
@Override
public String getName() {
return "JNLP-connect";
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return Messages.JnlpSlaveAgentProtocol_displayName();
+ }
+
@Override
public void handle(Socket socket) throws IOException, InterruptedException {
new Handler(hub.getHub(),socket).run();
@@ -156,4 +173,14 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol {
* This secret value is used as a seed for agents.
*/
public static final HMACConfidentialKey SLAVE_SECRET = new HMACConfidentialKey(JnlpSlaveAgentProtocol.class,"secret");
+
+ /**
+ * A/B test turning off this protocol by default.
+ */
+ private static final boolean OPT_IN;
+
+ static {
+ byte hash = Util.fromHexString(Jenkins.getInstance().getLegacyInstanceId())[0];
+ OPT_IN = (hash % 10) == 0;
+ }
}
diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java
index ff9ce64c1a..1a2bb649dc 100644
--- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java
+++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java
@@ -27,6 +27,22 @@ public class JnlpSlaveAgentProtocol2 extends JnlpSlaveAgentProtocol {
return "JNLP2-connect";
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptIn() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return Messages.JnlpSlaveAgentProtocol2_displayName();
+ }
+
@Override
public void handle(Socket socket) throws IOException, InterruptedException {
new Handler2(hub.getHub(),socket).run();
diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java
index 8174f841cc..6a3087e8ff 100644
--- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java
+++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java
@@ -33,15 +33,34 @@ import jenkins.util.SystemProperties;
* @author Akshay Dayal
* @since 1.XXX
*/
+// TODO @Deprecated once JENKINS-36871 is merged
@Extension
public class JnlpSlaveAgentProtocol3 extends AgentProtocol {
@Inject
NioChannelSelector hub;
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptIn() {
+ return !ENABLED;
+ }
+
@Override
public String getName() {
- if (ENABLED) return "JNLP3-connect";
- else return "JNLP3-disabled";
+ // we only want to force the protocol off for users that have explicitly banned it via system property
+ // everyone on the A/B test will just have the opt-in flag toggled
+ // TODO strip all this out and hardcode OptIn==TRUE once JENKINS-36871 is merged
+ return forceEnabled != Boolean.FALSE ? "JNLP3-connect" : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return Messages.JnlpSlaveAgentProtocol3_displayName();
}
@Override
@@ -128,12 +147,12 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol {
@SuppressFBWarnings(value = "MS_SHOULD_BE_REFACTORED_TO_BE_FINAL",
justification = "Part of the administrative API for System Groovy scripts.")
public static boolean ENABLED;
+ private static final Boolean forceEnabled;
static {
- String propName = JnlpSlaveAgentProtocol3.class.getName() + ".enabled";
- String propertyString = SystemProperties.getString(propName);
- if (propertyString != null)
- ENABLED = SystemProperties.getBoolean(propName);
+ forceEnabled = SystemProperties.optBoolean(JnlpSlaveAgentProtocol3.class.getName() + ".enabled");
+ if (forceEnabled != null)
+ ENABLED = forceEnabled;
else {
byte hash = Util.fromHexString(Jenkins.getActiveInstance().getLegacyInstanceId())[0];
ENABLED = (hash%10)==0;
diff --git a/core/src/main/java/jenkins/util/SystemProperties.java b/core/src/main/java/jenkins/util/SystemProperties.java
index 12643ff5a5..b90f23781c 100644
--- a/core/src/main/java/jenkins/util/SystemProperties.java
+++ b/core/src/main/java/jenkins/util/SystemProperties.java
@@ -204,6 +204,21 @@ public class SystemProperties implements ServletContextListener {
}
return def;
}
+
+ /**
+ * Returns {@link Boolean#TRUE} if the named system property exists and is equal to the string {@code "true}
+ * (ignoring case), returns {@link Boolean#FALSE} if the system property exists and doesn't equal {@code "true}
+ * otherwise returns {@code null} if the named system property does not exist.
+ *
+ * @param name the system property name.
+ * @return {@link Boolean#TRUE}, {@link Boolean#FALSE} or {@code null}
+ * @since 2.16
+ */
+ @CheckForNull
+ public static Boolean optBoolean(String name) {
+ String v = getString(name);
+ return v == null ? null : Boolean.parseBoolean(v);
+ }
/**
* Determines the integer value of the system property with the
diff --git a/core/src/main/java/jenkins/widgets/BuildListTable.java b/core/src/main/java/jenkins/widgets/BuildListTable.java
index ec8e60e368..7be989feaf 100644
--- a/core/src/main/java/jenkins/widgets/BuildListTable.java
+++ b/core/src/main/java/jenkins/widgets/BuildListTable.java
@@ -25,9 +25,13 @@
package jenkins.widgets;
import hudson.Functions;
+import hudson.Util;
import hudson.model.BallColor;
import hudson.model.Run;
import net.sf.json.JSONObject;
+
+import java.util.Date;
+
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -45,6 +49,7 @@ public class BuildListTable extends RunListProgressiveRendering {
element.put("displayName", build.getDisplayName());
element.put("timestampString", build.getTimestampString());
element.put("timestampString2", build.getTimestampString2());
+ element.put("timestampString3", Util.XS_DATETIME_FORMATTER.format(new Date(build.getStartTimeInMillis())));
Run.Summary buildStatusSummary = build.getBuildStatusSummary();
element.put("buildStatusSummaryWorse", buildStatusSummary.isWorse);
element.put("buildStatusSummaryMessage", buildStatusSummary.message);
diff --git a/core/src/main/resources/hudson/Messages.properties b/core/src/main/resources/hudson/Messages.properties
index 6722b6b940..ec7f6002b4 100644
--- a/core/src/main/resources/hudson/Messages.properties
+++ b/core/src/main/resources/hudson/Messages.properties
@@ -71,4 +71,13 @@ ProxyConfiguration.FailedToConnectViaProxy=Failed to connect to {0}.
ProxyConfiguration.FailedToConnect=Failed to connect to {0} (code {1}).
ProxyConfiguration.Success=Success
-Functions.NoExceptionDetails=No Exception details
\ No newline at end of file
+Functions.NoExceptionDetails=No Exception details
+
+PluginWrapper.missing={0} v{1} is missing. To fix, install v{1} or later.
+PluginWrapper.failed_to_load_plugin={0} v{1} failed to load.
+PluginWrapper.failed_to_load_dependency={0} v{1} failed to load. Fix this plugin first.
+PluginWrapper.disabledAndObsolete={0} v{1} is disabled and older than required. To fix, install v{2} or later and enable it.
+PluginWrapper.disabled={0} is disabled. To fix, enable it.
+PluginWrapper.obsolete={0} v{1} is older than required. To fix, install v{2} or later.
+PluginWrapper.obsoleteCore=You must update Jenkins from v{0} to v{1} or later to run this plugin.
+TcpSlaveAgentListener.PingAgentProtocol.displayName=Ping protocol
diff --git a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly
index 3a7110fdca..1c80b2d66a 100644
--- a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly
+++ b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly
@@ -28,7 +28,7 @@ THE SOFTWARE.
${%PluginCycles}
+
+ There are dependency errors loading some plugins:
+
+
+
${plugin.longName} v${plugin.version}
+
+
+
${d}
+
+
+
+
+
+
+
diff --git a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly
index 99585e9896..48854e95fb 100644
--- a/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly
+++ b/core/src/main/resources/hudson/TcpSlaveAgentListener/index.jelly
@@ -23,7 +23,8 @@ THE SOFTWARE.
-->
-
+
+
+
+
+
+
Jenkins
diff --git a/core/src/main/resources/hudson/cli/CliProtocol/description.jelly b/core/src/main/resources/hudson/cli/CliProtocol/description.jelly
new file mode 100644
index 0000000000..e24d569129
--- /dev/null
+++ b/core/src/main/resources/hudson/cli/CliProtocol/description.jelly
@@ -0,0 +1,4 @@
+
+
+ ${%Accepts connections from CLI clients}
+
diff --git a/core/src/main/resources/hudson/cli/CliProtocol2/description.jelly b/core/src/main/resources/hudson/cli/CliProtocol2/description.jelly
new file mode 100644
index 0000000000..66f27c511c
--- /dev/null
+++ b/core/src/main/resources/hudson/cli/CliProtocol2/description.jelly
@@ -0,0 +1,4 @@
+
+
+ ${%Extends the version 1 protocol by adding transport encryption}
+
diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties
index 9f223cedc5..f2cf34d4d6 100644
--- a/core/src/main/resources/hudson/cli/Messages.properties
+++ b/core/src/main/resources/hudson/cli/Messages.properties
@@ -90,3 +90,9 @@ ConnectNodeCommand.ShortDescription=Reconnect to a node(s)
DisconnectNodeCommand.ShortDescription=Disconnects from a node.
QuietDownCommand.ShortDescription=Quiet down Jenkins, in preparation for a restart. Don\u2019t start any builds.
CancelQuietDownCommand.ShortDescription=Cancel the effect of the "quiet-down" command.
+OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command.
+WaitNodeOnlineCommand.ShortDescription=Wait for a node to become online.
+WaitNodeOfflineCommand.ShortDescription=Wait for a node to become offline.
+
+CliProtocol.displayName=Jenkins CLI Protocol/1
+CliProtocol2.displayName=Jenkins CLI Protocol/2
diff --git a/core/src/main/resources/hudson/cli/Messages_bg.properties b/core/src/main/resources/hudson/cli/Messages_bg.properties
index 68c5cc94ee..52e27ca91c 100644
--- a/core/src/main/resources/hudson/cli/Messages_bg.properties
+++ b/core/src/main/resources/hudson/cli/Messages_bg.properties
@@ -134,3 +134,11 @@ QuietDownCommand.ShortDescription=\
\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043d\u0430 Jenkins \u0437\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435. \u0414\u0430 \u043d\u0435 \u0441\u0435 \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0442 \u043d\u043e\u0432\u0438 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f.
CancelQuietDownCommand.ShortDescription=\
\u041e\u0442\u043c\u044f\u043d\u0430 \u043d\u0430 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u2014 \u0449\u0435 \u0441\u0435 \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0442 \u043d\u043e\u0432\u0438 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f.
+OfflineNodeCommand.ShortDescription=\
+ \u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u0435\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f \u0434\u043e\u043a\u0430\u0442\u043e \u043d\u0435 \u0441\u0435\
+ \u0432\u044a\u0432\u0435\u0434\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0442\u0430 \u201eonline-node\u201c.
+WaitNodeOnlineCommand.ShortDescription=\
+ \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f.
+WaitNodeOfflineCommand.ShortDescription=\
+ \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u043d\u0435 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f.
+
diff --git a/core/src/main/resources/hudson/cli/Messages_da.properties b/core/src/main/resources/hudson/cli/Messages_da.properties
index 63bd44a17a..bfd9e8d040 100644
--- a/core/src/main/resources/hudson/cli/Messages_da.properties
+++ b/core/src/main/resources/hudson/cli/Messages_da.properties
@@ -8,3 +8,5 @@ DisconnectNodeCommand.ShortDescription=Afbryder forbindelsen til en node
ConnectNodeCommand.ShortDescription=Gentilslut en node
QuietDownCommand.ShortDescription=Forbereder nedlukning, starter ingen nye byg.
CancelQuietDownCommand.ShortDescription=Sl\u00e5 effekten af "quite-down" kommandoen fra.
+OfflineNodeCommand.ShortDescription=Hold midlertidigt op med at bygge p\u00e5 en node, indtil n\u00e6ste "online-node" kommando.
+
diff --git a/core/src/main/resources/hudson/cli/Messages_de.properties b/core/src/main/resources/hudson/cli/Messages_de.properties
index 1469580d2c..287aeef516 100644
--- a/core/src/main/resources/hudson/cli/Messages_de.properties
+++ b/core/src/main/resources/hudson/cli/Messages_de.properties
@@ -7,3 +7,5 @@ ConnectNodeCommand.ShortDescription=Erneut mit Knoten verbinden.
DisconnectNodeCommand.ShortDescription=Knoten trennen.
QuietDownCommand.ShortDescription=Jenkins' Aktivit\u00e4t reduzieren, z.B. zur Vorbereitung eines Neustarts. Es werden keine neuen Builds mehr gestartet.
CancelQuietDownCommand.ShortDescription=Wirkung des Befehls "quiet-down" wieder aufheben.
+OfflineNodeCommand.ShortDescription=Knoten wird bis zum n\u00e4chsten "online-node"-Kommando f\u00fcr keine neuen Builds verwendet.
+
diff --git a/core/src/main/resources/hudson/cli/Messages_es.properties b/core/src/main/resources/hudson/cli/Messages_es.properties
index ed2d492031..11525579ca 100644
--- a/core/src/main/resources/hudson/cli/Messages_es.properties
+++ b/core/src/main/resources/hudson/cli/Messages_es.properties
@@ -56,3 +56,7 @@ ConnectNodeCommand.ShortDescription=Reconectarse con un nodo
DisconnectNodeCommand.ShortDescription=Desconectarse de un nodo
QuietDownCommand.ShortDescription=Poner Jenkins en modo quieto y estar preparado para un reinicio. No comenzar ninguna ejecuci\u00f3n.
CancelQuietDownCommand.ShortDescription=Cancelar el efecto del comando "quiet-down".
+OfflineNodeCommand.ShortDescription=Dejar de utilizar un nodo temporalmente hasta que se ejecute el comando "online-node".
+WaitNodeOnlineCommand.ShortDescription=Esperando hasta que el nodo esté activado
+WaitNodeOfflineCommand.ShortDescription=Esperando a que el nodo esté desactivado
+
diff --git a/core/src/main/resources/hudson/cli/Messages_it.properties b/core/src/main/resources/hudson/cli/Messages_it.properties
index 0b7c38c2b6..ca09f191df 100644
--- a/core/src/main/resources/hudson/cli/Messages_it.properties
+++ b/core/src/main/resources/hudson/cli/Messages_it.properties
@@ -4,3 +4,7 @@ OnlineNodeCommand.ShortDescription=Resume using a node for performing builds, to
ClearQueueCommand.ShortDescription=Pulisce la coda di lavoro
ReloadConfigurationCommand.ShortDescription=Discard all the loaded data in memory and reload everything from file system. Useful when you modified config files directly on disk.
ConnectNodeCommand.ShortDescription=Riconnettersi ad un nodo
+OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command.
+WaitNodeOnlineCommand.ShortDescription=Attende che il nodo sia attivo
+WaitNodeOfflineCommand.ShortDescription=Attende che un nodo sia disattivo
+
diff --git a/core/src/main/resources/hudson/cli/Messages_ja.properties b/core/src/main/resources/hudson/cli/Messages_ja.properties
index 44578a51aa..fb1ddd6860 100644
--- a/core/src/main/resources/hudson/cli/Messages_ja.properties
+++ b/core/src/main/resources/hudson/cli/Messages_ja.properties
@@ -78,3 +78,7 @@ ConnectNodeCommand.ShortDescription=\u30ce\u30fc\u30c9\u3068\u518d\u63a5\u7d9a\u
DisconnectNodeCommand.ShortDescription=\u30ce\u30fc\u30c9\u3068\u306e\u63a5\u7d9a\u3092\u5207\u65ad\u3057\u307e\u3059\u3002
QuietDownCommand.ShortDescription=Jenkins\u306f\u518d\u8d77\u52d5\u306b\u5411\u3051\u3066\u7d42\u4e86\u51e6\u7406\u3092\u5b9f\u65bd\u4e2d\u3067\u3059\u3002\u30d3\u30eb\u30c9\u3092\u958b\u59cb\u3057\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002
CancelQuietDownCommand.ShortDescription="quiet-down"\u30b3\u30de\u30f3\u30c9\u306e\u51e6\u7406\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u307e\u3059\u3002
+OfflineNodeCommand.ShortDescription="online-node"\u30b3\u30de\u30f3\u30c9\u304c\u5b9f\u884c\u3055\u308c\u308b\u307e\u3067\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u4e00\u6642\u7684\u306b\u505c\u6b62\u3057\u307e\u3059\u3002
+WaitNodeOnlineCommand.ShortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30f3\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002
+WaitNodeOfflineCommand.ShortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30d5\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002
+
diff --git a/core/src/main/resources/hudson/cli/Messages_lt.properties b/core/src/main/resources/hudson/cli/Messages_lt.properties
index ef12b07b2f..f83756b5ab 100644
--- a/core/src/main/resources/hudson/cli/Messages_lt.properties
+++ b/core/src/main/resources/hudson/cli/Messages_lt.properties
@@ -82,14 +82,14 @@ BuildCommand.CLICause.CannotBuildUnknownReasons=\
Negalima vykdyti {0} d\u0117l ne\u017einom\u0173 prie\u017eas\u010di\u0173.
DeleteNodeCommand.ShortDescription=Trina mazg\u0105(-us)
-
ReloadJobCommand.ShortDescription=I\u0161 naujo \u012fkelti darb\u0105(-us)
-
OnlineNodeCommand.ShortDescription=V\u0117l naudoti mazg\u0105 vykdymams, atjungiant ankstesn\u012f re\u017eim\u0105 \u201eatsijung\u0119s\u201c.
-
ClearQueueCommand.ShortDescription=Valo vykdymo eil\u0119.
ReloadConfigurationCommand.ShortDescription=I\u0161mesti visus atminties duomenis ir visk\u0105 \u012fkelti i\u0161 fail\u0173 sistemos. Naudinga, jei konfig\u016bracinius failus keit\u0117te tiesiai diske.
-
DisconnectNodeCommand.ShortDescription=Atsijungia nuo mazgo.
QuietDownCommand.ShortDescription=Tyliai nuleisti Jenkins\u0105, pasiruo\u0161iant paleidimui i\u0161 naujo. Neprad\u0117ti joki\u0173 vykdym\u0173.
CancelQuietDownCommand.ShortDescription=Nutraukti \u201etylaus i\u0161jungimo\u201c komandos efekt\u0105.
+OfflineNodeCommand.ShortDescription=Laikinai nebenaudoti mazgo darb\u0173 vykdymui, kol bus \u012fvykdyta kita \u201emazgas \u012fjungtas\u201c komanda.
+WaitNodeOnlineCommand.ShortDescription=Laukti, kol mazgas prisijungs.
+WaitNodeOfflineCommand.ShortDescription=Laukti, kol mazgas atsijungs.
+
diff --git a/core/src/main/resources/hudson/cli/Messages_pt_BR.properties b/core/src/main/resources/hudson/cli/Messages_pt_BR.properties
index d51546dc17..7965b0b7e1 100644
--- a/core/src/main/resources/hudson/cli/Messages_pt_BR.properties
+++ b/core/src/main/resources/hudson/cli/Messages_pt_BR.properties
@@ -124,3 +124,7 @@ ConnectNodeCommand.ShortDescription=Reconectar ao n\u00f3
DisconnectNodeCommand.ShortDescription=Desconectar do n\u00f3
QuietDownCommand.ShortDescription=Desativar em modo silencioso, em prepara\u00e7\u00e3o para o rein\u00edcio. N\u00e3o come\u00e7e nenhum build.
CancelQuietDownCommand.ShortDescription=Cancela o comando "quiet-down"
+OfflineNodeCommand.ShortDescription=Temporariamente n\u00e3o usando um n\u00f3 para os builds. Aguardando o pr\u00f3ximo comando de modo-online
+WaitNodeOnlineCommand.ShortDescription=Aguarda por um n\u00f3 que se torne online
+WaitNodeOfflineCommand.ShortDescription=Aguarde por um n\u00f3 ficar offline.
+
diff --git a/core/src/main/resources/hudson/cli/Messages_zh_CN.properties b/core/src/main/resources/hudson/cli/Messages_zh_CN.properties
index 5365b3ec1a..c767020ed3 100644
--- a/core/src/main/resources/hudson/cli/Messages_zh_CN.properties
+++ b/core/src/main/resources/hudson/cli/Messages_zh_CN.properties
@@ -6,3 +6,5 @@ ReloadConfigurationCommand.ShortDescription=Discard all the loaded data in memor
ConnectNodeCommand.ShortDescription=Reconnect to a node
QuietDownCommand.ShortDescription=Quiet down Jenkins, in preparation for a restart. Don''t start any builds.
CancelQuietDownCommand.ShortDescription=Cancel the effect of the "quiet-down" command.
+OfflineNodeCommand.ShortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command.
+
diff --git a/core/src/main/resources/hudson/cli/Messages_zh_TW.properties b/core/src/main/resources/hudson/cli/Messages_zh_TW.properties
index 7fb1008db9..b2e5746b7a 100644
--- a/core/src/main/resources/hudson/cli/Messages_zh_TW.properties
+++ b/core/src/main/resources/hudson/cli/Messages_zh_TW.properties
@@ -81,3 +81,7 @@ ReloadConfigurationCommand.ShortDescription=\u653e\u68c4\u6240\u6709\u8a18\u61b6
ConnectNodeCommand.ShortDescription=\u9023\u7dda\u5230\u6307\u5b9a\u7bc0\u9ede\u3002
QuietDownCommand.ShortDescription=\u8b93 Jenkins \u6c89\u6fb1\u4e00\u4e0b\uff0c\u6e96\u5099\u91cd\u65b0\u555f\u52d5\u3002\u5148\u4e0d\u8981\u518d\u5efa\u7f6e\u4efb\u4f55\u4f5c\u696d\u3002
CancelQuietDownCommand.ShortDescription=\u53d6\u6d88 "quiet-down" \u6307\u4ee4\u3002
+OfflineNodeCommand.ShortDescription=\u66ab\u6642\u4e0d\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u76f4\u5230\u57f7\u884c "online-node" \u6307\u4ee4\u70ba\u6b62\u3002
+WaitNodeOnlineCommand.ShortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u4e0a\u7dda\u3002
+WaitNodeOfflineCommand.ShortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u96e2\u7dda\u3002
+
diff --git a/core/src/main/resources/hudson/model/Job/configure.jelly b/core/src/main/resources/hudson/model/Job/configure.jelly
index ac39d60dbb..ddf5467f8d 100644
--- a/core/src/main/resources/hudson/model/Job/configure.jelly
+++ b/core/src/main/resources/hudson/model/Job/configure.jelly
@@ -46,7 +46,7 @@ THE SOFTWARE.
-
+
diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties
index 46e360d433..f6d08fbd1f 100644
--- a/core/src/main/resources/hudson/model/Messages.properties
+++ b/core/src/main/resources/hudson/model/Messages.properties
@@ -95,9 +95,6 @@ CLI.clear-queue.shortDescription=Clears the build queue.
CLI.disable-job.shortDescription=Disables a job.
CLI.enable-job.shortDescription=Enables a job.
CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command.
-CLI.offline-node.shortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command.
-CLI.wait-node-online.shortDescription=Wait for a node to become online.
-CLI.wait-node-offline.shortDescription=Wait for a node to become offline.
Computer.Caption=Agent {0}
Computer.NoSuchSlaveExists=No such agent "{0}" exists. Did you mean "{1}"?
diff --git a/core/src/main/resources/hudson/model/Messages_bg.properties b/core/src/main/resources/hudson/model/Messages_bg.properties
index 3cb62a1d60..106bf1d055 100644
--- a/core/src/main/resources/hudson/model/Messages_bg.properties
+++ b/core/src/main/resources/hudson/model/Messages_bg.properties
@@ -148,13 +148,6 @@ CLI.delete-node.shortDescription=\
CLI.online-node.shortDescription=\
\u041e\u0442\u043d\u043e\u0432\u043e \u0434\u0430 \u0441\u0435 \u043f\u043e\u043b\u0437\u0432\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f. \u041e\u0442\u043c\u0435\u043d\u044f\u043d\u0435 \u043d\u0430 \u043f\u0440\u0435\u0434\u0445\u043e\u0434\u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\
\u201eoffline-node\u201c.
-CLI.offline-node.shortDescription=\
- \u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u0435\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0437\u0430 \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0438\u044f \u0434\u043e\u043a\u0430\u0442\u043e \u043d\u0435 \u0441\u0435\
- \u0432\u044a\u0432\u0435\u0434\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0442\u0430 \u201eonline-node\u201c.
-CLI.wait-node-online.shortDescription=\
- \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f.
-CLI.wait-node-offline.shortDescription=\
- \u0418\u0437\u0447\u0430\u043a\u0432\u0430\u043d\u0435 \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0430 \u0434\u0430 \u043d\u0435 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f.
ComputerSet.DisplayName=\
\u041a\u043e\u043c\u043f\u044e\u0442\u0440\u0438
diff --git a/core/src/main/resources/hudson/model/Messages_da.properties b/core/src/main/resources/hudson/model/Messages_da.properties
index af79835829..6cd683c17a 100644
--- a/core/src/main/resources/hudson/model/Messages_da.properties
+++ b/core/src/main/resources/hudson/model/Messages_da.properties
@@ -153,7 +153,6 @@ Job.NoRecentBuildFailed=Ingen byg har fejlet for nyligt.
Permalink.LastUnsuccessfulBuild=Seneste fejlede byg
RunParameterDefinition.DisplayName=K\u00f8rselsparameter
Hudson.JobAlreadyExists=Et job eksisterer allerede med navnet ''{0}''
-CLI.offline-node.shortDescription=Hold midlertidigt op med at bygge p\u00e5 en node, indtil n\u00e6ste "online-node" kommando.
MyViewsProperty.DisplayName=Mine visninger
UpdateCenter.PluginCategory.must-be-labeled=Ukatagoriserede
AbstractProject.Disabled=Byg sl\u00e5et fra
diff --git a/core/src/main/resources/hudson/model/Messages_de.properties b/core/src/main/resources/hudson/model/Messages_de.properties
index a160fefc7b..08765ea186 100644
--- a/core/src/main/resources/hudson/model/Messages_de.properties
+++ b/core/src/main/resources/hudson/model/Messages_de.properties
@@ -79,7 +79,6 @@ CLI.clear-queue.shortDescription=Build-Warteschlange l\u00f6schen.
CLI.disable-job.shortDescription=Job deaktivieren.
CLI.enable-job.shortDescription=Job aktivieren.
CLI.disconnect-node.shortDescription=Knoten trennen.
-CLI.offline-node.shortDescription=Knoten wird bis zum n\u00e4chsten "online-node"-Kommando f\u00fcr keine neuen Builds verwendet.
CLI.safe-restart.shortDescription=Startet Jenkins neu.
Queue.init=Build-Warteschlange neu initialisieren
diff --git a/core/src/main/resources/hudson/model/Messages_es.properties b/core/src/main/resources/hudson/model/Messages_es.properties
index 4d8a67d60d..d1f693619d 100644
--- a/core/src/main/resources/hudson/model/Messages_es.properties
+++ b/core/src/main/resources/hudson/model/Messages_es.properties
@@ -59,7 +59,6 @@ BallColor.Unstable=Inestable
CLI.disable-job.shortDescription=Desactivar una tarea
CLI.enable-job.shortDescription=Activar una tarea
CLI.online-node.shortDescription=Continuar usando un nodo y candelar el comando "offline-node" mas reciente.
-CLI.offline-node.shortDescription=Dejar de utilizar un nodo temporalmente hasta que se ejecute el comando "online-node".
Computer.Caption=Remoto {0}
Computer.Permissions.Title=Nodo
@@ -285,8 +284,6 @@ ResultTrend.Aborted=Cancelado
TextParameterDefinition.DisplayName=Parámetro de texto
AbstractProject.AssignedLabelString_NoMatch_DidYouMean=No hay ningún nodo que cumpla esta asignación. ¿Quizás se refiera a ''{1}'' en lugar de ''{0}''?
ResultTrend.Success=Correcto
-CLI.wait-node-online.shortDescription=Esperando hasta que el nodo esté activado
-CLI.wait-node-offline.shortDescription=Esperando a que el nodo esté desactivado
AbstractProject.CancelPermission.Description=Este permiso permite que se pueda cancelar un trabajo.
Run.Summary.NotBuilt=no se ha ejecutado
AbstractProject.DiscoverPermission.Description=Este permiso garantiza que se pueda descubrir tareas. \
diff --git a/core/src/main/resources/hudson/model/Messages_fr.properties b/core/src/main/resources/hudson/model/Messages_fr.properties
index f9d5295135..30e1b2e65b 100644
--- a/core/src/main/resources/hudson/model/Messages_fr.properties
+++ b/core/src/main/resources/hudson/model/Messages_fr.properties
@@ -1,17 +1,17 @@
# The MIT License
-#
+#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Eric Lefevre-Ardant, Damien Finck
-#
+#
# 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
@@ -55,7 +55,7 @@ Executor.NotAvailable=N/A
FreeStyleProject.DisplayName=Construire un projet free-style
-FreeStyleProject.Description=Ceci est la fonction principale de Jenkins qui sert \u00E0 builder (construire) votre projet. Vous pouvez int\u00E9grer tous les outils de gestion de version avec tous les syst\u00E8mes de build. Il est m\u00EAme possible d''utiliser Jenkins pour tout autre chose qu''un build logiciel.
+FreeStyleProject.Description=Ceci est la fonction principale de Jenkins qui sert \u00E0 builder (construire) votre projet. Vous pouvez int\u00E9grer tous les outils de gestion de version avec tous les syst\u00E8mes de build. Il est m\u00EAme possible d''utiliser Jenkins pour tout autre chose qu''un build logiciel.
Hudson.BadPortNumber=Num\u00e9ro de port incorrect {0}
Hudson.Computer.Caption=Ma\u00eetre
diff --git a/core/src/main/resources/hudson/model/Messages_it.properties b/core/src/main/resources/hudson/model/Messages_it.properties
index 67ec7630c1..8458a59a89 100644
--- a/core/src/main/resources/hudson/model/Messages_it.properties
+++ b/core/src/main/resources/hudson/model/Messages_it.properties
@@ -73,15 +73,10 @@ BallColor.Unstable=Instabile
CLI.disable-job.shortDescription=Disabilita un job
CLI.enable-job.shortDescription=Abilita un job
CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command.
-CLI.offline-node.shortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command.
-CLI.wait-node-online.shortDescription=Attende che il nodo sia attivo
-CLI.wait-node-offline.shortDescription=Attende che un nodo sia disattivo
-
ComputerSet.DisplayName=nodes
Executor.NotAvailable=N/A
-
FreeStyleProject.DisplayName=Configura una build di tipo generico
FreeStyleProject.Description=Questa \u00E8 la funzione principale di Jenkins. Jenkins effettuer\u00E0 una compilazione del progetto, combinando qualsiasi SCM con qualsiasi sistema di compilazione e si pu\u00F2 usare anche per qualcosa di altro che non sia un programma di compilazione.
diff --git a/core/src/main/resources/hudson/model/Messages_ja.properties b/core/src/main/resources/hudson/model/Messages_ja.properties
index 2699e4fcc8..3d51ee20c7 100644
--- a/core/src/main/resources/hudson/model/Messages_ja.properties
+++ b/core/src/main/resources/hudson/model/Messages_ja.properties
@@ -295,9 +295,6 @@ CLI.clear-queue.shortDescription=\u30d3\u30eb\u30c9\u30ad\u30e5\u30fc\u3092\u30a
CLI.disable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u7121\u52b9\u5316\u3057\u307e\u3059\u3002
CLI.enable-job.shortDescription=\u30b8\u30e7\u30d6\u3092\u6709\u52b9\u5316\u3057\u307e\u3059\u3002
CLI.online-node.shortDescription=\u76f4\u524d\u306b\u5b9f\u884c\u3057\u305f"online-node"\u30b3\u30de\u30f3\u30c9\u3092\u53d6\u308a\u6d88\u3057\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u518d\u958b\u3057\u307e\u3059\u3002
-CLI.offline-node.shortDescription="online-node"\u30b3\u30de\u30f3\u30c9\u304c\u5b9f\u884c\u3055\u308c\u308b\u307e\u3067\u3001\u30d3\u30eb\u30c9\u3092\u5b9f\u884c\u3059\u308b\u30ce\u30fc\u30c9\u306e\u4f7f\u7528\u3092\u4e00\u6642\u7684\u306b\u505c\u6b62\u3057\u307e\u3059\u3002
-CLI.wait-node-online.shortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30f3\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002
-CLI.wait-node-offline.shortDescription=\u30ce\u30fc\u30c9\u304c\u30aa\u30d5\u30e9\u30a4\u30f3\u306b\u306a\u308b\u306e\u3092\u5f85\u3061\u307e\u3059\u3002
BuildAuthorizationToken.InvalidTokenProvided=\u8a8d\u8a3c\u30c8\u30fc\u30af\u30f3\u304c\u9593\u9055\u3063\u3066\u3044\u307e\u3059\u3002
ManageJenkinsAction.DisplayName=Jenkins\u306e\u7ba1\u7406
diff --git a/core/src/main/resources/hudson/model/Messages_lt.properties b/core/src/main/resources/hudson/model/Messages_lt.properties
index b80d8ca16a..6be646658f 100644
--- a/core/src/main/resources/hudson/model/Messages_lt.properties
+++ b/core/src/main/resources/hudson/model/Messages_lt.properties
@@ -70,9 +70,6 @@ CLI.clear-queue.shortDescription=I\u0161valo vykdymo eil\u0119.
CLI.disable-job.shortDescription=I\u0161jungia darb\u0105.
CLI.enable-job.shortDescription=\u012ejungia darb\u0105.
CLI.disconnect-node.shortDescription=Atsijungia nuo mazgo.
-CLI.offline-node.shortDescription=Laikinai nebenaudoti mazgo darb\u0173 vykdymui, kol bus \u012fvykdyta kita \u201emazgas \u012fjungtas\u201c komanda.
-CLI.wait-node-online.shortDescription=Laukti, kol mazgas prisijungs.
-CLI.wait-node-offline.shortDescription=Laukti, kol mazgas atsijungs.
Computer.Caption=Agentas {0}
Computer.NoSuchSlaveExists=N\u0117ra tokio agento \u201e{0}\u201c. Ar tur\u0117jote omeny \u201e{1}\u201c?
diff --git a/core/src/main/resources/hudson/model/Messages_pt_BR.properties b/core/src/main/resources/hudson/model/Messages_pt_BR.properties
index 550313d9eb..4ed07299f1 100644
--- a/core/src/main/resources/hudson/model/Messages_pt_BR.properties
+++ b/core/src/main/resources/hudson/model/Messages_pt_BR.properties
@@ -271,8 +271,6 @@ Hudson.NodeDescription=N\u00f3 master do Jenkins
Permalink.LastUnsuccessfulBuild=\u00daltimo build que falhou
# Run Parameter
RunParameterDefinition.DisplayName=Rodar o par\u00e2metro
-# Stop using a node for performing builds temporarily, until the next "online-node" command.
-CLI.offline-node.shortDescription=Temporariamente n\u00e3o usando um n\u00f3 para os builds. Aguardando o pr\u00f3ximo comando de modo-online
# My Views
MyViewsProperty.DisplayName=Minhas views
# Uncategorized
@@ -349,8 +347,6 @@ AbstractProject.WorkspaceTitle=Workspace de {0}
UpdateCenter.PluginCategory.listview-column=Lista as colunas da view
# {0} is waiting for a checkpoint on {1}
Run._is_waiting_for_a_checkpoint_on_={0} est\u00e1 aguardando por um checkpoint em {1}
-# Wait for a node to become offline.
-CLI.wait-node-offline.shortDescription=Aguarde por um n\u00f3 ficar offline.
# \ in workspace {0}
AbstractBuild.BuildingInWorkspace=\ no workspace {0}
# Jenkins is restarting
@@ -441,8 +437,6 @@ AbstractProject.CancelPermission.Description=Esta permiss\u00e3o deixa que seja
Jenkins.CheckDisplayName.NameNotUniqueWarning=O nome de exibi\u00e7\u00e3o "{0}", \u00e9 usado por um job e pode causar confus\u00e3o nos resultados da pesquisa.
# Custom workspace is empty.
AbstractProject.CustomWorkspaceEmpty=O workspace customizado est\u00e1 vazio.
-# Wait for a node to become online.
-CLI.wait-node-online.shortDescription=Aguarda por um n\u00f3 que se torne online
# Artifacts of {0} {1}
Run.ArtifactsBrowserTitle=Artefatos de {0} {1}
# Build with Parameters
diff --git a/core/src/main/resources/hudson/model/Messages_zh_CN.properties b/core/src/main/resources/hudson/model/Messages_zh_CN.properties
index af1d3635d5..1d07150da1 100644
--- a/core/src/main/resources/hudson/model/Messages_zh_CN.properties
+++ b/core/src/main/resources/hudson/model/Messages_zh_CN.properties
@@ -64,7 +64,6 @@ CLI.disable-job.shortDescription=Disables a job
CLI.enable-job.shortDescription=Enables a job
CLI.disconnect-node.shortDescription=Disconnects from a node
CLI.online-node.shortDescription=Resume using a node for performing builds, to cancel out the earlier "offline-node" command.
-CLI.offline-node.shortDescription=Stop using a node for performing builds temporarily, until the next "online-node" command.
ComputerSet.DisplayName=nodes
diff --git a/core/src/main/resources/hudson/model/Messages_zh_TW.properties b/core/src/main/resources/hudson/model/Messages_zh_TW.properties
index a5fb398930..2ffc39e21f 100644
--- a/core/src/main/resources/hudson/model/Messages_zh_TW.properties
+++ b/core/src/main/resources/hudson/model/Messages_zh_TW.properties
@@ -80,10 +80,6 @@ CLI.disable-job.shortDescription=\u505c\u7528\u4f5c\u696d\u3002
CLI.enable-job.shortDescription=\u555f\u7528\u4f5c\u696d\u3002
CLI.disconnect-node.shortDescription=\u4e2d\u65b7\u8207\u6307\u5b9a\u7bc0\u9ede\u7684\u9023\u7dda\u3002
CLI.online-node.shortDescription=\u7e7c\u7e8c\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u53d6\u6d88\u5148\u524d\u7684 "offline-node" \u6307\u4ee4\u3002
-CLI.offline-node.shortDescription=\u66ab\u6642\u4e0d\u4f7f\u7528\u6307\u5b9a\u7bc0\u9ede\u4f86\u5efa\u7f6e\uff0c\u76f4\u5230\u57f7\u884c "online-node" \u6307\u4ee4\u70ba\u6b62\u3002
-CLI.wait-node-online.shortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u4e0a\u7dda\u3002
-CLI.wait-node-offline.shortDescription=\u7b49\u5019\u6307\u5b9a\u7bc0\u9ede\u96e2\u7dda\u3002
-
ComputerSet.DisplayName=\u7bc0\u9ede
@@ -91,14 +87,12 @@ Descriptor.From=(from {0})
Executor.NotAvailable=N/A
-
FreeStyleProject.DisplayName=\u5efa\u7f6e Free-Style \u8edf\u9ad4\u5c08\u6848
FreeStyleProject.Description=\
\u9019\u662f Jenkins \u7684\u4e3b\u8981\u529f\u80fd\u3002\
Jenkins \u80fd\u642d\u914d\u5404\u5f0f\u7a0b\u5f0f\u78bc\u7ba1\u7406\u3001\u5efa\u7f6e\u7cfb\u7d71\u4f86\u5efa\u7f6e\u60a8\u7684\u5c08\u6848\uff0c\
\u751a\u81f3\u9084\u80fd\u505a\u8edf\u9ad4\u5efa\u7f6e\u4ee5\u5916\u7684\u5176\u4ed6\u4e8b\u60c5\u3002
-
Hudson.BadPortNumber=\u9023\u63a5\u57e0\u865f {0} \u7121\u6548
Hudson.Computer.Caption=Master
Hudson.Computer.DisplayName=master
diff --git a/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly b/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly
index 9bac23608c..81b8c0190a 100644
--- a/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly
+++ b/core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly
@@ -29,7 +29,7 @@ THE SOFTWARE.
-
+
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/model/Run/confirmDelete.jelly b/core/src/main/resources/hudson/model/Run/confirmDelete.jelly
index 41c42b3eb0..c48788d423 100644
--- a/core/src/main/resources/hudson/model/Run/confirmDelete.jelly
+++ b/core/src/main/resources/hudson/model/Run/confirmDelete.jelly
@@ -24,20 +24,23 @@ THE SOFTWARE.
-
+
-
-
+
+
-