diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 7257307587..df64fc700f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -21,11 +21,15 @@ If the issue is not fully described in the ticket, add more information here (ju
+### Proposed upgrade guidelines
+
+N/A
+
### Submitter checklist
- [ ] JIRA issue is well described
-- [ ] Changelog entry appropriate for the audience affected by the change (users or developer, depending on the change). [Examples](https://github.com/jenkins-infra/jenkins.io/blob/master/content/_data/changelogs/weekly.yml)
- * Use the `Internal: ` prefix if the change has no user-visible impact (API, test frameworks, etc.)
+- [ ] Changelog entries and upgrade guidelines are appropriate for the audience affected by the change (users or developer, depending on the change). [Examples](https://github.com/jenkins-infra/jenkins.io/blob/master/content/_data/changelogs/weekly.yml)
+ * Fill-in the `Proposed changelog entries` section only if there are breaking changes or other changes which may require extra steps from users during the upgrade
- [ ] Appropriate autotests or explanation to why this change has no tests
- [ ] For dependency updates: links to external changelogs and, if possible, full diffs
@@ -38,3 +42,15 @@ The changelogs will be integrated by the core maintainers after the merge. See
+
+### Maintainer checklist
+
+Before the changes are marked as `ready-for-merge`:
+
+- [ ] There are at least 2 approvals for the pull request and no outstanding requests for change
+- [ ] Conversations in the pull request are over OR it is explicit that a reviewer does not block the change
+- [ ] Changelog entries in the PR title and/or `Proposed changelog entries` are correct
+- [ ] Proper changelog labels are set so that the changelog can be generated automatically
+- [ ] If the change needs additional upgrade steps from users, `upgrade-guide-needed` label is set and there is a `Proposed upgrade guidelines` section in the PR title. ([example](https://github.com/jenkinsci/jenkins/pull/4387))
+- [ ] If it would make sense to backport the change to LTS, a JIRA issue should exist and be labeled as `lts-candidate`
+
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index 84656fc8eb..17abd66723 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -1,29 +1,18 @@
# Configuration for Release Drafter: https://github.com/toolmantim/release-drafter
-name-template: $NEXT_PATCH_VERSION
-# Uses a more common 2-digit versioning in Jenkins weekly releases.
+_extends: .github
+# We use the 2-digit versioning in Jenkins weekly releases.
version-template: $MAJOR.$MINOR
+name-template: $NEXT_MINOR_VERSION
tag-template: jenkins-$NEXT_MINOR_VERSION
-exclude-labels:
- - reverted
- - no-changelog
- - skip-changelog
- - invalid
-change-template: |-
- - type: todo
- message: |-
- $TITLE
- pull: $NUMBER
- authors:
- - $AUTHOR
-
template: |
**Disclaimer**: This is an automatically generated changelog draft for Jenkins weekly releases.
- See https://jenkins.io/changelog/ for the official changelogs.
+ See https://jenkins.io/changelog/ for the official changelogs.
+ For `changelog.yaml` drafts see GitHub action artifacts attached to release commits.
- ```yaml
$CHANGES
- ```
+
+ All contributors: $CONTRIBUTORS
# Categories will be commented out, because we use YAML
# Now we use categories only for sorting
@@ -44,17 +33,3 @@ categories:
label: developer
- title: Internal changes
label: internal
-
-replacers:
- - search: '/\[*JENKINS-(\d+)\]*\s*-*\s*/g'
- replace: |-
- issue: $1
- message: |-
-
- - search: |-
- message: |-
- issue:
- replace: "issue:"
-
- - search: "##"
- replace: "#"
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
new file mode 100644
index 0000000000..1c16d431d0
--- /dev/null
+++ b/.github/workflows/changelog.yml
@@ -0,0 +1,43 @@
+# Automates creation of changelog drafts using Release Drafter
+# More Info: https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc
+name: Changelog Drafter
+
+on:
+ push:
+ # branches to consider in the event; optional, defaults to all
+ branches:
+ - master
+
+jobs:
+ update_draft_release:
+ runs-on: ubuntu-latest
+ steps:
+ # Drafts your next Release notes as Pull Requests are merged into "master"
+ - name: Generate GitHub Release Draft
+ id: release-drafter
+ uses: release-drafter/release-drafter@v5.6.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # Generates a YAML changelog file using https://github.com/jenkinsci/jenkins-core-changelog-generator
+ - name: Generate YAML changelog draft
+ id: jenkins-core-changelog-generator
+ uses: jenkinsci/jenkins-core-changelog-generator@master
+ env:
+ GITHUB_AUTH: github-actions:${{ secrets.GITHUB_TOKEN }}
+ - name: Upload Changelog YAML
+ uses: actions/upload-artifact@v1
+ with:
+ name: changelog.yaml
+ path: changelog.yaml
+#TODO(oleg-nenashev): It will not work well with Release Drafter which does not recreated releases. Asset does not get overwritten, and there is no ready-to-go API for overriding assets
+ # Upload YAML to the release draft assets
+# - name: Upload changelog.yaml to the Release Draft
+# id: upload-changelog-yaml
+# uses: actions/upload-release-asset@v1.0.1
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+# with:
+# upload_url: ${{ steps.release-drafter.outputs.upload_url }}
+# asset_path: ./changelog.yaml
+# asset_name: changelog.yaml
+# asset_content_type: text/yaml
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d0bd4d9eea..1c6cd2c431 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -31,7 +31,9 @@ There is a description of the [building and debugging process].
If you want simply to have the `jenkins.war` file as fast as possible without tests, run:
- mvn clean package -pl war -am -DskipTests -Dspotbugs.skip
+```sh
+mvn -am -pl war,bom -DskipTests -Dspotbugs.skip clean install
+```
The WAR file will be created in `war/target/jenkins.war`.
After that you can start Jenkins using Java CLI ([guide]).
@@ -39,16 +41,13 @@ If you want to debug this WAR file without using Maven plugins,
You can just start the executable with [Remote Debug Flags]
and then attach IDE Debugger to it.
-To compile and launch a development instance, run:
+To launch a development instance, after the above command run:
- mvn hudson-dev:run
+```sh
+mvn -pl war jetty:run
+```
-
+(Beware that `maven-plugin` builds will not work in this mode due to class loading conflicts.)
## Testing changes
diff --git a/Jenkinsfile b/Jenkinsfile
index 8f9e81bdd1..fd76d7f129 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -43,7 +43,7 @@ for(j = 0; j < jdks.size(); j++) {
"MAVEN_OPTS=-Xmx1536m -Xms512m"], buildType, jdk) {
// Actually run Maven!
// -Dmaven.repo.local=… tells Maven to create a subdir in the temporary directory for the local Maven repository
- def mvnCmd = "mvn -Pdebug -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -Dmaven.repo.local=$m2repo -s settings-azure.xml -e"
+ def mvnCmd = "mvn -Pdebug -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -ntp -Dmaven.repo.local=$m2repo -s settings-azure.xml -e"
if(isUnix()) {
sh mvnCmd
@@ -77,7 +77,6 @@ for(j = 0; j < jdks.size(); j++) {
// TODO: Restore ATH once https://groups.google.com/forum/#!topic/jenkinsci-dev/v9d-XosOp2s is resolved
// TODO: ATH flow now supports Java 8 only, it needs to be reworked (INFRA-1690)
-/*
builds.ath = {
node("docker&&highmem") {
// Just to be safe
@@ -88,7 +87,7 @@ builds.ath = {
checkout scm
withMavenEnv(["JAVA_OPTS=-Xmx1536m -Xms512m",
"MAVEN_OPTS=-Xmx1536m -Xms512m"], 8) {
- sh "mvn --batch-mode --show-version -DskipTests -am -pl war package -Dmaven.repo.local=${pwd tmp: true}/m2repo -s settings-azure.xml"
+ sh "mvn --batch-mode --show-version -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -DskipTests -am -pl war package -Dmaven.repo.local=${pwd tmp: true}/m2repo -s settings-azure.xml"
}
dir("war/target") {
fileUri = "file://" + pwd() + "/jenkins.war"
@@ -99,7 +98,7 @@ builds.ath = {
runATH jenkins: fileUri, metadataFile: metadataPath
}
}
-}*/
+}
builds.failFast = failFast
parallel builds
diff --git a/bom/pom.xml b/bom/pom.xml
index bef9f8bb9d..6b93b53647 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -148,11 +148,11 @@ THE SOFTWARE.
servlet-api[0]provided
diff --git a/cli/pom.xml b/cli/pom.xml
index 9f483141d4..c9ca340dbe 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -16,6 +16,7 @@
Medium
+ 5.5.2
@@ -31,26 +32,38 @@
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.jupiter.version}
+ test
+ org.kohsukeaccess-modifier-annotation
+ providedorg.jenkins-ciannotation-indexer
-
-
- commons-codec
- commons-codec
+ truecommons-iocommons-io
-
-
- ${project.groupId}
- remoting
-
+ truejunit
@@ -61,61 +74,88 @@
org.jvnet.localizerlocalizer1.26
+ trueorg.apache.sshdsshd-core1.7.0
- true
+ true
+
+
+
+ net.i2p.crypto
+ eddsa
+ 0.3.0
+ true
-
-
- net.i2p.crypto
- eddsa
- 0.3.0
- org.slf4jslf4j-jdk14
- true
+ true
+
+
+ org.glassfish.tyrus.bundles
+ tyrus-standalone-client-jdk
+ 1.12
+ truecom.github.spotbugsspotbugs-annotations
- true
+ providedcommons-langcommons-lang
+ true
- maven-assembly-plugin
-
-
-
-
- single
-
- package
-
-
- jar-with-dependencies
-
-
-
- hudson.cli.CLI
-
-
- ${project.version}
-
-
-
-
-
+ maven-surefire-plugin
+ 2.22.2
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.1
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ javax.websocket
+ io.jenkins.cli.shaded.javax.websocket
+
+
+ org
+ io.jenkins.cli.shaded.org
+
+
+ net
+ io.jenkins.cli.shaded.net
+
+
+
+
+ hudson.cli.CLI
+
+ ${project.version}
+
+
+
+
+
+
+ org.jvnet.localizer
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index b66f092448..1b9b4856ad 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -24,6 +24,7 @@
package hudson.cli;
import hudson.cli.client.Messages;
+import java.io.DataInputStream;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@@ -33,22 +34,33 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Properties;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
+import org.glassfish.tyrus.client.ClientManager;
+import org.glassfish.tyrus.client.ClientProperties;
+import org.glassfish.tyrus.container.jdk.client.JdkClientContainer;
/**
* CLI entry point to Jenkins.
@@ -91,7 +103,7 @@ public static void main(final String[] _args) throws Exception {
}
}
- private enum Mode {HTTP, SSH}
+ private enum Mode {HTTP, SSH, WEB_SOCKET}
public static int _main(String[] _args) throws Exception {
List args = Arrays.asList(_args);
PrivateKeyProvider provider = new PrivateKeyProvider();
@@ -101,8 +113,9 @@ public static int _main(String[] _args) throws Exception {
if (url==null)
url = System.getenv("HUDSON_URL");
- boolean tryLoadPKey = true;
+ boolean noKeyAuth = false;
+ // TODO perhaps allow mode to be defined by environment variable too (assuming $JENKINS_USER_ID can be used for -user)
Mode mode = null;
String user = null;
@@ -137,6 +150,15 @@ public static int _main(String[] _args) throws Exception {
args = args.subList(1, args.size());
continue;
}
+ if (head.equals("-webSocket")) {
+ if (mode != null) {
+ printUsage("-webSocket clashes with previously defined mode " + mode);
+ return -1;
+ }
+ mode = Mode.WEB_SOCKET;
+ args = args.subList(1, args.size());
+ continue;
+ }
if (head.equals("-remoting")) {
printUsage("-remoting mode is no longer supported");
return -1;
@@ -161,7 +183,7 @@ public boolean verify(String s, SSLSession sslSession) {
continue;
}
if (head.equals("-noKeyAuth")) {
- tryLoadPKey = false;
+ noKeyAuth = true;
args = args.subList(1,args.size());
continue;
}
@@ -229,9 +251,6 @@ public boolean verify(String s, SSLSession sslSession) {
if(args.isEmpty())
args = Arrays.asList("help"); // default to help
- if (tryLoadPKey && !provider.hasKeys())
- provider.readFromDefaultLocations();
-
if (mode == null) {
mode = Mode.HTTP;
}
@@ -248,6 +267,9 @@ public boolean verify(String s, SSLSession sslSession) {
LOGGER.warning("-user required when using -ssh");
return -1;
}
+ if (!noKeyAuth && !provider.hasKeys()) {
+ provider.readFromDefaultLocations();
+ }
return SSHCLI.sshConnection(url, user, args, provider, strictHostKey);
}
@@ -255,6 +277,10 @@ public boolean verify(String s, SSLSession sslSession) {
LOGGER.warning("-strictHostKey meaningful only with -ssh");
}
+ if (noKeyAuth) {
+ LOGGER.warning("-noKeyAuth meaningful only with -ssh");
+ }
+
if (user != null) {
LOGGER.warning("Warning: -user ignored unless using -ssh");
}
@@ -271,66 +297,63 @@ public boolean verify(String s, SSLSession sslSession) {
return plainHttpConnection(url, args, factory);
}
+ if (mode == Mode.WEB_SOCKET) {
+ return webSocketConnection(url, args, factory);
+ }
+
throw new AssertionError();
}
- private static int plainHttpConnection(String url, List args, CLIConnectionFactory factory) throws IOException, InterruptedException {
- LOGGER.log(FINE, "Trying to connect to {0} via plain protocol over HTTP", url);
- FullDuplexHttpStream streams = new FullDuplexHttpStream(new URL(url), "cli?remoting=false", factory.authorization);
- class ClientSideImpl extends PlainCLIProtocol.ClientSide {
- boolean complete;
- int exit = -1;
- ClientSideImpl(InputStream is, OutputStream os) throws IOException {
- super(is, os);
- if (is.read() != 0) { // cf. FullDuplexHttpService
- throw new IOException("expected to see initial zero byte; perhaps you are connecting to an old server which does not support -http?");
- }
- }
+ private static int webSocketConnection(String url, List args, CLIConnectionFactory factory) throws Exception {
+ LOGGER.fine(() -> "Trying to connect to " + url + " via plain protocol over WebSocket");
+ class CLIEndpoint extends Endpoint {
@Override
- protected void onExit(int code) {
- this.exit = code;
- finished();
- }
+ public void onOpen(Session session, EndpointConfig config) {}
+ }
+ class Authenticator extends ClientEndpointConfig.Configurator {
@Override
- protected void onStdout(byte[] chunk) throws IOException {
- System.out.write(chunk);
+ public void beforeRequest(Map> headers) {
+ if (factory.authorization != null) {
+ headers.put("Authorization", Collections.singletonList(factory.authorization));
+ }
}
+ }
+ ClientManager client = ClientManager.createClient(JdkClientContainer.class.getName()); // ~ ContainerProvider.getWebSocketContainer()
+ client.getProperties().put(ClientProperties.REDIRECT_ENABLED, true); // https://tyrus-project.github.io/documentation/1.13.1/index/tyrus-proprietary-config.html#d0e1775
+ Session session = client.connectToServer(new CLIEndpoint(), ClientEndpointConfig.Builder.create().configurator(new Authenticator()).build(), URI.create(url.replaceFirst("^http", "ws") + "cli/ws"));
+ PlainCLIProtocol.Output out = new PlainCLIProtocol.Output() {
@Override
- protected void onStderr(byte[] chunk) throws IOException {
- System.err.write(chunk);
+ public void send(byte[] data) throws IOException {
+ session.getBasicRemote().sendBinary(ByteBuffer.wrap(data));
}
@Override
- protected void handleClose() {
- finished();
- }
- private synchronized void finished() {
- complete = true;
- notifyAll();
+ public void close() throws IOException {
+ session.close();
}
+ };
+ try (ClientSideImpl connection = new ClientSideImpl(out)) {
+ session.addMessageHandler(InputStream.class, is -> {
+ try {
+ connection.handle(new DataInputStream(is));
+ } catch (IOException x) {
+ LOGGER.log(Level.WARNING, null, x);
+ }
+ });
+ connection.start(args);
+ return connection.exit();
}
- try (final ClientSideImpl connection = new ClientSideImpl(streams.getInputStream(), streams.getOutputStream())) {
- for (String arg : args) {
- connection.sendArg(arg);
+ }
+
+ private static int plainHttpConnection(String url, List args, CLIConnectionFactory factory) throws IOException, InterruptedException {
+ LOGGER.log(FINE, "Trying to connect to {0} via plain protocol over HTTP", url);
+ FullDuplexHttpStream streams = new FullDuplexHttpStream(new URL(url), "cli?remoting=false", factory.authorization);
+ try (final ClientSideImpl connection = new ClientSideImpl(new PlainCLIProtocol.FramedOutput(streams.getOutputStream()))) {
+ connection.start(args);
+ InputStream is = streams.getInputStream();
+ if (is.read() != 0) { // cf. FullDuplexHttpService
+ throw new IOException("expected to see initial zero byte; perhaps you are connecting to an old server which does not support -http?");
}
- connection.sendEncoding(Charset.defaultCharset().name());
- connection.sendLocale(Locale.getDefault().toString());
- connection.sendStart();
- connection.begin();
- new Thread("input reader") {
- @Override
- public void run() {
- try {
- final OutputStream stdin = connection.streamStdin();
- int c;
- while (!connection.complete && (c = System.in.read()) != -1) {
- stdin.write(c);
- }
- connection.sendEndStdin();
- } catch (IOException x) {
- LOGGER.log(Level.WARNING, null, x);
- }
- }
- }.start();
+ new PlainCLIProtocol.FramedReader(connection, is).start();
new Thread("ping") { // JENKINS-46659
@Override
public void run() {
@@ -347,13 +370,77 @@ public void run() {
}
}.start();
- synchronized (connection) {
- while (!connection.complete) {
- connection.wait();
+ return connection.exit();
+ }
+ }
+
+ private static final class ClientSideImpl extends PlainCLIProtocol.ClientSide {
+
+ volatile boolean complete;
+ private int exit = -1;
+
+ ClientSideImpl(PlainCLIProtocol.Output out) {
+ super(out);
+ }
+
+ void start(List args) throws IOException {
+ for (String arg : args) {
+ sendArg(arg);
+ }
+ sendEncoding(Charset.defaultCharset().name());
+ sendLocale(Locale.getDefault().toString());
+ sendStart();
+ new Thread("input reader") {
+ @Override
+ public void run() {
+ try {
+ final OutputStream stdin = streamStdin();
+ int c;
+ // TODO check available to avoid sending lots of one-byte frames
+ while (!complete && (c = System.in.read()) != -1) {
+ stdin.write(c);
+ }
+ sendEndStdin();
+ } catch (IOException x) {
+ LOGGER.log(Level.WARNING, null, x);
+ }
}
+ }.start();
+ }
+
+ @Override
+ protected synchronized void onExit(int code) {
+ this.exit = code;
+ finished();
+ }
+
+ @Override
+ protected void onStdout(byte[] chunk) throws IOException {
+ System.out.write(chunk);
+ }
+
+ @Override
+ protected void onStderr(byte[] chunk) throws IOException {
+ System.err.write(chunk);
+ }
+
+ @Override
+ protected void handleClose() {
+ finished();
+ }
+
+ private synchronized void finished() {
+ complete = true;
+ notifyAll();
+ }
+
+ synchronized int exit() throws InterruptedException {
+ while (!complete) {
+ wait();
}
- return connection.exit;
+ return exit;
}
+
}
private static String computeVersion() {
diff --git a/cli/src/main/java/hudson/cli/PlainCLIProtocol.java b/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
index ed5c453360..5359b7c738 100644
--- a/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
+++ b/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
@@ -37,6 +37,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.BoundedInputStream;
import org.apache.commons.io.input.CountingInputStream;
/**
@@ -76,111 +77,143 @@ private enum Op {
}
}
- static abstract class EitherSide implements Closeable {
+ interface Output extends Closeable {
+ void send(byte[] data) throws IOException;
+ }
- private final CountingInputStream cis;
- private final FlightRecorderInputStream flightRecorder;
- final DataInputStream dis;
- final DataOutputStream dos;
+ static final class FramedOutput implements Output {
- protected EitherSide(InputStream is, OutputStream os) {
- cis = new CountingInputStream(is);
- flightRecorder = new FlightRecorderInputStream(cis);
- dis = new DataInputStream(flightRecorder);
+ private final DataOutputStream dos;
+
+ FramedOutput(OutputStream os) {
dos = new DataOutputStream(os);
}
- final void begin() {
- new Reader().start();
+ @Override
+ public void send(byte[] data) throws IOException {
+ dos.writeInt(data.length - 1); // not counting the opcode
+ dos.write(data);
+ dos.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ dos.close();
}
- private class Reader extends Thread {
+ }
- Reader() {
- super("PlainCLIProtocol"); // TODO set distinctive Thread.name
- }
+ static final class FramedReader extends Thread {
- @Override
- public void run() {
- try {
- while (true) {
- LOGGER.finest("reading frame");
- int framelen;
- try {
- framelen = dis.readInt();
- } catch (EOFException x) {
- handleClose();
- break; // TODO verify that we hit EOF immediately, not partway into framelen
- }
- if (framelen < 0) {
- throw new IOException("corrupt stream: negative frame length");
- }
- byte b = dis.readByte();
- if (b < 0) { // i.e., >127
- throw new IOException("corrupt stream: negative operation code");
- }
- if (b >= Op.values().length) {
- LOGGER.log(Level.WARNING, "unknown operation #{0}: {1}", new Object[] {b, HexDump.toHex(flightRecorder.getRecord())});
- IOUtils.skipFully(dis, framelen);
- continue;
- }
- Op op = Op.values()[b];
- long start = cis.getByteCount();
- LOGGER.log(Level.FINEST, "handling frame with {0} of length {1}", new Object[] {op, framelen});
- boolean handled = handle(op, framelen);
- if (handled) {
- long actuallyRead = cis.getByteCount() - start;
- if (actuallyRead != framelen) {
- throw new IOException("corrupt stream: expected to read " + framelen + " bytes from " + op + " but read " + actuallyRead);
- }
- } else {
- LOGGER.log(Level.WARNING, "unexpected {0}: {1}", new Object[] {op, HexDump.toHex(flightRecorder.getRecord())});
- IOUtils.skipFully(dis, framelen);
+ private final EitherSide side;
+ private final CountingInputStream cis;
+ private final FlightRecorderInputStream flightRecorder;
+ private final DataInputStream dis;
+
+ FramedReader(EitherSide side, InputStream is) {
+ super("PlainCLIProtocol"); // TODO set distinctive Thread.name
+ this.side = side;
+ cis = new CountingInputStream(is);
+ flightRecorder = new FlightRecorderInputStream(cis);
+ dis = new DataInputStream(flightRecorder);
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ LOGGER.finest("reading frame");
+ int framelen;
+ try {
+ framelen = dis.readInt();
+ } catch (EOFException x) {
+ side.handleClose();
+ break; // TODO verify that we hit EOF immediately, not partway into framelen
+ }
+ if (framelen < 0) {
+ throw new IOException("corrupt stream: negative frame length");
+ }
+ LOGGER.finest("read frame length " + framelen);
+ long start = cis.getByteCount();
+ try {
+ side.handle(new DataInputStream(new BoundedInputStream(dis, /* op byte not counted */framelen + 1)));
+ } catch (ProtocolException x) {
+ LOGGER.log(Level.WARNING, null, x);
+ // but read another frame
+ } finally {
+ long actuallyRead = cis.getByteCount() - start;
+ long unread = framelen + 1 - actuallyRead;
+ if (unread > 0) {
+ LOGGER.warning(() -> "Did not read " + unread + " bytes");
+ IOUtils.skipFully(dis, unread);
}
}
- } catch (ClosedChannelException x) {
- LOGGER.log(Level.FINE, null, x);
- handleClose();
- } catch (IOException x) {
- LOGGER.log(Level.WARNING, null, flightRecorder.analyzeCrash(x, "broken stream"));
- } catch (ReadPendingException x) {
- // in case trick in CLIAction does not work
- LOGGER.log(Level.FINE, null, x);
- handleClose();
- } catch (RuntimeException x) {
- LOGGER.log(Level.WARNING, null, x);
- handleClose();
}
+ } catch (ClosedChannelException x) {
+ LOGGER.log(Level.FINE, null, x);
+ side.handleClose();
+ } catch (IOException x) {
+ LOGGER.log(Level.WARNING, null, flightRecorder.analyzeCrash(x, "broken stream"));
+ } catch (ReadPendingException x) {
+ // in case trick in CLIAction does not work
+ LOGGER.log(Level.FINE, null, x);
+ side.handleClose();
+ } catch (RuntimeException x) {
+ LOGGER.log(Level.WARNING, null, x);
+ side.handleClose();
}
+ }
+ }
+
+ private static final class ProtocolException extends IOException {
+ ProtocolException(String message) {
+ super(message);
}
+ }
- protected abstract void handleClose();
+ static abstract class EitherSide implements Closeable {
- protected abstract boolean handle(Op op, int framelen) throws IOException;
+ private final Output out;
- private void writeOp(Op op) throws IOException {
- dos.writeByte((byte) op.ordinal());
+ protected EitherSide(Output out) {
+ this.out = out;
}
+ protected abstract void handleClose();
+
+ final void handle(DataInputStream dis) throws IOException {
+ byte b = dis.readByte();
+ if (b < 0) { // i.e., >127
+ throw new IOException("corrupt stream: negative operation code");
+ }
+ if (b >= Op.values().length) {
+ throw new ProtocolException("unknown operation #" + b);
+ }
+ Op op = Op.values()[b];
+ LOGGER.finest(() -> "handling frame with " + op);
+ if (!handle(op, dis)) {
+ throw new ProtocolException("unhandled: " + op);
+ }
+ }
+
+ protected abstract boolean handle(Op op, DataInputStream dis) throws IOException;
+
protected final synchronized void send(Op op) throws IOException {
- dos.writeInt(0);
- writeOp(op);
- dos.flush();
+ send(op, new byte[0], 0, 0);
}
- protected final synchronized void send(Op op, int number) throws IOException {
- dos.writeInt(4);
- writeOp(op);
- dos.writeInt(number);
- dos.flush();
+ protected final synchronized void send(Op op, int v) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(4);
+ new DataOutputStream(baos).writeInt(v);
+ send(op, baos.toByteArray());
}
protected final synchronized void send(Op op, byte[] chunk, int off, int len) throws IOException {
- dos.writeInt(len);
- writeOp(op);
- dos.write(chunk, off, len);
- dos.flush();
+ byte[] data = new byte[len + 1];
+ data[0] = (byte) op.ordinal();
+ System.arraycopy(chunk, off, data, 1, len);
+ out.send(data);
}
protected final void send(Op op, byte[] chunk) throws IOException {
@@ -193,13 +226,6 @@ protected final void send(Op op, String text) throws IOException {
send(op, buf.toByteArray());
}
- protected final byte[] readChunk(int framelen) throws IOException {
- assert Thread.currentThread() instanceof EitherSide.Reader;
- byte[] buf = new byte[framelen];
- dis.readFully(buf);
- return buf;
- }
-
protected final OutputStream stream(final Op op) {
return new OutputStream() {
@Override
@@ -219,20 +245,19 @@ public void write(byte[] b) throws IOException {
@Override
public synchronized void close() throws IOException {
- dos.close();
+ out.close();
}
}
static abstract class ServerSide extends EitherSide {
- ServerSide(InputStream is, OutputStream os) {
- super(is, os);
+ ServerSide(Output out) {
+ super(out);
}
@Override
- protected final boolean handle(Op op, int framelen) throws IOException {
- assert Thread.currentThread() instanceof EitherSide.Reader;
+ protected final boolean handle(Op op, DataInputStream dis) throws IOException {
assert op.clientSide;
switch (op) {
case ARG:
@@ -248,7 +273,7 @@ protected final boolean handle(Op op, int framelen) throws IOException {
onStart();
return true;
case STDIN:
- onStdin(readChunk(framelen));
+ onStdin(IOUtils.toByteArray(dis));
return true;
case END_STDIN:
onEndStdin();
@@ -286,23 +311,22 @@ public final OutputStream streamStderr() {
static abstract class ClientSide extends EitherSide {
- ClientSide(InputStream is, OutputStream os) {
- super(is, os);
+ ClientSide(Output out) {
+ super(out);
}
@Override
- protected boolean handle(Op op, int framelen) throws IOException {
- assert Thread.currentThread() instanceof EitherSide.Reader;
+ protected boolean handle(Op op, DataInputStream dis) throws IOException {
assert !op.clientSide;
switch (op) {
case EXIT:
onExit(dis.readInt());
return true;
case STDOUT:
- onStdout(readChunk(framelen));
+ onStdout(IOUtils.toByteArray(dis));
return true;
case STDERR:
- onStderr(readChunk(framelen));
+ onStderr(IOUtils.toByteArray(dis));
return true;
default:
return false;
@@ -311,6 +335,7 @@ protected boolean handle(Op op, int framelen) throws IOException {
protected abstract void onExit(int code);
+ // TODO more efficient to change signature to InputStream, then use IOUtils.copy
protected abstract void onStdout(byte[] chunk) throws IOException;
protected abstract void onStderr(byte[] chunk) throws IOException;
diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties
index c789036505..de4cac2174 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -3,6 +3,7 @@ CLI.Usage=Jenkins CLI\n\
Options:\n\
\ -s URL : the server URL (defaults to the JENKINS_URL env var)\n\
\ -http : use a plain CLI protocol over HTTP(S) (the default; mutually exclusive with -ssh)\n\
+ \ -webSocket : like -http but using WebSocket (works better with most reverse proxies)\n\
\ -ssh : use SSH protocol (requires -user; SSH port must be open on server, and user must have registered a public key)\n\
\ -i KEY : SSH private key file used for authentication (for use with -ssh)\n\
\ -noCertificateCheck : bypass HTTPS certificate check entirely. Use with caution\n\
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_da.properties b/cli/src/main/resources/hudson/cli/client/Messages_da.properties
index 84a2e91e10..5414bbe31f 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_da.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_da.properties
@@ -22,7 +22,7 @@
CLI.VersionMismatch=Versionskonflikt. CLI''en fungerer ikke med denne Hudson server
CLI.Usage=Jenkins_ CLI\n\
-Brug: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
+Brug: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
Tilvalg:\n\
De tilg\u00e6ngelige kommandoer afh\u00e6nger af serveren. K\u00f8r 'help' kommandoen for at se listen.
CLI.NoURL=Hverken -s eller JENKINS_URL milj\u00f8variablen er defineret
diff --git a/cli/src/test/java/hudson/cli/HexDumpTest.java b/cli/src/test/java/hudson/cli/HexDumpTest.java
index 9cd1c78995..1e1aa9f70e 100644
--- a/cli/src/test/java/hudson/cli/HexDumpTest.java
+++ b/cli/src/test/java/hudson/cli/HexDumpTest.java
@@ -1,8 +1,12 @@
package hudson.cli;
+
import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+@Execution(ExecutionMode.CONCURRENT)
public class HexDumpTest {
@Test
diff --git a/cli/src/test/java/hudson/cli/PlainCLIProtocolTest.java b/cli/src/test/java/hudson/cli/PlainCLIProtocolTest.java
index 4663fbafd5..953b01d02c 100644
--- a/cli/src/test/java/hudson/cli/PlainCLIProtocolTest.java
+++ b/cli/src/test/java/hudson/cli/PlainCLIProtocolTest.java
@@ -24,12 +24,16 @@
package hudson.cli;
+import org.junit.jupiter.api.Test;
+
import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
-import static org.junit.Assert.*;
-import org.junit.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
public class PlainCLIProtocolTest {
@@ -41,7 +45,7 @@ class Client extends PlainCLIProtocol.ClientSide {
int code = -1;
final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
Client() throws IOException {
- super(new PipedInputStream(download), upload);
+ super(new PlainCLIProtocol.FramedOutput(upload));
}
@Override
protected synchronized void onExit(int code) {
@@ -62,6 +66,7 @@ void send() throws IOException {
streamStdin().write("hello".getBytes());
}
void newop() throws IOException {
+ DataOutputStream dos = new DataOutputStream(upload);
dos.writeInt(0);
dos.writeByte(99);
dos.flush();
@@ -72,7 +77,7 @@ class Server extends PlainCLIProtocol.ServerSide {
boolean started;
final ByteArrayOutputStream stdin = new ByteArrayOutputStream();
Server() throws IOException {
- super(new PipedInputStream(upload), download);
+ super(new PlainCLIProtocol.FramedOutput(download));
}
@Override
protected void onArg(String text) {
@@ -89,6 +94,13 @@ protected synchronized void onStart() {
}
@Override
protected void onStdin(byte[] chunk) throws IOException {
+ /* To inject a race condition:
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException x) {
+ throw new IOException(x);
+ }
+ */
stdin.write(chunk);
}
@Override
@@ -100,6 +112,7 @@ void send() throws IOException {
sendExit(2);
}
void newop() throws IOException {
+ DataOutputStream dos = new DataOutputStream(download);
dos.writeInt(0);
dos.writeByte(99);
dos.flush();
@@ -107,8 +120,8 @@ void newop() throws IOException {
}
Client client = new Client();
Server server = new Server();
- client.begin();
- server.begin();
+ new PlainCLIProtocol.FramedReader(client, new PipedInputStream(download)).start();
+ new PlainCLIProtocol.FramedReader(server, new PipedInputStream(upload)).start();
client.send();
client.newop();
synchronized (server) {
@@ -123,6 +136,9 @@ void newop() throws IOException {
client.wait();
}
}
+ while (server.stdin.size() == 0) {
+ Thread.sleep(100);
+ }
assertEquals("hello", server.stdin.toString());
assertEquals("command", server.arg);
assertEquals("goodbye", client.stdout.toString());
diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
index 9c7a61b396..b186263346 100644
--- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
+++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
@@ -1,6 +1,9 @@
package hudson.cli;
-import org.junit.Test;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
import java.io.File;
import java.io.IOException;
@@ -9,11 +12,13 @@
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
/**
keys were generated with ssh-keygen from OpenSSH_7.9p1, LibreSSL 2.7.3
*/
+@Execution(ExecutionMode.CONCURRENT)
public class PrivateKeyProviderTest {
/**
@@ -78,21 +83,21 @@ public void loadKeyOpenSSH() throws IOException, GeneralSecurityException {
/**
key command: ssh-keygen -f openssh-unsupported -t rsa -b 1024 -p password
*/
- @Test(expected = NoSuchAlgorithmException.class)
+ @Test
public void loadKeyUnsupportedCipher() throws IOException, GeneralSecurityException {
File file = new File(this.getClass().getResource("openssh-unsuported").getFile());
String password = "password";
- PrivateKeyProvider.loadKey(file, password);
+ assertThrows(NoSuchAlgorithmException.class, () -> PrivateKeyProvider.loadKey(file, password));
}
/**
key command: ssh-keygen -f openssh -t rsa -b 1024
in this key we remove some lines to break the key.
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void loadKeyBroken() throws IOException, GeneralSecurityException {
File file = new File(this.getClass().getResource("openssh-broken").getFile());
String password = "password";
- PrivateKeyProvider.loadKey(file, password);
+ assertThrows(IllegalArgumentException.class, () -> PrivateKeyProvider.loadKey(file, password));
}
}
diff --git a/core/pom.xml b/core/pom.xml
index 0402553742..1d3fc47089 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -642,6 +642,18 @@ THE SOFTWARE.
jzlib1.1.3-kohsuke-1
+
+ org.xmlunit
+ xmlunit-core
+ 2.2.1
+ test
+
+
+ org.xmlunit
+ xmlunit-matchers
+ 2.2.1
+ test
+
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_bg.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_bg.properties
deleted file mode 100644
index 95e74685ad..0000000000
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_bg.properties
+++ /dev/null
@@ -1,32 +0,0 @@
-# The MIT License
-#
-# Bulgarian translation: Copyright (c) 2015, 2016, Alexander Shopov
-#
-# 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.
-
-NewVersionAvailable=\
- \u041d\u043e\u0432\u0430\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044f \u043d\u0430 Jenkins ({0}) \u0435 \u043d\u0430\u043b\u0438\u0447\u043d\u0430 \u0437\u0430 \u0438\u0437\u0442\u0435\u0433\u043b\u044f\u043d\u0435\
- (\u0441\u043f\u0438\u0441\u044a\u043a \u0441 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435).
-UpgradeComplete=\
- \u041e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435\u0442\u043e \u043a\u044a\u043c \u043d\u043e\u0432\u0430\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044f Jenkins {0} \u043f\u0440\u0438\u043a\u043b\u044e\u0447\u0438. \u0422\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442\u0435.
-UpgradeCompleteRestartNotSupported=\
-\u041e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435\u0442\u043e \u043a\u044a\u043c \u043d\u043e\u0432\u0430\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044f Jenkins {0} \u043f\u0440\u0438\u043a\u043b\u044e\u0447\u0438. \u0422\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442\u0435.
-UpgradeProgress=\u0418\u0437\u043f\u044a\u043b\u043d\u044f\u0432\u0430 \u0441\u0435 \u043e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043a\u044a\u043c Jenkins {0}.
-UpgradeFailed=\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043a\u044a\u043c Jenkins {0}: {1}.
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
deleted file mode 100644
index 510a7f5f86..0000000000
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-NewVersionAvailable=Una nuova versione di Jenkins ({0}) disponibile per il download \
- (log delle modifiche).
-UpgradeComplete=Aggiornamento a Jenkins {0} completato, in attesa del riavvio.
-UpgradeCompleteRestartNotSupported=Aggiornamento a Jenkins {0} completato, in attesa del riavvio.
-UpgradeProgress=Aggiornamento a Jenkins {0} in corso.
-UpgradeFailed=Aggiornamento a Jenkins {0} non riuscito: {1}.
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_ru.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_ru.properties
deleted file mode 100644
index 97659c57c7..0000000000
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_ru.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-# The MIT License
-#
-# Copyright (c) 2004-2010, Sun Microsystems, 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.
-
-NewVersionAvailable=\u041D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F Jenkins ({0}) \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430 \u0434\u043B\u044F \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 (\u0441\u043F\u0438\u0441\u043E\u043A \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439).
-Or\ Upgrade\ Automatically=\u0418\u043B\u0438 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438
-UpgradeComplete=\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0434\u043E Jenkins {0} \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E, \u043E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438.
-UpgradeCompleteRestartNotSupported=\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0434\u043E Jenkins {0} \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E, \u043E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438.
-UpgradeProgress=\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043D\u0430 Jenkins {0} \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F \u0438\u043B\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u043E\u0441\u044C \u043D\u0435\u0443\u0434\u0430\u0447\u0435\u0439.
diff --git a/core/src/filter/resources/hudson/model/hudson-version.properties b/core/src/filter/resources/hudson/model/hudson-version.properties
index defbd48204..7650f6ad16 100644
--- a/core/src/filter/resources/hudson/model/hudson-version.properties
+++ b/core/src/filter/resources/hudson/model/hudson-version.properties
@@ -1 +1,2 @@
+# TODO unused?
version=${project.version}
diff --git a/core/src/filter/resources/jenkins/model/jenkins-version.properties b/core/src/filter/resources/jenkins/model/jenkins-version.properties
index defbd48204..662419771c 100644
--- a/core/src/filter/resources/jenkins/model/jenkins-version.properties
+++ b/core/src/filter/resources/jenkins/model/jenkins-version.properties
@@ -1 +1,2 @@
version=${project.version}
+changelog.url=${changelog.url}
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index 0dbec25143..e076b1e370 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -388,6 +388,15 @@ public void putIfNotNull(String key, String value) {
if (value!=null)
put(key,value);
}
+
+ /**
+ * Add entire map but filter null values out.
+ * @since TODO
+ */
+ public void putAllNonNull(Map map) {
+ map.forEach(this::putIfNotNull);
+ }
+
/**
* Takes a string that looks like "a=b" and adds that to this map.
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index 76a7908adc..ef5cfe1237 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -63,6 +63,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -482,26 +483,35 @@ public SezpozModule(List> index) {
* So this is an attempt to detect subset of problems eagerly, by invoking various reflection
* operations and try to find non-existent classes early.
*/
- private void resolve(Class c) {
+ private void resolve(Class> c) {
+ resolve(c, new HashSet<>());
+ }
+ private void resolve(Class> c, Set> encountered) {
+ if (!encountered.add(c)) {
+ return;
+ }
try {
- c.getGenericSuperclass();
- c.getGenericInterfaces();
ClassLoader ecl = c.getClassLoader();
- Method m = ClassLoader.class.getDeclaredMethod("resolveClass", Class.class);
- m.setAccessible(true);
- m.invoke(ecl, c);
- c.getConstructors();
- c.getMethods();
- for (Field f : c.getFields()) {
- if (f.getAnnotation(javax.inject.Inject.class) != null || f.getAnnotation(com.google.inject.Inject.class) != null) {
- resolve(f.getType());
+ if (ecl != null) { // Not bootstrap classloader
+ Method m = ClassLoader.class.getDeclaredMethod("resolveClass", Class.class);
+ m.setAccessible(true);
+ m.invoke(ecl, c);
+ }
+ for (Class cc = c; cc != Object.class; cc = cc.getSuperclass()) {
+ /**
+ * See {@link com.google.inject.spi.InjectionPoint#getInjectionPoints(TypeLiteral, boolean, Errors)}
+ */
+ cc.getGenericSuperclass();
+ cc.getGenericInterfaces();
+ cc.getDeclaredConstructors();
+ cc.getDeclaredMethods();
+ for (Field f : cc.getDeclaredFields()) {
+ if (f.getAnnotation(javax.inject.Inject.class) != null || f.getAnnotation(com.google.inject.Inject.class) != null) {
+ resolve(f.getType(), encountered);
+ }
}
}
LOGGER.log(Level.FINER, "{0} looks OK", c);
- while (c != Object.class) {
- c.getGenericSuperclass();
- c = c.getSuperclass();
- }
} catch (Exception x) {
throw (LinkageError)new LinkageError("Failed to resolve "+c).initCause(x);
}
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 84f6d112cf..f0c866e90a 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -1520,7 +1520,7 @@ public String invoke(File dir, VirtualChannel channel) throws IOException {
tempPath = Files.createTempDirectory(Util.fileToPath(dir), name,
PosixFilePermissions.asFileAttribute(EnumSet.allOf(PosixFilePermission.class)));
} else {
- tempPath = Files.createTempDirectory(Util.fileToPath(dir), name, new FileAttribute>[] {});
+ tempPath = Files.createTempDirectory(Util.fileToPath(dir), name);
}
if (tempPath.toFile() == null) {
@@ -2754,7 +2754,7 @@ public String invoke(File dir, VirtualChannel channel) throws IOException, Inter
int idx = findSeparator(f);
if(idx==-1) break;
- prefix.append(f.substring(0, idx)).append('/');
+ prefix.append(f, 0, idx).append('/');
f=f.substring(idx+1);
if(hasMatch(dir,prefix+fileMask,caseSensitive))
return Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest(fileMask, prefix+fileMask);
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index 3902d952f5..50339569b6 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -84,9 +84,12 @@
import hudson.views.ViewsTabBar;
import hudson.widgets.RenderOnDemandClosure;
+
import java.io.File;
import java.io.IOException;
+import java.io.PrintStream;
import java.io.PrintWriter;
+import java.io.Serializable;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.management.LockInfo;
@@ -158,7 +161,7 @@
import com.google.common.base.Predicates;
import hudson.model.PasswordParameterDefinition;
import hudson.util.RunList;
-import java.io.PrintStream;
+
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
@@ -386,6 +389,11 @@ public static Area getScreenResolution() {
return null;
}
+ @Restricted(NoExternalUse.class)
+ public static boolean useHidingPasswordFields() {
+ return SystemProperties.getBoolean(Functions.class.getName() + ".hidingPasswordFields", true);
+ }
+
/**
* URL decomposed for easier computation of relevant URLs.
*
@@ -645,6 +653,7 @@ public static void configureAutoRefresh(HttpServletRequest request, HttpServletR
// to avoid conflicts with any other web apps that might be on the same machine.
c.setPath("/");
c.setMaxAge(60*60*24*30); // persist it roughly for a month
+ c.setHttpOnly(true);
response.addCookie(c);
}
if (refresh) {
@@ -761,11 +770,15 @@ public static String encode(String s) {
/**
* Shortcut function for calling {@link URLEncoder#encode(String,String)} (with UTF-8 encoding).
- * Useful for encoding URL query parameters in jelly code (as in {@code "...?param=${h.urlEncode(something)}"}).
+ * Useful for encoding URL query parameters in jelly code (as in {@code "...?param=${h.urlEncode(something)}"}).
+ * For convenience in jelly code, it also accepts null parameter, and then returns an empty string.
*
* @since 2.200
*/
public static String urlEncode(String s) {
+ if (s == null) {
+ return "";
+ }
try {
return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
@@ -1300,7 +1313,7 @@ public static ThreadGroupMap sortThreadsAndGetGroupMap(ThreadInfo[] list) {
private static class ThreadSorterBase {
protected Map map = new HashMap<>();
- private ThreadSorterBase() {
+ public ThreadSorterBase() {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
while (tg.getParent() != null) tg = tg.getParent();
Thread[] threads = new Thread[tg.activeCount()*2];
@@ -1320,7 +1333,9 @@ protected int compare(long idA, long idB) {
}
}
- public static class ThreadGroupMap extends ThreadSorterBase implements Comparator {
+ public static class ThreadGroupMap extends ThreadSorterBase implements Comparator, Serializable {
+
+ private static final long serialVersionUID = 7803975728695308444L;
/**
* @return ThreadGroup name or null if unknown
@@ -1337,7 +1352,9 @@ public int compare(ThreadInfo a, ThreadInfo b) {
}
}
- private static class ThreadSorter extends ThreadSorterBase implements Comparator {
+ private static class ThreadSorter extends ThreadSorterBase implements Comparator, Serializable {
+
+ private static final long serialVersionUID = 5053631350439192685L;
public int compare(Thread a, Thread b) {
int result = compare(a.getId(), b.getId());
@@ -1474,12 +1491,10 @@ public static String getViewResource(Object it, String path) {
if(it instanceof Descriptor)
clazz = ((Descriptor)it).clazz;
- StringBuilder buf = new StringBuilder(Stapler.getCurrentRequest().getContextPath());
- buf.append(Jenkins.VIEW_RESOURCE_PATH).append('/');
- buf.append(clazz.getName().replace('.','/').replace('$','/'));
- buf.append('/').append(path);
-
- return buf.toString();
+ String buf = Stapler.getCurrentRequest().getContextPath() + Jenkins.VIEW_RESOURCE_PATH + '/' +
+ clazz.getName().replace('.', '/').replace('$', '/') +
+ '/' + path;
+ return buf;
}
public static boolean hasView(Object it, String path) throws IOException {
diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java
index fc688de296..d2007e14b1 100644
--- a/core/src/main/java/hudson/Launcher.java
+++ b/core/src/main/java/hudson/Launcher.java
@@ -23,7 +23,6 @@
*/
package hudson;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Proc.LocalProc;
import hudson.model.Computer;
import jenkins.util.MemoryReductionUtil;
@@ -1038,8 +1037,6 @@ public RemoteLauncher(@Nonnull TaskListener listener, @Nonnull VirtualChannel ch
@Override
@Nonnull
- @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
- justification = "We always require nonnull channel when we initialize this launcher")
public VirtualChannel getChannel() {
VirtualChannel vc = super.getChannel();
if (vc == null) {
diff --git a/core/src/main/java/hudson/Main.java b/core/src/main/java/hudson/Main.java
index 60a3a248bf..b4490d4188 100644
--- a/core/src/main/java/hudson/Main.java
+++ b/core/src/main/java/hudson/Main.java
@@ -220,7 +220,7 @@ private static HttpURLConnection open(URL url) throws IOException {
public static boolean isUnitTest = false;
/**
- * Set to true if we are running inside "mvn hpi:run" or "mvn hudson-dev:run"
+ * Set to true if we are running inside {@code mvn hpi:run} or {@code mvn jetty:run}.
*/
public static boolean isDevelopmentMode = SystemProperties.getBoolean(Main.class.getName()+".development");
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index 231b105dfe..d906466683 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -633,7 +633,7 @@ void considerDetachedPlugin(String shortName) {
protected @Nonnull Set loadPluginsFromWar(@Nonnull String fromPath, @CheckForNull FilenameFilter filter) {
Set names = new HashSet();
- ServletContext context = Jenkins.getActiveInstance().servletContext;
+ ServletContext context = Jenkins.get().servletContext;
Set plugins = Util.fixNull(context.getResourcePaths(fromPath));
Set copiedPlugins = new HashSet<>();
Set dependencies = new HashSet<>();
@@ -697,7 +697,7 @@ protected static void addDependencies(URL hpiResUrl, String fromPath, Set d
String dependencySpec = manifest.getMainAttributes().getValue("Plugin-Dependencies");
if (dependencySpec != null) {
String[] dependencyTokens = dependencySpec.split(",");
- ServletContext context = Jenkins.getActiveInstance().servletContext;
+ ServletContext context = Jenkins.get().servletContext;
for (String dependencyToken : dependencyTokens) {
if (dependencyToken.endsWith(";resolution:=optional")) {
@@ -709,7 +709,7 @@ protected static void addDependencies(URL hpiResUrl, String fromPath, Set d
String artifactId = artifactIdVersionPair[0];
VersionNumber dependencyVersion = new VersionNumber(artifactIdVersionPair[1]);
- PluginManager manager = Jenkins.getActiveInstance().getPluginManager();
+ PluginManager manager = Jenkins.get().getPluginManager();
VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId);
if (installedVersion != null && !installedVersion.isOlderThan(dependencyVersion)) {
// Do not downgrade dependencies that are already installed.
@@ -949,23 +949,6 @@ public void dynamicLoad(File arc, boolean removeExisting, @CheckForNull List plugins) throws Exception {
try (ACLContext context = ACL.as(ACL.SYSTEM)) {
- Jenkins.get().refreshExtensions();
-
- for (PluginWrapper p : plugins) {
- p.getPlugin().postInitialize();
- }
-
- // run initializers in the added plugins
- Reactor r = new Reactor(InitMilestone.ordering());
- Set loaders = plugins.stream().map(p -> p.classLoader).collect(Collectors.toSet());
- r.addAll(new InitializerFinder(uberClassLoader) {
- @Override
- protected boolean filter(Method e) {
- return !loaders.contains(e.getDeclaringClass().getClassLoader()) || super.filter(e);
- }
- }.discoverTasks(r));
- new InitReactorRunner().run(r);
-
Map pluginsByName = plugins.stream().collect(Collectors.toMap(p -> p.getShortName(), p -> p));
// recalculate dependencies of plugins optionally depending the newly deployed ones.
@@ -993,6 +976,20 @@ protected boolean filter(Method e) {
} catch (ExtensionRefreshException e) {
throw new IOException("Failed to refresh extensions after installing some plugins", e);
}
+ for (PluginWrapper p : plugins) {
+ p.getPlugin().postInitialize();
+ }
+
+ // run initializers in the added plugins
+ Reactor r = new Reactor(InitMilestone.ordering());
+ Set loaders = plugins.stream().map(p -> p.classLoader).collect(Collectors.toSet());
+ r.addAll(new InitializerFinder(uberClassLoader) {
+ @Override
+ protected boolean filter(Method e) {
+ return !loaders.contains(e.getDeclaringClass().getClassLoader()) || super.filter(e);
+ }
+ }.discoverTasks(r));
+ new InitReactorRunner().run(r);
}
}
@@ -1376,7 +1373,7 @@ public HttpResponse doPlugins() {
}
response.add(pluginInfo);
}
- for (UpdateSite site : Jenkins.getActiveInstance().getUpdateCenter().getSiteList()) {
+ for (UpdateSite site : Jenkins.get().getUpdateCenter().getSiteList()) {
for (UpdateSite.Plugin plugin: site.getAvailables()) {
JSONObject pluginInfo = allPlugins.get(plugin.name);
if(pluginInfo == null) {
@@ -1669,7 +1666,10 @@ public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, Servl
JSONArray dependencies = new JSONArray();
try {
- Manifest m = new JarFile(t).getManifest();
+ Manifest m;
+ try (JarFile jarFile = new JarFile(t)) {
+ m = jarFile.getManifest();
+ }
String deps = m.getMainAttributes().getValue("Plugin-Dependencies");
if (StringUtils.isNotBlank(deps)) {
diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java
index e217789166..b5b1b9c712 100644
--- a/core/src/main/java/hudson/PluginWrapper.java
+++ b/core/src/main/java/hudson/PluginWrapper.java
@@ -385,6 +385,7 @@ public Dependency(String s) {
String osgiProperty = osgiProperties[i].trim();
if (osgiProperty.equalsIgnoreCase("resolution:=optional")) {
isOptional = true;
+ break;
}
}
this.optional = isOptional;
@@ -521,20 +522,30 @@ public String getShortName() {
*/
@Exported
public String getUrl() {
- // first look for the manifest entry. This is new in maven-hpi-plugin 1.30
- String url = manifest.getMainAttributes().getValue("Url");
- if(url!=null) return url;
+ // first look in update center metadata
+ List siteMetadataList = getInfoFromAllSites();
+ String firstSiteUrl = null;
+ if (!siteMetadataList.isEmpty()) {
+ firstSiteUrl = siteMetadataList.get(0).wiki;
+ if (allUrlsMatch(firstSiteUrl, siteMetadataList)) {
+ return firstSiteUrl;
+ }
+ }
- // fallback to update center metadata
- UpdateSite.Plugin ui = getInfo();
- if(ui!=null) return ui.wiki;
+ // if update sites give different / empty results,
+ // use manifest (since maven-hpi-plugin 1.30)
+ String url = manifest.getMainAttributes().getValue("Url");
+ if (url != null) {
+ return url;
+ }
+ return firstSiteUrl;
+ }
- return null;
+ private boolean allUrlsMatch(String url, List uiList) {
+ return uiList.stream().allMatch(k -> k.wiki != null && k.wiki.equals(url));
}
-
-
- @Override
+ @Override
public String toString() {
return "Plugin:" + getShortName();
}
@@ -972,6 +983,11 @@ public UpdateSite.Plugin getInfo() {
return uc.getPlugin(getShortName());
}
+ private List getInfoFromAllSites() {
+ UpdateCenter uc = Jenkins.get().getUpdateCenter();
+ return uc.getPluginFromAllSites(getShortName(), getVersionNumber());
+ }
+
/**
* Returns true if this plugin has update in the update center.
*
@@ -1236,7 +1252,7 @@ public HttpResponse doUnpin() throws IOException {
@RequirePOST
public HttpResponse doDoUninstall() throws IOException {
- Jenkins jenkins = Jenkins.getActiveInstance();
+ Jenkins jenkins = Jenkins.get();
jenkins.checkPermission(Jenkins.ADMINISTER);
archive.delete();
diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java
index cecd2f6843..f79d15ef1d 100644
--- a/core/src/main/java/hudson/ProxyConfiguration.java
+++ b/core/src/main/java/hudson/ProxyConfiguration.java
@@ -62,7 +62,10 @@
import org.apache.commons.httpclient.methods.GetMethod;
import org.jenkinsci.Symbol;
import org.jvnet.robust_http_client.RetryableHttpStream;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -94,14 +97,15 @@ public final class ProxyConfiguration extends AbstractDescribableImpl getNoProxyHostPatterns() {
return getNoProxyHostPatterns(noProxyHost);
}
+ public String getNoProxyHost() {
+ return noProxyHost;
+ }
+
/**
* Returns the list of properly formatted no proxy host names.
*/
@@ -195,6 +223,26 @@ public static List getNoProxyHostPatterns(String noProxyHost) {
return r;
}
+ @DataBoundSetter
+ public void setSecretPassword(Secret secretPassword) {
+ this.secretPassword = secretPassword;
+ }
+
+ @DataBoundSetter
+ public void setTestUrl(String testUrl) {
+ this.testUrl = testUrl;
+ }
+
+ @DataBoundSetter
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ @DataBoundSetter
+ public void setNoProxyHost(String noProxyHost) {
+ this.noProxyHost = noProxyHost;
+ }
+
/**
* @deprecated
* Use {@link #createProxy(String)}
@@ -332,7 +380,7 @@ private static ProxyConfiguration get() {
private static ProxyConfiguration _get() {
JenkinsJVM.checkJenkinsJVM();
// this code could be called between the JVM flag being set and theInstance initialized
- Jenkins jenkins = Jenkins.get();
+ Jenkins jenkins = Jenkins.getInstanceOrNull();
return jenkins == null ? null : jenkins.proxy;
}
@@ -374,9 +422,10 @@ public FormValidation doCheckPort(@QueryParameter String value) {
}
@RequirePOST
+ @Restricted(NoExternalUse.class)
public FormValidation doValidateProxy(
@QueryParameter("testUrl") String testUrl, @QueryParameter("name") String name, @QueryParameter("port") int port,
- @QueryParameter("userName") String userName, @QueryParameter("password") String password,
+ @QueryParameter("userName") String userName, @QueryParameter("secretPassword") Secret password,
@QueryParameter("noProxyHost") String noProxyHost) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
@@ -401,7 +450,7 @@ public FormValidation doValidateProxy(
HttpClient client = new HttpClient();
if (Util.fixEmptyAndTrim(name) != null && !isNoProxyHost(host, noProxyHost)) {
client.getHostConfiguration().setProxy(name, port);
- Credentials credentials = createCredentials(userName, password);
+ Credentials credentials = createCredentials(userName, password != null ? password.getPlainText() : null);
AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT);
client.getState().setProxyCredentials(scope, credentials);
}
diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java
index b9f318ae87..7775b4ca6a 100644
--- a/core/src/main/java/hudson/Util.java
+++ b/core/src/main/java/hudson/Util.java
@@ -23,6 +23,7 @@
*/
package hudson;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.TaskListener;
import jenkins.util.MemoryReductionUtil;
import hudson.util.QuotedStringTokenizer;
@@ -692,6 +693,7 @@ public static byte[] fromHexString(@Nonnull String data) {
* number of milliseconds.
*/
@Nonnull
+ @SuppressFBWarnings(value = "ICAST_IDIV_CAST_TO_DOUBLE", justification = "We want to truncate here.")
public static String getTimeSpanString(long duration) {
// Break the duration up in to units.
long years = duration / ONE_YEAR_MS;
@@ -1579,10 +1581,10 @@ public static long daysElapsedSince(@Nonnull Date date){
public static boolean SYMLINK_ESCAPEHATCH = SystemProperties.getBoolean(Util.class.getName()+".symlinkEscapeHatch");
/**
- * The number of times we will attempt to delete files/directory trees
+ * The number of additional times we will attempt to delete files/directory trees
* before giving up and throwing an exception.
- * Specifying a value less than 1 is invalid and will be treated as if
- * a value of 1 (i.e. one attempt, no retries) was specified.
+ * Specifying a value less than 0 is invalid and will be treated as if
+ * a value of 0 (i.e. one attempt, no retries) was specified.
*
* e.g. if some of the child directories are big, it might take long enough
* to delete that it allows others to create new files in the directory we
@@ -1593,12 +1595,12 @@ public static long daysElapsedSince(@Nonnull Date date){
* give up, thus improving build reliability.
*/
@Restricted(value = NoExternalUse.class)
- static int DELETION_MAX = Math.max(1, SystemProperties.getInteger(Util.class.getName() + ".maxFileDeletionRetries", 3));
+ static int DELETION_RETRIES = Math.max(0, SystemProperties.getInteger(Util.class.getName() + ".maxFileDeletionRetries", 2));
/**
* The time (in milliseconds) that we will wait between attempts to
* delete files when retrying.
- * This has no effect unless {@link #DELETION_MAX} is non-zero.
+ * This has no effect unless {@link #DELETION_RETRIES} is non-zero.
*
* If zero, we will not delay between attempts.
* If negative, we will wait an (linearly) increasing multiple of this value
@@ -1611,7 +1613,7 @@ public static long daysElapsedSince(@Nonnull Date date){
* If this flag is set to true then we will request a garbage collection
* after a deletion failure before we next retry the delete.
* It defaults to {@code false} and is ignored unless
- * {@link #DELETION_MAX} is greater than 1.
+ * {@link #DELETION_RETRIES} is non zero.
*
* Setting this flag to true may resolve some problems on Windows,
* and also for directory trees residing on an NFS share, but it can
@@ -1632,7 +1634,7 @@ public static long daysElapsedSince(@Nonnull Date date){
static boolean GC_AFTER_FAILED_DELETE = SystemProperties.getBoolean(Util.class.getName() + ".performGCOnFailedDelete");
private static PathRemover newPathRemover(@Nonnull PathRemover.PathChecker pathChecker) {
- return PathRemover.newFilteredRobustRemover(pathChecker, DELETION_MAX - 1, GC_AFTER_FAILED_DELETE, WAIT_BETWEEN_DELETION_RETRIES);
+ return PathRemover.newFilteredRobustRemover(pathChecker, DELETION_RETRIES, GC_AFTER_FAILED_DELETE, WAIT_BETWEEN_DELETION_RETRIES);
}
/**
diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java
index b4116f3b58..102dbbc00b 100644
--- a/core/src/main/java/hudson/cli/CLIAction.java
+++ b/core/src/main/java/hudson/cli/CLIAction.java
@@ -43,20 +43,26 @@
import org.kohsuke.stapler.StaplerResponse;
import hudson.Extension;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
+import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.util.FullDuplexHttpService;
+import jenkins.websocket.WebSocketSession;
+import jenkins.websocket.WebSockets;
+import org.acegisecurity.Authentication;
+import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
/**
@@ -85,7 +91,7 @@ public String getUrlName() {
}
public void doCommand(StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
- final Jenkins jenkins = Jenkins.getActiveInstance();
+ final Jenkins jenkins = Jenkins.get();
jenkins.checkPermission(Jenkins.READ);
// Strip trailing slash
@@ -100,6 +106,74 @@ public void doCommand(StaplerRequest req, StaplerResponse rsp) throws ServletExc
req.getView(this, "command.jelly").forward(req, rsp);
}
+ /** for Jelly */
+ public boolean isWebSocketSupported() {
+ return WebSockets.isSupported();
+ }
+
+ /**
+ * WebSocket endpoint.
+ */
+ public HttpResponse doWs() {
+ if (!WebSockets.isSupported()) {
+ return HttpResponses.notFound();
+ }
+ Authentication authentication = Jenkins.getAuthentication();
+ return WebSockets.upgrade(new WebSocketSession() {
+ ServerSideImpl connection;
+ class OutputImpl implements PlainCLIProtocol.Output {
+ @Override
+ public void send(byte[] data) throws IOException {
+ sendBinary(ByteBuffer.wrap(data));
+ }
+ @Override
+ public void close() throws IOException {
+ doClose();
+ }
+ }
+ private void doClose() {
+ close();
+ }
+ @Override
+ protected void opened() {
+ try {
+ connection = new ServerSideImpl(new OutputImpl(), authentication);
+ } catch (IOException x) {
+ error(x);
+ return;
+ }
+ new Thread(() -> {
+ try {
+ try {
+ connection.run();
+ } finally {
+ connection.close();
+ }
+ } catch (Exception x) {
+ error(x);
+ }
+ }, "CLI handler for " + authentication.getName()).start();
+ }
+ @Override
+ protected void binary(byte[] payload, int offset, int len) {
+ try {
+ connection.handle(new DataInputStream(new ByteArrayInputStream(payload, offset, len)));
+ } catch (IOException x) {
+ error(x);
+ }
+ }
+ @Override
+ protected void error(Throwable cause) {
+ LOGGER.log(Level.WARNING, null, cause);
+ }
+ @Override
+ protected void closed(int statusCode, String reason) {
+ LOGGER.fine(() -> "closed: " + statusCode + ": " + reason);
+ connection.handleClose();
+ }
+ });
+ }
+
@Override
public Object getTarget() {
StaplerRequest req = Stapler.getCurrentRequest();
@@ -116,6 +190,105 @@ public Object getTarget() {
}
}
+ class ServerSideImpl extends PlainCLIProtocol.ServerSide {
+ private Thread runningThread;
+ private boolean ready;
+ private final List args = new ArrayList<>();
+ private Locale locale = Locale.getDefault();
+ private Charset encoding = Charset.defaultCharset();
+ private final PipedInputStream stdin = new PipedInputStream();
+ private final PipedOutputStream stdinMatch = new PipedOutputStream();
+ private final Authentication authentication;
+ ServerSideImpl(PlainCLIProtocol.Output out, Authentication authentication) throws IOException {
+ super(out);
+ stdinMatch.connect(stdin);
+ this.authentication = authentication;
+ }
+ @Override
+ protected void onArg(String text) {
+ args.add(text);
+ }
+ @Override
+ protected void onLocale(String text) {
+ for (Locale _locale : Locale.getAvailableLocales()) {
+ if (_locale.toString().equals(text)) {
+ locale = _locale;
+ return;
+ }
+ }
+ LOGGER.log(Level.WARNING, "unknown client locale {0}", text);
+ }
+ @Override
+ protected void onEncoding(String text) {
+ try {
+ encoding = Charset.forName(text);
+ } catch (UnsupportedCharsetException x) {
+ LOGGER.log(Level.WARNING, "unknown client charset {0}", text);
+ }
+ }
+ @Override
+ protected void onStart() {
+ ready();
+ }
+ @Override
+ protected void onStdin(byte[] chunk) throws IOException {
+ stdinMatch.write(chunk);
+ }
+ @Override
+ protected void onEndStdin() throws IOException {
+ stdinMatch.close();
+ }
+ @Override
+ protected void handleClose() {
+ ready();
+ if (runningThread != null) {
+ runningThread.interrupt();
+ }
+ }
+ private synchronized void ready() {
+ ready = true;
+ notifyAll();
+ }
+ void run() throws IOException, InterruptedException {
+ synchronized (this) {
+ while (!ready) {
+ wait();
+ }
+ }
+ PrintStream stdout = new PrintStream(streamStdout(), false, encoding.name());
+ PrintStream stderr = new PrintStream(streamStderr(), true, encoding.name());
+ if (args.isEmpty()) {
+ stderr.println("Connection closed before arguments received");
+ sendExit(2);
+ return;
+ }
+ String commandName = args.get(0);
+ CLICommand command = CLICommand.clone(commandName);
+ if (command == null) {
+ stderr.println("No such command " + commandName);
+ sendExit(2);
+ return;
+ }
+ command.setTransportAuth(authentication);
+ command.setClientCharset(encoding);
+ CLICommand orig = CLICommand.setCurrent(command);
+ try {
+ runningThread = Thread.currentThread();
+ int exit = command.main(args.subList(1, args.size()), locale, stdin, stdout, stderr);
+ stdout.flush();
+ sendExit(exit);
+ try { // seems to avoid ReadPendingException from Jetty
+ Thread.sleep(1000);
+ } catch (InterruptedException x) {
+ // expected; ignore
+ }
+ } finally {
+ CLICommand.setCurrent(orig);
+ runningThread = null;
+ }
+ }
+ }
+
/**
* Serves {@link PlainCLIProtocol} response.
*/
@@ -130,103 +303,9 @@ protected FullDuplexHttpService createService(StaplerRequest req, UUID uuid) thr
return new FullDuplexHttpService(uuid) {
@Override
protected void run(InputStream upload, OutputStream download) throws IOException, InterruptedException {
- final AtomicReference runningThread = new AtomicReference<>();
- class ServerSideImpl extends PlainCLIProtocol.ServerSide {
- boolean ready;
- List args = new ArrayList<>();
- Locale locale = Locale.getDefault();
- Charset encoding = Charset.defaultCharset();
- final PipedInputStream stdin = new PipedInputStream();
- final PipedOutputStream stdinMatch = new PipedOutputStream();
- ServerSideImpl(InputStream is, OutputStream os) throws IOException {
- super(is, os);
- stdinMatch.connect(stdin);
- }
- @Override
- protected void onArg(String text) {
- args.add(text);
- }
- @Override
- protected void onLocale(String text) {
- for (Locale _locale : Locale.getAvailableLocales()) {
- if (_locale.toString().equals(text)) {
- locale = _locale;
- return;
- }
- }
- LOGGER.log(Level.WARNING, "unknown client locale {0}", text);
- }
- @Override
- protected void onEncoding(String text) {
- try {
- encoding = Charset.forName(text);
- } catch (UnsupportedCharsetException x) {
- LOGGER.log(Level.WARNING, "unknown client charset {0}", text);
- }
- }
- @Override
- protected void onStart() {
- ready();
- }
- @Override
- protected void onStdin(byte[] chunk) throws IOException {
- stdinMatch.write(chunk);
- }
- @Override
- protected void onEndStdin() throws IOException {
- stdinMatch.close();
- }
- @Override
- protected void handleClose() {
- ready();
- Thread t = runningThread.get();
- if (t != null) {
- t.interrupt();
- }
- }
- private synchronized void ready() {
- ready = true;
- notifyAll();
- }
- }
- try (ServerSideImpl connection = new ServerSideImpl(upload, download)) {
- connection.begin();
- synchronized (connection) {
- while (!connection.ready) {
- connection.wait();
- }
- }
- PrintStream stdout = new PrintStream(connection.streamStdout(), false, connection.encoding.name());
- PrintStream stderr = new PrintStream(connection.streamStderr(), true, connection.encoding.name());
- if (connection.args.isEmpty()) {
- stderr.println("Connection closed before arguments received");
- connection.sendExit(2);
- return;
- }
- String commandName = connection.args.get(0);
- CLICommand command = CLICommand.clone(commandName);
- if (command == null) {
- stderr.println("No such command " + commandName);
- connection.sendExit(2);
- return;
- }
- command.setTransportAuth(Jenkins.getAuthentication());
- command.setClientCharset(connection.encoding);
- CLICommand orig = CLICommand.setCurrent(command);
- try {
- runningThread.set(Thread.currentThread());
- int exit = command.main(connection.args.subList(1, connection.args.size()), connection.locale, connection.stdin, stdout, stderr);
- stdout.flush();
- connection.sendExit(exit);
- try { // seems to avoid ReadPendingException from Jetty
- Thread.sleep(1000);
- } catch (InterruptedException x) {
- // expected; ignore
- }
- } finally {
- CLICommand.setCurrent(orig);
- runningThread.set(null);
- }
+ try (ServerSideImpl connection = new ServerSideImpl(new PlainCLIProtocol.FramedOutput(download), Jenkins.getAuthentication())) {
+ new PlainCLIProtocol.FramedReader(connection, upload).start();
+ connection.run();
}
}
};
diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java
index 15d496be1b..68747a6a8c 100644
--- a/core/src/main/java/hudson/cli/CLICommand.java
+++ b/core/src/main/java/hudson/cli/CLICommand.java
@@ -242,10 +242,8 @@ public int main(List args, Locale locale, InputStream stdin, PrintStream
sc.setAuthentication(auth = getTransportAuthentication());
if (!(this instanceof HelpCommand || this instanceof WhoAmICommand))
- Jenkins.getActiveInstance().checkPermission(Jenkins.READ);
+ Jenkins.get().checkPermission(Jenkins.READ);
p.parseArgument(args.toArray(new String[0]));
- if (!(this instanceof HelpCommand || this instanceof WhoAmICommand))
- Jenkins.getActiveInstance().checkPermission(Jenkins.READ);
LOGGER.log(Level.FINE, "Invoking CLI command {0}, with {1} arguments, as user {2}.",
new Object[] {getName(), args.size(), auth.getName()});
int res = run();
@@ -502,7 +500,7 @@ protected CLICommand createClone() {
*/
protected void registerOptionHandlers() {
try {
- for (Class c : Index.list(OptionHandlerExtension.class, Jenkins.getActiveInstance().pluginManager.uberClassLoader,Class.class)) {
+ for (Class c : Index.list(OptionHandlerExtension.class, Jenkins.get().pluginManager.uberClassLoader,Class.class)) {
Type t = Types.getBaseClass(c, OptionHandler.class);
CmdLineParser.registerHandler(Types.erasure(Types.getTypeArgument(t,0)), c);
}
diff --git a/core/src/main/java/hudson/cli/CancelQuietDownCommand.java b/core/src/main/java/hudson/cli/CancelQuietDownCommand.java
index 24b2ec3018..a521de769b 100644
--- a/core/src/main/java/hudson/cli/CancelQuietDownCommand.java
+++ b/core/src/main/java/hudson/cli/CancelQuietDownCommand.java
@@ -47,7 +47,7 @@ public String getShortDescription() {
@Override
protected int run() throws Exception {
- Jenkins.getActiveInstance().doCancelQuietDown();
+ Jenkins.get().doCancelQuietDown();
return 0;
}
}
diff --git a/core/src/main/java/hudson/cli/ClearQueueCommand.java b/core/src/main/java/hudson/cli/ClearQueueCommand.java
index 52cd7c4063..1a1b875bc5 100644
--- a/core/src/main/java/hudson/cli/ClearQueueCommand.java
+++ b/core/src/main/java/hudson/cli/ClearQueueCommand.java
@@ -47,7 +47,7 @@ public String getShortDescription() {
@Override
protected int run() throws Exception {
- Jenkins.getActiveInstance().getQueue().clear();
+ Jenkins.get().getQueue().clear();
return 0;
}
diff --git a/cli/src/main/java/hudson/cli/Connection.java b/core/src/main/java/hudson/cli/Connection.java
similarity index 100%
rename from cli/src/main/java/hudson/cli/Connection.java
rename to core/src/main/java/hudson/cli/Connection.java
diff --git a/core/src/main/java/hudson/cli/CopyJobCommand.java b/core/src/main/java/hudson/cli/CopyJobCommand.java
index 3eb8049c1f..fd6b658f8f 100644
--- a/core/src/main/java/hudson/cli/CopyJobCommand.java
+++ b/core/src/main/java/hudson/cli/CopyJobCommand.java
@@ -50,7 +50,7 @@ public String getShortDescription() {
public String dst;
protected int run() throws Exception {
- Jenkins jenkins = Jenkins.getActiveInstance();
+ Jenkins jenkins = Jenkins.get();
if (jenkins.getItemByFullName(dst)!=null) {
throw new IllegalStateException("Job '"+dst+"' already exists");
diff --git a/core/src/main/java/hudson/cli/CreateJobCommand.java b/core/src/main/java/hudson/cli/CreateJobCommand.java
index 0f0108c361..10ea5798fd 100644
--- a/core/src/main/java/hudson/cli/CreateJobCommand.java
+++ b/core/src/main/java/hudson/cli/CreateJobCommand.java
@@ -45,7 +45,7 @@ public String getShortDescription() {
public String name;
protected int run() throws Exception {
- Jenkins h = Jenkins.getActiveInstance();
+ Jenkins h = Jenkins.get();
if (h.getItemByFullName(name)!=null) {
throw new IllegalStateException("Job '"+name+"' already exists");
diff --git a/core/src/main/java/hudson/cli/CreateNodeCommand.java b/core/src/main/java/hudson/cli/CreateNodeCommand.java
index edab3f7e71..a1a826618e 100644
--- a/core/src/main/java/hudson/cli/CreateNodeCommand.java
+++ b/core/src/main/java/hudson/cli/CreateNodeCommand.java
@@ -52,7 +52,7 @@ public String getShortDescription() {
@Override
protected int run() throws Exception {
- final Jenkins jenkins = Jenkins.getActiveInstance();
+ final Jenkins jenkins = Jenkins.get();
jenkins.checkPermission(Computer.CREATE);
final Node newNode = (Node) Jenkins.XSTREAM2.fromXML(stdin);
diff --git a/core/src/main/java/hudson/cli/CreateViewCommand.java b/core/src/main/java/hudson/cli/CreateViewCommand.java
index d22a0b424f..dea72441fa 100644
--- a/core/src/main/java/hudson/cli/CreateViewCommand.java
+++ b/core/src/main/java/hudson/cli/CreateViewCommand.java
@@ -49,7 +49,7 @@ public String getShortDescription() {
@Override
protected int run() throws Exception {
- final Jenkins jenkins = Jenkins.getActiveInstance();
+ final Jenkins jenkins = Jenkins.get();
jenkins.checkPermission(View.CREATE);
View newView;
diff --git a/core/src/main/java/hudson/cli/DeleteJobCommand.java b/core/src/main/java/hudson/cli/DeleteJobCommand.java
index 375e158a87..d829340892 100644
--- a/core/src/main/java/hudson/cli/DeleteJobCommand.java
+++ b/core/src/main/java/hudson/cli/DeleteJobCommand.java
@@ -54,7 +54,7 @@ public String getShortDescription() {
protected int run() throws Exception {
boolean errorOccurred = false;
- final Jenkins jenkins = Jenkins.getActiveInstance();
+ final Jenkins jenkins = Jenkins.get();
final HashSet hs = new HashSet<>(jobs);
diff --git a/core/src/main/java/hudson/cli/DeleteNodeCommand.java b/core/src/main/java/hudson/cli/DeleteNodeCommand.java
index 76e5c29530..57aad43507 100644
--- a/core/src/main/java/hudson/cli/DeleteNodeCommand.java
+++ b/core/src/main/java/hudson/cli/DeleteNodeCommand.java
@@ -54,7 +54,7 @@ public String getShortDescription() {
protected int run() throws Exception {
boolean errorOccurred = false;
- final Jenkins jenkins = Jenkins.getActiveInstance();
+ final Jenkins jenkins = Jenkins.get();
final HashSet hs = new HashSet<>(nodes);
diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java
index 101576e904..58dbbb3291 100644
--- a/core/src/main/java/hudson/cli/DisablePluginCommand.java
+++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java
@@ -164,7 +164,7 @@ private void restartIfNecessary(List results)
/**
* Restart if this particular result of the disablement of a plugin and its dependent plugins (depending on the
- * strategy used) has a plugin disablexd.
+ * strategy used) has a plugin disabled.
* @param oneResult the result of a plugin (and its dependents).
* @return true if it end up in restarting jenkins.
*/
@@ -220,15 +220,15 @@ private int getResultCode(PluginWrapper.PluginDisableResult result) {
break;
case NO_SUCH_PLUGIN:
returnCode = RETURN_CODE_NO_SUCH_PLUGIN;
- }
-
- if (returnCode == 0) {
- for (PluginWrapper.PluginDisableResult oneDependentResult : result.getDependentsDisableStatus()) {
- returnCode = getResultCode(oneDependentResult);
- if (returnCode != 0) {
- break;
+ break;
+ default:
+ for (PluginWrapper.PluginDisableResult oneDependentResult : result.getDependentsDisableStatus()) {
+ returnCode = getResultCode(oneDependentResult);
+ if (returnCode != 0) {
+ break;
+ }
}
- }
+ break;
}
return returnCode;
diff --git a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
index 51a02f1afb..6135c932bc 100644
--- a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
+++ b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
@@ -59,7 +59,7 @@ public String getShortDescription() {
@Override
protected int run() throws Exception {
boolean errorOccurred = false;
- final Jenkins jenkins = Jenkins.getActiveInstance();
+ final Jenkins jenkins = Jenkins.get();
final HashSet hs = new HashSet<>(nodes);
diff --git a/core/src/main/java/hudson/cli/GroovyCommand.java b/core/src/main/java/hudson/cli/GroovyCommand.java
index f87ba80abc..9a802bc5a1 100644
--- a/core/src/main/java/hudson/cli/GroovyCommand.java
+++ b/core/src/main/java/hudson/cli/GroovyCommand.java
@@ -59,7 +59,7 @@ public String getShortDescription() {
protected int run() throws Exception {
// this allows the caller to manipulate the JVM state, so require the execute script privilege.
- Jenkins.getActiveInstance().checkPermission(Jenkins.RUN_SCRIPTS);
+ Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS);
Binding binding = new Binding();
binding.setProperty("out",new PrintWriter(stdout,true));
@@ -67,7 +67,7 @@ protected int run() throws Exception {
binding.setProperty("stdout",stdout);
binding.setProperty("stderr",stderr);
- GroovyShell groovy = new GroovyShell(Jenkins.getActiveInstance().getPluginManager().uberClassLoader, binding);
+ GroovyShell groovy = new GroovyShell(Jenkins.get().getPluginManager().uberClassLoader, binding);
groovy.run(loadScript(),"RemoteClass",remaining.toArray(new String[0]));
return 0;
}
diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java
index 1048bc53d7..dd2584ebf0 100644
--- a/core/src/main/java/hudson/cli/GroovyshCommand.java
+++ b/core/src/main/java/hudson/cli/GroovyshCommand.java
@@ -60,7 +60,7 @@ public String getShortDescription() {
@Override
protected int run() {
// this allows the caller to manipulate the JVM state, so require the admin privilege.
- Jenkins.getActiveInstance().checkPermission(Jenkins.RUN_SCRIPTS);
+ Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS);
// this being remote means no jline capability is available
System.setProperty("jline.terminal", UnsupportedTerminal.class.getName());
@@ -85,12 +85,12 @@ protected Groovysh createShell(InputStream stdin, PrintStream stdout,
Binding binding = new Binding();
// redirect "println" to the CLI
binding.setProperty("out", new PrintWriter(stdout,true));
- binding.setProperty("hudson", Jenkins.getActiveInstance()); // backward compatibility
- binding.setProperty("jenkins", Jenkins.getActiveInstance());
+ binding.setProperty("hudson", Jenkins.get()); // backward compatibility
+ binding.setProperty("jenkins", Jenkins.get());
IO io = new IO(new BufferedInputStream(stdin),stdout,stderr);
- final ClassLoader cl = Jenkins.getActiveInstance().pluginManager.uberClassLoader;
+ final ClassLoader cl = Jenkins.get().pluginManager.uberClassLoader;
Closure registrar = new Closure(null, null) {
private static final long serialVersionUID = 1L;
diff --git a/core/src/main/java/hudson/cli/HelpCommand.java b/core/src/main/java/hudson/cli/HelpCommand.java
index 071bce6a7f..a4623f5b8f 100644
--- a/core/src/main/java/hudson/cli/HelpCommand.java
+++ b/core/src/main/java/hudson/cli/HelpCommand.java
@@ -51,7 +51,7 @@ public String getShortDescription() {
@Override
protected int run() throws Exception {
- if (!Jenkins.getActiveInstance().hasPermission(Jenkins.READ)) {
+ if (!Jenkins.get().hasPermission(Jenkins.READ)) {
throw new AccessDeniedException("You must authenticate to access this Jenkins.\n"
+ CLI.usage());
}
diff --git a/core/src/main/java/hudson/cli/ListJobsCommand.java b/core/src/main/java/hudson/cli/ListJobsCommand.java
index 8dae08e674..dd7a1daf8f 100644
--- a/core/src/main/java/hudson/cli/ListJobsCommand.java
+++ b/core/src/main/java/hudson/cli/ListJobsCommand.java
@@ -49,7 +49,7 @@ public String getShortDescription() {
public String name;
protected int run() throws Exception {
- Jenkins h = Jenkins.getActiveInstance();
+ Jenkins h = Jenkins.get();
final Collection jobs;
// If name is given retrieve jobs for the given view.
diff --git a/core/src/main/java/hudson/cli/QuietDownCommand.java b/core/src/main/java/hudson/cli/QuietDownCommand.java
index 4d84055c8d..740ee10864 100644
--- a/core/src/main/java/hudson/cli/QuietDownCommand.java
+++ b/core/src/main/java/hudson/cli/QuietDownCommand.java
@@ -54,7 +54,7 @@ public String getShortDescription() {
@Override
protected int run() throws Exception {
- Jenkins.getActiveInstance().doQuietDown(block, timeout);
+ Jenkins.get().doQuietDown(block, timeout);
return 0;
}
}
diff --git a/core/src/main/java/hudson/cli/ReloadJobCommand.java b/core/src/main/java/hudson/cli/ReloadJobCommand.java
index 68a2bfda12..e8c51f9583 100644
--- a/core/src/main/java/hudson/cli/ReloadJobCommand.java
+++ b/core/src/main/java/hudson/cli/ReloadJobCommand.java
@@ -60,7 +60,7 @@ public String getShortDescription() {
protected int run() throws Exception {
boolean errorOccurred = false;
- final Jenkins jenkins = Jenkins.getActiveInstance();
+ final Jenkins jenkins = Jenkins.get();
final HashSet hs = new HashSet<>(jobs);
diff --git a/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
index 6107855812..2c901b06b4 100644
--- a/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
+++ b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
@@ -94,7 +94,7 @@ public int parseArguments(Parameters params) throws CmdLineException {
@CheckForNull
public View getView(final String name) {
- ViewGroup group = Jenkins.getActiveInstance();
+ ViewGroup group = Jenkins.get();
View view = null;
final StringTokenizer tok = new StringTokenizer(name, "/");
diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java
index 6625812e0e..4d2e2c2530 100644
--- a/core/src/main/java/hudson/console/ConsoleNote.java
+++ b/core/src/main/java/hudson/console/ConsoleNote.java
@@ -270,7 +270,8 @@ public static ConsoleNote readFrom(DataInputStream in) throws IOException, Class
}
}
- Jenkins jenkins = Jenkins.get();
+ Jenkins jenkins = Jenkins.getInstanceOrNull();
+
try (ObjectInputStream ois = new ObjectInputStreamEx(new GZIPInputStream(new ByteArrayInputStream(buf)),
jenkins != null ? jenkins.pluginManager.uberClassLoader : ConsoleNote.class.getClassLoader(),
ClassFilter.DEFAULT)) {
diff --git a/core/src/main/java/hudson/init/InitStrategy.java b/core/src/main/java/hudson/init/InitStrategy.java
index c6c5795e54..d4ba7d85b9 100644
--- a/core/src/main/java/hudson/init/InitStrategy.java
+++ b/core/src/main/java/hudson/init/InitStrategy.java
@@ -72,7 +72,7 @@ private void listPluginFiles(PluginManager pm, String extension, Collection r) {
diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java
index 61bb1bb187..c6ec3e1b41 100644
--- a/core/src/main/java/hudson/logging/LogRecorder.java
+++ b/core/src/main/java/hudson/logging/LogRecorder.java
@@ -50,6 +50,7 @@
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
+import java.io.Serializable;
import java.text.Collator;
import java.util.*;
import java.util.logging.Level;
@@ -252,7 +253,9 @@ public void disable() {
}
- private static class TargetComparator implements Comparator {
+ private static class TargetComparator implements Comparator, Serializable {
+
+ private static final long serialVersionUID = 9285340752515798L;
@Override
public int compare(Target left, Target right) {
diff --git a/core/src/main/java/hudson/markup/EscapedMarkupFormatter.java b/core/src/main/java/hudson/markup/EscapedMarkupFormatter.java
index f97e5e94c4..ca356e9499 100644
--- a/core/src/main/java/hudson/markup/EscapedMarkupFormatter.java
+++ b/core/src/main/java/hudson/markup/EscapedMarkupFormatter.java
@@ -47,7 +47,9 @@ public EscapedMarkupFormatter() {
@Override
public void translate(String markup, Writer output) throws IOException {
- output.write(Util.escape(markup));
+ if (markup != null) {
+ output.write(Util.escape(markup));
+ }
}
@Extension @Symbol("plainText")
diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java
index 28c2b926b9..6af442245d 100644
--- a/core/src/main/java/hudson/model/AbstractBuild.java
+++ b/core/src/main/java/hudson/model/AbstractBuild.java
@@ -863,8 +863,10 @@ private ChangeLogSet extends Entry> calcChangeSet() {
public EnvVars getEnvironment(TaskListener log) throws IOException, InterruptedException {
EnvVars env = super.getEnvironment(log);
FilePath ws = getWorkspace();
- if (ws!=null) // if this is done very early on in the build, workspace may not be decided yet. see HUDSON-3997
+ if (ws != null) { // if this is done very early on in the build, workspace may not be decided yet. see HUDSON-3997
env.put("WORKSPACE", ws.getRemote());
+ env.put("WORKSPACE_TMP", WorkspaceList.tempDir(ws).getRemote()); // JENKINS-60634
+ }
project.getScm().buildEnvVars(this,env);
diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index e1968ecfc1..d00fb66986 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -730,18 +730,16 @@ public void delete() throws IOException, InterruptedException {
if (subtask != null) {
Item item = Tasks.getItemOf(subtask);
- if (item != null) {
- while (item != null) {
- if (item == this) {
- buildsInProgress.put(e, e.getCurrentExecutable());
- e.interrupt(Result.ABORTED);
- break;
- }
- if (item.getParent() instanceof Item) {
- item = (Item) item.getParent();
- } else {
- break;
- }
+ while (item != null) {
+ if (item == this) {
+ buildsInProgress.put(e, e.getCurrentExecutable());
+ e.interrupt(Result.ABORTED);
+ break;
+ }
+ if (item.getParent() instanceof Item) {
+ item = (Item) item.getParent();
+ } else {
+ break;
}
}
}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 054f9f5717..52c327e350 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -510,6 +510,7 @@ public final FilePath getWorkspace() {
*
* @return An AbstractBuild for deprecated methods to use.
*/
+ @CheckForNull
private AbstractBuild getBuildForDeprecatedMethods() {
Executor e = Executor.currentExecutor();
if(e!=null) {
@@ -1071,7 +1072,7 @@ public CauseOfBlockage getCauseOfBlockage() {
return new BlockedBecauseOfBuildInProgress(lastBuild);
} else {
// The build has been likely deleted after the isLogUpdated() call.
- // Another cause may be an API implementation glitсh in the implementation for AbstractProject.
+ // Another cause may be an API implementation glitch 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");
}
diff --git a/core/src/main/java/hudson/model/AsyncAperiodicWork.java b/core/src/main/java/hudson/model/AsyncAperiodicWork.java
index 804f12b4a4..cd9f46b830 100644
--- a/core/src/main/java/hudson/model/AsyncAperiodicWork.java
+++ b/core/src/main/java/hudson/model/AsyncAperiodicWork.java
@@ -172,7 +172,7 @@ protected StreamTaskListener createListener() {
} else {
lastRotateMillis = System.currentTimeMillis();
// migrate old log files the first time we start-up
- File oldFile = new File(Jenkins.getActiveInstance().getRootDir(), f.getName());
+ File oldFile = new File(Jenkins.get().getRootDir(), f.getName());
if (oldFile.isFile()) {
File newFile = new File(f.getParentFile(), f.getName() + ".1");
if (!newFile.isFile()) {
diff --git a/core/src/main/java/hudson/model/AsyncPeriodicWork.java b/core/src/main/java/hudson/model/AsyncPeriodicWork.java
index e4560d9d0c..80031750b7 100644
--- a/core/src/main/java/hudson/model/AsyncPeriodicWork.java
+++ b/core/src/main/java/hudson/model/AsyncPeriodicWork.java
@@ -157,7 +157,7 @@ protected StreamTaskListener createListener() {
} else {
lastRotateMillis = System.currentTimeMillis();
// migrate old log files the first time we start-up
- File oldFile = new File(Jenkins.getActiveInstance().getRootDir(), f.getName());
+ File oldFile = new File(Jenkins.get().getRootDir(), f.getName());
if (oldFile.isFile()) {
File newFile = new File(f.getParentFile(), f.getName() + ".1");
if (!newFile.isFile()) {
diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java
index b6f120e211..08f6655236 100644
--- a/core/src/main/java/hudson/model/Cause.java
+++ b/core/src/main/java/hudson/model/Cause.java
@@ -47,7 +47,7 @@
/**
* Cause object base class. This class hierarchy is used to keep track of why
* a given build was started. This object encapsulates the UI rendering of the cause,
- * as well as providing more useful information in respective subypes.
+ * as well as providing more useful information in respective subtypes.
*
* The Cause object is connected to a build via the {@link CauseAction} object.
*
@@ -333,7 +333,6 @@ public static class ConverterImpl extends XStream2.PassthruConverter();
uc.upstreamCauses.add(uc.upstreamCause);
uc.upstreamCause = null;
OldDataMonitor.report(context, "1.288");
diff --git a/core/src/main/java/hudson/model/CheckPoint.java b/core/src/main/java/hudson/model/CheckPoint.java
index 934fe7ddb5..ab4e176f6f 100644
--- a/core/src/main/java/hudson/model/CheckPoint.java
+++ b/core/src/main/java/hudson/model/CheckPoint.java
@@ -101,7 +101,7 @@ public String toString() {
}
/**
- * Records that the execution of the build has reached to a check point, idenified
+ * Records that the execution of the build has reached to a check point, identified
* by the given identifier.
*
*
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index e7856b4c6c..bd0e246330 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -1069,13 +1069,13 @@ public final long getDemandStartMilliseconds() {
}
/**
- * Returns the {@link Node} description for this computer
+ * Returns the {@link Node} description for this computer. Empty String if the {@link Node} is {@code null}.
*/
@Restricted(DoNotUse.class)
@Exported
public @Nonnull String getDescription() {
Node node = getNode();
- return (node != null) ? node.getNodeDescription() : null;
+ return (node != null) ? node.getNodeDescription() : "";
}
@@ -1088,6 +1088,7 @@ protected void removeExecutor(final Executor e) {
public void run() {
synchronized (Computer.this) {
executors.remove(e);
+ oneOffExecutors.remove(e);
addNewExecutorIfNecessary();
if (!isAlive()) {
AbstractCIBase ciBase = Jenkins.getInstanceOrNull();
@@ -1475,7 +1476,7 @@ public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOE
}
if ((!proposedName.equals(nodeName))
- && Jenkins.getActiveInstance().getNode(proposedName) != null) {
+ && Jenkins.get().getNode(proposedName) != null) {
throw new FormException(Messages.ComputerSet_SlaveAlreadyExists(proposedName), "name");
}
diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java
index be6024e6e5..463cdee366 100644
--- a/core/src/main/java/hudson/model/Descriptor.java
+++ b/core/src/main/java/hudson/model/Descriptor.java
@@ -59,6 +59,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -644,7 +645,7 @@ public Object instantiate(Class actualType, JSONObject json) {
if (isApplicable(actualType, json)) {
LOGGER.log(Level.FINE, "switching to newInstance {0} {1}", new Object[] {actualType.getName(), json});
try {
- final Descriptor descriptor = Jenkins.getActiveInstance().getDescriptor(actualType);
+ final Descriptor descriptor = Jenkins.get().getDescriptor(actualType);
if (descriptor != null) {
return descriptor.newInstance(Stapler.getCurrentRequest(), json);
} else {
@@ -670,7 +671,7 @@ public Object onConvert(Type targetType, Class targetTypeErasure, Object jsonSou
if (isApplicable(targetTypeErasure, json)) {
LOGGER.log(Level.FINE, "switching to newInstance {0} {1}", new Object[] {targetTypeErasure.getName(), json});
try {
- return Jenkins.getActiveInstance().getDescriptor(targetTypeErasure).newInstance(Stapler.getCurrentRequest(), json);
+ return Jenkins.get().getDescriptor(targetTypeErasure).newInstance(Stapler.getCurrentRequest(), json);
} catch (Exception x) {
LOGGER.log(Level.WARNING, "falling back to default instantiation " + targetTypeErasure.getName() + " " + json, x);
}
@@ -690,7 +691,7 @@ public Object onConvert(Type targetType, Class targetTypeErasure, Object jsonSou
private T verifyNewInstance(T t) {
if (t!=null && t.getDescriptor()!=this) {
// TODO: should this be a fatal error?
- LOGGER.warning("Father of "+ t+" and its getDescriptor() points to two different instances. Probably malplaced @Extension. See http://hudson.361315.n4.nabble.com/Help-Hint-needed-Post-build-action-doesn-t-stay-activated-td2308833.html");
+ LOGGER.warning("Father of "+ t+" and its getDescriptor() points to two different instances. Probably misplaced @Extension. See http://hudson.361315.n4.nabble.com/Help-Hint-needed-Post-build-action-doesn-t-stay-activated-td2308833.html");
}
return t;
}
@@ -943,7 +944,7 @@ public void doHelp(StaplerRequest req, StaplerResponse rsp) throws IOException,
// TODO: generalize macro expansion and perhaps even support JEXL
rsp.setContentType("text/html;charset=UTF-8");
try (InputStream in = url.openStream()) {
- String literal = IOUtils.toString(in,"UTF-8");
+ String literal = IOUtils.toString(in, StandardCharsets.UTF_8);
rsp.getWriter().println(Util.replaceMacro(literal, Collections.singletonMap("rootURL",req.getContextPath())));
}
return;
diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
index 05db1a2425..fb45e0a5a6 100644
--- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
+++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
@@ -327,11 +327,8 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
boolean view = rest.equals("*view*");
if(rest.equals("*fingerprint*")) {
- InputStream fingerprintInput = baseFile.open();
- try {
+ try (InputStream fingerprintInput = baseFile.open()) {
rsp.forward(Jenkins.get().getFingerprint(Util.getDigestOf(fingerprintInput)), "/", req);
- } finally {
- fingerprintInput.close();
}
return;
}
diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java
index f4cd34d416..dfdd4e5c7a 100644
--- a/core/src/main/java/hudson/model/DownloadService.java
+++ b/core/src/main/java/hudson/model/DownloadService.java
@@ -43,6 +43,7 @@
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
@@ -111,7 +112,7 @@ public static String loadJSON(URL src) throws IOException {
((HttpURLConnection) con).setInstanceFollowRedirects(true);
}
try (InputStream is = con.getInputStream()) {
- String jsonp = IOUtils.toString(is, "UTF-8");
+ String jsonp = IOUtils.toString(is, StandardCharsets.UTF_8);
int start = jsonp.indexOf('{');
int end = jsonp.lastIndexOf('}');
if (start >= 0 && end > start) {
@@ -136,7 +137,7 @@ public static String loadJSONHTML(URL src) throws IOException {
((HttpURLConnection) con).setInstanceFollowRedirects(true);
}
try (InputStream is = con.getInputStream()) {
- String jsonp = IOUtils.toString(is, "UTF-8");
+ String jsonp = IOUtils.toString(is, StandardCharsets.UTF_8);
String preamble = "window.parent.postMessage(JSON.stringify(";
int start = jsonp.indexOf(preamble);
int end = jsonp.lastIndexOf("),'*');");
@@ -256,7 +257,7 @@ public String getUrl() {
*/
public List getUrls() {
List updateSites = new ArrayList<>();
- for (UpdateSite site : Jenkins.getActiveInstance().getUpdateCenter().getSiteList()) {
+ for (UpdateSite site : Jenkins.get().getUpdateCenter().getSiteList()) {
String siteUrl = site.getUrl();
int baseUrlEnd = siteUrl.indexOf("update-center.json");
if (baseUrlEnd != -1) {
@@ -326,7 +327,7 @@ private FormValidation load(String json, long dataTimestamp) throws IOException
public FormValidation updateNow() throws IOException {
List jsonList = new ArrayList<>();
boolean toolInstallerMetadataExists = false;
- for (UpdateSite updatesite : Jenkins.getActiveInstance().getUpdateCenter().getSiteList()) {
+ for (UpdateSite updatesite : Jenkins.get().getUpdateCenter().getSiteList()) {
String site = updatesite.getMetadataUrlForDownloadable(url);
if (site == null) {
return FormValidation.warning("The update site " + updatesite.getId() + " does not look like an update center");
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index 1dc112c531..e69fa2100b 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -39,6 +39,7 @@
import org.acegisecurity.Authentication;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
+import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
@@ -828,7 +829,7 @@ public void start() {
/**
* @deprecated as of 1.489
- * Use {@link #doStop()}.
+ * Use {@link #doStop()} or {@link #doStopBuild(String)}.
*/
@RequirePOST
@Deprecated
@@ -837,17 +838,38 @@ public void doStop( StaplerRequest req, StaplerResponse rsp ) throws IOException
}
/**
- * Stops the current build.
+ * Stops the current build.
+ * You can use {@link #doStopBuild(String)} instead to ensure what will be
+ * interrupted is actually what you want to interrupt.
*
* @since 1.489
+ * @see #doStopBuild(String)
*/
@RequirePOST
public HttpResponse doStop() {
+ return doStopBuild(null);
+ }
+
+ /**
+ * Stops the current build, if matching the specified external id
+ * (or no id is specified, or the current {@link Executable} is not a {@link Run}).
+ *
+ * @param runExtId
+ * if not null, the externalizable id ({@link Run#getExternalizableId()})
+ * of the build the user expects to interrupt
+ * @since TODO
+ */
+ @RequirePOST
+ @Restricted(NoExternalUse.class)
+ public HttpResponse doStopBuild(@CheckForNull @QueryParameter(fixEmpty = true) String runExtId) {
lock.writeLock().lock(); // need write lock as interrupt will change the field
try {
if (executable != null) {
- getParentOf(executable).getOwnerTask().checkAbortPermission();
- interrupt();
+ if (runExtId == null || runExtId.isEmpty() || ! (executable instanceof Run)
+ || (executable instanceof Run && runExtId.equals(((Run,?>) executable).getExternalizableId()))) {
+ getParentOf(executable).getOwnerTask().checkAbortPermission();
+ interrupt();
+ }
}
} finally {
lock.writeLock().unlock();
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index b5644b0d5b..e628c6bd54 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -1203,9 +1203,7 @@ public int size() {
public int compare(FingerprintFacet o1, FingerprintFacet o2) {
long a = o1.getTimestamp();
long b = o2.getTimestamp();
- if (a < b) return -1;
- if (a == b) return 0;
- return 1;
+ return Long.compare(a, b);
}
});
return r;
diff --git a/core/src/main/java/hudson/model/HealthReport.java b/core/src/main/java/hudson/model/HealthReport.java
index c43cd0a132..461264ed6a 100644
--- a/core/src/main/java/hudson/model/HealthReport.java
+++ b/core/src/main/java/hudson/model/HealthReport.java
@@ -326,7 +326,7 @@ public boolean isAggregateReport() {
@Override
public int compareTo(HealthReport o) {
- return (this.score < o.score ? -1 : (this.score == o.score ? 0 : 1));
+ return Integer.compare(this.score, o.score);
}
/**
diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java
index f7329c7ee3..c6b06e322c 100644
--- a/core/src/main/java/hudson/model/Items.java
+++ b/core/src/main/java/hudson/model/Items.java
@@ -225,10 +225,7 @@ public static List fromNameList(ItemGroup context, @Nonnull
final Jenkins jenkins = Jenkins.get();
List r = new ArrayList<>();
- if (jenkins == null) {
- return r;
- }
-
+
StringTokenizer tokens = new StringTokenizer(list,",");
while(tokens.hasMoreTokens()) {
String fullName = tokens.nextToken().trim();
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index ea90279274..f62ba2bd21 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -403,7 +403,7 @@ public EnvVars getCharacteristicEnvVars() {
}
/**
- * Programatically updates the next build number.
+ * Programmatically updates the next build number.
*
*
* Much of Hudson assumes that the build number is unique and monotonic, so
@@ -548,7 +548,7 @@ public T removeProperty(Class clazz) throws IOExcepti
public Map> getProperties() {
Map result = Descriptor.toMap((Iterable) properties);
if (logRotator != null) {
- result.put(Jenkins.getActiveInstance().getDescriptorByType(BuildDiscarderProperty.DescriptorImpl.class), new BuildDiscarderProperty(logRotator));
+ result.put(Jenkins.get().getDescriptorByType(BuildDiscarderProperty.DescriptorImpl.class), new BuildDiscarderProperty(logRotator));
}
return result;
}
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index 4a112b68a4..ddf419604a 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -55,6 +55,7 @@
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
+import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
@@ -71,6 +72,7 @@
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import javax.annotation.Nonnull;
/**
* Group of {@link Node}s.
@@ -84,16 +86,19 @@ public abstract class Label extends Actionable implements Comparable