diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..a7842e0f66c76f14ba418fec1d01350b48b637a8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://jenkins.io/donate/ diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000000000000000000000000000000000000..ee1096aa39093b66c43095737d32d29225c5dd40 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,59 @@ +# 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. +version-template: $MAJOR.$MINOR +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. + + ```yaml + $CHANGES + ``` + +# Categories will be commented out, because we use YAML +# Now we use categories only for sorting +categories: + - title: Major BUGs and regressions + labels: + - major-bug + - regression-fix + - title: Major RFE + label: major-rfe + - title: RFEs + label: rfe + - title: Bug fixes + label: bug + - title: Localization + label: localization + # TODO: consider merging category or changing emojis + - title: Internal/Developer changes + label: internal + +replacers: + - search: '/\[*JENKINS-(\d+)\]*\s*-*\s*/g' + replace: |- + issue: $1 + message: |- + + - search: |- + message: |- + issue: + replace: "issue:" + + - search: "##" + replace: "#" diff --git a/.mvn/maven.config b/.mvn/maven.config index e54fdacfe62468e4e85ebe51890384926933b476..2a0299c4865d58b47a576f7effc4a15f3fcf4ee9 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1 +1,2 @@ +-Pconsume-incrementals -Pmight-produce-incrementals diff --git a/Dockerfile b/Dockerfile index d4541bdfcbaa7904cfc9264f6a823d80d1b55992..4566c7265f56fda510013ff263b911d92c6a2a3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ COPY cli/ /jenkins/src/cli/ COPY core/ /jenkins/src/core/ COPY src/ /jenkins/src/src/ COPY test/ /jenkins/src/test/ +COPY test-pom/ /jenkins/src/test-pom/ +COPY test-jdk8/ /jenkins/src/test-jdk8/ COPY war/ /jenkins/src/war/ COPY *.xml /jenkins/src/ COPY LICENSE.txt /jenkins/src/LICENSE.txt diff --git a/Jenkinsfile b/Jenkinsfile index 3cecdfcbb7b16bac414c800ad0163e37e80dbd5e..95c9becb3fd0161ff306ac7c7598634d4b8ca205 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,20 +3,16 @@ /* * This Jenkinsfile is intended to run on https://ci.jenkins.io and may fail anywhere else. * It makes assumptions about plugins being installed, labels mapping to nodes that can build what is needed, etc. - * - * The required labels are "java" and "docker" - "java" would be any node that can run Java builds. It doesn't need - * to have Java installed, but some setups may have nodes that shouldn't have heavier builds running on them, so we - * make this explicit. "docker" would be any node with docker installed. */ // TEST FLAG - to make it easier to turn on/off unit tests for speeding up access to later stuff. def runTests = true def failFast = false -properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '20')), durabilityHint('PERFORMANCE_OPTIMIZED')]) +properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '3')), durabilityHint('PERFORMANCE_OPTIMIZED')]) -// see https://github.com/jenkins-infra/documentation/blob/master/ci.adoc for information on what node types are available -def buildTypes = ['Linux', 'Windows'] +// TODO: Restore 'Windows' once https://groups.google.com/forum/#!topic/jenkinsci-dev/v9d-XosOp2s is resolved +def buildTypes = ['Linux'] def jdks = [8, 11] def builds = [:] @@ -25,8 +21,8 @@ for(j = 0; j < jdks.size(); j++) { def buildType = buildTypes[i] def jdk = jdks[j] builds["${buildType}-jdk${jdk}"] = { - node(buildType.toLowerCase()) { - timestamps { + // see https://github.com/jenkins-infra/documentation/blob/master/ci.adoc#node-labels for information on what node types are available + node(buildType == 'Linux' ? (jdk == 8 ? 'maven' : 'maven-11') : buildType.toLowerCase()) { // First stage is actually checking out the source. Since we're using Multibranch // currently, we can use "checkout scm". stage('Checkout') { @@ -42,7 +38,7 @@ for(j = 0; j < jdks.size(); j++) { // See below for what this method does - we're passing an arbitrary environment // variable to it so that JAVA_OPTS and MAVEN_OPTS are set correctly. withMavenEnv(["JAVA_OPTS=-Xmx1536m -Xms512m", - "MAVEN_OPTS=-Xmx1536m -Xms512m"], jdk) { + "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" @@ -63,7 +59,7 @@ for(j = 0; j < jdks.size(); j++) { junit healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/*.xml' archiveArtifacts allowEmptyArchive: true, artifacts: '**/target/surefire-reports/*.dumpstream' } - if (buildType == 'Linux') { + if (buildType == 'Linux' && jdk == jdks[0]) { def changelist = readFile(changelistF) dir(m2repo) { archiveArtifacts artifacts: "**/*$changelist/*$changelist*", @@ -73,12 +69,13 @@ 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 @@ -100,7 +97,7 @@ builds.ath = { runATH jenkins: fileUri, metadataFile: metadataPath } } -} +}*/ builds.failFast = failFast parallel builds @@ -109,7 +106,14 @@ infra.maybePublishIncrementals() // This method sets up the Maven and JDK tools, puts them in the environment along // with whatever other arbitrary environment variables we passed in, and runs the // body we passed in within that environment. -void withMavenEnv(List envVars = [], def javaVersion, def body) { +void withMavenEnv(List envVars = [], def buildType, def javaVersion, def body) { + if (buildType == 'Linux') { + // I.e., a Maven container using ACI. No need to install tools. + return withEnv(envVars) { + body.call() + } + } + // The names here are currently hardcoded for my test environment. This needs // to be made more flexible. // Using the "tool" Workflow call automatically installs those tools on the diff --git a/README.md b/README.md index 7ef6c08a8ec40120d755931ce679e5ca870c5bda..ee4ed648cbcbe3526a3e8c6870de015b5d2f9f60 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ [![][ButlerImage]][website] # About + +[![Weekly Release](https://img.shields.io/badge/dynamic/json.svg?url=https://updates.jenkins.io/update-center.actual.json&label=Weekly%20Release&query=$.core.version&color=green)](https://jenkins.io/changelog/) +[![LTS Release](https://img.shields.io/badge/dynamic/json.svg?url=https://updates.jenkins.io/stable/update-center.actual.json&label=LTS%20Release&query=$.core.version&color=orange)](https://jenkins.io/changelog-stable/) +[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/jenkins.svg)](https://hub.docker.com/r/jenkins/jenkins/) + In a nutshell, Jenkins is the leading open-source automation server. -Built with Java, it provides over 1000 plugins to support automating virtually anything, +Built with Java, it provides over 1600 [plugins](https://plugins.jenkins.io/) to support automating virtually anything, so that humans can actually spend their time doing things machines cannot. # What to Use Jenkins for and When to Use It @@ -29,32 +34,11 @@ Follow the [contributing](CONTRIBUTING.md) file. All information about Jenkins can be found on our [website]. Follow us on Twitter [@jenkinsci]. # License -Jenkins is **licensed** under the **[MIT License]**. The terms of the license are as follows: - - The MIT License (MIT) - - Copyright (c) 2004 Kohsuke Kawaguchi, Sun Microsystems Inc., and a number of other contributors. - - 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. +Jenkins is **licensed** under the **[MIT License]**. - 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. [ButlerImage]: https://jenkins.io/sites/default/files/jenkins_logo.png -[MIT License]: https://github.com/jenkinsci/jenkins/raw/master/LICENSE.txt +[MIT License]: https://github.com/jenkinsci/jenkins/blob/master/LICENSE.txt [Mirrors]: http://mirrors.jenkins-ci.org [GitHub]: https://github.com/jenkinsci/jenkins [website]: https://jenkins.io/ diff --git a/cli/pom.xml b/cli/pom.xml index 0a5aa6a5c328e6072e7a164f4bf974656b676cb0..5d3738bd4202853908b39d102bd1f852dfc1c819 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -58,7 +58,7 @@ org.jvnet.localizer localizer - 1.24 + 1.26 org.apache.sshd @@ -77,15 +77,10 @@ slf4j-jdk14 true - - org.jenkins-ci - trilead-ssh2 - build214-jenkins-1 - - com.google.code.findbugs - annotations - provided + com.github.spotbugs + spotbugs-annotations + true commons-lang @@ -139,7 +134,21 @@ org.codehaus.mojo - findbugs-maven-plugin + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/localizer + + + + diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java index 0ffef53f9305a6e9d260826e092e805fe9b8465e..5b436b02b085d9ca964561828d4b2fdeafb41715 100644 --- a/cli/src/main/java/hudson/cli/CLI.java +++ b/cli/src/main/java/hudson/cli/CLI.java @@ -24,54 +24,25 @@ package hudson.cli; import hudson.cli.client.Messages; -import hudson.remoting.Channel; -import hudson.remoting.NamingThreadFactory; -import hudson.remoting.PingThread; -import hudson.remoting.Pipe; -import hudson.remoting.RemoteInputStream; -import hudson.remoting.RemoteOutputStream; -import hudson.remoting.SocketChannelStream; -import hudson.remoting.SocketOutputStream; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PrintStream; -import java.io.StringReader; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.security.KeyPair; -import java.security.PublicKey; import java.security.SecureRandom; -import java.security.Signature; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; @@ -81,240 +52,10 @@ import org.apache.commons.lang.StringUtils; /** * CLI entry point to Jenkins. - * - * @author Kohsuke Kawaguchi */ -public class CLI implements AutoCloseable { - private final ExecutorService pool; - private final Channel channel; - private final CliEntryPoint entryPoint; - private final boolean ownsPool; - private final List closables = new ArrayList(); // stuff to close in the close method - private final String httpsProxyTunnel; - private final String authorization; +public class CLI { - /** - * For tests only. - * @deprecated Specific to {@link Mode#REMOTING}. - */ - @Deprecated - public CLI(URL jenkins) throws IOException, InterruptedException { - this(jenkins,null); - } - - /** - * @deprecated - * Use {@link CLIConnectionFactory} to create {@link CLI} - */ - @Deprecated - public CLI(URL jenkins, ExecutorService exec) throws IOException, InterruptedException { - this(jenkins,exec,null); - } - - /** - * @deprecated - * Use {@link CLIConnectionFactory} to create {@link CLI} - */ - @Deprecated - public CLI(URL jenkins, ExecutorService exec, String httpsProxyTunnel) throws IOException, InterruptedException { - this(new CLIConnectionFactory().url(jenkins).executorService(exec).httpsProxyTunnel(httpsProxyTunnel)); - } - - /** - * @deprecated Specific to {@link Mode#REMOTING}. - */ - @Deprecated - /*package*/ CLI(CLIConnectionFactory factory) throws IOException, InterruptedException { - URL jenkins = factory.jenkins; - this.httpsProxyTunnel = factory.httpsProxyTunnel; - this.authorization = factory.authorization; - ExecutorService exec = factory.exec; - - ownsPool = exec==null; - pool = exec!=null ? exec : Executors.newCachedThreadPool(new NamingThreadFactory(Executors.defaultThreadFactory(), "CLI.pool")); - - Channel _channel; - try { - _channel = connectViaCliPort(jenkins, getCliTcpPort(jenkins)); - } catch (IOException e) { - LOGGER.log(Level.FINE, "Failed to connect via CLI port. Falling back to HTTP", e); - try { - _channel = connectViaHttp(jenkins); - } catch (IOException e2) { - e.addSuppressed(e2); - throw e; - } - } - this.channel = _channel; - - // execute the command - entryPoint = (CliEntryPoint)_channel.waitForRemoteProperty(CliEntryPoint.class.getName()); - - if(entryPoint.protocolVersion()!=CliEntryPoint.VERSION) - throw new IOException(Messages.CLI_VersionMismatch()); - } - - /** - * @deprecated Specific to {@link Mode#REMOTING}. - */ - @Deprecated - private Channel connectViaHttp(URL url) throws IOException { - LOGGER.log(FINE, "Trying to connect to {0} via Remoting over HTTP", url); - - FullDuplexHttpStream con = new FullDuplexHttpStream(url, "cli?remoting=true", authorization); - Channel ch = new Channel("Chunked connection to " + url, - pool,con.getInputStream(),con.getOutputStream()); - final long interval = 15*1000; - final long timeout = (interval * 3) / 4; - new PingThread(ch,timeout,interval) { - protected void onDead() { - // noop. the point of ping is to keep the connection alive - // as most HTTP servers have a rather short read time out - } - }.start(); - return ch; - } - - /** - * @deprecated Specific to {@link Mode#REMOTING}. - */ - @Deprecated - private Channel connectViaCliPort(URL jenkins, CliPort clip) throws IOException { - LOGGER.log(FINE, "Trying to connect directly via Remoting over TCP/IP to {0}", clip.endpoint); - - if (authorization != null) { - LOGGER.warning("-auth ignored when using JNLP agent port"); - } - - final Socket s = new Socket(); - // this prevents a connection from silently terminated by the router in between or the other peer - // and that goes without unnoticed. However, the time out is often very long (for example 2 hours - // by default in Linux) that this alone is enough to prevent that. - s.setKeepAlive(true); - // we take care of buffering on our own - s.setTcpNoDelay(true); - OutputStream out; - - if (httpsProxyTunnel!=null) { - String[] tokens = httpsProxyTunnel.split(":"); - LOGGER.log(Level.FINE, "Using HTTP proxy {0}:{1} to connect to CLI port", new Object[]{tokens[0], tokens[1]}); - s.connect(new InetSocketAddress(tokens[0], Integer.parseInt(tokens[1]))); - PrintStream o = new PrintStream(s.getOutputStream()); - o.print("CONNECT " + clip.endpoint.getHostString() + ":" + clip.endpoint.getPort() + " HTTP/1.0\r\n\r\n"); - - // read the response from the proxy - ByteArrayOutputStream rsp = new ByteArrayOutputStream(); - while (!rsp.toString("ISO-8859-1").endsWith("\r\n\r\n")) { - int ch = s.getInputStream().read(); - if (ch<0) throw new IOException("Failed to read the HTTP proxy response: "+rsp); - rsp.write(ch); - } - String head = new BufferedReader(new StringReader(rsp.toString("ISO-8859-1"))).readLine(); - - if (head == null) { - throw new IOException("Unexpected empty response"); - } - if (!(head.startsWith("HTTP/1.0 200 ") || head.startsWith("HTTP/1.1 200 "))) { - s.close(); - LOGGER.log(Level.SEVERE, "Failed to tunnel the CLI port through the HTTP proxy. Falling back to HTTP."); - throw new IOException("Failed to establish a connection through HTTP proxy: " + rsp); - } - - // HTTP proxies (at least the one I tried --- squid) doesn't seem to do half-close very well. - // So instead of relying on it, we'll just send the close command and then let the server - // cut their side, then close the socket after the join. - out = new SocketOutputStream(s) { - @Override - public void close() throws IOException { - // ignore - } - }; - } else { - s.connect(clip.endpoint,3000); - out = SocketChannelStream.out(s); - } - - closables.add(new Closeable() { - public void close() throws IOException { - s.close(); - } - }); - - Connection c = new Connection(SocketChannelStream.in(s),out); - - switch (clip.version) { - case 1: - DataOutputStream dos = new DataOutputStream(s.getOutputStream()); - dos.writeUTF("Protocol:CLI-connect"); - // we aren't checking greeting from the server here because I'm too lazy. It gets ignored by Channel constructor. - break; - case 2: - DataInputStream dis = new DataInputStream(s.getInputStream()); - dos = new DataOutputStream(s.getOutputStream()); - dos.writeUTF("Protocol:CLI2-connect"); - String greeting = dis.readUTF(); - if (!greeting.equals("Welcome")) - throw new IOException("Handshaking failed: "+greeting); - try { - byte[] secret = c.diffieHellman(false).generateSecret(); - SecretKey sessionKey = new SecretKeySpec(Connection.fold(secret,128/8),"AES"); - c = c.encryptConnection(sessionKey,"AES/CFB8/NoPadding"); - - // validate the instance identity, so that we can be sure that we are talking to the same server - // and there's no one in the middle. - byte[] signature = c.readByteArray(); - - if (clip.identity!=null) { - Signature verifier = Signature.getInstance("SHA1withRSA"); - verifier.initVerify(clip.getIdentity()); - verifier.update(secret); - if (!verifier.verify(signature)) - throw new IOException("Server identity signature validation failed."); - } - - } catch (GeneralSecurityException e) { - throw (IOException)new IOException("Failed to negotiate transport security").initCause(e); - } - } - - return new Channel("CLI connection to "+jenkins, pool, - new BufferedInputStream(c.in), new BufferedOutputStream(c.out)); - } - - /** - * If the server advertises CLI endpoint, returns its location. - * @deprecated Specific to {@link Mode#REMOTING}. - */ - @Deprecated - protected CliPort getCliTcpPort(URL url) throws IOException { - if (url.getHost()==null || url.getHost().length()==0) { - throw new IOException("Invalid URL: "+url); - } - URLConnection head = url.openConnection(); - try { - head.connect(); - } catch (IOException e) { - throw (IOException)new IOException("Failed to connect to "+url).initCause(e); - } - - String h = head.getHeaderField("X-Jenkins-CLI-Host"); - if (h==null) h = head.getURL().getHost(); - String p1 = head.getHeaderField("X-Jenkins-CLI-Port"); - if (p1==null) p1 = head.getHeaderField("X-Hudson-CLI-Port"); // backward compatibility - String p2 = head.getHeaderField("X-Jenkins-CLI2-Port"); - - String identity = head.getHeaderField("X-Instance-Identity"); - - flushURLConnection(head); - if (p1==null && p2==null) { - verifyJenkinsConnection(head); - - throw new IOException("No X-Jenkins-CLI2-Port among " + head.getHeaderFields().keySet()); - } - - if (p2!=null) return new CliPort(new InetSocketAddress(h,Integer.parseInt(p2)),identity,2); - else return new CliPort(new InetSocketAddress(h,Integer.parseInt(p1)),identity,1); - } + private CLI() {} /** * Make sure the connection is open against Jenkins server. @@ -337,91 +78,6 @@ public class CLI implements AutoCloseable { } } - /** - * Flush the supplied {@link URLConnection} input and close the - * connection nicely. - * @param conn the connection to flush/close - */ - private void flushURLConnection(URLConnection conn) { - byte[] buf = new byte[1024]; - try { - InputStream is = conn.getInputStream(); - while (is.read(buf) >= 0) { - // Ignore - } - is.close(); - } catch (IOException e) { - try { - InputStream es = ((HttpURLConnection)conn).getErrorStream(); - if (es!=null) { - while (es.read(buf) >= 0) { - // Ignore - } - es.close(); - } - } catch (IOException ex) { - // Ignore - } - } - } - - /** - * Shuts down the channel and closes the underlying connection. - */ - public void close() throws IOException, InterruptedException { - channel.close(); - channel.join(); - if(ownsPool) - pool.shutdown(); - for (Closeable c : closables) - c.close(); - } - - public int execute(List args, InputStream stdin, OutputStream stdout, OutputStream stderr) { - return entryPoint.main(args, Locale.getDefault(), - new RemoteInputStream(stdin), - new RemoteOutputStream(stdout), - new RemoteOutputStream(stderr)); - } - - public int execute(List args) { - return execute(args, System.in, System.out, System.err); - } - - public int execute(String... args) { - return execute(Arrays.asList(args)); - } - - /** - * Returns true if the named command exists. - */ - public boolean hasCommand(String name) { - return entryPoint.hasCommand(name); - } - - /** - * Accesses the underlying communication channel. - * @since 1.419 - */ - public Channel getChannel() { - return channel; - } - - /** - * Attempts to lift the security restriction on the underlying channel. - * This requires the administer privilege on the server. - * - * @throws SecurityException - * If we fail to upgrade the connection. - */ - public void upgrade() { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (execute(Arrays.asList("groovy", "="), - new ByteArrayInputStream("hudson.remoting.Channel.current().setRestricted(false)".getBytes()), - out,out)!=0) - throw new SecurityException(out.toString()); // failed to upgrade - } - public static void main(final String[] _args) throws Exception { try { System.exit(_main(_args)); @@ -435,12 +91,10 @@ public class CLI implements AutoCloseable { } } - private enum Mode {HTTP, SSH, REMOTING} + private enum Mode {HTTP, SSH} public static int _main(String[] _args) throws Exception { List args = Arrays.asList(_args); PrivateKeyProvider provider = new PrivateKeyProvider(); - boolean sshAuthRequestedExplicitly = false; - String httpProxy=null; String url = System.getenv("JENKINS_URL"); @@ -484,13 +138,8 @@ public class CLI implements AutoCloseable { continue; } if (head.equals("-remoting")) { - if (mode != null) { - printUsage("-remoting clashes with previously defined mode " + mode); - return -1; - } - mode = Mode.REMOTING; - args = args.subList(1, args.size()); - continue; + printUsage("-remoting mode is no longer supported"); + return -1; } if(head.equals("-s") && args.size()>=2) { url = args.get(1); @@ -526,7 +175,6 @@ public class CLI implements AutoCloseable { provider.readFrom(f); args = args.subList(2,args.size()); - sshAuthRequestedExplicitly = true; continue; } if (head.equals("-strictHostKey")) { @@ -544,11 +192,6 @@ public class CLI implements AutoCloseable { args = args.subList(2, args.size()); continue; } - if(head.equals("-p") && args.size()>=2) { - httpProxy = args.get(1); - args = args.subList(2,args.size()); - continue; - } if (head.equals("-logger") && args.size() >= 2) { Level level = parse(args.get(1)); for (Handler h : Logger.getLogger("").getHandlers()) { @@ -616,7 +259,7 @@ public class CLI implements AutoCloseable { LOGGER.warning("Warning: -user ignored unless using -ssh"); } - CLIConnectionFactory factory = new CLIConnectionFactory().url(url).httpsProxyTunnel(httpProxy); + CLIConnectionFactory factory = new CLIConnectionFactory(); String userInfo = new URL(url).getUserInfo(); if (userInfo != null) { factory = factory.basicAuth(userInfo); @@ -628,39 +271,7 @@ public class CLI implements AutoCloseable { return plainHttpConnection(url, args, factory); } - CLI cli = factory.connect(); - try { - if (provider.hasKeys()) { - try { - // TODO: server verification - cli.authenticate(provider.getKeys()); - } catch (IllegalStateException e) { - if (sshAuthRequestedExplicitly) { - LOGGER.warning("The server doesn't support public key authentication"); - return -1; - } - } catch (UnsupportedOperationException e) { - if (sshAuthRequestedExplicitly) { - LOGGER.warning("The server doesn't support public key authentication"); - return -1; - } - } catch (GeneralSecurityException e) { - if (sshAuthRequestedExplicitly) { - LOGGER.log(WARNING, null, e); - return -1; - } - LOGGER.warning("Failed to authenticate with your SSH keys. Proceeding as anonymous"); - LOGGER.log(FINE, null, e); - } - } - - // execute the command - // Arrays.asList is not serializable --- see 6835580 - args = new ArrayList(args); - return cli.execute(args, System.in, System.out, System.err); - } finally { - cli.close(); - } + throw new AssertionError(); } private static int plainHttpConnection(String url, List args, CLIConnectionFactory factory) throws IOException, InterruptedException { @@ -784,47 +395,6 @@ public class CLI implements AutoCloseable { return loadKey(pemString, null); } - /** - * Authenticate ourselves against the server. - * - * @return - * identity of the server represented as a public key. - * @deprecated Specific to {@link Mode#REMOTING}. - */ - @Deprecated - public PublicKey authenticate(Iterable privateKeys) throws IOException, GeneralSecurityException { - Pipe c2s = Pipe.createLocalToRemote(); - Pipe s2c = Pipe.createRemoteToLocal(); - entryPoint.authenticate("ssh",c2s, s2c); - Connection c = new Connection(s2c.getIn(), c2s.getOut()); - - try { - byte[] sharedSecret = c.diffieHellman(false).generateSecret(); - PublicKey serverIdentity = c.verifyIdentity(sharedSecret); - - // try all the public keys - for (KeyPair key : privateKeys) { - c.proveIdentity(sharedSecret,key); - if (c.readBoolean()) - return serverIdentity; // succeeded - } - if (privateKeys.iterator().hasNext()) - throw new GeneralSecurityException("Authentication failed. No private key accepted."); - else - throw new GeneralSecurityException("No private key is available for use in authentication"); - } finally { - c.close(); - } - } - - /** - * @deprecated Specific to {@link Mode#REMOTING}. - */ - @Deprecated - public PublicKey authenticate(KeyPair key) throws IOException, GeneralSecurityException { - return authenticate(Collections.singleton(key)); - } - /** For access from {@code HelpCommand}. */ static String usage() { return Messages.CLI_Usage(); diff --git a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java index 894e4c0fdac60ff446e3a67ecf68e44192d93f97..288696b0da0828be52b2bd7c8cafd62e90719914 100644 --- a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java +++ b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java @@ -2,10 +2,6 @@ package hudson.cli; import org.apache.commons.codec.binary.Base64; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.concurrent.ExecutorService; /** * Fluent-API to instantiate {@link CLI}. @@ -13,41 +9,8 @@ import java.util.concurrent.ExecutorService; * @author Kohsuke Kawaguchi */ public class CLIConnectionFactory { - URL jenkins; - ExecutorService exec; - String httpsProxyTunnel; String authorization; - /** - * Top URL of the Jenkins to connect to. - */ - public CLIConnectionFactory url(URL jenkins) { - this.jenkins = jenkins; - return this; - } - - public CLIConnectionFactory url(String jenkins) throws MalformedURLException { - return url(new URL(jenkins)); - } - - /** - * This {@link ExecutorService} is used to execute closures received from the server. - * Used only in Remoting mode. - */ - public CLIConnectionFactory executorService(ExecutorService es) { - this.exec = es; - return this; - } - - /** - * Configures the HTTP proxy that we use for making a plain TCP/IP connection. - * "host:port" that points to an HTTP proxy or null. - */ - public CLIConnectionFactory httpsProxyTunnel(String value) { - this.httpsProxyTunnel = value; - return this; - } - /** * For CLI connection that goes through HTTP, sometimes you need * to pass in the custom authentication header (before Jenkins even get to authenticate @@ -74,11 +37,4 @@ public class CLIConnectionFactory { return authorization("Basic " + new String(Base64.encodeBase64((userInfo).getBytes()))); } - /** - * @deprecated Specific to Remoting-based protocol. - */ - @Deprecated - public CLI connect() throws IOException, InterruptedException { - return new CLI(this); - } } diff --git a/cli/src/main/java/hudson/cli/CliEntryPoint.java b/cli/src/main/java/hudson/cli/CliEntryPoint.java deleted file mode 100644 index fe1f6c835097888be71632babbe31f1b6530fab5..0000000000000000000000000000000000000000 --- a/cli/src/main/java/hudson/cli/CliEntryPoint.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, 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. - */ -package hudson.cli; - -import hudson.remoting.Pipe; - -import java.io.OutputStream; -import java.io.InputStream; -import java.util.List; -import java.util.Locale; - -/** - * Remotable interface for CLI entry point on the server side. - * - * @author Kohsuke Kawaguchi - * @deprecated Specific to Remoting-based protocol. - */ -@Deprecated -public interface CliEntryPoint { - /** - * Just like the static main method. - * - * @param locale - * Locale of this client. - */ - int main(List args, Locale locale, InputStream stdin, OutputStream stdout, OutputStream stderr); - - /** - * Does the named command exist? - */ - boolean hasCommand(String name); - - /** - * Returns {@link #VERSION}, so that the client and the server can detect version incompatibility - * gracefully. - */ - int protocolVersion(); - - /** - * Initiates authentication out of band. - *

- * This method starts two-way byte channel that allows the client and the server to perform authentication. - * The current supported implementation is based on SSH public key authentication that mutually authenticates - * clients and servers. - * - * @param protocol - * Currently only "ssh" is supported. - * @throws UnsupportedOperationException - * If the specified protocol is not supported by the server. - */ - void authenticate(String protocol, Pipe c2s, Pipe s2c); - - int VERSION = 1; -} diff --git a/cli/src/main/java/hudson/cli/CliPort.java b/cli/src/main/java/hudson/cli/CliPort.java deleted file mode 100644 index 56165e766ba6186489d4e3017b0a7a35510358ab..0000000000000000000000000000000000000000 --- a/cli/src/main/java/hudson/cli/CliPort.java +++ /dev/null @@ -1,44 +0,0 @@ -package hudson.cli; - -import org.apache.commons.codec.binary.Base64; - -import java.net.InetSocketAddress; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.spec.X509EncodedKeySpec; - - /** - * @deprecated Specific to Remoting mode. - */ -public final class CliPort { - /** - * The TCP endpoint to talk to. - */ - final InetSocketAddress endpoint; - - /** - * CLI protocol version. 1 and 2 are currently defined. - */ - final int version; - - /** - * Server instance identity. Can be null. - */ - final String identity; - - public CliPort(InetSocketAddress endpoint, String identity, int version) { - this.endpoint = endpoint; - this.identity = identity; - this.version = version; - } - - /** - * Gets the public part of the RSA key that represents the server identity. - */ - public PublicKey getIdentity() throws GeneralSecurityException { - if (identity==null) return null; - byte[] image = Base64.decodeBase64(identity); - return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(image)); - } -} diff --git a/cli/src/main/java/hudson/cli/Connection.java b/cli/src/main/java/hudson/cli/Connection.java index eacc567a7e5d822afc9e4d1b20afc5473cbb0b3b..1961447778d04ab829c9b550305ac016309bcd35 100644 --- a/cli/src/main/java/hudson/cli/Connection.java +++ b/cli/src/main/java/hudson/cli/Connection.java @@ -58,8 +58,9 @@ import java.security.spec.X509EncodedKeySpec; import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** - * Used by Jenkins core only in deprecated Remoting-based CLI. + * @deprecated No longer used. */ +@Deprecated public class Connection { public final InputStream in; public final OutputStream out; diff --git a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java index 9b18142b7824257af32e510e4eb99d5cc677cf69..723e97f16c26805a3550a0d9b8fcb17476396fee 100644 --- a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java +++ b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java @@ -9,8 +9,6 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.commons.codec.binary.Base64; - /** * Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over * HTTP, which is a request/response protocol. @@ -19,10 +17,6 @@ import org.apache.commons.codec.binary.Base64; */ public class FullDuplexHttpStream { private final URL base; - /** - * Authorization header value needed to get through the HTTP layer. - */ - private final String authorization; private final OutputStream output; private final InputStream input; @@ -43,28 +37,6 @@ public class FullDuplexHttpStream { return output; } - @Deprecated - public FullDuplexHttpStream(URL target) throws IOException { - this(target,basicAuth(target.getUserInfo())); - } - - private static String basicAuth(String userInfo) { - if (userInfo != null) - return "Basic "+new String(Base64.encodeBase64(userInfo.getBytes())); - return null; - } - - /** - * @param target something like {@code http://jenkins/cli?remoting=true} - * which we then need to split into {@code http://jenkins/} + {@code cli?remoting=true} - * in order to construct a crumb issuer request - * @deprecated use {@link #FullDuplexHttpStream(URL, String, String)} instead - */ - @Deprecated - public FullDuplexHttpStream(URL target, String authorization) throws IOException { - this(new URL(target.toString().replaceFirst("/cli.*$", "/")), target.toString().replaceFirst("^.+/(cli.*)$", "$1"), authorization); - } - /** * @param base the base URL of Jenkins * @param relativeTarget @@ -81,7 +53,6 @@ public class FullDuplexHttpStream { } this.base = tryToResolveRedirects(base, authorization); - this.authorization = authorization; URL target = new URL(this.base, relativeTarget); diff --git a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java index 9f87dd4a7b38a174229ccfc02b04779b51002951..bbf7873e2917f1b4edd456c8d53a23d48bf74459 100644 --- a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java +++ b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java @@ -24,26 +24,25 @@ package hudson.cli; import static java.util.logging.Level.FINE; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.Console; import java.io.DataInputStream; +import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.security.GeneralSecurityException; -import java.security.KeyFactory; import java.security.KeyPair; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.DSAPublicKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; -import com.trilead.ssh2.crypto.PEMDecoder; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.util.security.SecurityUtils; /** * Read DSA or RSA key from file(s) asking for password interactively. @@ -141,22 +140,9 @@ public class PrivateKeyProvider { } public static KeyPair loadKey(String pemString, String passwd) throws IOException, GeneralSecurityException { - Object key = PEMDecoder.decode(pemString.toCharArray(), passwd); - if (key instanceof com.trilead.ssh2.signature.RSAPrivateKey) { - com.trilead.ssh2.signature.RSAPrivateKey x = (com.trilead.ssh2.signature.RSAPrivateKey)key; - - return x.toJCEKeyPair(); - } - if (key instanceof com.trilead.ssh2.signature.DSAPrivateKey) { - com.trilead.ssh2.signature.DSAPrivateKey x = (com.trilead.ssh2.signature.DSAPrivateKey)key; - KeyFactory kf = KeyFactory.getInstance("DSA"); - - return new KeyPair( - kf.generatePublic(new DSAPublicKeySpec(x.getY(), x.getP(), x.getQ(), x.getG())), - kf.generatePrivate(new DSAPrivateKeySpec(x.getX(), x.getP(), x.getQ(), x.getG()))); - } - - throw new UnsupportedOperationException("Unrecognizable key format: " + key); + return SecurityUtils.loadKeyPairIdentity("key", + new ByteArrayInputStream(pemString.getBytes(UTF_8)), + FilePasswordProvider.of(passwd)); } private static final Logger LOGGER = Logger.getLogger(PrivateKeyProvider.class.getName()); diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties index 2e7fde44cb075a87813b4cced7eb48f2952c85cd..c789036505014e64496809f9f56405146ea84be7 100644 --- a/cli/src/main/resources/hudson/cli/client/Messages.properties +++ b/cli/src/main/resources/hudson/cli/client/Messages.properties @@ -2,24 +2,21 @@ CLI.Usage=Jenkins CLI\n\ Usage: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\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 and -remoting)\n\ + \ -http : use a plain CLI protocol over HTTP(S) (the default; mutually exclusive with -ssh)\n\ \ -ssh : use SSH protocol (requires -user; SSH port must be open on server, and user must have registered a public key)\n\ - \ -remoting : use deprecated Remoting channel protocol (if enabled on server; for compatibility with legacy commands or command modes only)\n\ - \ -i KEY : SSH private key file used for authentication (for use with -ssh or -remoting)\n\ - \ -p HOST:PORT : HTTP proxy host and port for HTTPS proxy tunneling. See https://jenkins.io/redirect/cli-https-proxy-tunnel\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\ - \ -noKeyAuth : don't try to load the SSH authentication private key. Conflicts with -i\n\ + \ -noKeyAuth : don''t try to load the SSH authentication private key. Conflicts with -i\n\ \ -user : specify user (for use with -ssh)\n\ \ -strictHostKey : request strict host key checking (for use with -ssh)\n\ \ -logger FINE : enable detailed logging from the client\n\ \ -auth [ USER:SECRET | @FILE ] : specify username and either password or API token (or load from them both from a file);\n\ - \ for use with -http, or -remoting but only when the JNLP agent port is disabled.\n\ - \ Passing crendentials by a file is recommended.\n\ + \ for use with -http.\n\ + \ Passing credentials by file is recommended.\n\ \ See https://jenkins.io/redirect/cli-http-connection-mode for more info and options.\n\ \n\ - The available commands depend on the server. Run the 'help' command to \ + The available commands depend on the server. Run the ''help'' command to \ see the list. CLI.NoURL=Neither -s nor the JENKINS_URL env var is specified. -CLI.VersionMismatch=Version mismatch. This CLI cannot work with this Jenkins server. CLI.NoSuchFileExists=No such file exists: {0} CLI.BadAuth=The JENKINS_USER_ID and JENKINS_API_TOKEN env vars should be both set or left empty. diff --git a/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties deleted file mode 100644 index 16fa41c48c9a4aad3d63e9f40e2d017b3bd9fb77..0000000000000000000000000000000000000000 --- a/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties +++ /dev/null @@ -1,44 +0,0 @@ -# The MIT License -# -# Copyright (c) 2013, Sun Microsystems, Inc., Kohsuke Kawaguchi, Pei-Tang Huang, -# Chunghwa Telecom Co., Ltd., and a number of other of contributers -# -# 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. - -CLI.Usage=Jenkins CLI\n\ - \u7528\u6cd5: java -jar jenkins-cli.jar [-s url] \u547d\u4ee4 [\u9009\u9879...] \u53c2\u6570...\n\ - \u9009\u9879:\n\ - -s URL : \u670d\u52a1\u5668url \uff08\u9ed8\u8ba4\u662fjenkins_url\u73af\u5883\u53d8\u91cf\uff09\n\ - -http : \u5728http%28s%29\u4e0a\u4f7f\u7528\u539f\u59cb\u7684cli\u534f\u8bae\uff08\u9ed8\u8ba4\u503c%3b \u4e0e -ssh \u548c -remoting \u4e92\u65a5\uff09\n\ - -ssh : \u4f7f\u7528ssh\u534f\u8bae\uff08\u9700\u8981 -user%3b \u670d\u52a1\u5668\u7684ssh\u7aef\u53e3\u5fc5\u987b\u6253\u5f00\uff0c\u4e14\u7528\u6237\u5fc5\u987b\u5df2\u6ce8\u518c\u516c\u94a5\u3002\uff09\n\ - -remoting : \u4f7f\u7528\u4e0d\u63a8\u8350\u7684\u8fdc\u7a0b\u4fe1\u9053\u534f\u8bae \uff08\u5982\u679c\u670d\u52a1\u5668\u4e0a\u662f\u6253\u5f00\u7684\uff1b\u4ec5\u7528\u4e8e\u517c\u5bb9\u9057\u7559\u547d\u4ee4\u6216\u547d\u4ee4\u6a21\u5f0f\uff09\n\ - -i KEY : \u7528\u4e8e\u8ba4\u8bc1\u7684ssh\u79c1\u94a5\u6587\u4ef6\uff08\u4e0e -ssh \u6216 -remoting \u4e00\u8d77\u4f7f\u7528\uff09\n\ - -p HOST:PORT : \u7528\u6237https\u4ee3\u7406\u96a7\u9053\u7684http\u4ee3\u7406\u4e3b\u673a\u548c\u7aef\u53e3\u3002\u53c2\u89c1 https://jenkins.io/redirect/cli-https-proxy-tunnel\n\ - -noCertificateCheck : \u5b8c\u5168\u5ffd\u7565https\u8bc1\u4e66\u8ba4\u8bc1\u3002\u8c28\u614e\u4f7f\u7528\n\ - -noKeyAuth : \u65e0\u9700\u5c1d\u8bd5\u52a0\u8f7dssh\u8ba4\u8bc1\u79c1\u94a5\u3002\u4e0e -i \u76f8\u53cd\n\ - -user : \u6307\u5b9a\u7528\u6237\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\ - -strictHostKey : \u8981\u6c42\u9a8c\u8bc1\u4e3b\u673akey\u68c0\u67e5\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\ - -logger FINE : \u5141\u8bb8\u5ba2\u6237\u7aef\u8be6\u7ec6\u65e5\u5fd7\u8bb0\u5f55\n\ - -auth [ USER:SECRET | @FILE ] : \u6307\u5b9a\u7528\u6237\u540d\u4e0e\u5bc6\u7801\u6216\u7528\u6237\u540d\u4e0eapi token\uff08\u6216\u8005\u4ece\u6587\u4ef6\u52a0\u8f7d\uff09\uff1b\n\ - \u4e0e -http \u4e00\u8d77\u4f7f\u7528\uff0c\u6216\u8005\u53ea\u5728jnlp\u4ee3\u7406\u7aef\u53e3\u7981\u7528\u65f6\u4e0e -remoting \u4e00\u8d77\u4f7f\u7528\n\ - \n\ - \u53ef\u7528\u7684\u547d\u4ee4\u53d6\u51b3\u4e8e\u670d\u52a1\u5668\u3002\u6267\u884c 'help' \u547d\u4ee4\u53ef\u4ee5\u67e5\u770b\u5b8c\u6574\u6e05\u5355\u3002 -CLI.NoURL=\u6c92\u6709\u6307\u5b9a -s \u53c2\u6570\u6216\u8005 jenkins_url \u73af\u5883\u53d8\u91cf\u3002 -CLI.VersionMismatch=\u7248\u672c\u4e0d\u5339\u914d\u3002cli \u65e0\u6cd5\u5728\u6b64 jenkins \u670d\u52a1\u5668\u4e0a\u8fd0\u884c\u3002 -CLI.NoSuchFileExists=\u6587\u4ef6\u4e0d\u5b58\u5728: {0} diff --git a/cli/src/test/java/hudson/cli/ConnectionTest.java b/cli/src/test/java/hudson/cli/ConnectionTest.java deleted file mode 100644 index f4cb0e8b1d595516130553bbe6568f8255324327..0000000000000000000000000000000000000000 --- a/cli/src/test/java/hudson/cli/ConnectionTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package hudson.cli; - -import static org.junit.Assert.*; - -import hudson.remoting.FastPipedInputStream; -import hudson.remoting.FastPipedOutputStream; -import org.codehaus.groovy.runtime.Security218; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; - -/** - * @author Kohsuke Kawaguchi - */ -public class ConnectionTest { - - Throwable e; - private Connection c1; - private Connection c2; - - @Before - public void setUp() throws IOException { - FastPipedInputStream i = new FastPipedInputStream(); - FastPipedInputStream j = new FastPipedInputStream(); - - c1 = new Connection(i,new FastPipedOutputStream(j)); - c2 = new Connection(j,new FastPipedOutputStream(i)); - } - - @Test - public void testEncrypt() throws Throwable { - final SecretKey sessionKey = new SecretKeySpec(new byte[16],"AES"); - - Thread t1 = new Thread() { - @Override - public void run() { - try { - c1.encryptConnection(sessionKey,"AES/CFB8/NoPadding").writeUTF("Hello"); - } catch (Throwable x) { - e = x; - } - } - }; - t1.start(); - - Thread t2 = new Thread() { - @Override - public void run() { - try { - String data = c2.encryptConnection(sessionKey,"AES/CFB8/NoPadding").readUTF(); - assertEquals("Hello", data); - } catch (Throwable x) { - e = x; - } - } - }; - t2.start(); - - t1.join(9999); - t2.join(9999); - - if (e != null) { - throw e; - } - - if (t1.isAlive() || t2.isAlive()) { - t1.interrupt(); - t2.interrupt(); - throw new Error("thread is still alive"); - } - } - - @Test - public void testSecurity218() throws Exception { - c1.writeObject(new Security218()); - try { - c2.readObject(); - fail(); - } catch (SecurityException e) { - assertTrue(e.getMessage().contains(Security218.class.getName())); - } - } -} diff --git a/cli/src/test/java/hudson/cli/HexDumpTest.java b/cli/src/test/java/hudson/cli/HexDumpTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9cd1c78995f5bc5405046faedb8a844e6870190c --- /dev/null +++ b/cli/src/test/java/hudson/cli/HexDumpTest.java @@ -0,0 +1,37 @@ +package hudson.cli; + +import org.junit.Assert; +import org.junit.Test; + +public class HexDumpTest { + + @Test + public void testToHex1() { + Assert.assertEquals("'fooBar'", + HexDump.toHex(new byte[] {'f', 'o', 'o', 'B', 'a', 'r'})); + Assert.assertEquals("0xc3", + HexDump.toHex(new byte[] {(byte)'Ã'})); + Assert.assertEquals("0xac '100'", + HexDump.toHex(new byte[] {(byte)'€', '1', '0', '0'})); + Assert.assertEquals("'1' 0xf7 '2'", + HexDump.toHex(new byte[] {'1', (byte)'÷', '2'})); + Assert.assertEquals("'foo' 0x0a\n'Bar'", + HexDump.toHex(new byte[] {'f', 'o', 'o', '\n', 'B', 'a', 'r'})); + } + + @Test + public void testToHex2() { + Assert.assertEquals("'ooBa'", + HexDump.toHex(new byte[] {'f', 'o', 'o', 'B', 'a', 'r'}, 1, 4)); + Assert.assertEquals("0xc3", + HexDump.toHex(new byte[] {(byte)'Ã'}, 0, 1)); + Assert.assertEquals("0xac '10'", + HexDump.toHex(new byte[] {(byte)'€', '1', '0', '0'}, 0, 3)); + Assert.assertEquals("0xf7 '2'", + HexDump.toHex(new byte[] {'1', (byte)'÷', '2'}, 1, 2)); + Assert.assertEquals("'Bar'", + HexDump.toHex(new byte[] {'f', 'o', 'o', '\n', 'B', 'a', 'r'}, 4, 3)); + Assert.assertEquals("", + HexDump.toHex(new byte[] {'f', 'o', 'o', 'B', 'a', 'r'}, 0, 0)); + } +} diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java index a29af91e6eb818245140e987dda1c323786a3fd8..1bb49866ba6140ff1f8e27e905dfe4cd6e972862 100644 --- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java +++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java @@ -1,134 +1,104 @@ -/* - * The MIT License - * - * Copyright (c) 2014 Red Hat, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package hudson.cli; -import static org.mockito.Mockito.verify; -import static org.powermock.api.mockito.PowerMockito.doReturn; -import static org.powermock.api.mockito.PowerMockito.mock; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.whenNew; +import org.junit.Test; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; +import java.lang.IllegalArgumentException; import java.security.GeneralSecurityException; -import java.security.Key; import java.security.KeyPair; -import java.util.Arrays; +import java.security.NoSuchAlgorithmException; -import org.hamcrest.Description; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; -import org.mockito.Mockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; +import static org.junit.Assert.assertNotNull; -@RunWith(PowerMockRunner.class) -@PrepareForTest({CLI.class, CLIConnectionFactory.class}) // When mocking new operator caller has to be @PreparedForTest, not class itself +/** +keys were generated with ssh-keygen from OpenSSH_7.9p1, LibreSSL 2.7.3 +*/ public class PrivateKeyProviderTest { + /** + key command: ssh-keygen -f dsa -t dsa -b 1024 -m PEM + */ @Test - public void specifyKeysExplicitly() throws Exception { - final CLI cli = fakeCLI(); - - final File dsaKey = keyFile(".ssh/id_dsa"); - final File rsaKey = keyFile(".ssh/id_rsa"); - - run("-remoting", "-i", dsaKey.getAbsolutePath(), "-i", rsaKey.getAbsolutePath(), "-s", "http://example.com"); - - verify(cli).authenticate(withKeyPairs( - keyPair(dsaKey), - keyPair(rsaKey) - )); + public void loadKeyDSA() throws IOException, GeneralSecurityException { + File file = new File(this.getClass().getResource("dsa").getFile()); + String password = null; + KeyPair keyPair = PrivateKeyProvider.loadKey(file, password); + assertNotNull(keyPair); + assertNotNull(keyPair.getPrivate()); + assertNotNull(keyPair.getPublic()); } + /** + key command: ssh-keygen -f dsa-password -t dsa -b 1024 -m PEM -p password + */ @Test - public void useDefaultKeyLocations() throws Exception { - final CLI cli = fakeCLI(); - - final File rsaKey = keyFile(".ssh/id_rsa"); - final File dsaKey = keyFile(".ssh/id_dsa"); - - fakeHome(); - run("-remoting", "-s", "http://example.com"); - - verify(cli).authenticate(withKeyPairs( - keyPair(rsaKey), - keyPair(dsaKey) - )); - } - - private CLI fakeCLI() throws Exception { - final CLI cli = mock(CLI.class); - - final CLIConnectionFactory factory = mock(CLIConnectionFactory.class, Mockito.CALLS_REAL_METHODS); - factory.jenkins = new URL("http://example.com"); - doReturn(cli).when(factory).connect(); - - mockStatic(CLIConnectionFactory.class); - whenNew(CLIConnectionFactory.class).withNoArguments().thenReturn(factory); - - return cli; + public void loadKeyDSAPassword() throws IOException, GeneralSecurityException { + File file = new File(this.getClass().getResource("dsa-password").getFile()); + String password = "password"; + KeyPair keyPair = PrivateKeyProvider.loadKey(file, password); + assertNotNull(keyPair); + assertNotNull(keyPair.getPrivate()); + assertNotNull(keyPair.getPublic()); } - - private void fakeHome() throws URISyntaxException { - final File home = new File(this.getClass().getResource(".ssh").toURI()).getParentFile(); - System.setProperty("user.home", home.getAbsolutePath()); + + /** + key command: ssh-keygen -f rsa -t rsa -b 1024 -m PEM + */ + @Test + public void loadKeyRSA() throws IOException, GeneralSecurityException { + File file = new File(this.getClass().getResource("rsa").getFile()); + String password = null; + KeyPair keyPair = PrivateKeyProvider.loadKey(file, password); + assertNotNull(keyPair); + assertNotNull(keyPair.getPrivate()); + assertNotNull(keyPair.getPublic()); } - private int run(String... args) throws Exception { - return CLI._main(args); + /** + key command: ssh-keygen -f rsa-password -t rsa -b 1024 -m PEM -p password + */ + @Test + public void loadKeyRSAPassword() throws IOException, GeneralSecurityException { + File file = new File(this.getClass().getResource("rsa-password").getFile()); + String password = "password"; + KeyPair keyPair = PrivateKeyProvider.loadKey(file, password); + assertNotNull(keyPair); + assertNotNull(keyPair.getPrivate()); + assertNotNull(keyPair.getPublic()); } - - private File keyFile(String name) throws URISyntaxException { - return new File(this.getClass().getResource(name).toURI()); + + /** + key command: ssh-keygen -f openssh -t rsa -b 1024 + */ + @Test + public void loadKeyOpenSSH() throws IOException, GeneralSecurityException { + File file = new File(this.getClass().getResource("openssh").getFile()); + String password = null; + KeyPair keyPair = PrivateKeyProvider.loadKey(file, password); + assertNotNull(keyPair); + assertNotNull(keyPair.getPrivate()); + assertNotNull(keyPair.getPublic()); } - - private KeyPair keyPair(File file) throws IOException, GeneralSecurityException { - return PrivateKeyProvider.loadKey(file, null); + + /** + key command: ssh-keygen -f openssh-unsupported -t rsa -b 1024 -p password + */ + @Test(expected = NoSuchAlgorithmException.class) + public void loadKeyUnsupportedCipher() throws IOException, GeneralSecurityException { + File file = new File(this.getClass().getResource("openssh-unsuported").getFile()); + String password = "password"; + PrivateKeyProvider.loadKey(file, password); } - private Iterable withKeyPairs(final KeyPair... expected) { - return Mockito.argThat(new ArgumentMatcher>() { - - @Override - public boolean matches(Iterable actual) { - int i = 0; - for (KeyPair akp: actual) { - if (!eq(expected[i].getPublic(), akp.getPublic())) return false; - if (!eq(expected[i].getPrivate(), akp.getPrivate())) return false; - i++; - } - - return i == expected.length; - } - - private boolean eq(final Key expected, final Key actual) { - return Arrays.equals(expected.getEncoded(), actual.getEncoded()); - } - }); + /** + 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) + public void loadKeyBroken() throws IOException, GeneralSecurityException { + File file = new File(this.getClass().getResource("openssh-broken").getFile()); + String password = "password"; + PrivateKeyProvider.loadKey(file, password); } } diff --git a/cli/src/test/java/org/codehaus/groovy/runtime/Security218.java b/cli/src/test/java/org/codehaus/groovy/runtime/Security218.java deleted file mode 100644 index cc3dfeef041c6ac07b91f80d3dc98a5f1530e4c7..0000000000000000000000000000000000000000 --- a/cli/src/test/java/org/codehaus/groovy/runtime/Security218.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.codehaus.groovy.runtime; - -import java.io.Serializable; - -/** - * Test payload in a prohibited package name. - * - * @author Kohsuke Kawaguchi - */ -public class Security218 implements Serializable { -} diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_dsa b/cli/src/test/resources/hudson/cli/.ssh/id_dsa deleted file mode 100644 index be556daa6d02984101686a02939b6777c780db86..0000000000000000000000000000000000000000 --- a/cli/src/test/resources/hudson/cli/.ssh/id_dsa +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBugIBAAKBgQCA9mMzB1O52hpObIyaJXgFJQUmc1HV0NEJXsFFGh8U2l0Tkgv4 -fp3MWadiAMmc5H1ot4KQLXl7SwU7dHCCFcGcfQiOjeD5rWeZuHoPAJSDMilcJGE3 -Xo2C+wlescTByEgRRA16vdSlNaDJXKVxq9Wr59G8P4JC6/5EvpeypgYdTQIVAMTf -aC0O2EGLnJrNBsUdc1s+iUp9AoGAZA7pZYPMJHJWTanJb2DlWHn/QM63jfh38N6W -ERzmQQks6QdS7UkFlg9cbVGUtn0Yz2SfX3VKiMXNMkAdGD8loBcJS5w6oMMU7rcj -lldRQ63+fMgdVZYMF5bchC6RhQeGZQ8Imf2iFF28SsE4bi+K12HYgIO5bFxPFUTH -WSWsMLcCgYBgHJ90ZLU400axB5P0qw/0s4arPD0g53Vzi/Y2h5TJr3KPF2sEIbAc -2gpFEzUNY0hvH6REKJ+VPPUvlH6ieaXomW8pSGjv4SdxZhJRrDe+Ac/xQse1QdYx -uWJzpVm3cIGfqLxmQnrklnutI/1F62VZQlq9vjiZL7ir/00vdUTYHwIUUkttGGgl -a0rWLzPTPF4X4lZfFhk= ------END DSA PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub b/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub deleted file mode 100644 index 4a42a845727297abcfa1c1204d0a270392c5a9a6..0000000000000000000000000000000000000000 --- a/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAID2YzMHU7naGk5sjJoleAUlBSZzUdXQ0QlewUUaHxTaXROSC/h+ncxZp2IAyZzkfWi3gpAteXtLBTt0cIIVwZx9CI6N4PmtZ5m4eg8AlIMyKVwkYTdejYL7CV6xxMHISBFEDXq91KU1oMlcpXGr1avn0bw/gkLr/kS+l7KmBh1NAAAAFQDE32gtDthBi5yazQbFHXNbPolKfQAAAIBkDullg8wkclZNqclvYOVYef9AzreN+Hfw3pYRHOZBCSzpB1LtSQWWD1xtUZS2fRjPZJ9fdUqIxc0yQB0YPyWgFwlLnDqgwxTutyOWV1FDrf58yB1VlgwXltyELpGFB4ZlDwiZ/aIUXbxKwThuL4rXYdiAg7lsXE8VRMdZJawwtwAAAIBgHJ90ZLU400axB5P0qw/0s4arPD0g53Vzi/Y2h5TJr3KPF2sEIbAc2gpFEzUNY0hvH6REKJ+VPPUvlH6ieaXomW8pSGjv4SdxZhJRrDe+Ac/xQse1QdYxuWJzpVm3cIGfqLxmQnrklnutI/1F62VZQlq9vjiZL7ir/00vdUTYHw== ogondza@localhost.localdomain diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_rsa b/cli/src/test/resources/hudson/cli/.ssh/id_rsa deleted file mode 100644 index ee2ad6b569a3b5ebbea0d5d1bfcf6ed2bd0dbe27..0000000000000000000000000000000000000000 --- a/cli/src/test/resources/hudson/cli/.ssh/id_rsa +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAyTqwFqp5Ww2Tr/52D7hhdOwgzYGBUqxrOFopa+kjNEL1Yqwb -+mApUWZ+D3zN9PurhUcVUfeYVXiYWFJ0kG72HIJawL/0BR5oYxRJfumK8Z/sAzAL -xdhc5O5twETrr9gU3cxtvF5oJNP0I9HickAOeC+ZNpiDIIblrhvxXl/QwqrR+/Gv -Nb8TApj+rxXEfNp+N69iGnnxzWn1FeKeOAWpwoBAxZNoqBQAFacF7xfQnoygyekC -xk+ts2O5Zzv8iJ10sVf+x2Q79rxAtsc0xOGhZbBAzbmFTz0PE4iWuo/Vo1c6mM7u -/dam+FxB2NqPNw7W+4eiCnEVkiQZlrxmuGvK7wIDAQABAoIBACml1+QZDFzoBnUa -eVzvkFwesvtVnmp5/QcAwinvarXaVedCL9g2JtcOG3EhJ49YtzsyZxs7329xMja1 -eiKalJ157UaPc/XLQVegT0XRGEzCCJrwSr979F39awGsQgt28XqmYN/nui5FH/Z5 -7iAvWc9OKqu+DQWiZc8PQXmC4zYmvhGQ8vKx44RSqlWCjd9IqBVhpE5gxpI/SmCx -umUNNtoH0hBWr+MsVHzr6UUrC3a99+7bB4We8XMXXFLzbTUSgiYFmK+NxPs/Fux/ -IAyXAMbDw2HeqZ7g4kTaf4cvmVOwhh4zlvB4p7j301LdO1jmvs9z0fn/QJcTpVM7 -ISMKwAECgYEA/uKVdmOKTk3dKzKRFXtWJjqypOXakoX+25lUcVv2PXYRr8Sln9jC -A13fbhvwq+FqbdnNlB23ag5niCVLfUpB1DYYP5jd4lU8D6HZQiHlmokB6nLT9NIW -iTcG88E58Bta/l1Ue5Yn+LqluBC4i289wFbH1kZyxQ565s5dJEv9uAECgYEAyhwF -ZOqTK2lZe5uuN4owVLQaYFj9fsdFHULzlK/UAtkG1gCJhjBmwSEpZFFMH6WgwHk5 -SHJEom0uB4qRv8gQcxl9OSiDsp56ymr0NBhlPVXWr6IzLotLy5XBC1muqvYYlj7E -kHgSet/h8RUM/FeEiwOFHDU2DkMb8Qx1hfMdAu8CgYBSEsYL9CuB4WK5WTQMlcV8 -0+PYY0dJbSpOrgXZ5sHYsp8pWQn3+cUnbl/WxdpujkxGCR9AdX0tAmxmE5RGSNX/ -rleKiv/PtKB9bCFYQS/83ecnBkioCcpF7tknPm4YmcZoJ8dfcE94sSlRpti11WEu -AQOiRNcKCwqaLZMib/HIAQKBgQCdiOffeERMYypfgcJzAiCX9WZV0SeOCS7jFwub -ys17hsSgS/zl/pYpVXrY+dFXHZfGTvcKdB7xaB6nvCfND9lajfSgd+bndEYLvwAo -Fxfajizv64LvdZ4XytuUyEuwcHBLtBMs9Jqa8iU/8AOWMXVbkdvQV92RkleWNPrp -9MyZOwKBgQD9x8MnX5LVBfQKuL9qX6l9Da06EyMkzfz3obKn9AAJ3Xj9+45TNPJu -HnZyvJWesl1vDjXQTm+PVkdyE0WQgoiVX+wxno0hsoly5Uqb5EYHtTUrZzRpkyLK -1VmtDxT5D8gorUgn6crzk4PKaxRkPfAimZdlkQm6iOtuR3kqn5BtIQ== ------END RSA PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub b/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub deleted file mode 100644 index 91f8ff7180e5f05f989b9aec2104d102372e88a4..0000000000000000000000000000000000000000 --- a/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJOrAWqnlbDZOv/nYPuGF07CDNgYFSrGs4Wilr6SM0QvVirBv6YClRZn4PfM30+6uFRxVR95hVeJhYUnSQbvYcglrAv/QFHmhjFEl+6Yrxn+wDMAvF2Fzk7m3AROuv2BTdzG28Xmgk0/Qj0eJyQA54L5k2mIMghuWuG/FeX9DCqtH78a81vxMCmP6vFcR82n43r2IaefHNafUV4p44BanCgEDFk2ioFAAVpwXvF9CejKDJ6QLGT62zY7lnO/yInXSxV/7HZDv2vEC2xzTE4aFlsEDNuYVPPQ8TiJa6j9WjVzqYzu791qb4XEHY2o83Dtb7h6IKcRWSJBmWvGa4a8rv your_email@example.com diff --git a/cli/src/test/resources/hudson/cli/dsa b/cli/src/test/resources/hudson/cli/dsa new file mode 100644 index 0000000000000000000000000000000000000000..4f3f64f95d0e0f1ce121fe33a5a00d25361991f5 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/dsa @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvAIBAAKBgQCUlgM7pckNS/AU9w80QDlw304vdyK6u6cWXW7F1PFYMhCrsC7C +ZcbSKo2mTPHk6P77z3zceSAHeYxkXx34N2HYWCth+79N3VIz/EZC/IaN0sea1teF +XYLvdnDWazDcRdq+3d4iLAL+46QX6tIaEmUh9SFd4kz7P0rwPHz7SP7+MwIVAPnJ +QmOwaMbyvb0Q0/OjtXF1orn5AoGBAIP6gOIhNErjgnyzHSfAaR/F2wNFW/TR5HFH +GEnsVuxTXE00mjqZJaDznviDoVtWF/HlKnR6OcnVr7fm61PXKogeLeJ/zgNPWYCe +7b45iO38Ztnmimmo9GbzTcDYOVDhNhgZEOUWes9Cdrku8yB3Ugf6nN6GY17LoQ9s +EDk1Fa8TAoGAD3cFbpbnnw0sHaKMRopCPJKgmCtvzJFgf+xGUZRgEPbeB+1UnG2Z +T8xPyPF4Zwyo0Mf/b0gxjynLpvzLrr3TMQjqjL4vGE1UIx638ff1Jw08tRRCfBIr +GpIA5AIzZhxRE6aZREN5wqvnWnlEN+s6mOhcQ8gtNOObHaKuGdU+jAwCFQCvo83Q +shI+la9gd69CWzJx5GJkJw== +-----END DSA PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/dsa-password b/cli/src/test/resources/hudson/cli/dsa-password new file mode 100644 index 0000000000000000000000000000000000000000..8a28798a8c0d1b8f37ee9d1b15d02c0c311efa44 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/dsa-password @@ -0,0 +1,15 @@ +-----BEGIN DSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,CC014AB7E7C376D246B02422C40ED26A + +VjIeNQw7EVgiMDJVglpma1CIvRlfzhBBR6r0i5QsVEPTNGLNNUvbqMxUATud8ql0 +Dc84nDj47qbp+jfsvtSfau32hONynT91mNZiCTPUMGFQqiBDMXUMYBv64NXNFi51 ++QpI+bW85KLJyRZSmxmBHH31s+6buWwzpYQ2ImwA/Zzkn/1+Evts+VYWEAwXZNBR +rlpouBiY7XIP3hifU6vBwkH1Dr5Qff0hq13b4T74X2cL2cmWwmPjWDm8nXgDW2Of +B+5+w2vCp/QDBUpc8LsxNqUK2/B7KA22wqpmQhD7dY4orzaHQWGKwojSd4LwfX6r +3NEeptKAAn4TusyREypO67g7xUDT0BfZmCNUg6J9dLPWGusx8IvpK/hABzo5Etn/ +CnBRMc1QaDUASHRgzCkadM1DpKmyVQFlMLXCRJaS+51CHGVVGFILcgPsCcrzFL0r +HOzDbBymhU9Mfw1AygdKNMyZ8NdDsdZ1H3fkkZu7sMgt3PRaYZG+wNImpkdnQDoN +V3VqTZ5orF4Lo4OqxOuEd7dakpdpyidRvQQc5GaOZuG+2X/NlKhzdGEjt0iubMnX +S94J+KPJUeVfPphtA2Lb8w== +-----END DSA PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/openssh b/cli/src/test/resources/hudson/cli/openssh new file mode 100644 index 0000000000000000000000000000000000000000..6d5ce34bdaa8dc9f3e87e499171327ce96938486 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/openssh @@ -0,0 +1,17 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bk +xjv/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnM +fgCEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAIYX2d44V9neOEAAAAH +c3NoLXJzYQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bkxj +v/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnMfg +CEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAADAQABAAAAgBRXdq7kj/ +iR+WIEs7uifSMwuPGDjtxksg2Uj09kSGRLFmZdu4EWtvUh0uV0J37vbfBSkubU3fAvrP99 +bRxUHhD5Z444BIyht8jlBetfoJOBSE/TQJ/69xguSmHB8XH8/WUqEaNZ2F+q0AAkRt5CTs +lkML/YJI1mPzy+0ny6tS8hAAAAQQD6N3CByknj5WrDIJQCce+zbhbftnN4RM6OBuaHv4mm +y0qIAH7i6ZHFqHlr1OnCPzqtzIt4McoyIDEf+9eH3ktKAAAAQQD8uQ/jcs27tMPwb2GYyU +vSNZ9g225seV+Y1dU+GSk8zuqmkN1Cbc0dmJW8hqncYrbDuFbRH3kPiLTLO3Aifn2xAAAA +QQDsirzo7Ox+f2SC0TCu6+wbar6f617IhWRlUyPDNa18+ju+BHdw1890sRSpany5RbJekq +KoumXchRmzl8vZPoVPAAAAHWluaWZjQFRoZS10b3hpYy1hdmVuZ2VyLmxvY2FsAQIDBAU= + +-----END OPENSSH PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/openssh-broken b/cli/src/test/resources/hudson/cli/openssh-broken new file mode 100644 index 0000000000000000000000000000000000000000..f4ebcef2d10778153ea17af9d2f1390189f17dc4 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/openssh-broken @@ -0,0 +1,16 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bk +xjv/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnM +fgCEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAIYX2d44V9neOEAAAAH +c3NoLXJzYQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bkxj +v/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnMfg +CEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAADAQABAAAAgBRXdq7kj/ +iR+WIEs7uifSMwuPGDjtxksg2Uj09kSGRLFmZdu4EWtvUh0uV0J37vbfBSkubU3fAvrP99 +bRxUHhD5Z444BIyht8jlBetfoJOBSE/TQJ/69xguSmHB8XH8/WUqEaNZ2F+q0AAkRt5CTs +y0qIAH7i6ZHFqHlr1OnCPzqtzIt4McoyIDEf+9eH3ktKAAAAQQD8uQ/jcs27tMPwb2GYyU +vSNZ9g225seV+Y1dU+GSk8zuqmkN1Cbc0dmJW8hqncYrbDuFbRH3kPiLTLO3Aifn2xAAAA +QQDsirzo7Ox+f2SC0TCu6+wbar6f617IhWRlUyPDNa18+ju+BHdw1890sRSpany5RbJekq +KoumXchRmzl8vZPoVPAAAAHWluaWZjQFRoZS10b3hpYy1hdmVuZ2VyLmxvY2FsAQIDBAU= + +-----END OPENSSH PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/openssh-unsuported b/cli/src/test/resources/hudson/cli/openssh-unsuported new file mode 100644 index 0000000000000000000000000000000000000000..4ed6878f12c88cf120eb680b5cca53e904ff54cb --- /dev/null +++ b/cli/src/test/resources/hudson/cli/openssh-unsuported @@ -0,0 +1,17 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDIzkiSdr +mRRbRY1S2YtBacAAAAEAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQCpXxUyU7MR +b4GrSMnufZb6kaBb1lHBywI7+dBNnpHyq1rpZJRi36lpxxZoaiG+inOeSBh7QPnDVvm2aN +DdT7V0PFzfZKWzxl2PRSd6EIXUsaaXuqaFcJM7rSqU1EB+9qM9JfGIqkdNKwzGdu3kdJJG +3VQQUQVtYXAOLIrApslndQAAAiDAF+/lDARNPSMcOlH6uGqfSBszQBwOf22df5yGHLbnpi +e5yslN+9Z3jFrl2XNDb8sD9e7gZtB/CbkJaDqFCqSOoW979xLLl89jJYle2L48SBbpV7i2 +0/51YYrxs4/75kJUd8uMaOBpNanyI8CBqA5IPmms1NLrSOpAbU20imQNoLUb1B8v2zNBxo +R64UyU8AkKDMwPmAHwdrM73c7eirAsuVknhg0fFjy4iCxurqz5RO0Hpjf8GHfaCh37gim/ +8f/XqwS+MlAn9KaAGi4hZJFaSuyPPVLmRTS+gleoM0zSt5J+Fvdrs/JnL/XIpf64QZafKZ +HyiZXnbJKxWhhhpslHz6QoiZ76LxwRS6bP//fsl7KWPY6IUGMD8h6JGi8o6xpsj16xIGUf +7K+c9TNcjzaO8jtC8ggaixoKdC48LdenfdaagAtBgyWQqxfxTg9ZLiQ2lrZIaF4C0aZi3+ +byeT/mzPyh6aSAsBETjHCbdy/ixi0BNbDB10LlD44J8i9yBROk7WQtM3lJL78Va4KfxSCC +dsJQPu1ABZEr1SGVqVprUaM43C/VKZk5PjXmkbAy5TCztztiWwa8HtKk4V7sH4G/ENjAtJ +w2SEybRTx0WNhD9viTSu8z+BqH1mWP2ek/orE0OWU+H6r1zSoBo9scvpUrLC2NYvcUMw+I +cDupua/5kKQgTzY+kEyRClwvziHEVe4TLZen3UMf2GM3fVC9AUC8 +-----END OPENSSH PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/rsa b/cli/src/test/resources/hudson/cli/rsa new file mode 100644 index 0000000000000000000000000000000000000000..850ecb0e3d710f1477eb482677b041c8de4e5bd9 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/rsa @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDcPcvFMiF7OsfaV9MuHQytEOffnyyFraVyYJrgRsdMsFMJcUVJ +JY2oo2OdMF42CzstSe1fasAZnxalkGBvSdfH6PphgIhBcVj1eCgPR/76q2OyuRk7 +GE5R8GDlQSCsFsV658zyoBXELVFL9HvhXOw/0u7h60h/ertIoYawz44e3wIDAQAB +AoGBAKTP7bQ07o88DqCbRmJkxL6iPxK+F+A1cPDl0CBzduMxtAIF7LZvTtHa60mP +D4Fb6D3c67CSvwytW5IsN64wUTNaB6pOXmVnscgsXzNXz4UGsxsEOFUcYkEGJZ8M +EMKDSqaCI43EMR7nJi/UNrJyj37Axn4cU3bgnDP5DAxO+AK5AkEA7kWA7zw86AoR +vuSSWEINNSglzTRLZjciDX2b93kh97voRV0q/CHIoSvB3sLU0R/oLpOjaUhPFWrM +JfI9GQemNQJBAOyg3MHBQhAd1rhXzNkW62903ICjlNGLOTemi+E11iIfGyA0bhLH +DJQfR+Tx/VLQEVbAMU1pkUg33GRck4SPA0MCQHbmuDCqHrqsS6624VCppW2hWzvL +nNSlLpkM1YfpKso1OvNiStEHCtdivpwrHYg+I98aTbF8I/rMEJPfDh4vcwECQQCv +LBbAyNSjIbPHHBhlzXXVOOnTwUV2Kl7dN8ntmvE+qVBncujZtck2DkIm1o32NFnh +or3c1P3cPJ5HHdGHHGgJAkBdbOMa8VIFBliHr22akFD43gakZ/7ymRNTXA1uj6xt +NO/1mklbaj4u41UQZU9JjsqebOMGnIc88KMFh6wdfaed +-----END RSA PRIVATE KEY----- diff --git a/cli/src/test/resources/hudson/cli/rsa-password b/cli/src/test/resources/hudson/cli/rsa-password new file mode 100644 index 0000000000000000000000000000000000000000..55c3f7568176f238069be9abc1d01ef4eb9455d5 --- /dev/null +++ b/cli/src/test/resources/hudson/cli/rsa-password @@ -0,0 +1,18 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,B937F90AFDFDE86CD9023373F5079F8F + +N6ohIfZ+nmqodFzFlFCG6gBXEGIBOl/ojaV+GSHpCJRKJs6lgGSwVmSHESqiKpAI +ZNQpWEURIVXRpScAuqB+BNQyNjnlFwoo47aBkfDFR2DTY6y+21Z1Fll9ZExABgRJ +IsMoRVjVbd4eJ1njln73b290EUBNu4ej14pPFsHqSdHQ4atPXw3Alph0NuFYz1CI +Jg/0vUFFX1u2UjLYAHRU7EKJUk027H1GjZdlV7kcogMRblINokGK0rpmuize6fE9 +WHzEkeGb7qwU4fJ1Sa396TwA5sU6K7xPqV+sArWevavVC4xi0yKoSMmzR2ps5rg+ +XDNBnscNo0dTxnR/Dku2fsqiTsvXZ/LQDWTyTkC0Sx/gYKmfmYfmOpFh6pW0PRUF +h8Cxm/XkAZV90pK+SrwKA/Pj2vi5nJvAsQlHD+5f7GYLMiB2pWJdbTuXjg0MJjvP +3TMZm7SqOMe7G4iyXKmlg7Hri2jMPkzvmoGMQZk8bFoX87579w4s0bCdP23lcI2H +6F7OHTHnPbPc302vN2NWARuqP6XJGRlwJJaNkL8/cvMh42UeVZEZ5WwacUW5g0IN +IE+K/L5EemBVI2whRsgBBxbyKymBMTolKDhLbGicxAb7E1jr3UXsRv17LaoaPMDN +TbCjhRkXiYAWXzR/BVwG2vhCI9geCXbLXAVhp2U/z6+KDAVXUIf2Hwz1v1g+fabV +DGQBzCobfJv9ML9/oO6+KP/dgAA/kBMsLNmQTAR9NslDqdbVujNJ0fSvqaEG1lbq +7oc36BIyjJgPEehE0kCfzAeVRHq73N8lfbZaz5cnvdMjCXwMubcIL1kBfUpv5eSg +-----END RSA PRIVATE KEY----- diff --git a/core/move-l10n.groovy b/core/move-l10n.groovy index c52e66ae53aecf6318efc70b079e6777df580bd6..8d002348827a6c76ec3b4832278384c74a91d692 100644 --- a/core/move-l10n.groovy +++ b/core/move-l10n.groovy @@ -1,30 +1,30 @@ // Usage: groovy move-l10n.groovy hudson/model/OldClass/old-view jenkins/model/NewClass/new-view 'Some\ Translatable\ Text' // (The new view may be given as '-' to simply delete the key.) -def oldview = args[0]; -def newview = args[1]; -def key = args[2]; +def oldview = args[0] +def newview = args[1] +def key = args[2] -def scriptDir = new File(getClass().protectionDomain.codeSource.location.path).parent; -def resDir = new File(scriptDir, 'src/main/resources'); +def scriptDir = new File(getClass().protectionDomain.codeSource.location.path).parent +def resDir = new File(scriptDir, 'src/main/resources') -def basename = new File(resDir, oldview).name; +def basename = new File(resDir, oldview).name for (p in new File(resDir, oldview).parentFile.listFiles()) { - def n = p.name; + def n = p.name if (n == "${basename}.properties" || n.startsWith("${basename}_") && n.endsWith(".properties")) { - def lines = p.readLines('ISO-8859-1'); + def lines = p.readLines('ISO-8859-1') // TODO does not handle multiline values correctly - def matches = lines.findAll({it.startsWith("${key}=")}); + def matches = lines.findAll({it.startsWith("${key}=")}) if (!matches.isEmpty()) { - lines.removeAll(matches); + lines.removeAll(matches) p.withWriter('ISO-8859-1') {out -> lines.each {line -> out.writeLine(line)} } if (newview == '-') { - println("deleting ${matches.size()} matches from ${n}"); + println("deleting ${matches.size()} matches from ${n}") } else { - def nue = new File(resDir, newview + n.substring(basename.length())); - println("moving ${matches.size()} matches from ${n} to ${nue.name}"); + def nue = new File(resDir, newview + n.substring(basename.length())) + println("moving ${matches.size()} matches from ${n} to ${nue.name}") // TODO if the original lacked a trailing newline, this will corrupt previously final key nue.withWriterAppend('ISO-8859-1') {out -> matches.each {line -> out.writeLine(line)} diff --git a/core/pom.xml b/core/pom.xml index 4ba0498ae9a876d95afc42bad9b0b84eb47eda0a..41c58c486f36bd2da15944db611de066481285a8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,7 +39,7 @@ THE SOFTWARE. true - 1.256 + 1.257.2 2.5.6.SEC03 2.4.12 @@ -63,7 +63,7 @@ THE SOFTWARE. org.jenkins-ci version-number - 1.4 + 1.6 org.jenkins-ci @@ -101,6 +101,11 @@ THE SOFTWARE. + + org.connectbot.jbcrypt + jbcrypt + 1.0.0 + org.jruby.ext.posix jna-posix @@ -111,16 +116,6 @@ THE SOFTWARE. jnr-posix 3.0.45 - - org.kohsuke - trilead-putty-extension - 1.2 - - - org.jenkins-ci - trilead-ssh2 - build-217-jenkins-14 - org.kohsuke.stapler stapler-groovy @@ -180,7 +175,7 @@ THE SOFTWARE. io.jenkins.stapler jenkins-stapler-support - 1.0 + 1.1 org.hamcrest @@ -207,7 +202,7 @@ THE SOFTWARE. args4j args4j - 2.0.31 + 2.33 org.jenkins-ci @@ -226,7 +221,7 @@ THE SOFTWARE. org.jvnet.localizer localizer - 1.24 + 1.26 antlr @@ -297,13 +292,9 @@ THE SOFTWARE. - commons-beanutils commons-beanutils - 1.8.3 + 1.9.3 org.apache.commons @@ -518,7 +509,7 @@ THE SOFTWARE. org.jvnet.winp winp - 1.27 + 1.28 org.jenkins-ci @@ -531,14 +522,14 @@ THE SOFTWARE. 3.2.9 - org.jenkins-ci + org.jmdns jmdns - 3.4.0-jenkins-3 + 3.5.5 net.java.dev.jna jna - 4.5.2 + 5.3.1 org.kohsuke @@ -598,9 +589,9 @@ THE SOFTWARE. - com.google.code.findbugs - annotations - provided + com.github.spotbugs + spotbugs-annotations + true @@ -810,10 +801,6 @@ THE SOFTWARE. - - org.codehaus.mojo - findbugs-maven-plugin - @@ -876,11 +863,11 @@ THE SOFTWARE. - - - findbugs + + + spotbugs - + true diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message.properties index fffef47e59cd5c72bb4ac44cd9f2d6bc2ca5cee4..a76332f6767c045688896db6808c5ca074b21cf6 100644 --- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message.properties +++ b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message.properties @@ -20,8 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -NewVersionAvailable=New version of Jenkins ({0}) is available for download \ - (changelog). +NewVersionAvailable=New version of Jenkins ({0}) is available for download \ + (changelog). +ChangelogUrl=${changelog.url} UpgradeComplete=Upgrade to Jenkins {0} is complete, awaiting restart. UpgradeCompleteRestartNotSupported=Upgrade to Jenkins {0} is complete, awaiting restart. UpgradeProgress=Upgrade to Jenkins {0} is in progress. diff --git a/core/src/main/java/hudson/AbstractMarkupText.java b/core/src/main/java/hudson/AbstractMarkupText.java index 1e78b3e10cfadca15f5745415141a6d4d8edf4dc..2398f6f7446972a1f724a0ee85fc58d251f1b22e 100644 --- a/core/src/main/java/hudson/AbstractMarkupText.java +++ b/core/src/main/java/hudson/AbstractMarkupText.java @@ -141,7 +141,7 @@ public abstract class AbstractMarkupText { public List findTokens(Pattern pattern) { String text = getText(); Matcher m = pattern.matcher(text); - List r = new ArrayList(); + List r = new ArrayList<>(); while(m.find()) { int idx = m.start(); diff --git a/core/src/main/java/hudson/BulkChange.java b/core/src/main/java/hudson/BulkChange.java index 28b9fde4261074ca2e4828534139d16e18c8f464..e65fa4d3acb6d96705125c78274e129d53352235 100644 --- a/core/src/main/java/hudson/BulkChange.java +++ b/core/src/main/java/hudson/BulkChange.java @@ -132,7 +132,7 @@ public class BulkChange implements Closeable { /** * {@link BulkChange}s that are effective currently. */ - private static final ThreadLocal INSCOPE = new ThreadLocal(); + private static final ThreadLocal INSCOPE = new ThreadLocal<>(); /** * Gets the {@link BulkChange} instance currently in scope for the current thread. diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index 5d173c3839022d162265f7b2a2779c436e30791a..e46014f1a9208589b1d9aa645a7b49d5cac2cd57 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -23,29 +23,21 @@ */ package hudson; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; - -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import jenkins.util.AntWithFindResourceClassLoader; -import jenkins.util.SystemProperties; import com.google.common.collect.Lists; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Plugin.DummyImpl; import hudson.PluginWrapper.Dependency; import hudson.model.Hudson; -import jenkins.util.AntClassLoader; import hudson.util.CyclicGraphDetector; import hudson.util.CyclicGraphDetector.CycleDetectedException; import hudson.util.IOUtils; import hudson.util.MaskingClassLoader; -import hudson.util.VersionNumber; import jenkins.ClassLoaderReflectionToolkit; import jenkins.ExtensionFilter; +import jenkins.plugins.DetachedPluginsUtil; +import jenkins.util.AntClassLoader; +import jenkins.util.AntWithFindResourceClassLoader; +import jenkins.util.SystemProperties; import org.apache.commons.io.output.NullOutputStream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -60,37 +52,36 @@ import org.apache.tools.ant.util.GlobPatternMapper; import org.apache.tools.zip.ZipEntry; import org.apache.tools.zip.ZipExtraField; import org.apache.tools.zip.ZipOutputStream; +import org.jenkinsci.bytecode.Transformer; +import javax.annotation.Nonnull; import java.io.Closeable; import java.io.File; +import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; +import java.io.InputStream; import java.net.URL; -import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.jenkinsci.bytecode.Transformer; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - -import javax.annotation.Nonnull; import static org.apache.commons.io.FilenameUtils.getBaseName; public class ClassicPluginStrategy implements PluginStrategy { + private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName()); + /** * Filter for jar files. */ @@ -146,6 +137,7 @@ public class ClassicPluginStrategy implements PluginStrategy { } catch (InvalidPathException e) { throw new IOException(e); } + //noinspection StatementWithEmptyBody if (firstLine.startsWith("Manifest-Version:")) { // this is the manifest already } else { @@ -167,7 +159,7 @@ public class ClassicPluginStrategy implements PluginStrategy { @Override public PluginWrapper createPluginWrapper(File archive) throws IOException { final Manifest manifest; - URL baseResourceURL = null; + URL baseResourceURL; File expandDir = null; // if .hpi, this is the directory where war is expanded @@ -194,6 +186,10 @@ public class ClassicPluginStrategy implements PluginStrategy { } catch (InvalidPathException e) { throw new IOException(e); } + String canonicalName = manifest.getMainAttributes().getValue("Short-Name") + ".jpi"; + if (!archive.getName().equals(canonicalName)) { + LOGGER.warning(() -> "encountered " + archive + " under a nonstandard name; expected " + canonicalName); + } } final Attributes atts = manifest.getMainAttributes(); @@ -201,7 +197,7 @@ public class ClassicPluginStrategy implements PluginStrategy { // TODO: define a mechanism to hide classes // String export = manifest.getMainAttributes().getValue("Export"); - List paths = new ArrayList(); + List paths = new ArrayList<>(); if (isLinked) { parseClassPath(manifest, archive, paths, "Libraries", ","); parseClassPath(manifest, archive, paths, "Class-Path", " +"); // backward compatibility @@ -227,8 +223,8 @@ public class ClassicPluginStrategy implements PluginStrategy { } // compute dependencies - List dependencies = new ArrayList(); - List optionalDependencies = new ArrayList(); + List dependencies = new ArrayList<>(); + List optionalDependencies = new ArrayList<>(); String v = atts.getValue("Plugin-Dependencies"); if (v != null) { for (String s : v.split(",")) { @@ -259,39 +255,27 @@ public class ClassicPluginStrategy implements PluginStrategy { createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies); } - private static void fix(Attributes atts, List optionalDependencies) { + private void fix(Attributes atts, List optionalDependencies) { String pluginName = atts.getValue("Short-Name"); String jenkinsVersion = atts.getValue("Jenkins-Version"); if (jenkinsVersion==null) jenkinsVersion = atts.getValue("Hudson-Version"); - - optionalDependencies.addAll(getImpliedDependencies(pluginName, jenkinsVersion)); + + for (Dependency d : DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion)) { + LOGGER.fine(() -> "implied dep " + pluginName + " → " + d.shortName); + pluginManager.considerDetachedPlugin(d.shortName); + optionalDependencies.add(d); + } } - + /** - * Returns all the plugin dependencies that are implicit based on a particular Jenkins version - * @since 2.0 + * @see DetachedPluginsUtil#getImpliedDependencies(String, String) */ + @Deprecated // since TODO @Nonnull public static List getImpliedDependencies(String pluginName, String jenkinsVersion) { - List out = new ArrayList<>(); - for (DetachedPlugin detached : DETACHED_LIST) { - // don't fix the dependency for itself, or else we'll have a cycle - if (detached.shortName.equals(pluginName)) { - continue; - } - if (BREAK_CYCLES.contains(pluginName + ' ' + detached.shortName)) { - LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {pluginName, detached.shortName}); - continue; - } - // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them. - if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(detached.splitWhen) <= 0) { - out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requiredVersion)); - LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {pluginName, detached.shortName, jenkinsVersion}); - } - } - return out; + return DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion); } @Deprecated @@ -319,140 +303,6 @@ public class ClassicPluginStrategy implements PluginStrategy { return classLoader; } - /** - * Get the list of all plugins that have ever been {@link DetachedPlugin detached} from Jenkins core. - * @return A {@link List} of {@link DetachedPlugin}s. - */ - @Restricted(NoExternalUse.class) - public static @Nonnull List getDetachedPlugins() { - return DETACHED_LIST; - } - - /** - * Get the list of plugins that have been detached since a specific Jenkins release version. - * @param since The Jenkins version. - * @return A {@link List} of {@link DetachedPlugin}s. - */ - @Restricted(NoExternalUse.class) - public static @Nonnull List getDetachedPlugins(@Nonnull VersionNumber since) { - List detachedPlugins = new ArrayList<>(); - - for (DetachedPlugin detachedPlugin : DETACHED_LIST) { - if (!detachedPlugin.getSplitWhen().isOlderThan(since)) { - detachedPlugins.add(detachedPlugin); - } - } - - return detachedPlugins; - } - - /** - * Is the named plugin a plugin that was detached from Jenkins at some point in the past. - * @param pluginId The plugin ID. - * @return {@code true} if the plugin is a plugin that was detached from Jenkins at some - * point in the past, otherwise {@code false}. - */ - @Restricted(NoExternalUse.class) - public static boolean isDetachedPlugin(@Nonnull String pluginId) { - for (DetachedPlugin detachedPlugin : DETACHED_LIST) { - if (detachedPlugin.getShortName().equals(pluginId)) { - return true; - } - } - - return false; - } - - /** - * Information about plugins that were originally in the core. - *

- * A detached plugin is one that has any of the following characteristics: - *

    - *
  • - * Was an existing plugin that at some time previously bundled with the Jenkins war file. - *
  • - *
  • - * Was previous code in jenkins core that was split to a separate-plugin (but may not have - * ever been bundled in a jenkins war file - i.e. it gets split after this 2.0 update). - *
  • - *
- */ - @Restricted(NoExternalUse.class) - public static final class DetachedPlugin { - private final String shortName; - /** - * Plugins built for this Jenkins version (and earlier) will automatically be assumed to have - * this plugin in its dependency. - * - * When core/pom.xml version is 1.123-SNAPSHOT when the code is removed, then this value should - * be "1.123.*" (because 1.124 will be the first version that doesn't include the removed code.) - */ - private final VersionNumber splitWhen; - private final String requiredVersion; - - private DetachedPlugin(String shortName, String splitWhen, String requiredVersion) { - this.shortName = shortName; - this.splitWhen = new VersionNumber(splitWhen); - this.requiredVersion = requiredVersion; - } - - /** - * Get the short name of the plugin. - * @return The short name of the plugin. - */ - public String getShortName() { - return shortName; - } - - /** - * Get the Jenkins version from which the plugin was detached. - * @return The Jenkins version from which the plugin was detached. - */ - public VersionNumber getSplitWhen() { - return splitWhen; - } - - /** - * Gets the minimum required version for the current version of Jenkins. - * - * @return the minimum required version for the current version of Jenkins. - * @since 2.16 - */ - public VersionNumber getRequiredVersion() { - return new VersionNumber(requiredVersion); - } - - @Override - public String toString() { - return shortName + " " + splitWhen.toString().replace(".*", "") + " " + requiredVersion; - } - } - - /** Record of which plugins which removed from core and when. */ - private static final List DETACHED_LIST; - - /** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */ - private static final Set BREAK_CYCLES; - - static { - try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugins.txt")) { - DETACHED_LIST = ImmutableList.copyOf(configLines(is).map(line -> { - String[] pieces = line.split(" "); - return new DetachedPlugin(pieces[0], pieces[1] + ".*", pieces[2]); - }).collect(Collectors.toList())); - } catch (IOException x) { - throw new ExceptionInInitializerError(x); - } - try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugin-cycles.txt")) { - BREAK_CYCLES = ImmutableSet.copyOf(configLines(is).collect(Collectors.toSet())); - } catch (IOException x) { - throw new ExceptionInInitializerError(x); - } - } - private static Stream configLines(InputStream is) throws IOException { - return org.apache.commons.io.IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*")); - } - /** * Computes the classloader that takes the class masking into account. * @@ -474,7 +324,7 @@ public class ClassicPluginStrategy implements PluginStrategy { List finders; if (type==ExtensionFinder.class) { // Avoid infinite recursion of using ExtensionFinders to find ExtensionFinders - finders = Collections.singletonList(new ExtensionFinder.Sezpoz()); + finders = Collections.singletonList(new ExtensionFinder.Sezpoz()); } else { finders = hudson.getExtensionList(ExtensionFinder.class); } @@ -483,7 +333,7 @@ public class ClassicPluginStrategy implements PluginStrategy { * See {@link ExtensionFinder#scout(Class, Hudson)} for the dead lock issue and what this does. */ if (LOGGER.isLoggable(Level.FINER)) - LOGGER.log(Level.FINER,"Scout-loading ExtensionList: "+type, new Throwable()); + LOGGER.log(Level.FINER, "Scout-loading ExtensionList: "+type, new Throwable()); for (ExtensionFinder finder : finders) { finder.scout(type, hudson); } @@ -495,7 +345,7 @@ public class ClassicPluginStrategy implements PluginStrategy { } catch (AbstractMethodError e) { // backward compatibility for (T t : finder.findExtensions(type, hudson)) - r.add(new ExtensionComponent(t)); + r.add(new ExtensionComponent<>(t)); } } @@ -737,7 +587,7 @@ public class ClassicPluginStrategy implements PluginStrategy { CyclicGraphDetector cgd = new CyclicGraphDetector() { @Override protected List getEdges(PluginWrapper pw) { - List dep = new ArrayList(); + List dep = new ArrayList<>(); for (Dependency d : pw.getDependencies()) { PluginWrapper p = pluginManager.getPlugin(d.shortName); if (p!=null && p.isActive()) @@ -803,7 +653,7 @@ public class ClassicPluginStrategy implements PluginStrategy { @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", justification = "Should not produce network overheads since the URL is local. JENKINS-53793 is a follow-up") protected Enumeration findResources(String name) throws IOException { - HashSet result = new HashSet(); + HashSet result = new HashSet<>(); if (PluginManager.FAST_LOOKUP) { for (PluginWrapper pw : getTransitiveDependencies()) { @@ -864,6 +714,5 @@ public class ClassicPluginStrategy implements PluginStrategy { } public static boolean useAntClassLoader = SystemProperties.getBoolean(ClassicPluginStrategy.class.getName()+".useAntClassLoader"); - private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName()); public static boolean DISABLE_TRANSFORMER = SystemProperties.getBoolean(ClassicPluginStrategy.class.getName()+".noBytecodeTransformer"); } diff --git a/core/src/main/java/hudson/DNSMultiCast.java b/core/src/main/java/hudson/DNSMultiCast.java index aa71b2e23b2be38ba799c7d3f3742e9442a5c550..9352fd9decbed61f704cc53fcfc2f69d744d827f 100644 --- a/core/src/main/java/hudson/DNSMultiCast.java +++ b/core/src/main/java/hudson/DNSMultiCast.java @@ -4,14 +4,21 @@ import jenkins.util.SystemProperties; import jenkins.model.Jenkins; import jenkins.model.Jenkins.MasterComputer; -import javax.jmdns.JmDNS; +import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; +import javax.jmdns.ServiceListener; +import javax.jmdns.impl.JmDNSImpl; import java.io.Closeable; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetAddress; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; import java.util.logging.Level; import java.util.logging.Logger; @@ -21,7 +28,7 @@ import java.util.logging.Logger; * @author Kohsuke Kawaguchi */ public class DNSMultiCast implements Closeable { - private JmDNS jmdns; + private JenkinsJmDNS jmdns; public DNSMultiCast(final Jenkins jenkins) { if (disabled) return; // escape hatch @@ -30,9 +37,9 @@ public class DNSMultiCast implements Closeable { MasterComputer.threadPoolForRemoting.submit(new Callable() { public Object call() { try { - jmdns = JmDNS.create(); + jmdns = new JenkinsJmDNS(null, null); - Map props = new HashMap(); + Map props = new HashMap<>(); String rootURL = jenkins.getRootUrl(); if (rootURL==null) return null; @@ -77,16 +84,125 @@ public class DNSMultiCast implements Closeable { public void close() { if (jmdns!=null) { -// try { + try { jmdns.abort(); jmdns = null; -// } catch (final IOException e) { -// LOGGER.log(Level.WARNING,"Failed to close down JmDNS instance!",e); -// } + } catch (final IOException e) { + LOGGER.log(Level.WARNING,"Failed to close down JmDNS instance!",e); + } } } private static final Logger LOGGER = Logger.getLogger(DNSMultiCast.class.getName()); public static boolean disabled = SystemProperties.getBoolean(DNSMultiCast.class.getName()+".disabled"); + + /** + * Class that extends {@link JmDNSImpl} to add an abort method. Since {@link javax.jmdns.JmDNS#close()} might + * make the instance hang during the shutdown, the abort method terminate uncleanly, but rapidly and + * without blocking. + * + * Initially it was part of the jenkinsci/jmdns forked library, but now this class is responsible for aborting, + * allowing to have a direct and clean dependency to the original library. + * + * The abort() method is pretty similar to close() method. To access private methods and fields uses + * reflection. + * + * @since 2.178 + * + * See JENKINS-25369 for further details + */ + private static class JenkinsJmDNS extends JmDNSImpl { + private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName()); + private final Class parent; + + /** + * Create an instance of JmDNS and bind it to a specific network interface given its IP-address. + * + * @param address IP address to bind to. + * @param name name of the newly created JmDNS + * @throws IOException + */ + public JenkinsJmDNS(InetAddress address, String name) throws IOException { + super(address, name); + this.parent = this.getClass().getSuperclass(); + } + + /** + * Works like {@link #close()} but terminate uncleanly, but rapidly and without blocking. + */ + public void abort() throws IOException { + if (this.isClosing()) { + return; + } + + if (logger.isLoggable(Level.FINER)) { + logger.finer("Aborting JmDNS: " + this); + } + // Stop JmDNS + // This protects against recursive calls + if (this.closeState()) { + // We got the tie break now clean up + + // Stop the timer + logger.finer("Canceling the timer"); + this.cancelTimer(); + + // Cancel all services + // KK: this is a blocking call that doesn't fit 'abort' + // this.unregisterAllServices(); + executePrivateParentMethod("disposeServiceCollectors"); + +// KK: another blocking call +// if (logger.isLoggable(Level.FINER)) { +// logger.finer("Wait for JmDNS cancel: " + this); +// } +// this.waitForCanceled(DNSConstants.CLOSE_TIMEOUT); + + // Stop the canceler timer + logger.finer("Canceling the state timer"); + this.cancelStateTimer(); + + // Stop the executor + shutdown(); + + // close socket + executePrivateParentMethod("closeMulticastSocket"); + + // remove the shutdown hook + if (_shutdown != null) { + Runtime.getRuntime().removeShutdownHook(_shutdown); + } + + if (logger.isLoggable(Level.FINER)) { + logger.finer("JmDNS closed."); + } + } + advanceState(null); + } + + private void shutdown() throws IOException { + try { + Field executor = this.parent.getDeclaredField("_executor"); + executor.setAccessible(true); + ExecutorService _executor = (ExecutorService) executor.get(this); + _executor.shutdown(); + } catch (NoSuchFieldException | IllegalAccessException e) { + logger.log(Level.SEVERE, "Error trying to abort JmDNS", e); + throw new IOException(e); + } + } + + private void executePrivateParentMethod(String method) throws IOException { + try { + Method m = this.parent.getDeclaredMethod(method); + m.setAccessible(true); + m.invoke(this); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.log(Level.SEVERE, "Error trying to abort JmDNS", e); + throw new IOException(e); + } + } + + } } diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java index 03efea55e4036a69b1b3297e083c8a3a86a351af..4a967b47da645a948e32554a37724a6dec5a3a15 100644 --- a/core/src/main/java/hudson/DependencyRunner.java +++ b/core/src/main/java/hudson/DependencyRunner.java @@ -59,7 +59,7 @@ public class DependencyRunner implements Runnable { Set topLevelProjects = new HashSet(); // Get all top-level projects LOGGER.fine("assembling top level projects"); - for (AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class)) + for (AbstractProject p : Jenkins.get().allItems(AbstractProject.class)) if (p.getUpstreamProjects().size() == 0) { LOGGER.fine("adding top level project " + p.getName()); topLevelProjects.add(p); diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java index 8452227e5e9467b1442fbe2b611c4a32e582df56..4557034ccc32c636e3bd559809ca2ef70cbfd7b5 100644 --- a/core/src/main/java/hudson/DescriptorExtensionList.java +++ b/core/src/main/java/hudson/DescriptorExtensionList.java @@ -73,10 +73,10 @@ public class DescriptorExtensionList, D extends Descrip @SuppressWarnings({"unchecked", "rawtypes"}) public static ,D extends Descriptor> DescriptorExtensionList createDescriptorList(Jenkins jenkins, Class describableType) { - if (describableType == (Class) Publisher.class) { + if (describableType == Publisher.class) { return (DescriptorExtensionList) new Publisher.DescriptorExtensionListImpl(jenkins); } - return new DescriptorExtensionList(jenkins,describableType); + return new DescriptorExtensionList<>(jenkins, describableType); } /** @@ -199,7 +199,7 @@ public class DescriptorExtensionList, D extends Descrip } private List> _load(Iterable> set) { - List> r = new ArrayList>(); + List> r = new ArrayList<>(); for( ExtensionComponent c : set ) { Descriptor d = c.getInstance(); try { diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java index a24976b219169c690bba916faa467f4c0a1d4bdf..0dbec25143ab235d054050153041aa49cd68f332 100644 --- a/core/src/main/java/hudson/EnvVars.java +++ b/core/src/main/java/hudson/EnvVars.java @@ -74,6 +74,7 @@ import javax.annotation.CheckForNull; * @author Kohsuke Kawaguchi */ public class EnvVars extends TreeMap { + private static final long serialVersionUID = 4320331661987259022L; private static Logger LOGGER = Logger.getLogger(EnvVars.class.getName()); /** * If this {@link EnvVars} object represents the whole environment variable set, @@ -119,6 +120,7 @@ public class EnvVars extends TreeMap { } } + @SuppressWarnings("CopyConstructorMissesField") // does not set #platform, see its Javadoc public EnvVars(@Nonnull EnvVars m) { // this constructor is so that in future we can get rid of the downcasting. this((Map)m); @@ -199,7 +201,7 @@ public class EnvVars extends TreeMap { } public void clear() { - referredVariables = new TreeSet(comparator); + referredVariables = new TreeSet<>(comparator); } public String resolve(String name) { @@ -225,8 +227,8 @@ public class EnvVars extends TreeMap { } return refereeSetMap.get(n); } - }; - + } + private final Comparator comparator; @Nonnull @@ -287,8 +289,8 @@ public class EnvVars extends TreeMap { * Scan all variables and list all referring variables. */ public void scan() { - refereeSetMap = new TreeMap>(comparator); - List extendingVariableNames = new ArrayList(); + refereeSetMap = new TreeMap<>(comparator); + List extendingVariableNames = new ArrayList<>(); TraceResolver resolver = new TraceResolver(comparator); @@ -326,10 +328,10 @@ public class EnvVars extends TreeMap { // When A refers B, the last appearance of B always comes after // the last appearance of A. - List reversedDuplicatedOrder = new ArrayList(sorter.getSorted()); + List reversedDuplicatedOrder = new ArrayList<>(sorter.getSorted()); Collections.reverse(reversedDuplicatedOrder); - orderedVariableNames = new ArrayList(overrides.size()); + orderedVariableNames = new ArrayList<>(overrides.size()); for(String key: reversedDuplicatedOrder) { if(overrides.containsKey(key) && !orderedVariableNames.contains(key)) { orderedVariableNames.add(key); diff --git a/core/src/main/java/hudson/ExpressionFactory2.java b/core/src/main/java/hudson/ExpressionFactory2.java index 277858c933d5691c4f285a51de3cf8d80dc7c7d1..c8704fccfdb8b06bf0c5b63e1a6fac0969e268cf 100644 --- a/core/src/main/java/hudson/ExpressionFactory2.java +++ b/core/src/main/java/hudson/ExpressionFactory2.java @@ -168,5 +168,5 @@ final class ExpressionFactory2 implements ExpressionFactory { * * @see Functions#getCurrentJellyContext() */ - protected static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal(); + protected static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal<>(); } diff --git a/core/src/main/java/hudson/ExtensionComponent.java b/core/src/main/java/hudson/ExtensionComponent.java index 136d52f81ae7b564db0ca7c1738d34bff73a741c..98e03969f3cc631ad458b9f5b276b9979d108728 100644 --- a/core/src/main/java/hudson/ExtensionComponent.java +++ b/core/src/main/java/hudson/ExtensionComponent.java @@ -95,9 +95,7 @@ public class ExtensionComponent implements Comparable> if (this.instance instanceof Descriptor && that.instance instanceof Descriptor) { try { return Util.fixNull(((Descriptor)this.instance).getDisplayName()).compareTo(Util.fixNull(((Descriptor)that.instance).getDisplayName())); - } catch (RuntimeException x) { - LOG.log(Level.WARNING, null, x); - } catch (LinkageError x) { + } catch (RuntimeException | LinkageError x) { LOG.log(Level.WARNING, null, x); } } diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 1ff0638c10d6b578927ea90757d5acb8bdbeff6b..a31120f73841deb9f3e164a0ac167c9d05a11b93 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -42,7 +42,6 @@ import com.google.inject.spi.ProvisionListener; import hudson.init.InitMilestone; import hudson.model.Descriptor; import hudson.model.Hudson; -import hudson.util.ReflectionUtils; import jenkins.ExtensionComponentSet; import jenkins.ExtensionFilter; import jenkins.ExtensionRefreshException; @@ -264,13 +263,13 @@ public abstract class ExtensionFinder implements ExtensionPoint { public GuiceFinder() { refreshExtensionAnnotations(); - SezpozModule extensions = new SezpozModule(loadSezpozIndices(Jenkins.getInstance().getPluginManager().uberClassLoader)); + SezpozModule extensions = new SezpozModule(loadSezpozIndices(Jenkins.get().getPluginManager().uberClassLoader)); List modules = new ArrayList<>(); modules.add(new AbstractModule() { @Override protected void configure() { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); bind(Jenkins.class).toInstance(j); bind(PluginManager.class).toInstance(j.getPluginManager()); } @@ -292,7 +291,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { } // expose Injector via lookup mechanism for interop with non-Guice clients - Jenkins.getInstance().lookup.set(Injector.class,new ProxyInjector() { + Jenkins.get().lookup.set(Injector.class,new ProxyInjector() { protected Injector resolve() { return getContainer(); } @@ -534,8 +533,9 @@ public abstract class ExtensionFinder implements ExtensionPoint { } else if (e instanceof Method) { extType = ((Method)e).getReturnType(); - } else + } else { throw new AssertionError(); + } resolve(extType); @@ -643,7 +643,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { // 4. thread Y decides to load extensions, now blocked on SZ. // 5. dead lock if (indices==null) { - ClassLoader cl = Jenkins.getInstance().getPluginManager().uberClassLoader; + ClassLoader cl = Jenkins.get().getPluginManager().uberClassLoader; indices = ImmutableList.copyOf(Index.load(Extension.class, Object.class, cl)); } return indices; @@ -677,7 +677,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { static List> listDelta(Class annotationType, List> old) { // list up newly discovered components final List> delta = Lists.newArrayList(); - ClassLoader cl = Jenkins.getInstance().getPluginManager().uberClassLoader; + ClassLoader cl = Jenkins.get().getPluginManager().uberClassLoader; for (IndexItem ii : Index.load(annotationType, Object.class, cl)) { if (!old.contains(ii)) { delta.add(ii); @@ -698,18 +698,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { for (IndexItem item : indices) { try { - AnnotatedElement e = item.element(); - Class extType; - if (e instanceof Class) { - extType = (Class) e; - } else - if (e instanceof Field) { - extType = ((Field)e).getType(); - } else - if (e instanceof Method) { - extType = ((Method)e).getReturnType(); - } else - throw new AssertionError(); + Class extType = getClassFromIndex(item); if(type.isAssignableFrom(extType)) { Object instance = item.instance(); @@ -734,18 +723,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { // but we can't synchronize this --- if we do, the one thread that's supposed to load a class // can block while other threads wait for the entry into the element call(). // looking at the sezpoz code, it should be safe to do so - AnnotatedElement e = item.element(); - Class extType; - if (e instanceof Class) { - extType = (Class) e; - } else - if (e instanceof Field) { - extType = ((Field)e).getType(); - } else - if (e instanceof Method) { - extType = ((Method)e).getReturnType(); - } else - throw new AssertionError(); + Class extType = getClassFromIndex(item); // according to JDK-4993813 this is the only way to force class initialization Class.forName(extType.getName(),true,extType.getClassLoader()); } catch (Exception | LinkageError e) { @@ -759,5 +737,20 @@ public abstract class ExtensionFinder implements ExtensionPoint { } } + private static Class getClassFromIndex(IndexItem item) throws InstantiationException { + AnnotatedElement e = item.element(); + Class extType; + if (e instanceof Class) { + extType = (Class) e; + } else if (e instanceof Field) { + extType = ((Field) e).getType(); + } else if (e instanceof Method) { + extType = ((Method) e).getReturnType(); + } else { + throw new AssertionError(); + } + return extType; + } + private static final Logger LOGGER = Logger.getLogger(ExtensionFinder.class.getName()); } diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java index 566e3de404911db2fbc418bab933a4bdd8261c79..c3f408c4777ca3ced9451299c738b7e91e4e2b06 100644 --- a/core/src/main/java/hudson/ExtensionList.java +++ b/core/src/main/java/hudson/ExtensionList.java @@ -86,7 +86,7 @@ public class ExtensionList extends AbstractList implements OnMaster { @CopyOnWrite private volatile List> extensions; - private final List listeners = new CopyOnWriteArrayList(); + private final List listeners = new CopyOnWriteArrayList<>(); /** * Place to store manually registered instances with the per-Hudson scope. @@ -104,7 +104,7 @@ public class ExtensionList extends AbstractList implements OnMaster { } protected ExtensionList(Jenkins jenkins, Class extensionType) { - this(jenkins,extensionType,new CopyOnWriteArrayList>()); + this(jenkins,extensionType, new CopyOnWriteArrayList<>()); } /** @@ -237,7 +237,7 @@ public class ExtensionList extends AbstractList implements OnMaster { private synchronized boolean removeSync(Object o) { boolean removed = removeComponent(legacyInstances, o); if(extensions!=null) { - List> r = new ArrayList>(extensions); + List> r = new ArrayList<>(extensions); removed |= removeComponent(r,o); extensions = sort(r); } @@ -245,8 +245,7 @@ public class ExtensionList extends AbstractList implements OnMaster { } private boolean removeComponent(Collection> collection, Object t) { - for (Iterator> itr = collection.iterator(); itr.hasNext();) { - ExtensionComponent c = itr.next(); + for (ExtensionComponent c : collection) { if (c.getInstance().equals(t)) { return collection.remove(c); } @@ -280,11 +279,11 @@ public class ExtensionList extends AbstractList implements OnMaster { } private synchronized boolean addSync(T t) { - legacyInstances.add(new ExtensionComponent(t)); + legacyInstances.add(new ExtensionComponent<>(t)); // if we've already filled extensions, add it if(extensions!=null) { - List> r = new ArrayList>(extensions); - r.add(new ExtensionComponent(t)); + List> r = new ArrayList<>(extensions); + r.add(new ExtensionComponent<>(t)); extensions = sort(r); } return true; @@ -374,8 +373,10 @@ public class ExtensionList extends AbstractList implements OnMaster { * Loads all the extensions. */ protected List> load() { - if (LOGGER.isLoggable(Level.FINE)) - LOGGER.log(Level.FINE,"Loading ExtensionList: "+extensionType, new Throwable()); + LOGGER.fine(() -> String.format("Loading ExtensionList '%s'", extensionType.getName())); + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, String.format("Loading ExtensionList '%s' from", extensionType.getName()), new Throwable("Only present for stacktrace information")); + } return jenkins.getPluginManager().getPluginStrategy().findComponents(extensionType, hudson); } @@ -396,7 +397,7 @@ public class ExtensionList extends AbstractList implements OnMaster { * The implementation should copy a list, do a sort, and return the new instance. */ protected List> sort(List> r) { - r = new ArrayList>(r); + r = new ArrayList<>(r); Collections.sort(r); return r; } @@ -413,7 +414,7 @@ public class ExtensionList extends AbstractList implements OnMaster { @SuppressWarnings({"unchecked", "rawtypes"}) public static ExtensionList create(Jenkins jenkins, Class type) { if(type.getAnnotation(LegacyInstancesAreScopedToHudson.class)!=null) - return new ExtensionList(jenkins,type); + return new ExtensionList<>(jenkins, type); else { return new ExtensionList(jenkins, type, staticLegacyInstances.computeIfAbsent(type, key -> new CopyOnWriteArrayList())); } diff --git a/core/src/main/java/hudson/ExtensionListView.java b/core/src/main/java/hudson/ExtensionListView.java index 100dad6fb758520864230cd18157b52bc062986e..e3689ccf616a97672714ff6a4880c68cc4cd6a9c 100644 --- a/core/src/main/java/hudson/ExtensionListView.java +++ b/core/src/main/java/hudson/ExtensionListView.java @@ -58,7 +58,7 @@ public class ExtensionListView { public static List createList(final Class type) { return new AbstractList() { private ExtensionList storage() { - return Jenkins.getInstance().getExtensionList(type); + return Jenkins.get().getExtensionList(type); } @Override @@ -103,7 +103,7 @@ public class ExtensionListView { public static CopyOnWriteList createCopyOnWriteList(final Class type) { return new CopyOnWriteList() { private ExtensionList storage() { - return Jenkins.getInstance().getExtensionList(type); + return Jenkins.get().getExtensionList(type); } @Override diff --git a/core/src/main/java/hudson/ExtensionPoint.java b/core/src/main/java/hudson/ExtensionPoint.java index e544ed7576ad411694e868a149dbf2ee1a6971b2..f49a5a096cc7ba2b6671b3ec8a0dd5b23746f3a2 100644 --- a/core/src/main/java/hudson/ExtensionPoint.java +++ b/core/src/main/java/hudson/ExtensionPoint.java @@ -29,7 +29,6 @@ import static java.lang.annotation.ElementType.TYPE; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; -import jenkins.util.io.OnMaster; /** * Marker interface that designates extensible components diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 92add6dbe72b396897367f7bd9ea0a2289660e61..c8ede78bb590d6283accc8e398ce80fee47771eb 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -79,6 +79,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.nio.file.FileSystemException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -129,13 +130,16 @@ import org.apache.tools.zip.ZipEntry; import org.apache.tools.zip.ZipFile; import org.jenkinsci.remoting.RoleChecker; import org.jenkinsci.remoting.RoleSensitive; +import org.jenkinsci.remoting.SerializableOnlyOverRemoting; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.Function; import org.kohsuke.stapler.Stapler; import static hudson.FilePath.TarCompression.GZIP; import static hudson.Util.fileToPath; import static hudson.Util.fixEmpty; +import java.io.NotSerializableException; import java.util.Collections; import org.apache.tools.ant.BuildException; @@ -205,7 +209,7 @@ import org.apache.tools.ant.BuildException; * @author Kohsuke Kawaguchi * @see VirtualFile */ -public final class FilePath implements Serializable { +public final class FilePath implements SerializableOnlyOverRemoting { /** * Maximum http redirects we will follow. This defaults to the same number as Firefox/Chrome tolerates. */ @@ -323,7 +327,7 @@ public final class FilePath implements Serializable { } boolean isAbsolute = buf.length() > 0; // Split remaining path into tokens, trimming any duplicate or trailing separators - List tokens = new ArrayList(); + List tokens = new ArrayList<>(); int s = 0, end = path.length(); for (int i = 0; i < end; i++) { char c = path.charAt(i); @@ -331,7 +335,9 @@ public final class FilePath implements Serializable { tokens.add(path.substring(s, i)); s = i; // Skip any extra separator chars - while (++i < end && ((c = path.charAt(i)) == '/' || c == '\\')) { } + //noinspection StatementWithEmptyBody + while (++i < end && ((c = path.charAt(i)) == '/' || c == '\\')) + ; // Add token for separator unless we reached the end if (i < end) tokens.add(path.substring(s, s+1)); s = i; @@ -384,7 +390,7 @@ public final class FilePath implements Serializable { return false; // Windows can handle '/' as a path separator but Unix can't, // so err on Unix side - return remote.indexOf("\\")==-1; + return !remote.contains("\\"); } /** @@ -635,14 +641,13 @@ public final class FilePath implements Serializable { private void unzip(File dir, File zipFile) throws IOException { dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail ZipFile zip = new ZipFile(zipFile); - @SuppressWarnings("unchecked") Enumeration entries = zip.getEntries(); try { while (entries.hasMoreElements()) { ZipEntry e = entries.nextElement(); File f = new File(dir, e.getName()); - if (!f.toPath().normalize().startsWith(dir.toPath())) { + if (!f.getCanonicalPath().startsWith(dir.getCanonicalPath())) { throw new IOException( "Zip " + zipFile.getPath() + " contains illegal file name that breaks out of the target directory: " + e.getName()); } @@ -1060,7 +1065,7 @@ public final class FilePath implements Serializable { if(channel!=null) { // run this on a remote system try { - DelegatingCallable wrapper = new FileCallableWrapper(callable, cl); + DelegatingCallable wrapper = new FileCallableWrapper<>(callable, cl); for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) { wrapper = factory.wrap(wrapper); } @@ -1135,7 +1140,7 @@ public final class FilePath implements Serializable { */ public Future actAsync(final FileCallable callable) throws IOException, InterruptedException { try { - DelegatingCallable wrapper = new FileCallableWrapper(callable); + DelegatingCallable wrapper = new FileCallableWrapper<>(callable); for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) { wrapper = factory.wrap(wrapper); } @@ -1838,7 +1843,7 @@ public final class FilePath implements Serializable { return Collections.emptyList(); } - ArrayList r = new ArrayList(children.length); + ArrayList r = new ArrayList<>(children.length); for (File child : children) r.add(new FilePath(child)); @@ -1929,8 +1934,7 @@ public final class FilePath implements Serializable { } catch (BuildException x) { throw new IOException(x.getMessage()); } - String[] files = ds.getIncludedFiles(); - return files; + return ds.getIncludedFiles(); } /** @@ -2361,12 +2365,7 @@ public final class FilePath implements Serializable { future.get(); return future2.get(); } catch (ExecutionException e) { - Throwable cause = e.getCause(); - if (cause == null) cause = e; - throw cause instanceof IOException - ? (IOException) cause - : new IOException(cause) - ; + throw ioWithCause(e); } } else { // remote -> local copy @@ -2391,15 +2390,20 @@ public final class FilePath implements Serializable { try { return future.get(); } catch (ExecutionException e) { - Throwable cause = e.getCause(); - if (cause == null) cause = e; - throw cause instanceof IOException - ? (IOException) cause - : new IOException(cause) - ; + throw ioWithCause(e); } } } + + private IOException ioWithCause(ExecutionException e) { + Throwable cause = e.getCause(); + if (cause == null) cause = e; + return cause instanceof IOException + ? (IOException) cause + : new IOException(cause) + ; + } + private class CopyRecursiveLocal extends SecureFileCallable { private final FilePath target; private final DirScanner scanner; @@ -2728,12 +2732,12 @@ public final class FilePath implements Serializable { // // this is not a very efficient/clever way to do it, but it's relatively simple - String prefix=""; + StringBuilder prefix = new StringBuilder(); while(true) { int idx = findSeparator(f); if(idx==-1) break; - prefix+=f.substring(0,idx)+'/'; + prefix.append(f.substring(0, idx)).append('/'); f=f.substring(idx+1); if(hasMatch(dir,prefix+fileMask,caseSensitive)) return Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest(fileMask, prefix+fileMask); @@ -2950,7 +2954,7 @@ public final class FilePath implements Serializable { private static void checkPermissionForValidate() { AccessControlled subject = Stapler.getCurrentRequest().findAncestorObject(AbstractProject.class); if (subject == null) - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); else subject.checkPermission(Item.CONFIGURE); } @@ -2985,18 +2989,26 @@ public final class FilePath implements Serializable { } private void writeObject(ObjectOutputStream oos) throws IOException { - Channel target = Channel.current(); - - if(channel!=null && channel!=target) - throw new IllegalStateException("Can't send a remote FilePath to a different remote channel"); + Channel target = _getChannelForSerialization(); + if (channel != null && channel != target) { + throw new IllegalStateException("Can't send a remote FilePath to a different remote channel (current=" + channel + ", target=" + target + ")"); + } oos.defaultWriteObject(); oos.writeBoolean(channel==null); } + private Channel _getChannelForSerialization() { + try { + return getChannelForSerialization(); + } catch (NotSerializableException x) { + LOGGER.log(Level.WARNING, "A FilePath object is being serialized when it should not be, indicating a bug in a plugin. See https://jenkins.io/redirect/filepath-serialization for details.", x); + return null; + } + } + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - Channel channel = Channel.current(); - assert channel!=null; + Channel channel = _getChannelForSerialization(); ois.defaultReadObject(); if(ois.readBoolean()) { @@ -3263,10 +3275,15 @@ public final class FilePath implements Serializable { Path parentAbsolutePath = Util.fileToPath(parentFile.getAbsoluteFile()); Path parentRealPath; try { - parentRealPath = parentAbsolutePath.toRealPath(); + if (Functions.isWindows()) { + parentRealPath = this.windowsToRealPath(parentAbsolutePath); + } else { + parentRealPath = parentAbsolutePath.toRealPath(); + } } - catch(NoSuchFileException e) { - throw new IllegalArgumentException("The parent does not exist"); + catch (NoSuchFileException e) { + LOGGER.log(Level.FINE, String.format("Cannot find the real path to the parentFile: %s", parentAbsolutePath), e); + return false; } // example: "a/b/c" that will become "b/c" then just "c", and finally an empty string @@ -3297,16 +3314,20 @@ public final class FilePath implements Serializable { try{ Path child = currentFileAbsolutePath.toRealPath(); if (!child.startsWith(parentRealPath)) { + LOGGER.log(Level.FINE, "Child [{0}] does not start with parent [{1}] => not descendant", new Object[]{ child, parentRealPath }); return false; } } catch (NoSuchFileException e) { - // nonexistent file + // nonexistent file / Windows Server 2016 + MSFT docker // in case this folder / file will be copied somewhere else, // it becomes the responsibility of that system to check the isDescendant with the existing links // we are not taking the parentRealPath to avoid possible problem Path child = currentFileAbsolutePath.normalize(); Path parent = parentAbsolutePath.normalize(); return child.startsWith(parent); + } catch(FileSystemException e) { + LOGGER.log(Level.WARNING, String.format("Problem during call to the method toRealPath on %s", currentFileAbsolutePath), e); + return false; } } @@ -3320,6 +3341,21 @@ public final class FilePath implements Serializable { } return current; } + + private @Nonnull Path windowsToRealPath(@Nonnull Path path) throws IOException { + try { + return path.toRealPath(); + } + catch (IOException e) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, String.format("relaxedToRealPath cannot use the regular toRealPath on %s, trying with toRealPath(LinkOption.NOFOLLOW_LINKS)", path), e); + } + } + + // that's required for specific environment like Windows Server 2016, running MSFT docker + // where the root is a + return path.toRealPath(LinkOption.NOFOLLOW_LINKS); + } } private static final SoloFilePathFilter UNRESTRICTED = SoloFilePathFilter.wrap(FilePathFilter.UNRESTRICTED); diff --git a/core/src/main/java/hudson/FileSystemProvisioner.java b/core/src/main/java/hudson/FileSystemProvisioner.java index 13f55b7370028832eba66ea7e16af84cb19547aa..153b9397b31ba9857487e30a14a0ff229737b8fd 100644 --- a/core/src/main/java/hudson/FileSystemProvisioner.java +++ b/core/src/main/java/hudson/FileSystemProvisioner.java @@ -40,7 +40,6 @@ import org.jenkinsci.Symbol; import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -178,7 +177,7 @@ public abstract class FileSystemProvisioner implements ExtensionPoint, Describab public abstract WorkspaceSnapshot snapshot(AbstractBuild build, FilePath ws, String glob, TaskListener listener) throws IOException, InterruptedException; public FileSystemProvisionerDescriptor getDescriptor() { - return (FileSystemProvisionerDescriptor) Jenkins.getInstance().getDescriptorOrDie(getClass()); + return (FileSystemProvisionerDescriptor) Jenkins.get().getDescriptorOrDie(getClass()); } /** @@ -190,7 +189,7 @@ public abstract class FileSystemProvisioner implements ExtensionPoint, Describab * Returns all the registered {@link FileSystemProvisioner} descriptors. */ public static DescriptorExtensionList all() { - return Jenkins.getInstance().getDescriptorList(FileSystemProvisioner.class); + return Jenkins.get().getDescriptorList(FileSystemProvisioner.class); } /** diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index a2275d7d1fbbe8ad66808c8a92bd98d259ec5f16..003c5e9ddb08cf48fcf7b22623a6f4a9f2ffdcd8 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -240,6 +240,7 @@ public class Functions { public static void initPageVariables(JellyContext context) { StaplerRequest currentRequest = Stapler.getCurrentRequest(); + currentRequest.getWebApp().getDispatchValidator().allowDispatch(currentRequest, Stapler.getCurrentResponse()); String rootURL = currentRequest.getContextPath(); Functions h = new Functions(); @@ -287,7 +288,7 @@ public class Functions { } public JDK.DescriptorImpl getJDKDescriptor() { - return Jenkins.getInstance().getDescriptorByType(JDK.DescriptorImpl.class); + return Jenkins.get().getDescriptorByType(JDK.DescriptorImpl.class); } /** @@ -465,7 +466,7 @@ public class Functions { } public static Map getSystemProperties() { - return new TreeMap(System.getProperties()); + return new TreeMap<>(System.getProperties()); } /** @@ -479,7 +480,7 @@ public class Functions { } public static Map getEnvVars() { - return new TreeMap(EnvVars.masterEnvVars); + return new TreeMap<>(EnvVars.masterEnvVars); } public static boolean isWindows() { @@ -544,7 +545,7 @@ public class Functions { * @since 1.525 */ public static Iterable reverse(Collection collection) { - List list = new ArrayList(collection); + List list = new ArrayList<>(collection); Collections.reverse(list); return list; } @@ -762,7 +763,7 @@ public class Functions { } public static void checkPermission(Permission permission) throws IOException, ServletException { - checkPermission(Jenkins.getInstance(),permission); + checkPermission(Jenkins.get(),permission); } public static void checkPermission(AccessControlled object, Permission permission) throws IOException, ServletException { @@ -791,7 +792,7 @@ public class Functions { return; } } - checkPermission(Jenkins.getInstance(),permission); + checkPermission(Jenkins.get(),permission); } } @@ -802,7 +803,7 @@ public class Functions { * If null, returns true. This defaulting is convenient in making the use of this method terse. */ public static boolean hasPermission(Permission permission) throws IOException, ServletException { - return hasPermission(Jenkins.getInstance(),permission); + return hasPermission(Jenkins.get(),permission); } /** @@ -822,7 +823,7 @@ public class Functions { return ((AccessControlled)o).hasPermission(permission); } } - return Jenkins.getInstance().hasPermission(permission); + return Jenkins.get().hasPermission(permission); } } @@ -845,7 +846,7 @@ public class Functions { * Infers the hudson installation URL from the given request. */ public static String inferHudsonURL(StaplerRequest req) { - String rootUrl = Jenkins.getInstance().getRootUrl(); + String rootUrl = Jenkins.get().getRootUrl(); if(rootUrl !=null) // prefer the one explicitly configured, to work with load-balancer, frontend, etc. return rootUrl; @@ -912,7 +913,7 @@ public class Functions { @Restricted(DoNotUse.class) @RestrictedSince("2.12") public static List> getComputerLauncherDescriptors() { - return Jenkins.getInstance().>getDescriptorList(ComputerLauncher.class); + return Jenkins.get().>getDescriptorList(ComputerLauncher.class); } /** @@ -951,7 +952,7 @@ public class Functions { @RestrictedSince("2.12") public static List getNodePropertyDescriptors(Class clazz) { List result = new ArrayList(); - Collection list = (Collection) Jenkins.getInstance().getDescriptorList(NodeProperty.class); + Collection list = (Collection) Jenkins.get().getDescriptorList(NodeProperty.class); for (NodePropertyDescriptor npd : list) { if (npd.isApplicable(clazz)) { result.add(npd); @@ -967,7 +968,7 @@ public class Functions { */ public static List getGlobalNodePropertyDescriptors() { List result = new ArrayList(); - Collection list = (Collection) Jenkins.getInstance().getDescriptorList(NodeProperty.class); + Collection list = (Collection) Jenkins.get().getDescriptorList(NodeProperty.class); for (NodePropertyDescriptor npd : list) { if (npd.isApplicableAsGlobal()) { result.add(npd); @@ -1010,7 +1011,7 @@ public class Functions { List answer = new ArrayList(r.size()); for (Tag d : r) answer.add(d.d); - return DescriptorVisibilityFilter.apply(Jenkins.getInstance(),answer); + return DescriptorVisibilityFilter.apply(Jenkins.get(),answer); } /** @@ -1110,7 +1111,7 @@ public class Functions { ItemGroup ig = i.getParent(); url = i.getShortUrl()+url; - if(ig== Jenkins.getInstance() || (view != null && ig == view.getOwner().getItemGroup())) { + if(ig== Jenkins.get() || (view != null && ig == view.getOwner().getItemGroup())) { assert i instanceof TopLevelItem; if (view != null) { // assume p and the current page belong to the same view, so return a relative path @@ -1556,30 +1557,13 @@ public class Functions { /** * Converts the Hudson build status to CruiseControl build status, * which is either Success, Failure, Exception, or Unknown. + * + * @deprecated This functionality has been moved to ccxml plugin. */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("since TODO") public static String toCCStatus(Item i) { - if (i instanceof Job) { - Job j = (Job) i; - switch (j.getIconColor()) { - case ABORTED: - case ABORTED_ANIME: - case RED: - case RED_ANIME: - case YELLOW: - case YELLOW_ANIME: - return "Failure"; - case BLUE: - case BLUE_ANIME: - return "Success"; - case DISABLED: - case DISABLED_ANIME: - case GREY: - case GREY_ANIME: - case NOTBUILT: - case NOTBUILT_ANIME: - return "Unknown"; - } - } return "Unknown"; } @@ -1699,7 +1683,7 @@ public class Functions { public String getServerName() { // Try to infer this from the configured root URL. // This makes it work correctly when Hudson runs behind a reverse proxy. - String url = Jenkins.getInstance().getRootUrl(); + String url = Jenkins.get().getRootUrl(); try { if(url!=null) { String host = new URL(url).getHost(); @@ -2006,7 +1990,7 @@ public class Functions { if(size < 1024){ return size + " " + measure; } - Double number = new Double(size); + double number = size; if(number>=1024){ number = number/1024; measure = "KB"; @@ -2071,14 +2055,4 @@ public class Functions { return true; } } - - @Restricted(NoExternalUse.class) // for cc.xml.jelly - public static Collection getCCItems(View v) { - if (Stapler.getCurrentRequest().getParameter("recursive") != null) { - return v.getOwner().getItemGroup().getAllItems(TopLevelItem.class); - } else { - return v.getItems(); - } - } - } diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java index f0f18b4f81df12bb56440e68872bf0871fca1a55..278d03c410d385fbe4c09161a54705a11d08a42a 100644 --- a/core/src/main/java/hudson/Launcher.java +++ b/core/src/main/java/hudson/Launcher.java @@ -147,7 +147,7 @@ public abstract class Launcher { @Deprecated @CheckForNull public Computer getComputer() { - for( Computer c : Jenkins.getInstance().getComputers() ) + for( Computer c : Jenkins.get().getComputers() ) if(c.getChannel()==channel) return c; return null; @@ -197,14 +197,14 @@ public abstract class Launcher { } public ProcStarter cmds(File program, String... args) { - commands = new ArrayList(args.length+1); + commands = new ArrayList<>(args.length + 1); commands.add(program.getPath()); commands.addAll(Arrays.asList(args)); return this; } public ProcStarter cmds(List args) { - commands = new ArrayList(args); + commands = new ArrayList<>(args); return this; } @@ -761,6 +761,7 @@ public abstract class Launcher { buf.append(c); } listener.getLogger().println(buf.toString()); + listener.getLogger().flush(); } /** @@ -774,7 +775,7 @@ public abstract class Launcher { */ protected final void maskedPrintCommandLine(@Nonnull List cmd, @CheckForNull boolean[] mask, @CheckForNull FilePath workDir) { if(mask==null) { - printCommandLine(cmd.toArray(new String[cmd.size()]),workDir); + printCommandLine(cmd.toArray(new String[0]),workDir); return; } @@ -1085,6 +1086,11 @@ public abstract class Launcher { getChannel().call(new KillTask(modelEnvVars)); } + @Override + public String toString() { + return "RemoteLauncher[" + getChannel() + "]"; + } + private static final class KillTask extends MasterToSlaveCallable { private final Map modelEnvVars; diff --git a/core/src/main/java/hudson/LocalPluginManager.java b/core/src/main/java/hudson/LocalPluginManager.java index bde452ca7aa58713435fa70c8d09fd84d676bcc1..4c5e757362b65b1543aacb86392c7e864275b0f9 100644 --- a/core/src/main/java/hudson/LocalPluginManager.java +++ b/core/src/main/java/hudson/LocalPluginManager.java @@ -43,7 +43,7 @@ import java.util.logging.Logger; public class LocalPluginManager extends PluginManager { /** * Creates a new LocalPluginManager - * @param context Servlet context. Provided for compatibility as {@code Jenkins.getInstance().servletContext} should be used. + * @param context Servlet context. Provided for compatibility as {@code Jenkins.get().servletContext} should be used. * @param rootDir Jenkins home directory. */ public LocalPluginManager(@CheckForNull ServletContext context, @NonNull File rootDir) { @@ -66,12 +66,6 @@ public class LocalPluginManager extends PluginManager { this(null, rootDir); } - /** - * If the war file has any "/WEB-INF/plugins/*.jpi", extract them into the plugin directory. - * - * @return - * File names of the bundled plugins. Like {"ssh-slaves.jpi","subversion.jpi"} - */ @Override protected Collection loadBundledPlugins() { // this is used in tests, when we want to override the default bundled plugins with .jpl (or .hpl) versions diff --git a/core/src/main/java/hudson/Lookup.java b/core/src/main/java/hudson/Lookup.java index 090f3eb28449490f2ed72c8d675a0cc21e773301..b0b40ba89151d9d5a9f8a1a5e55c70b0c89f99c5 100644 --- a/core/src/main/java/hudson/Lookup.java +++ b/core/src/main/java/hudson/Lookup.java @@ -32,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; * @author Kohsuke Kawaguchi */ public class Lookup { - private final ConcurrentHashMap data = new ConcurrentHashMap(); + private final ConcurrentHashMap data = new ConcurrentHashMap<>(); public T get(Class type) { return type.cast(data.get(type)); diff --git a/core/src/main/java/hudson/Main.java b/core/src/main/java/hudson/Main.java index 11cb516cee31be90a3f118b308f27179083f9d5a..14ae9ee38f775574416bb650b641a40b95113de5 100644 --- a/core/src/main/java/hudson/Main.java +++ b/core/src/main/java/hudson/Main.java @@ -25,6 +25,7 @@ package hudson; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.InvalidPathException; import jenkins.util.SystemProperties; @@ -41,6 +42,7 @@ import java.net.HttpRetryException; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.nio.charset.Charset; @@ -97,7 +99,7 @@ public class Main { // check for authentication info String auth = new URL(home).getUserInfo(); - if(auth != null) auth = "Basic " + new Base64Encoder().encode(auth.getBytes("UTF-8")); + if(auth != null) auth = "Basic " + new Base64Encoder().encode(auth.getBytes(StandardCharsets.UTF_8)); {// check if the home is set correctly HttpURLConnection con = open(new URL(home)); @@ -143,7 +145,7 @@ public class Main { try { int ret; try (OutputStream os = Files.newOutputStream(tmpFile.toPath()); - Writer w = new OutputStreamWriter(os,"UTF-8")) { + Writer w = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { w.write(""); w.write(""); w.flush(); @@ -151,9 +153,8 @@ public class Main { // run the command long start = System.currentTimeMillis(); - List cmd = new ArrayList(); - for( int i=1; i cmd = new ArrayList<>(); + cmd.addAll(Arrays.asList(args).subList(1, args.length)); Proc proc = new Proc.LocalProc(cmd.toArray(new String[0]),(String[])null,System.in, new DualOutputStream(System.out,new EncodingStream(os))); diff --git a/core/src/main/java/hudson/MarkupText.java b/core/src/main/java/hudson/MarkupText.java index 12a9b3b8484c2f538d658459259fc4f03fed27fe..b83301a0acb154d5cbe9927c702638c7fb5a5fd6 100644 --- a/core/src/main/java/hudson/MarkupText.java +++ b/core/src/main/java/hudson/MarkupText.java @@ -45,7 +45,7 @@ public class MarkupText extends AbstractMarkupText { /** * Added mark up tags. */ - private final List tags = new ArrayList(); + private final List tags = new ArrayList<>(); /** * Represents one mark up inserted into text. @@ -313,7 +313,7 @@ public class MarkupText extends AbstractMarkupText { buf.append(tag.markup); } if (copied { * Finds the closest name match by its ID. */ public Permalink findNearest(String id) { - List ids = new ArrayList(); + List ids = new ArrayList<>(); for (Permalink p : this) ids.add(p.getId()); String nearest = EditDistance.findNearest(id, ids); diff --git a/core/src/main/java/hudson/Platform.java b/core/src/main/java/hudson/Platform.java index 718570f58983d6e5319b09936752892544a521f4..080538693da45598fac960e5b6bedb41c71de121 100644 --- a/core/src/main/java/hudson/Platform.java +++ b/core/src/main/java/hudson/Platform.java @@ -47,7 +47,7 @@ public enum Platform { */ public final char pathSeparator; - private Platform(char pathSeparator) { + Platform(char pathSeparator) { this.pathSeparator = pathSeparator; } diff --git a/core/src/main/java/hudson/Plugin.java b/core/src/main/java/hudson/Plugin.java index 2f065102becc4c8a3a247a9869a6ebb8a0150844..1a3ed3dc8341f07e00e112825e1bd0869db5822d 100644 --- a/core/src/main/java/hudson/Plugin.java +++ b/core/src/main/java/hudson/Plugin.java @@ -290,7 +290,7 @@ public abstract class Plugin implements Saveable { */ protected XmlFile getConfigXml() { return new XmlFile(Jenkins.XSTREAM, - new File(Jenkins.getInstance().getRootDir(),wrapper.getShortName()+".xml")); + new File(Jenkins.get().getRootDir(),wrapper.getShortName()+".xml")); } diff --git a/core/src/main/java/hudson/PluginFirstClassLoader.java b/core/src/main/java/hudson/PluginFirstClassLoader.java index 018e5ea1987949aab163562969fe99e29cc89233..31c93a1fd36dbb629308b48a85a095cd2bd89ee6 100644 --- a/core/src/main/java/hudson/PluginFirstClassLoader.java +++ b/core/src/main/java/hudson/PluginFirstClassLoader.java @@ -80,29 +80,25 @@ public class PluginFirstClassLoader protected Enumeration findResources( String arg0, boolean arg1 ) throws IOException { - Enumeration enu = super.findResources( arg0, arg1 ); - return enu; + return super.findResources( arg0, arg1 ); } @Override protected Enumeration findResources( String name ) throws IOException { - Enumeration enu = super.findResources( name ); - return enu; + return super.findResources( name ); } @Override public URL getResource( String arg0 ) { - URL url = super.getResource( arg0 ); - return url; + return super.getResource( arg0 ); } @Override public InputStream getResourceAsStream( String name ) { - InputStream is = super.getResourceAsStream( name ); - return is; + return super.getResourceAsStream( name ); } } diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index d6f8f794f6e0142fb99510eab13716787e79a240..75e0873c4380fe057088bdd01a4376776251d0b0 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -62,7 +62,9 @@ import jenkins.YesNoMaybe; import jenkins.install.InstallState; import jenkins.install.InstallUtil; import jenkins.model.Jenkins; +import jenkins.plugins.DetachedPluginsUtil; import jenkins.security.CustomClassFilter; +import jenkins.telemetry.impl.java11.MissingClassTelemetry; import jenkins.util.SystemProperties; import jenkins.util.io.OnMaster; import jenkins.util.xml.RestrictiveEntityResolver; @@ -82,7 +84,6 @@ import org.jenkinsci.Symbol; import org.jenkinsci.bytecode.Transformer; import org.jvnet.hudson.reactor.Executable; import org.jvnet.hudson.reactor.Reactor; -import org.jvnet.hudson.reactor.ReactorException; import org.jvnet.hudson.reactor.TaskBuilder; import org.jvnet.hudson.reactor.TaskGraphBuilder; import org.kohsuke.accmod.Restricted; @@ -148,6 +149,7 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import static hudson.init.InitMilestone.*; import static java.util.logging.Level.*; @@ -283,9 +285,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas /** * All active plugins, topologically sorted so that when X depends on Y, Y appears in the list before X does. */ - protected final List activePlugins = new CopyOnWriteArrayList(); + protected final List activePlugins = new CopyOnWriteArrayList<>(); - protected final List failedPlugins = new ArrayList(); + protected final List failedPlugins = new ArrayList<>(); /** * Plug-in root directory. @@ -368,7 +370,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } public Api getApi() { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); return new Api(this); } @@ -421,7 +423,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas // once we've listed plugins, we can fill in the reactor with plugin-specific initialization tasks TaskGraphBuilder g = new TaskGraphBuilder(); - final Map inspectedShortNames = new HashMap(); + final Map inspectedShortNames = new HashMap<>(); for( final File arc : archives ) { g.followedBy().notFatal().attains(PLUGINS_LISTED).add("Inspecting plugin " + arc, new Executable() { @@ -464,7 +466,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas CyclicGraphDetector cgd = new CyclicGraphDetector() { @Override protected List getEdges(PluginWrapper p) { - List next = new ArrayList(); + List next = new ArrayList<>(); addTo(p.getDependencies(), next); addTo(p.getOptionalDependencies(), next); return next; @@ -532,11 +534,11 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * Once the plugins are listed, schedule their initialization. */ public void run(Reactor session) throws Exception { - Jenkins.getInstance().lookup.set(PluginInstanceStore.class, new PluginInstanceStore()); + Jenkins.get().lookup.set(PluginInstanceStore.class, new PluginInstanceStore()); TaskGraphBuilder g = new TaskGraphBuilder(); // schedule execution of loading plugins - for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) { + for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[0])) { g.followedBy().notFatal().attains(PLUGINS_PREPARED).add(String.format("Loading plugin %s v%s (%s)", p.getLongName(), p.getVersion(), p.getShortName()), new Executable() { public void run(Reactor session) throws Exception { try { @@ -559,7 +561,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } // schedule execution of initializing plugins - for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) { + for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[0])) { g.followedBy().notFatal().attains(PLUGINS_STARTED).add("Initializing plugin " + p.getShortName(), new Executable() { public void run(Reactor session) throws Exception { if (!activePlugins.contains(p)) { @@ -590,15 +592,37 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas }); // All plugins are loaded. Now we can figure out who depends on who. - requires(PLUGINS_PREPARED).attains(COMPLETED).add("Resolving Dependant Plugins Graph", new Executable() { + requires(PLUGINS_PREPARED).attains(COMPLETED).add("Resolving Dependent Plugins Graph", new Executable() { @Override public void run(Reactor reactor) throws Exception { - resolveDependantPlugins(); + resolveDependentPlugins(); } }); }}); } + void considerDetachedPlugin(String shortName) { + if (new File(rootDir, shortName + ".jpi").isFile() || + new File(rootDir, shortName + ".hpi").isFile() || + new File(rootDir, shortName + ".jpl").isFile() || + new File(rootDir, shortName + ".hpl").isFile()) { + LOGGER.fine(() -> "not considering loading a detached dependency " + shortName + " as it is already on disk"); + return; + } + LOGGER.fine(() -> "considering loading a detached dependency " + shortName); + for (String loadedFile : loadPluginsFromWar("/WEB-INF/detached-plugins", (dir, name) -> normalisePluginName(name).equals(shortName))) { + String loaded = normalisePluginName(loadedFile); + File arc = new File(rootDir, loaded + ".jpi"); + LOGGER.info(() -> "Loading a detached plugin as a dependency: " + arc); + try { + plugins.add(strategy.createPluginWrapper(arc)); + } catch (IOException e) { + failedPlugins.add(new FailedPlugin(arc.getName(), e)); + } + + } + } + protected @Nonnull Set loadPluginsFromWar(@Nonnull String fromPath) { return loadPluginsFromWar(fromPath, null); } @@ -609,7 +633,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas Set names = new HashSet(); ServletContext context = Jenkins.getActiveInstance().servletContext; - Set plugins = Util.fixNull((Set) context.getResourcePaths(fromPath)); + Set plugins = Util.fixNull(context.getResourcePaths(fromPath)); Set copiedPlugins = new HashSet<>(); Set dependencies = new HashSet<>(); @@ -725,7 +749,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas LOGGER.log(INFO, "Upgrading Jenkins. The last running version was {0}. This Jenkins is version {1}.", new Object[] {lastExecVersion, Jenkins.VERSION}); - final List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(lastExecVersion); + final List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(lastExecVersion); Set loadedDetached = loadPluginsFromWar("/WEB-INF/detached-plugins", new FilenameFilter() { @Override @@ -734,7 +758,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas // If this was a plugin that was detached some time in the past i.e. not just one of the // plugins that was bundled "for fun". - if (ClassicPluginStrategy.isDetachedPlugin(name)) { + if (DetachedPluginsUtil.isDetachedPlugin(name)) { VersionNumber installedVersion = getPluginVersion(rootDir, name); VersionNumber bundledVersion = getPluginVersion(dir, name); // If the plugin is already installed, we need to decide whether to replace it with the bundled version. @@ -747,7 +771,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } // If it's a plugin that was detached since the last running version. - for (ClassicPluginStrategy.DetachedPlugin detachedPlugin : detachedPlugins) { + for (DetachedPluginsUtil.DetachedPlugin detachedPlugin : detachedPlugins) { if (detachedPlugin.getShortName().equals(name)) { return true; } @@ -763,9 +787,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas InstallUtil.saveLastExecVersion(); } else { - final Set forceUpgrade = new HashSet<>(); + final Set forceUpgrade = new HashSet<>(); // TODO using getDetachedPlugins here seems wrong; should be forcing an upgrade when the installed version is older than that in WEB-INF/detached-plugins/ - for (ClassicPluginStrategy.DetachedPlugin p : ClassicPluginStrategy.getDetachedPlugins()) { + for (DetachedPluginsUtil.DetachedPlugin p : DetachedPluginsUtil.getDetachedPlugins()) { VersionNumber installedVersion = getPluginVersion(rootDir, p.getShortName()); VersionNumber requiredVersion = p.getRequiredVersion(); if (installedVersion != null && installedVersion.isOlderThan(requiredVersion)) { @@ -780,7 +804,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Override public boolean accept(File dir, String name) { name = normalisePluginName(name); - for (ClassicPluginStrategy.DetachedPlugin detachedPlugin : forceUpgrade) { + for (DetachedPluginsUtil.DetachedPlugin detachedPlugin : forceUpgrade) { if (detachedPlugin.getShortName().equals(name)) { return true; } @@ -848,16 +872,16 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * TODO: revisit where/how to expose this. This is an experiment. */ public void dynamicLoad(File arc) throws IOException, InterruptedException, RestartRequiredException { - dynamicLoad(arc, false); + dynamicLoad(arc, false, null); } /** * Try the dynamicLoad, removeExisting to attempt to dynamic load disabled plugins */ @Restricted(NoExternalUse.class) - public void dynamicLoad(File arc, boolean removeExisting) throws IOException, InterruptedException, RestartRequiredException { + public void dynamicLoad(File arc, boolean removeExisting, @CheckForNull List batch) throws IOException, InterruptedException, RestartRequiredException { try (ACLContext context = ACL.as(ACL.SYSTEM)) { - LOGGER.info("Attempting to dynamic load "+arc); + LOGGER.log(FINE, "Attempting to dynamic load {0}", arc); PluginWrapper p = null; String sn; try { @@ -874,7 +898,6 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas pw = i.next(); if(sn.equals(pw.getShortName())) { i.remove(); - pw = null; break; } } @@ -905,9 +928,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas p.resolvePluginDependencies(); strategy.load(p); - Jenkins.getInstance().refreshExtensions(); + if (batch != null) { + batch.add(p); + } else { + start(Collections.singletonList(p)); + } - p.getPlugin().postInitialize(); } catch (Exception e) { failedPlugins.add(new FailedPlugin(sn, e)); activePlugins.remove(p); @@ -915,84 +941,93 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas throw new IOException("Failed to install "+ sn +" plugin",e); } - // run initializers in the added plugin - Reactor r = new Reactor(InitMilestone.ordering()); - final ClassLoader loader = p.classLoader; - r.addAll(new InitializerFinder(loader) { - @Override - protected boolean filter(Method e) { - return e.getDeclaringClass().getClassLoader() != loader || super.filter(e); - } - }.discoverTasks(r)); - try { - new InitReactorRunner().run(r); - } catch (ReactorException e) { - throw new IOException("Failed to initialize "+ sn +" plugin",e); + LOGGER.log(FINE, "Plugin {0}:{1} dynamically {2}", new Object[] {p.getShortName(), p.getVersion(), batch != null ? "loaded but not yet started" : "installed"}); + } + } + + @Restricted(NoExternalUse.class) + public void start(List plugins) throws Exception { + 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); - // recalculate dependencies of plugins optionally depending the newly deployed one. - for (PluginWrapper depender: plugins) { - if (depender.equals(p)) { - // skip itself. - continue; - } - for (Dependency d: depender.getOptionalDependencies()) { - if (d.shortName.equals(p.getShortName())) { - // this plugin depends on the newly loaded one! - // recalculate dependencies! - getPluginStrategy().updateDependency(depender, p); - break; - } + Map pluginsByName = plugins.stream().collect(Collectors.toMap(p -> p.getShortName(), p -> p)); + + // recalculate dependencies of plugins optionally depending the newly deployed ones. + for (PluginWrapper depender: this.plugins) { + if (plugins.contains(depender)) { + // skip itself. + continue; + } + for (Dependency d: depender.getOptionalDependencies()) { + PluginWrapper dependee = pluginsByName.get(d.shortName); + if (dependee != null) { + // this plugin depends on the newly loaded one! + // recalculate dependencies! + getPluginStrategy().updateDependency(depender, dependee); + break; } } + } - // Redo who depends on who. - resolveDependantPlugins(); + // Redo who depends on who. + resolveDependentPlugins(); - try { - Jenkins.get().refreshExtensions(); - } catch (ExtensionRefreshException e) { - throw new IOException("Failed to refresh extensions after installing " + sn + " plugin", e); - } - LOGGER.info("Plugin " + p.getShortName()+":"+p.getVersion() + " dynamically installed"); + try { + Jenkins.get().refreshExtensions(); + } catch (ExtensionRefreshException e) { + throw new IOException("Failed to refresh extensions after installing some plugins", e); } } @Restricted(NoExternalUse.class) - public synchronized void resolveDependantPlugins() { + public synchronized void resolveDependentPlugins() { for (PluginWrapper plugin : plugins) { - // Set of optional dependants plugins of plugin - Set optionalDependants = new HashSet<>(); - Set dependants = new HashSet<>(); - for (PluginWrapper possibleDependant : plugins) { - // No need to check if plugin is dependant of itself - if(possibleDependant.getShortName().equals(plugin.getShortName())) { + // Set of optional dependents plugins of plugin + Set optionalDependents = new HashSet<>(); + Set dependents = new HashSet<>(); + for (PluginWrapper possibleDependent : plugins) { + // No need to check if plugin is dependent of itself + if(possibleDependent.getShortName().equals(plugin.getShortName())) { continue; } // The plugin could have just been deleted. If so, it doesn't - // count as a dependant. - if (possibleDependant.isDeleted()) { + // count as a dependent. + if (possibleDependent.isDeleted()) { continue; } - List dependencies = possibleDependant.getDependencies(); + List dependencies = possibleDependent.getDependencies(); for (Dependency dependency : dependencies) { if (dependency.shortName.equals(plugin.getShortName())) { - dependants.add(possibleDependant.getShortName()); + dependents.add(possibleDependent.getShortName()); - // If, in addition, the dependency is optional, add to the optionalDependants list + // If, in addition, the dependency is optional, add to the optionalDependents list if (dependency.optional) { - optionalDependants.add(possibleDependant.getShortName()); + optionalDependents.add(possibleDependent.getShortName()); } - // already know possibleDependant depends on plugin, no need to continue with the rest of - // dependencies. We continue with the next possibleDependant + // already know possibleDependent depends on plugin, no need to continue with the rest of + // dependencies. We continue with the next possibleDependent break; } } } - plugin.setDependants(dependants); - plugin.setOptionalDependants(optionalDependants); + plugin.setDependents(dependents); + plugin.setOptionalDependents(optionalDependents); } } @@ -1000,15 +1035,18 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * If the war file has any "/WEB-INF/plugins/[*.jpi | *.hpi]", extract them into the plugin directory. * * @return - * File names of the bundled plugins. Like {"ssh-slaves.hpi","subversion.jpi"} + * File names of the bundled plugins. Normally empty (not to be confused with {@link #loadDetachedPlugins}) but OEM WARs may have some. * @throws Exception * Any exception will be reported and halt the startup. */ protected abstract Collection loadBundledPlugins() throws Exception; /** - * Copies the bundled plugin from the given URL to the destination of the given file name (like 'abc.jpi'), - * with a reasonable up-to-date check. A convenience method to be used by the {@link #loadBundledPlugins()}. + * Copies the plugin from the given URL to the given destination. + * Despite the name, this is used also from {@link #loadDetachedPlugins}. + * Includes a reasonable up-to-date check. + * A convenience method to be used by {@link #loadBundledPlugins()}. + * @param fileName like {@code abc.jpi} */ protected void copyBundledPlugin(URL src, String fileName) throws IOException { LOGGER.log(FINE, "Copying {0}", src); @@ -1044,8 +1082,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas URL res = cl.findResource(PluginWrapper.MANIFEST_FILENAME); if (res!=null) { in = getBundledJpiManifestStream(res); - Manifest manifest = new Manifest(in); - return manifest; + return new Manifest(in); } } finally { Util.closeAndLogFailures(in, LOGGER, PluginWrapper.MANIFEST_FILENAME, bundledJpi.toString()); @@ -1116,7 +1153,6 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas final JarURLConnection connection = (JarURLConnection) uc; final URL jarURL = connection.getJarFileURL(); if (jarURL.getProtocol().equals("file")) { - uc = null; String file = jarURL.getFile(); return new File(file).lastModified(); } else { @@ -1241,7 +1277,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * @return The list of plugins implementing the specified class. */ public List getPlugins(Class pluginSuperclass) { - List result = new ArrayList(); + List result = new ArrayList<>(); for (PluginWrapper p : getPlugins()) { if(pluginSuperclass.isInstance(p.getPlugin())) result.add(p); @@ -1264,7 +1300,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas */ @Deprecated public Collection> discover( Class spi ) { - Set> result = new HashSet>(); + Set> result = new HashSet<>(); for (PluginWrapper p : activePlugins) { Service.load(spi, p.classLoader, result); @@ -1311,7 +1347,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas */ @Restricted(DoNotUse.class) // WebOnly public HttpResponse doPlugins() { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); JSONArray response = new JSONArray(); Map allPlugins = new HashMap<>(); for (PluginWrapper plugin : plugins) { @@ -1358,10 +1394,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @RequirePOST public HttpResponse doUpdateSources(StaplerRequest req) throws IOException { - Jenkins.getInstance().checkPermission(CONFIGURE_UPDATECENTER); + Jenkins.get().checkPermission(CONFIGURE_UPDATECENTER); if (req.hasParameter("remove")) { - UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); + UpdateCenter uc = Jenkins.get().getUpdateCenter(); BulkChange bc = new BulkChange(uc); try { for (String id : req.getParameterValues("sources")) @@ -1383,7 +1419,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @RequirePOST @Restricted(DoNotUse.class) // WebOnly public void doInstallPluginsDone() { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); j.checkPermission(Jenkins.ADMINISTER); InstallUtil.proceedToNextStateFrom(InstallState.INITIAL_PLUGINS_INSTALLING); } @@ -1393,7 +1429,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas */ @RequirePOST public void doInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); Set plugins = new LinkedHashSet<>(); Enumeration en = req.getParameterNames(); @@ -1422,7 +1458,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @RequirePOST @Restricted(DoNotUse.class) // WebOnly public HttpResponse doInstallPlugins(StaplerRequest req) throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); String payload = IOUtils.toString(req.getInputStream(), req.getCharacterEncoding()); JSONObject request = JSONObject.fromObject(payload); JSONArray pluginListJSON = request.getJSONArray("plugins"); @@ -1463,6 +1499,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas private List> install(@Nonnull Collection plugins, boolean dynamicLoad, @CheckForNull UUID correlationId) { List> installJobs = new ArrayList<>(); + LOGGER.log(INFO, "Starting installation of a batch of {0} plugins plus their dependencies", plugins.size()); + long start = System.nanoTime(); + List batch = new ArrayList<>(); + for (String n : plugins) { // JENKINS-22080 plugin names can contain '.' as could (according to rumour) update sites int index = n.indexOf('.'); @@ -1494,21 +1534,20 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas if (p == null) { throw new Failure("No such plugin: " + n); } - Future jobFuture = p.deploy(dynamicLoad, correlationId); + Future jobFuture = p.deploy(dynamicLoad, correlationId, batch); installJobs.add(jobFuture); } - trackInitialPluginInstall(installJobs); + final Jenkins jenkins = Jenkins.get(); + final UpdateCenter updateCenter = jenkins.getUpdateCenter(); - return installJobs; - } + if (dynamicLoad) { + installJobs.add(updateCenter.addJob(updateCenter.new CompleteBatchJob(batch, start, correlationId))); + } - private void trackInitialPluginInstall(@Nonnull final List> installJobs) { - final Jenkins jenkins = Jenkins.getInstance(); - final UpdateCenter updateCenter = jenkins.getUpdateCenter(); final Authentication currentAuth = Jenkins.getAuthentication(); - if (!Jenkins.getInstance().getInstallState().isSetupComplete()) { + if (!jenkins.getInstallState().isSetupComplete()) { jenkins.setInstallState(InstallState.INITIAL_PLUGINS_INSTALLING); updateCenter.persistInstallStatus(); new Thread() { @@ -1543,36 +1582,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } }.start(); } - - // Fire a one-off thread to wait for the plugins to be deployed and then - // refresh the dependant plugins list. - new Thread() { - @Override - public void run() { - INSTALLING: while (true) { - for (Future deployJob : installJobs) { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - LOGGER.log(SEVERE, "Unexpected error while waiting for some plugins to install. Plugin Manager state may be invalid. Please restart Jenkins ASAP.", e); - } - if (!deployJob.isCancelled() && !deployJob.isDone()) { - // One of the plugins is not installing/canceled, so - // go back to sleep and try again in a while. - continue INSTALLING; - } - } - // All the plugins are installed. It's now safe to refresh. - resolveDependantPlugins(); - break; - } - } - }.start(); - + + return installJobs; } private UpdateSite.Plugin getPlugin(String pluginName, String siteName) { - UpdateSite updateSite = Jenkins.getInstance().getUpdateCenter().getById(siteName); + UpdateSite updateSite = Jenkins.get().getUpdateCenter().getById(siteName); if (updateSite == null) { throw new Failure("No such update center: " + siteName); } @@ -1584,7 +1599,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas */ @RequirePOST public HttpResponse doSiteConfigure(@QueryParameter String site) throws IOException { - Jenkins hudson = Jenkins.getInstance(); + Jenkins hudson = Jenkins.get(); hudson.checkPermission(CONFIGURE_UPDATECENTER); UpdateCenter uc = hudson.getUpdateCenter(); PersistedList sites = uc.getSites(); @@ -1599,7 +1614,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @RequirePOST public HttpResponse doProxyConfigure(StaplerRequest req) throws IOException, ServletException { - Jenkins jenkins = Jenkins.getInstance(); + Jenkins jenkins = Jenkins.get(); jenkins.checkPermission(CONFIGURE_UPDATECENTER); ProxyConfiguration pc = req.bindJSON(ProxyConfiguration.class, req.getSubmittedForm()); @@ -1619,7 +1634,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @RequirePOST public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, ServletException { try { - Jenkins.getInstance().checkPermission(UPLOAD_PLUGINS); + Jenkins.get().checkPermission(UPLOAD_PLUGINS); ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); @@ -1685,7 +1700,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Restricted(NoExternalUse.class) @RequirePOST public HttpResponse doCheckUpdatesServer() throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); // We'll check the update servers with a try-retry mechanism. The retrier is built with a builder Retrier updateServerRetrier = new Retrier.Builder<>( @@ -1770,12 +1785,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas protected String identifyPluginShortName(File t) { try { - JarFile j = new JarFile(t); - try { + try (JarFile j = new JarFile(t)) { String name = j.getManifest().getMainAttributes().getValue("Short-Name"); - if (name!=null) return name; - } finally { - j.close(); + if (name != null) return name; } } catch (IOException e) { LOGGER.log(WARNING, "Failed to identify the short name from "+t,e); @@ -1784,7 +1796,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } public Descriptor getProxyDescriptor() { - return Jenkins.getInstance().getDescriptor(ProxyConfiguration.class); + return Jenkins.get().getDescriptor(ProxyConfiguration.class); } /** @@ -1806,9 +1818,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * @since 1.483 */ public List> prevalidateConfig(InputStream configXml) throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); - List> jobs = new ArrayList>(); - UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + List> jobs = new ArrayList<>(); + UpdateCenter uc = Jenkins.get().getUpdateCenter(); // TODO call uc.updateAllSites() when available? perhaps not, since we should not block on network here for (Map.Entry requestedPlugin : parseRequestedPlugins(configXml).entrySet()) { PluginWrapper pw = getPlugin(requestedPlugin.getKey()); @@ -1868,7 +1880,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas */ @RequirePOST public JSONArray doPrevalidateConfig(StaplerRequest req) throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); JSONArray response = new JSONArray(); @@ -1901,7 +1913,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * Parses configuration XML files and picks up references to XML files. */ public Map parseRequestedPlugins(InputStream configXml) throws IOException { - final Map requestedPlugins = new TreeMap(); + final Map requestedPlugins = new TreeMap<>(); try { SAXParserFactory.newInstance().newSAXParser().parse(configXml, new DefaultHandler() { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { @@ -1941,10 +1953,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } /** - * Disable a list of plugins using a strategy for their dependants plugins. - * @param strategy the strategy regarding how the dependant plugins are processed + * Disable a list of plugins using a strategy for their dependents plugins. + * @param strategy the strategy regarding how the dependent plugins are processed * @param plugins the list of plugins - * @return the list of results for every plugin and their dependant plugins. + * @return the list of results for every plugin and their dependent plugins. * @throws IOException see {@link PluginWrapper#disable()} */ public @NonNull List disablePlugins(@NonNull PluginWrapper.PluginDisableStrategy strategy, @NonNull List plugins) throws IOException { @@ -1981,16 +1993,16 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * Make generated types visible. * Keyed by the generated class name. */ - private ConcurrentMap> generatedClasses = new ConcurrentHashMap>(); + private ConcurrentMap> generatedClasses = new ConcurrentHashMap<>(); /** Cache of loaded, or known to be unloadable, classes. */ - private final Map> loaded = new HashMap>(); + private final Map> loaded = new HashMap<>(); public UberClassLoader() { super(PluginManager.class.getClassLoader()); } public void addNamedClass(String className, Class c) { - generatedClasses.put(className,new WeakReference(c)); + generatedClasses.put(className, new WeakReference<>(c)); } @Override @@ -2048,7 +2060,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas loaded.put(name, null); } // not found in any of the classloader. delegate. - throw new ClassNotFoundException(name); + ClassNotFoundException cnfe = new ClassNotFoundException(name); + MissingClassTelemetry.reportException(name, cnfe); + throw cnfe; } @Override @@ -2071,7 +2085,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Override protected Enumeration findResources(String name) throws IOException { - List resources = new ArrayList(); + List resources = new ArrayList<>(); if (FAST_LOOKUP) { for (PluginWrapper p : activePlugins) { resources.addAll(Collections.list(ClassLoaderReflectionToolkit._findResources(p.classLoader, name))); @@ -2137,7 +2151,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas public boolean isActivated() { if(pluginsWithCycle == null){ pluginsWithCycle = new ArrayList<>(); - for (PluginWrapper p : Jenkins.getInstance().getPluginManager().getPlugins()) { + for (PluginWrapper p : Jenkins.get().getPluginManager().getPlugins()) { if(p.hasCycleDependency()){ pluginsWithCycle.add(p); isActive = true; @@ -2177,7 +2191,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * @param message the message to show (plain text) */ public void ifPluginOlderThenReport(String pluginName, String requiredVersion, String message){ - Plugin plugin = Jenkins.getInstance().getPlugin(pluginName); + Plugin plugin = Jenkins.get().getPlugin(pluginName); if(plugin != null){ if(plugin.getWrapper().getVersionNumber().isOlderThan(new VersionNumber(requiredVersion))) { pluginsToBeUpdated.put(pluginName, new PluginUpdateInfo(pluginName, message)); @@ -2222,7 +2236,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Restricted(NoExternalUse.class) public Object getTarget() { if (!SKIP_PERMISSION_CHECK) { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); } return this; } diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index b9cdd610db771c0ebd07c1b5f8c23fbe53ec328e..879dd3a4860039b742734a679afe83c898b1de57 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -33,6 +33,7 @@ import hudson.model.ModelObject; import hudson.model.UpdateCenter; import hudson.model.UpdateSite; import hudson.util.VersionNumber; +import io.jenkins.lib.versionnumber.JavaSpecificationVersion; import jenkins.YesNoMaybe; import jenkins.model.Jenkins; import jenkins.util.java.JavaUtils; @@ -83,6 +84,7 @@ import static hudson.PluginWrapper.PluginDisableStatus.ERROR_DISABLING; import static hudson.PluginWrapper.PluginDisableStatus.NOT_DISABLED_DEPENDANTS; import static hudson.PluginWrapper.PluginDisableStatus.NO_SUCH_PLUGIN; import static java.util.logging.Level.WARNING; +import jenkins.plugins.DetachedPluginsUtil; import static org.apache.commons.io.FilenameUtils.getBaseName; /** @@ -207,12 +209,12 @@ public class PluginWrapper implements Comparable, ModelObject { /** * List of plugins that depend on this plugin. */ - private Set dependants = Collections.emptySet(); + private Set dependents = Collections.emptySet(); /** * List of plugins that depend optionally on this plugin. */ - private Set optionalDependants = Collections.emptySet(); + private Set optionalDependents = Collections.emptySet(); /** * The core can depend on a plugin if it is bundled. Sometimes it's the only thing that @@ -222,46 +224,109 @@ public class PluginWrapper implements Comparable, ModelObject { /** * Set the list of components that depend on this plugin. - * @param dependants The list of components that depend on this plugin. + * @param dependents The list of components that depend on this plugin. */ - public void setDependants(@Nonnull Set dependants) { - this.dependants = dependants; + public void setDependents(@Nonnull Set dependents) { + this.dependents = dependents; + } + + /** + * @deprecated Please use {@link setDependents}. + */ + @Deprecated + public void setDependants(@Nonnull Set dependents) { + setDependents(dependents); } /** * Set the list of components that depend optionally on this plugin. - * @param optionalDependants The list of components that depend optionally on this plugin. + * @param optionalDependents The list of components that depend optionally on this plugin. + */ + public void setOptionalDependents(@Nonnull Set optionalDependents) { + this.optionalDependents = optionalDependents; + } + + /** + * @deprecated Please use {@link setOptionalDependents}. */ - public void setOptionalDependants(@Nonnull Set optionalDependants) { - this.optionalDependants = optionalDependants; + @Deprecated + public void setOptionalDependants(@Nonnull Set optionalDependents) { + setOptionalDependents(dependents); } /** * Get the list of components that depend on this plugin. + * Note that the list will include elements of {@link #getOptionalDependents}. * @return The list of components that depend on this plugin. */ - public @Nonnull Set getDependants() { - if (isBundled && dependants.isEmpty()) { + public @Nonnull Set getDependents() { + if (isBundled && dependents.isEmpty()) { return CORE_ONLY_DEPENDANT; } else { - return dependants; + return dependents; } } + /** + * @deprecated Please use {@link getDependents}. + */ + @Deprecated + public @Nonnull Set getDependants() { + return getDependents(); + } + + /** + * Like {@link #getDependents} but excluding optional dependencies. + * @since TODO + */ + public @Nonnull Set getMandatoryDependents() { + Set s = new HashSet<>(dependents); + s.removeAll(optionalDependents); + return s; + } + /** * @return The list of components that depend optionally on this plugin. */ + public @Nonnull Set getOptionalDependents() { + return optionalDependents; + } + + /** + * @deprecated Please use {@link getOptionalDependents}. + */ + @Deprecated public @Nonnull Set getOptionalDependants() { - return optionalDependants; + return getOptionalDependents(); } /** * Does this plugin have anything that depends on it. + * Note that optional dependents are included. * @return {@code true} if something (Jenkins core, or another plugin) depends on this * plugin, otherwise {@code false}. */ + public boolean hasDependents() { + return (isBundled || !dependents.isEmpty()); + } + + /** + * Like {@link #hasDependents} but excluding optional dependencies. + * @since TODO + */ + public boolean hasMandatoryDependents() { + if (isBundled) { + return true; + } + return dependents.stream().anyMatch(d -> !optionalDependents.contains(d)); + } + + /** + * @deprecated Please use {@link hasDependents}. + */ + @Deprecated public boolean hasDependants() { - return (isBundled || !dependants.isEmpty()); + return hasDependents(); } /** @@ -269,17 +334,33 @@ public class PluginWrapper implements Comparable, ModelObject { * @return {@code true} if something (Jenkins core, or another plugin) depends optionally on this * plugin, otherwise {@code false}. */ - public boolean hasOptionalDependants() { - return !optionalDependants.isEmpty(); + public boolean hasOptionalDependents() { + return !optionalDependents.isEmpty(); } + /** + * @deprecated Please use {@link hasOptionalDependents}. + */ + @Deprecated + public boolean hasOptionalDependants() { + return hasOptionalDependents(); + } /** * Does this plugin depend on any other plugins. + * Note that this include optional dependencies. * @return {@code true} if this plugin depends on other plugins, otherwise {@code false}. */ public boolean hasDependencies() { - return (dependencies != null && !dependencies.isEmpty()); + return !dependencies.isEmpty(); + } + + /** + * Like {@link #hasDependencies} but omitting optional dependencies. + * @since TODO + */ + public boolean hasMandatoryDependencies() { + return dependencies.stream().anyMatch(d -> !d.optional); } @ExportedBean @@ -346,6 +427,9 @@ public class PluginWrapper implements Comparable, ModelObject { this.active = !disableFile.exists(); this.dependencies = dependencies; this.optionalDependencies = optionalDependencies; + for (Dependency d : optionalDependencies) { + assert d.optional : d + " included among optionalDependencies of " + shortName + " but was not marked optional"; + } this.archive = archive; } @@ -354,7 +438,7 @@ public class PluginWrapper implements Comparable, ModelObject { } public Api getApi() { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); return new Api(this); } @@ -390,11 +474,24 @@ public class PluginWrapper implements Comparable, ModelObject { return getBaseName(fileName); } + /** + * Gets all dependencies of this plugin on other plugins. + * Note that the list will usually include the members of {@link #getOptionalDependencies} + * (missing optional dependencies will however be omitted). + */ @Exported public List getDependencies() { return dependencies; } + /** + * Like {@link #getDependencies} but omits optional dependencies. + * @since TODO + */ + public List getMandatoryDependencies() { + return dependencies.stream().filter(d -> !d.optional).collect(Collectors.toList()); + } + public List getOptionalDependencies() { return optionalDependencies; } @@ -596,7 +693,7 @@ public class PluginWrapper implements Comparable, ModelObject { /** * Disable this plugin using a strategy. * @param strategy strategy to use - * @return an object representing the result of the disablement of this plugin and its dependants plugins. + * @return an object representing the result of the disablement of this plugin and its dependents plugins. */ public @Nonnull PluginDisableResult disable(@Nonnull PluginDisableStrategy strategy) { PluginDisableResult result = new PluginDisableResult(shortName); @@ -607,55 +704,55 @@ public class PluginWrapper implements Comparable, ModelObject { return result; } - // Act as a flag indicating if this plugin, finally, can be disabled. If there is a not-disabled-dependant + // Act as a flag indicating if this plugin, finally, can be disabled. If there is a not-disabled-dependent // plugin, this one couldn't be disabled. - String aDependantNotDisabled = null; + String aDependentNotDisabled = null; - // List of dependants plugins to 'check'. 'Check' means disable for mandatory or all strategies, or review if - // this dependant-mandatory plugin is enabled in order to return an error for the NONE strategy. - Set dependantsToCheck = dependantsToCheck(strategy); + // List of dependents plugins to 'check'. 'Check' means disable for mandatory or all strategies, or review if + // this dependent-mandatory plugin is enabled in order to return an error for the NONE strategy. + Set dependentsToCheck = dependentsToCheck(strategy); - // Review all the dependants and add to the plugin result what happened with its dependants - for (String dependant : dependantsToCheck) { - PluginWrapper dependantPlugin = parent.getPlugin(dependant); + // Review all the dependents and add to the plugin result what happened with its dependents + for (String dependent : dependentsToCheck) { + PluginWrapper dependentPlugin = parent.getPlugin(dependent); - // The dependant plugin doesn't exist, add an error to the report - if (dependantPlugin == null) { - PluginDisableResult dependantStatus = new PluginDisableResult(dependant, NO_SUCH_PLUGIN, Messages.PluginWrapper_NoSuchPlugin(dependant)); - result.addDependantDisableStatus(dependantStatus); + // The dependent plugin doesn't exist, add an error to the report + if (dependentPlugin == null) { + PluginDisableResult dependentStatus = new PluginDisableResult(dependent, NO_SUCH_PLUGIN, Messages.PluginWrapper_NoSuchPlugin(dependent)); + result.addDependentDisableStatus(dependentStatus); - // If the strategy is none and there is some enabled dependant plugin, the plugin cannot be disabled. If - // this dependant plugin is not enabled, continue searching for one enabled. + // If the strategy is none and there is some enabled dependent plugin, the plugin cannot be disabled. If + // this dependent plugin is not enabled, continue searching for one enabled. } else if (strategy.equals(PluginDisableStrategy.NONE)) { - if (dependantPlugin.isEnabled()) { - aDependantNotDisabled = dependant; - break; // in this case, we don't need to continue with the rest of its dependants + if (dependentPlugin.isEnabled()) { + aDependentNotDisabled = dependent; + break; // in this case, we don't need to continue with the rest of its dependents } - // If the strategy is not none and this dependant plugin is not enabled, add it as already disabled - } else if (!dependantPlugin.isEnabled()) { - PluginDisableResult dependantStatus = new PluginDisableResult(dependant, ALREADY_DISABLED, Messages.PluginWrapper_Already_Disabled(dependant)); - result.addDependantDisableStatus(dependantStatus); + // If the strategy is not none and this dependent plugin is not enabled, add it as already disabled + } else if (!dependentPlugin.isEnabled()) { + PluginDisableResult dependentStatus = new PluginDisableResult(dependent, ALREADY_DISABLED, Messages.PluginWrapper_Already_Disabled(dependent)); + result.addDependentDisableStatus(dependentStatus); - // If the strategy is not none and this dependant plugin is enabled, disable it + // If the strategy is not none and this dependent plugin is enabled, disable it } else { // As there is no cycles in the plugin dependencies, the recursion shouldn't be infinite. The - // strategy used is the same for its dependants plugins - PluginDisableResult dependantResult = dependantPlugin.disable(strategy); - PluginDisableStatus dependantStatus = dependantResult.status; - - // If something wrong happened, flag this dependant plugin to set the plugin later as not-disabled due - // to its dependants plugins. - if (ERROR_DISABLING.equals(dependantStatus) || NOT_DISABLED_DEPENDANTS.equals(dependantStatus)) { - aDependantNotDisabled = dependant; - break; // we found a dependant plugin enabled, stop looking for dependant plugins to disable. + // strategy used is the same for its dependents plugins + PluginDisableResult dependentResult = dependentPlugin.disable(strategy); + PluginDisableStatus dependentStatus = dependentResult.status; + + // If something wrong happened, flag this dependent plugin to set the plugin later as not-disabled due + // to its dependents plugins. + if (ERROR_DISABLING.equals(dependentStatus) || NOT_DISABLED_DEPENDANTS.equals(dependentStatus)) { + aDependentNotDisabled = dependent; + break; // we found a dependent plugin enabled, stop looking for dependent plugins to disable. } - result.addDependantDisableStatus(dependantResult); + result.addDependentDisableStatus(dependentResult); } } - // If there is no enabled-dependant plugin, disable this plugin and add it to the result - if (aDependantNotDisabled == null) { + // If there is no enabled-dependent plugin, disable this plugin and add it to the result + if (aDependentNotDisabled == null) { try { this.disableWithoutCheck(); result.setMessage(Messages.PluginWrapper_Plugin_Disabled(shortName)); @@ -664,30 +761,30 @@ public class PluginWrapper implements Comparable, ModelObject { result.setMessage(Messages.PluginWrapper_Error_Disabling(shortName, io.toString())); result.setStatus(ERROR_DISABLING); } - // if there is yet some not disabled dependant plugin (only possible with none strategy), this plugin cannot + // if there is yet some not disabled dependent plugin (only possible with none strategy), this plugin cannot // be disabled. } else { - result.setMessage(Messages.PluginWrapper_Plugin_Has_Dependant(shortName, aDependantNotDisabled, strategy)); + result.setMessage(Messages.PluginWrapper_Plugin_Has_Dependent(shortName, aDependentNotDisabled, strategy)); result.setStatus(NOT_DISABLED_DEPENDANTS); } return result; } - private Set dependantsToCheck(PluginDisableStrategy strategy) { - Set dependantsToCheck; + private Set dependentsToCheck(PluginDisableStrategy strategy) { + Set dependentsToCheck; switch (strategy) { case ALL: - // getDependants returns all the dependant plugins, mandatory or optional. - dependantsToCheck = this.getDependants(); + // getDependents returns all the dependent plugins, mandatory or optional. + dependentsToCheck = this.getDependents(); break; default: // It includes MANDATORY, NONE: - // with NONE, the process only fail if mandatory dependant plugins exists - // As of getDependants has all the dependants, we get the difference between them and only the optionals - dependantsToCheck = Sets.difference(this.getDependants(), this.getOptionalDependants()); + // with NONE, the process only fail if mandatory dependent plugins exists + // As of getDependents has all the dependents, we get the difference between them and only the optionals + dependentsToCheck = Sets.difference(this.getDependents(), this.getOptionalDependents()); } - return dependantsToCheck; + return dependentsToCheck; } /** @@ -705,7 +802,12 @@ public class PluginWrapper implements Comparable, ModelObject { public void setHasCycleDependency(boolean hasCycle){ hasCycleDependency = hasCycle; } - + + /** + * Is this plugin bundled in the WAR? + * Normally false as noted in {@link PluginManager#loadBundledPlugins}: + * this does not apply to “detached” plugins. + */ @Exported public boolean isBundled() { return isBundled; @@ -763,8 +865,8 @@ public class PluginWrapper implements Comparable, ModelObject { String minimumJavaVersion = getMinimumJavaVersion(); if (minimumJavaVersion != null) { - VersionNumber actualVersion = JavaUtils.getCurrentJavaRuntimeVersionNumber(); - if (actualVersion.isOlderThan(new VersionNumber(minimumJavaVersion))) { + JavaSpecificationVersion actualVersion = JavaUtils.getCurrentJavaRuntimeVersionNumber(); + if (actualVersion.isOlderThan(new JavaSpecificationVersion(minimumJavaVersion))) { versionDependencyError(Messages.PluginWrapper_obsoleteJava(actualVersion.toString(), minimumJavaVersion), actualVersion.toString(), minimumJavaVersion); } } @@ -854,7 +956,7 @@ public class PluginWrapper implements Comparable, ModelObject { * the user may have installed a plugin locally developed. */ public UpdateSite.Plugin getUpdateInfo() { - UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); + UpdateCenter uc = Jenkins.get().getUpdateCenter(); UpdateSite.Plugin p = uc.getPlugin(getShortName(), getVersionNumber()); if(p!=null && p.isNewerThan(getVersion())) return p; return null; @@ -864,7 +966,7 @@ public class PluginWrapper implements Comparable, ModelObject { * returns the {@link hudson.model.UpdateSite.Plugin} object, or null. */ public UpdateSite.Plugin getInfo() { - UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); + UpdateCenter uc = Jenkins.get().getUpdateCenter(); UpdateSite.Plugin p = uc.getPlugin(getShortName(), getVersionNumber()); if (p != null) return p; return uc.getPlugin(getShortName()); @@ -898,6 +1000,15 @@ public class PluginWrapper implements Comparable, ModelObject { return !archive.exists(); } + /** + * Same as {@link DetachedPluginsUtil#isDetachedPlugin}. + * @since TODO + */ + @Exported + public boolean isDetached() { + return DetachedPluginsUtil.isDetachedPlugin(shortName); + } + /** * Sort by short name. */ @@ -917,7 +1028,7 @@ public class PluginWrapper implements Comparable, ModelObject { * Where is the backup file? */ public File getBackupFile() { - return new File(Jenkins.getInstance().getRootDir(),"plugins/"+getShortName() + ".bak"); + return new File(Jenkins.get().getRootDir(),"plugins/"+getShortName() + ".bak"); } /** @@ -1000,13 +1111,13 @@ public class PluginWrapper implements Comparable, ModelObject { } /** - * The result of the disablement of a plugin and its dependants plugins. + * The result of the disablement of a plugin and its dependents plugins. */ public static class PluginDisableResult { private String plugin; private PluginDisableStatus status; private String message; - private Set dependantsDisableStatus = new HashSet<>(); + private Set dependentsDisableStatus = new HashSet<>(); public PluginDisableResult(String plugin) { this.plugin = plugin; @@ -1051,12 +1162,12 @@ public class PluginWrapper implements Comparable, ModelObject { this.message = message; } - public Set getDependantsDisableStatus() { - return dependantsDisableStatus; + public Set getDependentsDisableStatus() { + return dependentsDisableStatus; } - public void addDependantDisableStatus(PluginDisableResult dependantDisableStatus) { - dependantsDisableStatus.add(dependantDisableStatus); + public void addDependentDisableStatus(PluginDisableResult dependentDisableStatus) { + dependentsDisableStatus.add(dependentDisableStatus); } } @@ -1093,14 +1204,14 @@ public class PluginWrapper implements Comparable, ModelObject { // @RequirePOST public HttpResponse doMakeEnabled() throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); enable(); return HttpResponses.ok(); } @RequirePOST public HttpResponse doMakeDisabled() throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); disable(); return HttpResponses.ok(); } @@ -1108,7 +1219,7 @@ public class PluginWrapper implements Comparable, ModelObject { @RequirePOST @Deprecated public HttpResponse doPin() throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); // See https://groups.google.com/d/msg/jenkinsci-dev/kRobm-cxFw8/6V66uhibAwAJ LOGGER.log(WARNING, "Call to pin plugin has been ignored. Plugin name: " + shortName); return HttpResponses.ok(); @@ -1117,7 +1228,7 @@ public class PluginWrapper implements Comparable, ModelObject { @RequirePOST @Deprecated public HttpResponse doUnpin() throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); // See https://groups.google.com/d/msg/jenkinsci-dev/kRobm-cxFw8/6V66uhibAwAJ LOGGER.log(WARNING, "Call to unpin plugin has been ignored. Plugin name: " + shortName); return HttpResponses.ok(); @@ -1131,7 +1242,7 @@ public class PluginWrapper implements Comparable, ModelObject { archive.delete(); // Redo who depends on who. - jenkins.getPluginManager().resolveDependantPlugins(); + jenkins.getPluginManager().resolveDependentPlugins(); return HttpResponses.redirectViaContextPath("/pluginManager/installed"); // send back to plugin manager } diff --git a/core/src/main/java/hudson/Proc.java b/core/src/main/java/hudson/Proc.java index 26d74984cfa6551f8cf58f32cbfa33826214a6bc..6acb1ceebaf5f6e9af1c3a858a732d4a5d614de6 100644 --- a/core/src/main/java/hudson/Proc.java +++ b/core/src/main/java/hudson/Proc.java @@ -26,6 +26,7 @@ package hudson; import hudson.Launcher.ProcStarter; import hudson.model.TaskListener; import hudson.remoting.Channel; +import hudson.util.ClassLoaderSanityThreadFactory; import hudson.util.DaemonThreadFactory; import hudson.util.ExceptionCatchingThreadFactory; import hudson.util.NamingThreadFactory; @@ -138,7 +139,7 @@ public abstract class Proc { @CheckForNull public abstract OutputStream getStdin(); - private static final ExecutorService executor = Executors.newCachedThreadPool(new ExceptionCatchingThreadFactory(new NamingThreadFactory(new DaemonThreadFactory(), "Proc.executor"))); + private static final ExecutorService executor = Executors.newCachedThreadPool(new ExceptionCatchingThreadFactory(new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "Proc.executor"))); /** * Like {@link #join} but can be given a maximum time to wait. @@ -231,7 +232,7 @@ public abstract class Proc { m.clear(); for (String e : env) { int idx = e.indexOf('='); - m.put(e.substring(0,idx),e.substring(idx+1,e.length())); + m.put(e.substring(0,idx),e.substring(idx+1)); } } return pb; @@ -325,8 +326,8 @@ public abstract class Proc { // see https://jenkins.io/redirect/troubleshooting/process-leaked-file-descriptors // problems like that shows up as infinite wait in join(), which confuses great many users. // So let's do a timed wait here and try to diagnose the problem - if (copier!=null) copier.join(10*1000); - if(copier2!=null) copier2.join(10*1000); + if (copier!=null) copier.join(TimeUnit.SECONDS.toMillis(10)); + if(copier2!=null) copier2.join(TimeUnit.SECONDS.toMillis(10)); if((copier!=null && copier.isAlive()) || (copier2!=null && copier2.isAlive())) { // looks like handles are leaking. // closing these handles should terminate the threads. diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java index 11d90e17d9909cbdd6b0790a14f6ae27a930018e..cecd2f68430b8cd62ae31c65126ea4273bf81cc6 100644 --- a/core/src/main/java/hudson/ProxyConfiguration.java +++ b/core/src/main/java/hudson/ProxyConfiguration.java @@ -47,6 +47,7 @@ import java.net.URL; import java.net.URLConnection; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import javax.annotation.CheckForNull; import jenkins.model.Jenkins; @@ -85,7 +86,7 @@ public final class ProxyConfiguration extends AbstractDescribableImpl 0 ? DEFAULT_CONNECT_TIMEOUT_MILLIS : new Integer(30 * 1000)); + method.getParams().setParameter("http.socket.timeout", DEFAULT_CONNECT_TIMEOUT_MILLIS > 0 ? DEFAULT_CONNECT_TIMEOUT_MILLIS : (int)TimeUnit.SECONDS.toMillis(30)); HttpClient client = new HttpClient(); if (Util.fixEmptyAndTrim(name) != null && !isNoProxyHost(host, noProxyHost)) { diff --git a/core/src/main/java/hudson/StructuredForm.java b/core/src/main/java/hudson/StructuredForm.java index f4f044ae864e0833487e28146805ffc1dcb37a78..d6a78748dabf0a7b721ec9adaa81503dcc4145ea 100644 --- a/core/src/main/java/hudson/StructuredForm.java +++ b/core/src/main/java/hudson/StructuredForm.java @@ -68,7 +68,7 @@ public class StructuredForm { if(v instanceof JSONObject) return Collections.singletonList((JSONObject)v); if(v instanceof JSONArray) - return (List)(JSONArray)v; + return (List) v; throw new IllegalArgumentException(); } diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 40a3fc5d41ec5fedb4d95dd9d454003e67d3e93a..f7918d6bd8d49807a1bb6f38b7a98543070101e2 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -27,11 +27,12 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.ByteArrayInputStream; import java.io.SequenceInputStream; import java.io.Writer; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.interfaces.RSAPublicKey; import javax.annotation.Nullable; import hudson.model.AperiodicWork; +import hudson.util.VersionNumber; import jenkins.model.Jenkins; import jenkins.model.identity.InstanceIdentityProvider; import jenkins.security.stapler.StaplerAccessibleType; @@ -41,13 +42,13 @@ import hudson.slaves.OfflineCause; import java.io.DataOutputStream; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.net.SocketAddress; import java.util.Arrays; import jenkins.AgentProtocol; import java.io.BufferedWriter; import java.io.DataInputStream; +import java.io.EOFException; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; @@ -68,7 +69,7 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; /** - * Listens to incoming TCP connections from JNLP agents and deprecated Remoting-based CLI. + * Listens to incoming TCP connections, for example from agents. * *

* Aside from the HTTP endpoint, Jenkins runs {@link TcpSlaveAgentListener} that listens on a TCP socket. @@ -138,7 +139,7 @@ public final class TcpSlaveAgentListener extends Thread { @Nullable public String getIdentityPublicKey() { RSAPublicKey key = InstanceIdentityProvider.RSA.getPublicKey(); - return key == null ? null : new String(Base64.encodeBase64(key.getEncoded()), Charset.forName("UTF-8")); + return key == null ? null : new String(Base64.encodeBase64(key.getEncoded()), StandardCharsets.UTF_8); } /** @@ -149,7 +150,15 @@ public final class TcpSlaveAgentListener extends Thread { * @since 2.16 */ public String getAgentProtocolNames() { - return StringUtils.join(Jenkins.getInstance().getAgentProtocols(), ", "); + return StringUtils.join(Jenkins.get().getAgentProtocols(), ", "); + } + + /** + * Gets Remoting minimum supported version to prevent unsupported agents from connecting + * @since 2.171 + */ + public VersionNumber getRemotingMinimumVersion() { + return RemotingVersionInfo.getMinimumSupportedVersion(); } @Override @@ -236,7 +245,7 @@ public final class TcpSlaveAgentListener extends Thread { DataInputStream in = new DataInputStream(s.getInputStream()); PrintWriter out = new PrintWriter( - new BufferedWriter(new OutputStreamWriter(s.getOutputStream(),"UTF-8")), + new BufferedWriter(new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8)), true); // DEPRECATED: newer protocol shouldn't use PrintWriter but should use DataOutputStream // peek the first few bytes to determine what to do with this client @@ -257,7 +266,7 @@ public final class TcpSlaveAgentListener extends Thread { String protocol = s.substring(9); AgentProtocol p = AgentProtocol.of(protocol); if (p!=null) { - if (Jenkins.getInstance().getAgentProtocols().contains(protocol)) { + if (Jenkins.get().getAgentProtocols().contains(protocol)) { LOGGER.log(p instanceof PingAgentProtocol ? Level.FINE : Level.INFO, "Accepted {0} connection #{1} from {2}", new Object[] {protocol, id, this.s.getRemoteSocketAddress()}); p.handle(this.s); } else { @@ -276,7 +285,11 @@ public final class TcpSlaveAgentListener extends Thread { // try to clean up the socket } } catch (IOException e) { - LOGGER.log(Level.WARNING,"Connection #"+id+" failed",e); + if (e instanceof EOFException) { + LOGGER.log(Level.INFO, "Connection #{0} failed: {1}", new Object[] {id, e}); + } else { + LOGGER.log(Level.WARNING, "Connection #" + id + " failed", e); + } try { s.close(); } catch (IOException ex) { @@ -291,9 +304,8 @@ public final class TcpSlaveAgentListener extends Thread { */ private void respondHello(String header, Socket s) throws IOException { try { - Writer o = new OutputStreamWriter(s.getOutputStream(), "UTF-8"); - - //TODO: expose version about minimum supported Remoting version (JENKINS-48766) + Writer o = new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8); + if (header.startsWith("GET / ")) { o.write("HTTP/1.0 200 OK\r\n"); o.write("Content-Type: text/plain;charset=UTF-8\r\n"); @@ -303,7 +315,7 @@ public final class TcpSlaveAgentListener extends Thread { o.write("Jenkins-Session: " + Jenkins.SESSION_HASH + "\r\n"); o.write("Client: " + s.getInetAddress().getHostAddress() + "\r\n"); o.write("Server: " + s.getLocalAddress().getHostAddress() + "\r\n"); - o.write("Remoting-Minimum-Version: " + RemotingVersionInfo.getMinimumSupportedVersion() + "\r\n"); + o.write("Remoting-Minimum-Version: " + getRemotingMinimumVersion() + "\r\n"); o.flush(); s.shutdownOutput(); } else { @@ -358,11 +370,7 @@ public final class TcpSlaveAgentListener extends Thread { private final byte[] ping; public PingAgentProtocol() { - try { - ping = "Ping\n".getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("JLS mandates support for UTF-8 charset", e); - } + ping = "Ping\n".getBytes(StandardCharsets.UTF_8); } /** @@ -414,9 +422,9 @@ public final class TcpSlaveAgentListener extends Thread { } else { LOGGER.log(Level.FINE, "Expected ping response from {0} of {1} got {2}", new Object[]{ socket.getRemoteSocketAddress(), - new String(ping, "UTF-8"), + new String(ping, StandardCharsets.UTF_8), responseLength > 0 && responseLength <= response.length ? - new String(response, 0, responseLength, "UTF-8") : + new String(response, 0, responseLength, StandardCharsets.UTF_8) : "bad response length " + responseLength }); return false; @@ -479,7 +487,7 @@ public final class TcpSlaveAgentListener extends Thread { if (originThread.isAlive()) { originThread.interrupt(); } - int port = Jenkins.getInstance().getSlaveAgentPort(); + int port = Jenkins.get().getSlaveAgentPort(); if (port != -1) { new TcpSlaveAgentListener(port).start(); LOGGER.log(Level.INFO, "Restarted TcpSlaveAgentListener"); @@ -545,37 +553,3 @@ public final class TcpSlaveAgentListener extends Thread { @Restricted(NoExternalUse.class) public static Integer CLI_PORT = SystemProperties.getInteger(TcpSlaveAgentListener.class.getName()+".port"); } - -/* -Pasted from http://today.java.net/pub/a/today/2005/09/01/webstart.html - - Is it unrealistic to try to control access to JWS files? - Is anyone doing this? - -It is not unrealistic, and we are doing it. Create a protected web page -with a download button or link that makes a servlet call. If the user has -already logged in to your website, of course they can go there without -further authentication. The servlet reads the cookies sent by the browser -when the link is activated. It then generates a dynamic JNLP file adding -the authentication cookie and any other required cookies (JSESSIONID, etc.) -via tags. Write the WebStart application so that it picks up -any required cookies from the argument list, and adds these cookies to its -request headers on subsequent calls to the server. (Note: in the dynamic -JNLP file, do NOT put href= in the opening jnlp tag. If you do, JWS will -try to reload the JNLP from disk and since it's dynamic, it won't be there. -Leave it off and JWS will be happy.) - -When returning the dynamic JNLP, the servlet should invoke setHeader( -"Expires", 0 ) and addDateHeader() twice on the servlet response to set -both "Date" and "Last-Modified" to the current date. This keeps the browser -from using a cached copy of a prior dynamic JNLP obtained from the same URL. - -Note also that the JAR file(s) for the JWS application should not be on -a password-protected path - the launcher won't know about the authentication -cookie. But once the application starts, you can run all its requests -through a protected path requiring the authentication cookie, because -the application gets it from the dynamic JNLP. Just write it so that it -can't do anything useful without going through a protected path or doing -something to present credentials that could only have come from a valid -user. -*/ diff --git a/core/src/main/java/hudson/UDPBroadcastThread.java b/core/src/main/java/hudson/UDPBroadcastThread.java index 03bdbc3bba5258a6efc0726cac002552a37dd417..446c621a3e67a11ca0cf826997aeb8c0bbae92cd 100644 --- a/core/src/main/java/hudson/UDPBroadcastThread.java +++ b/core/src/main/java/hudson/UDPBroadcastThread.java @@ -37,6 +37,7 @@ import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.channels.ClosedByInterruptException; +import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; @@ -78,6 +79,7 @@ public class UDPBroadcastThread extends Thread { mcs.joinGroup(MULTICAST); ready.signal(); + //noinspection InfiniteLoopStatement while(true) { byte[] buf = new byte[2048]; DatagramPacket p = new DatagramPacket(buf,buf.length); @@ -99,7 +101,7 @@ public class UDPBroadcastThread extends Thread { rsp.append(""); - byte[] response = rsp.toString().getBytes("UTF-8"); + byte[] response = rsp.toString().getBytes(StandardCharsets.UTF_8); mcs.send(new DatagramPacket(response,response.length,sender)); } } catch (ClosedByInterruptException e) { diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 2aeeb74bd57f23bef2379da30962c151369b4031..76611efbd97a5416ead4aab03bab5a239102d40b 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -91,7 +91,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.io.FileUtils; -import org.kohsuke.stapler.Ancestor; import org.kohsuke.stapler.StaplerRequest; /** @@ -115,7 +114,7 @@ public class Util { */ @Nonnull public static List filter( @Nonnull Iterable base, @Nonnull Class type ) { - List r = new ArrayList(); + List r = new ArrayList<>(); for (Object i : base) { if(type.isInstance(i)) r.add(type.cast(i)); @@ -145,7 +144,7 @@ public class Util { */ @Nullable public static String replaceMacro( @CheckForNull String s, @Nonnull Map properties) { - return replaceMacro(s,new VariableResolver.ByMap(properties)); + return replaceMacro(s, new VariableResolver.ByMap<>(properties)); } /** @@ -784,7 +783,7 @@ public class Util { */ @Nonnull public static List createSubList(@Nonnull Collection source, @Nonnull Class type ) { - List r = new ArrayList(); + List r = new ArrayList<>(); for (Object item : source) { if(type.isInstance(item)) r.add(type.cast(item)); @@ -867,29 +866,50 @@ public class Util { CharBuffer buf = null; char c; for (int i = 0, m = s.length(); i < m; i++) { - c = s.charAt(i); - if (c > 122 || uriMap[c]) { + int codePoint = Character.codePointAt(s, i); + if((codePoint&0xffffff80)==0) { // 1 byte + c = s.charAt(i); + if (c > 122 || uriMap[c]) { + if (!escaped) { + out = new StringBuilder(i + (m - i) * 3); + out.append(s, 0, i); + enc = StandardCharsets.UTF_8.newEncoder(); + buf = CharBuffer.allocate(1); + escaped = true; + } + // 1 char -> UTF8 + buf.put(0, c); + buf.rewind(); + try { + ByteBuffer bytes = enc.encode(buf); + while (bytes.hasRemaining()) { + byte b = bytes.get(); + out.append('%'); + out.append(toDigit((b >> 4) & 0xF)); + out.append(toDigit(b & 0xF)); + } + } catch (CharacterCodingException ex) { + } + } else if (escaped) { + out.append(c); + } + } else { if (!escaped) { out = new StringBuilder(i + (m - i) * 3); - out.append(s.substring(0, i)); - enc = StandardCharsets.UTF_8.newEncoder(); - buf = CharBuffer.allocate(1); + out.append(s, 0, i); escaped = true; } - // 1 char -> UTF8 - buf.put(0,c); - buf.rewind(); - try { - ByteBuffer bytes = enc.encode(buf); - while (bytes.hasRemaining()) { - byte b = bytes.get(); - out.append('%'); - out.append(toDigit((b >> 4) & 0xF)); - out.append(toDigit(b & 0xF)); - } - } catch (CharacterCodingException ex) { } - } else if (escaped) { - out.append(c); + + byte[] bytes = new String(new int[] { codePoint }, 0, 1).getBytes(StandardCharsets.UTF_8); + for(int j=0;j> 4) & 0xF)); + out.append(toDigit(bytes[j] & 0xF)); + } + + if(Character.charCount(codePoint) > 1) { + i++; // we processed two characters + } } } return escaped ? out.toString() : s; @@ -1036,7 +1056,7 @@ public class Util { */ @Nonnull public static List fixNull(@CheckForNull List l) { - return fixNull(l, Collections.emptyList()); + return fixNull(l, Collections.emptyList()); } /** @@ -1050,7 +1070,7 @@ public class Util { */ @Nonnull public static Set fixNull(@CheckForNull Set l) { - return fixNull(l, Collections.emptySet()); + return fixNull(l, Collections.emptySet()); } /** @@ -1064,7 +1084,7 @@ public class Util { */ @Nonnull public static Collection fixNull(@CheckForNull Collection l) { - return fixNull(l, Collections.emptySet()); + return fixNull(l, Collections.emptySet()); } /** @@ -1078,7 +1098,7 @@ public class Util { */ @Nonnull public static Iterable fixNull(@CheckForNull Iterable l) { - return fixNull(l, Collections.emptySet()); + return fixNull(l, Collections.emptySet()); } /** @@ -1118,7 +1138,7 @@ public class Util { int size = 0; for (Collection item : items) size += item.size(); - List r = new ArrayList(size); + List r = new ArrayList<>(size); for (Collection item : items) r.addAll(item); return r; @@ -1464,9 +1484,9 @@ public class Util { public static int permissionsToMode(Set permissions) { PosixFilePermission[] allPermissions = PosixFilePermission.values(); int result = 0; - for (int i = 0; i < allPermissions.length; i++) { + for (PosixFilePermission allPermission : allPermissions) { result <<= 1; - result |= permissions.contains(allPermissions[i]) ? 1 : 0; + result |= permissions.contains(allPermission) ? 1 : 0; } return result; } @@ -1569,7 +1589,7 @@ public class Util { * give up, thus improving build reliability. */ @Restricted(value = NoExternalUse.class) - static int DELETION_MAX = Math.max(1, SystemProperties.getInteger(Util.class.getName() + ".maxFileDeletionRetries", 3).intValue()); + static int DELETION_MAX = Math.max(1, SystemProperties.getInteger(Util.class.getName() + ".maxFileDeletionRetries", 3)); /** * The time (in milliseconds) that we will wait between attempts to @@ -1581,7 +1601,7 @@ public class Util { * between attempts. */ @Restricted(value = NoExternalUse.class) - static int WAIT_BETWEEN_DELETION_RETRIES = SystemProperties.getInteger(Util.class.getName() + ".deletionRetryWait", 100).intValue(); + static int WAIT_BETWEEN_DELETION_RETRIES = SystemProperties.getInteger(Util.class.getName() + ".deletionRetryWait", 100); /** * If this flag is set to true then we will request a garbage collection diff --git a/core/src/main/java/hudson/WebAppMain.java b/core/src/main/java/hudson/WebAppMain.java index 927b533424928598582fa3de1e33ae12e27f95ea..bf2bf3f4555c9b048c2b5fbf2efb40e38f5c6ad1 100644 --- a/core/src/main/java/hudson/WebAppMain.java +++ b/core/src/main/java/hudson/WebAppMain.java @@ -276,7 +276,7 @@ public class WebAppMain implements ServletContextListener { */ private void recordBootAttempt(File home) { try (OutputStream o=Files.newOutputStream(BootFailure.getBootFailureFile(home).toPath(), StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { - o.write((new Date().toString() + System.getProperty("line.separator", "\n")).toString().getBytes()); + o.write((new Date().toString() + System.getProperty("line.separator", "\n")).getBytes()); } catch (IOException | InvalidPathException e) { LOGGER.log(WARNING, "Failed to record boot attempts",e); } diff --git a/core/src/main/java/hudson/cli/AddJobToViewCommand.java b/core/src/main/java/hudson/cli/AddJobToViewCommand.java index 792ff4feaa4cacdce65ddafceaf8c61cf0701ba6..3a57c9ffb50cb391a299c681d6889453ff31b36e 100644 --- a/core/src/main/java/hudson/cli/AddJobToViewCommand.java +++ b/core/src/main/java/hudson/cli/AddJobToViewCommand.java @@ -42,6 +42,7 @@ public class AddJobToViewCommand extends CLICommand { @Argument(usage="Name of the view", required=true, index=0) private View view; + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @Argument(usage="Job names", required=true, index=1) private List jobs; diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java index f9b0ef6d92472d9bf677e9003049805d7bf9d66b..b4116f3b58a96b83b33716d29a32d810c7f3652c 100644 --- a/core/src/main/java/hudson/cli/CLIAction.java +++ b/core/src/main/java/hudson/cli/CLIAction.java @@ -43,8 +43,6 @@ import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import hudson.Extension; -import hudson.model.FullDuplexHttpChannel; -import hudson.remoting.Channel; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; @@ -109,9 +107,8 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy { // CLI connection request if ("false".equals(req.getParameter("remoting"))) { throw new PlainCliEndpointResponse(); - } else if (jenkins.CLI.get().isEnabled()) { - throw new RemotingCliEndpointResponse(); } else { + // remoting=true (the historical default) no longer supported. throw HttpResponses.forbidden(); } } else { @@ -236,30 +233,4 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy { } } - /** - * Serves Remoting-over-HTTP response. - */ - private class RemotingCliEndpointResponse extends FullDuplexHttpService.Response { - - RemotingCliEndpointResponse() { - super(duplexServices); - } - - @Override - protected FullDuplexHttpService createService(StaplerRequest req, UUID uuid) throws IOException { - // do not require any permission to establish a CLI connection - // the actual authentication for the connecting Channel is done by CLICommand - - return new FullDuplexHttpChannel(uuid, !Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - @SuppressWarnings("deprecation") - @Override - protected void main(Channel channel) throws IOException, InterruptedException { - // capture the identity given by the transport, since this can be useful for SecurityRealm.createCliAuthenticator() - channel.setProperty(CLICommand.TRANSPORT_AUTHENTICATION, Jenkins.getAuthentication()); - channel.setProperty(CliEntryPoint.class.getName(), new CliManagerImpl(channel)); - } - }; - } - } - } diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java index 879f8989af41de507ba42d35614a29f8b63c7270..15d496be1b00771072a4c02fd86518721c087d4f 100644 --- a/core/src/main/java/hudson/cli/CLICommand.java +++ b/core/src/main/java/hudson/cli/CLICommand.java @@ -30,24 +30,15 @@ import hudson.ExtensionPoint; import hudson.cli.declarative.CLIMethod; import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson; import hudson.Functions; -import hudson.security.ACL; -import jenkins.security.SecurityListener; -import jenkins.util.SystemProperties; import hudson.cli.declarative.OptionHandlerExtension; import jenkins.model.Jenkins; -import hudson.remoting.Callable; import hudson.remoting.Channel; -import hudson.remoting.ChannelProperty; -import hudson.security.CliAuthenticator; import hudson.security.SecurityRealm; -import jenkins.security.MasterToSlaveCallable; import org.acegisecurity.AccessDeniedException; import org.acegisecurity.Authentication; import org.acegisecurity.BadCredentialsException; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.userdetails.User; -import org.acegisecurity.userdetails.UserDetails; import org.apache.commons.discovery.ResourceClassIterator; import org.apache.commons.discovery.ResourceNameIterator; import org.apache.commons.discovery.resource.ClassLoaders; @@ -57,7 +48,6 @@ import org.jvnet.hudson.annotation_indexer.Index; import org.jvnet.tiger_types.Types; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.args4j.ClassParser; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.spi.OptionHandler; @@ -69,7 +59,6 @@ import java.io.InputStream; import java.io.PrintStream; import java.lang.reflect.Type; import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; import java.util.List; import java.util.Locale; import java.util.UUID; @@ -85,7 +74,7 @@ import javax.annotation.Nonnull; *

* The users starts {@linkplain CLI the "CLI agent"} on a remote system, by specifying arguments, like * {@code "java -jar jenkins-cli.jar command arg1 arg2 arg3"}. The CLI agent creates - * a remoting channel with the server, and it sends the entire arguments to the server, along with + * a connection to the server, and it sends the entire arguments to the server, along with * the remoted stdin/out/err. * *

@@ -108,10 +97,6 @@ import javax.annotation.Nonnull; *

  • * stdin, stdout, stderr are remoted, so proper buffering is necessary for good user experience. * - *
  • - * Send {@link Callable} to a CLI agent by using {@link #channel} to get local interaction, - * such as uploading a file, asking for a password, etc. - * * * * @author Kohsuke Kawaguchi @@ -147,17 +132,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { public transient InputStream stdin; /** - * {@link Channel} that represents the CLI JVM. You can use this to - * execute {@link Callable} on the CLI JVM, among other things. - * - *

    - * Starting 1.445, CLI transports are not required to provide a channel - * (think of sshd, telnet, etc), so in such a case this field is null. - * - *

    - * See {@link #checkChannel()} to get a channel and throw an user-friendly - * exception - * @deprecated Specific to Remoting-based protocol. + * @deprecated No longer used. */ @Deprecated public transient Channel channel; @@ -174,8 +149,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { /** * Set by the caller of the CLI system if the transport already provides - * authentication. Due to the compatibility issue, we still allow the user - * to use command line switches to authenticate as other users. + * authentication. */ private transient Authentication transportAuth; @@ -215,7 +189,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { * The default implementation uses args4j to parse command line arguments and call {@link #run()}, * but if that processing is undesirable, subtypes can directly override this method and leave {@link #run()} * to an empty method. - * You would however then have to consider {@link CliAuthenticator} and {@link #getTransportAuthentication}, + * You would however then have to consider {@link #getTransportAuthentication}, * so this is not really recommended. * * @param args @@ -265,18 +239,12 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { sc = SecurityContextHolder.getContext(); old = sc.getAuthentication(); - CliAuthenticator authenticator = Jenkins.getActiveInstance().getSecurityRealm().createCliAuthenticator(this); - sc.setAuthentication(getTransportAuthentication()); - new ClassParser().parse(authenticator,p); + sc.setAuthentication(auth = getTransportAuthentication()); - if (!(this instanceof LoginCommand || this instanceof LogoutCommand || this instanceof HelpCommand || this instanceof WhoAmICommand)) + if (!(this instanceof HelpCommand || this instanceof WhoAmICommand)) Jenkins.getActiveInstance().checkPermission(Jenkins.READ); - p.parseArgument(args.toArray(new String[args.size()])); - auth = authenticator.authenticate(); - if (auth==Jenkins.ANONYMOUS) - auth = loadStoredAuthentication(); - sc.setAuthentication(auth); // run the CLI with the right credential - if (!(this instanceof LoginCommand || this instanceof LogoutCommand || this instanceof HelpCommand || this instanceof WhoAmICommand)) + 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()}); @@ -287,33 +255,33 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { } catch (CmdLineException e) { LOGGER.log(Level.FINE, String.format("Failed call to CLI command %s, with %d arguments, as user %s.", getName(), args.size(), auth != null ? auth.getName() : ""), e); - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); printUsage(stderr, p); return 2; } catch (IllegalStateException e) { LOGGER.log(Level.FINE, String.format("Failed call to CLI command %s, with %d arguments, as user %s.", getName(), args.size(), auth != null ? auth.getName() : ""), e); - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 4; } catch (IllegalArgumentException e) { LOGGER.log(Level.FINE, String.format("Failed call to CLI command %s, with %d arguments, as user %s.", getName(), args.size(), auth != null ? auth.getName() : ""), e); - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 3; } catch (AbortException e) { LOGGER.log(Level.FINE, String.format("Failed call to CLI command %s, with %d arguments, as user %s.", getName(), args.size(), auth != null ? auth.getName() : ""), e); // signals an error without stack trace - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 5; } catch (AccessDeniedException e) { LOGGER.log(Level.FINE, String.format("Failed call to CLI command %s, with %d arguments, as user %s.", getName(), args.size(), auth != null ? auth.getName() : ""), e); - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 6; } catch (BadCredentialsException e) { @@ -321,13 +289,13 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { // do that to the server log instead String id = UUID.randomUUID().toString(); LOGGER.log(Level.INFO, "CLI login attempt failed: " + id, e); - stderr.println(""); + stderr.println(); stderr.println("ERROR: Bad Credentials. Search the server log for " + id + " for more details."); return 7; } catch (Throwable e) { final String errorMsg = String.format("Unexpected exception occurred while performing %s command.", getName()); - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + errorMsg); LOGGER.log(Level.WARNING, errorMsg, e); Functions.printStackTrace(e, stderr); @@ -353,34 +321,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { */ @Deprecated public Channel checkChannel() throws AbortException { - if (channel==null) - throw new AbortException("This command is requesting the deprecated -remoting mode. See https://jenkins.io/redirect/cli-command-requires-channel"); - return channel; - } - - /** - * Loads the persisted authentication information from {@link ClientAuthenticationCache} - * if the current transport provides {@link Channel}. - * @deprecated Assumes Remoting, and vulnerable to JENKINS-12543. - */ - @Deprecated - protected Authentication loadStoredAuthentication() throws InterruptedException { - try { - if (channel!=null){ - Authentication authLoadedFromCache = new ClientAuthenticationCache(channel).get(); - - if(!ACL.isAnonymous(authLoadedFromCache)){ - UserDetails userDetails = new CLIUserDetails(authLoadedFromCache); - SecurityListener.fireAuthenticated(userDetails); - } - - return authLoadedFromCache; - } - } catch (IOException e) { - stderr.println("Failed to access the stored credential"); - Functions.printStackTrace(e, stderr); // recover - } - return Jenkins.ANONYMOUS; + throw new AbortException("This command is requesting the -remoting mode which is no longer supported. See https://jenkins.io/redirect/cli-command-requires-channel"); } /** @@ -514,21 +455,8 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { */ @Deprecated protected String getClientSystemProperty(String name) throws IOException, InterruptedException { - return checkChannel().call(new GetSystemProperty(name)); - } - - private static final class GetSystemProperty extends MasterToSlaveCallable { - private final String name; - - private GetSystemProperty(String name) { - this.name = name; - } - - public String call() throws IOException { - return SystemProperties.getString(name); - } - - private static final long serialVersionUID = 1L; + checkChannel(); + return null; // never run } /** @@ -543,26 +471,9 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { if (encoding != null) { return encoding; } - if (channel==null) - // for SSH, assume the platform default encoding - // this is in-line with the standard SSH behavior - return Charset.defaultCharset(); - - String charsetName = checkChannel().call(new GetCharset()); - try { - return Charset.forName(charsetName); - } catch (UnsupportedCharsetException e) { - LOGGER.log(Level.FINE,"Server doesn't have charset "+charsetName); - return Charset.defaultCharset(); - } - } - - private static final class GetCharset extends MasterToSlaveCallable { - public String call() throws IOException { - return Charset.defaultCharset().name(); - } - - private static final long serialVersionUID = 1L; + // for SSH, assume the platform default encoding + // this is in-line with the standard SSH behavior + return Charset.defaultCharset(); } /** @@ -571,21 +482,8 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { */ @Deprecated protected String getClientEnvironmentVariable(String name) throws IOException, InterruptedException { - return checkChannel().call(new GetEnvironmentVariable(name)); - } - - private static final class GetEnvironmentVariable extends MasterToSlaveCallable { - private final String name; - - private GetEnvironmentVariable(String name) { - this.name = name; - } - - public String call() throws IOException { - return System.getenv(name); - } - - private static final long serialVersionUID = 1L; + checkChannel(); + return null; // never run } /** @@ -632,12 +530,6 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { private static final Logger LOGGER = Logger.getLogger(CLICommand.class.getName()); - /** - * Key for {@link Channel#getProperty(Object)} that links to the {@link Authentication} object - * which captures the identity of the client given by the transport layer. - */ - public static final ChannelProperty TRANSPORT_AUTHENTICATION = new ChannelProperty<>(Authentication.class, "transportAuthentication"); - private static final ThreadLocal CURRENT_COMMAND = new ThreadLocal<>(); /*package*/ static CLICommand setCurrent(CLICommand cmd) { @@ -673,15 +565,4 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { } } - /** - * User details loaded from the CLI {@link ClientAuthenticationCache} - * The user is never anonymous since it must be authenticated to be stored in the cache - */ - @Deprecated - @Restricted(NoExternalUse.class) - private static class CLIUserDetails extends User { - private CLIUserDetails(Authentication auth) { - super(auth.getName(), "", true, true, true, true, auth.getAuthorities()); - } - } } diff --git a/core/src/main/java/hudson/cli/CliCrumbExclusion.java b/core/src/main/java/hudson/cli/CliCrumbExclusion.java index ac49349ccfcc91207325bd3933b75c2c8593ed77..12cca148f3b6c106314ac9b7aec7e46feb07561f 100644 --- a/core/src/main/java/hudson/cli/CliCrumbExclusion.java +++ b/core/src/main/java/hudson/cli/CliCrumbExclusion.java @@ -43,7 +43,7 @@ public class CliCrumbExclusion extends CrumbExclusion { @Override public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String pathInfo = request.getPathInfo(); - if (pathInfo != null && "/cli".equals(pathInfo)) { + if ("/cli".equals(pathInfo)) { chain.doFilter(request, response); return true; } diff --git a/core/src/main/java/hudson/cli/CliManagerImpl.java b/core/src/main/java/hudson/cli/CliManagerImpl.java deleted file mode 100644 index d799e6bf997a8a4223bcc4b9ea8c1ce26eb6c9ce..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/CliManagerImpl.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, 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. - */ -package hudson.cli; - -import hudson.remoting.CallableFilter; -import hudson.remoting.Channel; -import hudson.remoting.Pipe; -import org.acegisecurity.Authentication; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.Serializable; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.Callable; -import java.util.logging.Logger; - -/** - * {@link CliEntryPoint} implementation exposed to the remote CLI. - * - * @author Kohsuke Kawaguchi - * @deprecated Specific to Remoting-based protocol. - */ -@Deprecated -public class CliManagerImpl implements CliEntryPoint, Serializable { - private transient final Channel channel; - - private Authentication transportAuth; - - //TODO: Migrate the code to Callable decorator - /** - * Runs callable from this CLI client with the transport authentication credential. - */ - private transient final CallableFilter authenticationFilter = new CallableFilter() { - public V call(Callable callable) throws Exception { - SecurityContext context = SecurityContextHolder.getContext(); - Authentication old = context.getAuthentication(); - if (transportAuth!=null) - context.setAuthentication(transportAuth); - try { - return callable.call(); - } finally { - context.setAuthentication(old); - } - } - }; - - public CliManagerImpl(Channel channel) { - this.channel = channel; - channel.addLocalExecutionInterceptor(authenticationFilter); - } - - public int main(List args, Locale locale, InputStream stdin, OutputStream stdout, OutputStream stderr) { - // remoting sets the context classloader to the RemoteClassLoader, - // which slows down the classloading. we don't load anything from CLI, - // so counter that effect. - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - - PrintStream out = new PrintStream(stdout); - PrintStream err = new PrintStream(stderr); - - String subCmd = args.get(0); - CLICommand cmd = CLICommand.clone(subCmd); - if(cmd!=null) { - cmd.channel = Channel.current(); - final CLICommand old = CLICommand.setCurrent(cmd); - try { - transportAuth = Channel.currentOrFail().getProperty(CLICommand.TRANSPORT_AUTHENTICATION); - cmd.setTransportAuth(transportAuth); - return cmd.main(args.subList(1,args.size()),locale, stdin, out, err); - } finally { - CLICommand.setCurrent(old); - } - } - - err.println("No such command: "+subCmd); - new HelpCommand().main(Collections.emptyList(), locale, stdin, out, err); - return -1; - } - - public void authenticate(final String protocol, final Pipe c2s, final Pipe s2c) { - for (final CliTransportAuthenticator cta : CliTransportAuthenticator.all()) { - if (cta.supportsProtocol(protocol)) { - new Thread() { - @Override - public void run() { - cta.authenticate(protocol,channel,new Connection(c2s.getIn(), s2c.getOut())); - } - }.start(); - return; - } - } - throw new UnsupportedOperationException("Unsupported authentication protocol: "+protocol); - } - - public boolean hasCommand(String name) { - return CLICommand.clone(name)!=null; - } - - public int protocolVersion() { - return VERSION; - } - - private Object writeReplace() { - return Channel.current().export(CliEntryPoint.class,this); - } - - private static final Logger LOGGER = Logger.getLogger(CliManagerImpl.class.getName()); -} diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java deleted file mode 100644 index 30e18b9cb0efd5b92d24b294e1f843c6009994f8..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/CliProtocol.java +++ /dev/null @@ -1,112 +0,0 @@ -package hudson.cli; - -import hudson.Extension; -import hudson.Util; -import hudson.model.Computer; -import hudson.remoting.Channel; -import hudson.remoting.Channel.Mode; -import hudson.remoting.ChannelBuilder; -import jenkins.AgentProtocol; -import jenkins.model.Jenkins; -import jenkins.slaves.NioChannelSelector; -import org.jenkinsci.Symbol; -import org.jenkinsci.remoting.nio.NioChannelHub; - -import javax.inject.Inject; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.net.Socket; - -/** - * {@link AgentProtocol} that accepts connection from CLI clients. - * - * @author Kohsuke Kawaguchi - * @since 1.467 - * @deprecated Implementing Remoting-based protocol. - */ -@Deprecated -@Extension @Symbol("cli") -public class CliProtocol extends AgentProtocol { - @Inject - NioChannelSelector nio; - - /** - * {@inheritDoc} - */ - @Override - public boolean isOptIn() { - return true; - } - - @Override - public String getName() { - return jenkins.CLI.get().isEnabled() ? "CLI-connect" : null; - } - - @Override - public boolean isDeprecated() { - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return "Jenkins CLI Protocol/1 (deprecated, unencrypted)"; - } - - @Override - public void handle(Socket socket) throws IOException, InterruptedException { - new Handler(nio.getHub(),socket).run(); - } - - protected static class Handler { - protected final NioChannelHub hub; - protected final Socket socket; - - /** - * @deprecated as of 1.559 - * Use {@link #Handler(NioChannelHub, Socket)} - */ - @Deprecated - public Handler(Socket socket) { - this(null,socket); - } - - public Handler(NioChannelHub hub, Socket socket) { - this.hub = hub; - this.socket = socket; - } - - public void run() throws IOException, InterruptedException { - PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8")),true); - out.println("Welcome"); - runCli(new Connection(socket)); - } - - protected void runCli(Connection c) throws IOException, InterruptedException { - ChannelBuilder cb; - String name = "CLI channel from " + socket.getInetAddress(); - - // Connection can contain cipher wrapper, which can't be NIO-ed. -// if (hub!=null) -// cb = hub.newChannelBuilder(name, Computer.threadPoolForRemoting); -// else - cb = new ChannelBuilder(name, Computer.threadPoolForRemoting); - - Channel channel = cb - .withMode(Mode.BINARY) - .withRestricted(true) - .withBaseLoader(Jenkins.getActiveInstance().pluginManager.uberClassLoader) - .build(new BufferedInputStream(c.in), new BufferedOutputStream(c.out)); - - channel.setProperty(CliEntryPoint.class.getName(),new CliManagerImpl(channel)); - channel.join(); - } - } -} diff --git a/core/src/main/java/hudson/cli/CliProtocol2.java b/core/src/main/java/hudson/cli/CliProtocol2.java deleted file mode 100644 index 6e181714bb93b88640b06d728ce6cefa1ac2ab38..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/CliProtocol2.java +++ /dev/null @@ -1,107 +0,0 @@ -package hudson.cli; - -import hudson.Extension; -import jenkins.model.Jenkins; -import org.jenkinsci.Symbol; -import org.jenkinsci.remoting.nio.NioChannelHub; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.io.DataOutputStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.Socket; -import java.security.GeneralSecurityException; -import java.security.PrivateKey; -import java.security.Signature; - -/** - * {@link CliProtocol} Version 2, which adds transport encryption. - * - * @author Kohsuke Kawaguchi - * @since 1.467 - * @deprecated Implementing Remoting-based protocol. - */ -@Deprecated -@Extension @Symbol("cli2") -public class CliProtocol2 extends CliProtocol { - @Override - public String getName() { - return jenkins.CLI.get().isEnabled() ? "CLI2-connect" : null; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isOptIn() { - return true; - } - - @Override - public boolean isDeprecated() { - // We do not recommend it though it may be required for Remoting CLI - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return "Jenkins CLI Protocol/2 (deprecated)"; - } - - @Override - public void handle(Socket socket) throws IOException, InterruptedException { - new Handler2(nio.getHub(), socket).run(); - } - - protected static class Handler2 extends Handler { - /** - * @deprecated as of 1.559 - * Use {@link #Handler2(NioChannelHub, Socket)} - */ - @Deprecated - public Handler2(Socket socket) { - super(socket); - } - - public Handler2(NioChannelHub hub, Socket socket) { - super(hub, socket); - } - - @Override - public void run() throws IOException, InterruptedException { - try { - DataOutputStream out = new DataOutputStream(socket.getOutputStream()); - out.writeUTF("Welcome"); - - // perform coin-toss and come up with a session key to encrypt data - Connection c = new Connection(socket); - byte[] secret = c.diffieHellman(true).generateSecret(); - SecretKey sessionKey = new SecretKeySpec(Connection.fold(secret,128/8),"AES"); - c = c.encryptConnection(sessionKey,"AES/CFB8/NoPadding"); - - try { - // HACK: TODO: move the transport support into modules - Class cls = Jenkins.getActiveInstance().pluginManager.uberClassLoader.loadClass("org.jenkinsci.main.modules.instance_identity.InstanceIdentity"); - Object iid = cls.getDeclaredMethod("get").invoke(null); - PrivateKey instanceId = (PrivateKey)cls.getDeclaredMethod("getPrivate").invoke(iid); - - // send a signature to prove our identity - Signature signer = Signature.getInstance("SHA1withRSA"); - signer.initSign(instanceId); - signer.update(secret); - c.writeByteArray(signer.sign()); - } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new Error(e); - } - - runCli(c); - } catch (GeneralSecurityException e) { - throw new IOException("Failed to encrypt the CLI channel",e); - } - } - } -} diff --git a/core/src/main/java/hudson/cli/CliTransportAuthenticator.java b/core/src/main/java/hudson/cli/CliTransportAuthenticator.java index 327a4e2707a1fd760ada27a3dbbd667fc8e29b36..1271074a71921a5856c2f2f4fda7e6d5cc00ae17 100644 --- a/core/src/main/java/hudson/cli/CliTransportAuthenticator.java +++ b/core/src/main/java/hudson/cli/CliTransportAuthenticator.java @@ -3,24 +3,9 @@ package hudson.cli; import hudson.ExtensionList; import hudson.ExtensionPoint; import hudson.remoting.Channel; -import hudson.security.SecurityRealm; /** - * Perform {@link SecurityRealm} independent authentication. - * - *

    - * Implementing this extension point requires changes in the CLI module, as during authentication - * neither side trusts each other enough to start code-transfer. But it does allow us to - * use different implementations of the same protocol. - * - *

    Current Implementations

    - *

    - * Starting 1.419, CLI supports SSH public key based client/server mutual authentication. - * The protocol name of this is "ssh". - * - * @author Kohsuke Kawaguchi - * @since 1.419 - * @deprecated Specific to Remoting-based protocol. + * @deprecated No longer used. */ @Deprecated public abstract class CliTransportAuthenticator implements ExtensionPoint { diff --git a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java deleted file mode 100644 index 611f3918d9d1b4ebd6378a8e8e96eddc65eeca9f..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java +++ /dev/null @@ -1,280 +0,0 @@ -package hudson.cli; - -import com.google.common.annotations.VisibleForTesting; -import hudson.FilePath; -import hudson.model.User; -import hudson.remoting.Channel; -import hudson.util.Secret; -import jenkins.model.Jenkins; -import jenkins.security.MasterToSlaveCallable; -import jenkins.security.seed.UserSeedProperty; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; -import org.acegisecurity.userdetails.UserDetails; -import org.springframework.dao.DataAccessException; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; -import jenkins.security.HMACConfidentialKey; - -import javax.annotation.Nonnull; - -import javax.annotation.CheckForNull; - -/** - * Represents the authentication credential store of the CLI client. - * - *

    - * This object encapsulates a remote manipulation of the credential store. - * We store encrypted user names. - * - * @author Kohsuke Kawaguchi - * @since 1.351 - * @deprecated Assumes Remoting, and vulnerable to JENKINS-12543. - */ -@Deprecated -public class ClientAuthenticationCache implements Serializable { - - private static final HMACConfidentialKey MAC = new HMACConfidentialKey(ClientAuthenticationCache.class, "MAC"); - private static final Logger LOGGER = Logger.getLogger(ClientAuthenticationCache.class.getName()); - private static final String VERIFICATION_FRAGMENT_SEPARATOR = "_"; - private static final String USERNAME_VERIFICATION_SEPARATOR = ":"; - private static final String VERSION_2 = "v2"; - - /** - * Where the store should be placed. - */ - private final FilePath store; - - /** - * Loaded contents of the store. - */ - @VisibleForTesting - final Properties props = new Properties(); - - public ClientAuthenticationCache(Channel channel) throws IOException, InterruptedException { - store = (channel==null ? FilePath.localChannel : channel).call(new CredentialsFilePathMasterToSlaveCallable()); - if (store.exists()) { - try (InputStream istream = store.read()) { - props.load(istream); - } - } - } - - /** - * Gets the persisted authentication for this Jenkins. - * - * @return {@link jenkins.model.Jenkins#ANONYMOUS} if no such credential is found, or if the stored credential is invalid. - */ - public @Nonnull Authentication get() { - String val = props.getProperty(getPropertyKey()); - if (val == null) { - LOGGER.finer("No stored CLI authentication"); - return Jenkins.ANONYMOUS; - } - Secret oldSecret = Secret.decrypt(val); - if (oldSecret != null) { - LOGGER.log(Level.FINE, "Ignoring insecure stored CLI authentication for {0}", oldSecret.getPlainText()); - return Jenkins.ANONYMOUS; - } - int idx = val.lastIndexOf(USERNAME_VERIFICATION_SEPARATOR); - if (idx == -1) { - LOGGER.log(Level.FINE, "Ignoring malformed stored CLI authentication: {0}", val); - return Jenkins.ANONYMOUS; - } - String username = val.substring(0, idx); - String verificationPart = val.substring(idx + 1); - int indexOfSeparator = verificationPart.indexOf(VERIFICATION_FRAGMENT_SEPARATOR); - if (indexOfSeparator == -1) { - return legacy(username, verificationPart, val); - } - - /* - * Format of the cache data: [username]:[verificationToken] - * Where the verificationToken is: [mac]_[version]_[restOfFragments] - */ - - String[] verificationFragments = verificationPart.split(VERIFICATION_FRAGMENT_SEPARATOR); - if (verificationFragments.length < 2) { - LOGGER.log(Level.FINE, "Ignoring malformed stored CLI authentication verification: {0}", val); - return Jenkins.ANONYMOUS; - } - - // the mac is only verifying the username - String macFragment = verificationFragments[0]; - String version = verificationFragments[1]; - String[] restOfFragments = Arrays.copyOfRange(verificationFragments, 2, verificationFragments.length); - - Authentication authFromVersion; - if (VERSION_2.equals(version)) { - authFromVersion = version2(username, restOfFragments, val); - } else { - LOGGER.log(Level.FINE, "Unrecognized version for stored CLI authentication verification: {0}", val); - return Jenkins.ANONYMOUS; - } - - if (authFromVersion != null) { - return authFromVersion; - } - - return getUserAuthIfValidMac(username, macFragment, val); - } - - private Authentication legacy(String username, String mac, String fullValueStored){ - return getUserAuthIfValidMac(username, mac, fullValueStored); - } - - /** - * restOfFragments format: [userSeed] - * - * @return {@code null} when the method wants to let the default behavior to proceed - */ - private @CheckForNull Authentication version2(String username, String[] restOfFragments, String fullValueStored){ - if (restOfFragments.length != 1) { - LOGGER.log(Level.FINE, "Number of fragments invalid for stored CLI authentication verification: {0}", fullValueStored); - return Jenkins.ANONYMOUS; - } - - if (UserSeedProperty.DISABLE_USER_SEED) { - return null; - } - - User user = User.getById(username, false); - if (user == null) { - LOGGER.log(Level.FINE, "User not found for stored CLI authentication verification: {0}", fullValueStored); - return Jenkins.ANONYMOUS; - } - - UserSeedProperty property = user.getProperty(UserSeedProperty.class); - if (property == null) { - LOGGER.log(Level.INFO, "User does not have a user seed but one is contained in CLI authentication: {0}", fullValueStored); - return Jenkins.ANONYMOUS; - } - - String receivedUserSeed = restOfFragments[0]; - String actualUserSeed = property.getSeed(); - if (!receivedUserSeed.equals(actualUserSeed)) { - LOGGER.log(Level.FINE, "Actual user seed does not correspond to the one in stored CLI authentication: {0}", fullValueStored); - return Jenkins.ANONYMOUS; - } - - return null; - } - - private Authentication getUserAuthIfValidMac(String username, String mac, String fullValueStored) { - if (!MAC.checkMac(username, mac)) { - LOGGER.log(Level.FINE, "Ignoring stored CLI authentication due to MAC mismatch: {0}", fullValueStored); - return Jenkins.ANONYMOUS; - } - try { - UserDetails u = Jenkins.get().getSecurityRealm().loadUserByUsername(username); - LOGGER.log(Level.FINER, "Loaded stored CLI authentication for {0}", username); - return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities()); - } catch (AuthenticationException | DataAccessException e) { - //TODO there is no check to be consistent with User.ALLOW_NON_EXISTENT_USER_TO_LOGIN - LOGGER.log(Level.FINE, "Stored CLI authentication did not correspond to a valid user: " + username, e); - return Jenkins.ANONYMOUS; - } - } - - /** - * Computes the key that identifies this Hudson among other Hudsons that the user has a credential for. - */ - @VisibleForTesting - String getPropertyKey() { - Jenkins j = Jenkins.getActiveInstance(); - String url = j.getRootUrl(); - if (url!=null) return url; - - return j.getLegacyInstanceId(); - } - - /** - * Persists the specified authentication. - */ - public void set(Authentication a) throws IOException, InterruptedException { - Jenkins h = Jenkins.getActiveInstance(); - - // make sure that this security realm is capable of retrieving the authentication by name, - // as it's not required. - UserDetails u = h.getSecurityRealm().loadUserByUsername(a.getName()); - String username = u.getUsername(); - - User user; - if (a instanceof AnonymousAuthenticationToken) { - user = null; - } else { - user = User.getById(a.getName(), false); - } - - if (user == null) { - // anonymous case or user not existing case, but normally should not occur - // since the only call to it is by LoginCommand after a non-anonymous login. - setUsingLegacyMethod(username); - return; - } - - String userSeed; - UserSeedProperty userSeedProperty = user.getProperty(UserSeedProperty.class); - if (userSeedProperty == null) { - userSeed = "no-user-seed"; - } else { - userSeed = userSeedProperty.getSeed(); - } - String mac = getMacOf(username); - String validationFragment = String.join(VERIFICATION_FRAGMENT_SEPARATOR, mac, VERSION_2, userSeed); - - String propertyValue = username + USERNAME_VERIFICATION_SEPARATOR + validationFragment; - props.setProperty(getPropertyKey(), propertyValue); - - save(); - } - - @VisibleForTesting - void setUsingLegacyMethod(String username) throws IOException, InterruptedException { - props.setProperty(getPropertyKey(), username + USERNAME_VERIFICATION_SEPARATOR + getMacOf(username)); - save(); - } - - @VisibleForTesting - @Nonnull String getMacOf(@Nonnull String value){ - return MAC.mac(value); - } - - /** - * Removes the persisted credential, if there's one. - */ - public void remove() throws IOException, InterruptedException { - if (props.remove(getPropertyKey())!=null) - save(); - } - - @VisibleForTesting - void save() throws IOException, InterruptedException { - try (OutputStream os = store.write()) { - props.store(os, "Credential store"); - } - // try to protect this file from other users, if we can. - store.chmod(0600); - } - - private static class CredentialsFilePathMasterToSlaveCallable extends MasterToSlaveCallable { - public FilePath call() throws IOException { - File home = new File(System.getProperty("user.home")); - File hudsonHome = new File(home, ".hudson"); - if (hudsonHome.exists()) { - return new FilePath(new File(hudsonHome, "cli-credentials")); - } - return new FilePath(new File(home, ".jenkins/cli-credentials")); - } - } -} diff --git a/core/src/main/java/hudson/cli/CommandDuringBuild.java b/core/src/main/java/hudson/cli/CommandDuringBuild.java deleted file mode 100644 index 17d154d6fa3531a9ec7bd3df8a92c0dbe7d132a9..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/CommandDuringBuild.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2010, InfraDNA, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package hudson.cli; - -import jenkins.model.Jenkins; -import hudson.model.Job; -import hudson.model.Run; -import jenkins.security.MasterToSlaveCallable; -import org.kohsuke.args4j.CmdLineException; - -import java.io.IOException; - -/** - * Base class for those commands that are valid only during a build. - * - * @author Kohsuke Kawaguchi - * @deprecated Limited to Remoting-based protocol. - */ -@Deprecated -public abstract class CommandDuringBuild extends CLICommand { - /** - * This method makes sense only when called from within the build kicked by Jenkins. - * We use the environment variables that Jenkins sets to determine the build that is being run. - */ - protected Run getCurrentlyBuilding() throws CmdLineException { - Run r = optCurrentlyBuilding(); - if (r==null) - throw new IllegalStateException("This CLI command works only when invoked from inside a build"); - return r; - } - - /** - * If the command is currently running inside a build, return it. Otherwise null. - */ - protected Run optCurrentlyBuilding() throws CmdLineException { - try { - CLICommand c = CLICommand.getCurrent(); - if (c==null) - throw new IllegalStateException("Not executing a CLI command"); - String[] envs = c.checkChannel().call(new GetCharacteristicEnvironmentVariables()); - - if (envs[0]==null || envs[1]==null) - return null; - - Job j = Jenkins.getActiveInstance().getItemByFullName(envs[0],Job.class); - if (j==null) - throw new IllegalArgumentException("No such job: "+envs[0]); - - try { - Run r = j.getBuildByNumber(Integer.parseInt(envs[1])); - if (r==null) - throw new IllegalArgumentException("No such build #"+envs[1]+" in "+envs[0]); - if (!r.isBuilding()) { - throw new IllegalStateException(r + " is not currently being built"); - } - return r; - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid build number: "+envs[1]); - } - } catch (IOException | InterruptedException e) { - throw new IllegalArgumentException("Failed to identify the build being executed",e); - } - } - - /** - * Gets the environment variables that points to the build being executed. - */ - private static final class GetCharacteristicEnvironmentVariables extends MasterToSlaveCallable { - public String[] call() throws IOException { - return new String[] { - System.getenv("JOB_NAME"), - System.getenv("BUILD_NUMBER") - }; - } - } -} diff --git a/core/src/main/java/hudson/cli/ConnectNodeCommand.java b/core/src/main/java/hudson/cli/ConnectNodeCommand.java index 189d2b5406376a2e88c6411308aa991238d5339e..012237190f057c84fbd9d15023343d0102b12491 100644 --- a/core/src/main/java/hudson/cli/ConnectNodeCommand.java +++ b/core/src/main/java/hudson/cli/ConnectNodeCommand.java @@ -44,6 +44,7 @@ import java.util.logging.Logger; @Extension public class ConnectNodeCommand extends CLICommand { + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @Argument(metaVar="NAME", usage="Slave name, or empty string for master; comma-separated list is supported", required=true, multiValued=true) private List nodes; @@ -62,13 +63,12 @@ public class ConnectNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet<>(); - hs.addAll(nodes); + final HashSet hs = new HashSet<>(nodes); List names = null; for (String node_s : hs) { - Computer computer = null; + Computer computer; try { computer = jenkins.getComputer(node_s); diff --git a/core/src/main/java/hudson/cli/DeleteJobCommand.java b/core/src/main/java/hudson/cli/DeleteJobCommand.java index 03e63e8579da3f9b631577120b30bb873ab6d8ae..f6c4fe5c9ededb72a1d5a048fa1800817b72c523 100644 --- a/core/src/main/java/hudson/cli/DeleteJobCommand.java +++ b/core/src/main/java/hudson/cli/DeleteJobCommand.java @@ -40,6 +40,7 @@ import java.util.HashSet; @Extension public class DeleteJobCommand extends CLICommand { + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @Argument(usage="Name of the job(s) to delete", required=true, multiValued=true) private List jobs; @@ -55,11 +56,10 @@ public class DeleteJobCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet<>(); - hs.addAll(jobs); + final HashSet hs = new HashSet<>(jobs); for (String job_s: hs) { - AbstractItem job = null; + AbstractItem job; try { job = (AbstractItem) jenkins.getItemByFullName(job_s); diff --git a/core/src/main/java/hudson/cli/DeleteNodeCommand.java b/core/src/main/java/hudson/cli/DeleteNodeCommand.java index 60a8821e0f7134b396344ce4fc6078dd4c5eac24..ed1a178267a20f9bc0509e301e29d9cef8954b7e 100644 --- a/core/src/main/java/hudson/cli/DeleteNodeCommand.java +++ b/core/src/main/java/hudson/cli/DeleteNodeCommand.java @@ -40,6 +40,7 @@ import java.util.List; @Extension public class DeleteNodeCommand extends CLICommand { + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @Argument(usage="Names of nodes to delete", required=true, multiValued=true) private List nodes; @@ -55,11 +56,10 @@ public class DeleteNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet<>(); - hs.addAll(nodes); + final HashSet hs = new HashSet<>(nodes); for (String node_s : hs) { - Node node = null; + Node node; try { node = jenkins.getNode(node_s); diff --git a/core/src/main/java/hudson/cli/DeleteViewCommand.java b/core/src/main/java/hudson/cli/DeleteViewCommand.java index 955a476ad96c009dac15484c864e180b8f337ce0..b2ebffac80b6dd8735bb60bd2e56d40f83c73a09 100644 --- a/core/src/main/java/hudson/cli/DeleteViewCommand.java +++ b/core/src/main/java/hudson/cli/DeleteViewCommand.java @@ -41,6 +41,7 @@ import java.util.List; @Extension public class DeleteViewCommand extends CLICommand { + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @Argument(usage="View names to delete", required=true, multiValued=true) private List views; @@ -56,13 +57,12 @@ public class DeleteViewCommand extends CLICommand { boolean errorOccurred = false; // Remove duplicates - final HashSet hs = new HashSet<>(); - hs.addAll(views); + final HashSet hs = new HashSet<>(views); ViewOptionHandler voh = new ViewOptionHandler(null, null, null); for(String view_s : hs) { - View view = null; + View view; try { view = voh.getView(view_s); diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index 354d69a561f8ed44b6584e43f7a53f74cfee713f..101576e90442b3b20c901563bfb356b02364ac83 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -47,10 +47,10 @@ public class DisablePluginCommand extends CLICommand { @Option(name = "-restart", aliases = "-r", usage = "Restart Jenkins after disabling plugins.") private boolean restart; - @Option(name = "-strategy", aliases = "-s", metaVar = "strategy", usage = "How to process the dependant plugins. \n" + - "- none: if a mandatory dependant plugin exists and it is enabled, the plugin cannot be disabled (default value).\n" + - "- mandatory: all mandatory dependant plugins are also disabled, optional dependant plugins remain enabled.\n" + - "- all: all dependant plugins are also disabled, no matter if its dependency is optional or mandatory.") + @Option(name = "-strategy", aliases = "-s", metaVar = "strategy", usage = "How to process the dependent plugins. \n" + + "- none: if a mandatory dependent plugin exists and it is enabled, the plugin cannot be disabled (default value).\n" + + "- mandatory: all mandatory dependent plugins are also disabled, optional dependent plugins remain enabled.\n" + + "- all: all dependent plugins are also disabled, no matter if its dependency is optional or mandatory.") private String strategy = PluginWrapper.PluginDisableStrategy.NONE.toString(); @Option(name = "-quiet", aliases = "-q", usage = "Be quiet, print only the error messages") @@ -140,10 +140,10 @@ public class DisablePluginCommand extends CLICommand { } printIndented(indent, Messages.DisablePluginCommand_StatusMessage(oneResult.getPlugin(), oneResult.getStatus(), oneResult.getMessage())); - if (oneResult.getDependantsDisableStatus().size() > 0) { + if (oneResult.getDependentsDisableStatus().size() > 0) { indent += INDENT_SPACE; - for (PluginWrapper.PluginDisableResult oneDependantResult : oneResult.getDependantsDisableStatus()) { - printResult(oneDependantResult, indent); + for (PluginWrapper.PluginDisableResult oneDependentResult : oneResult.getDependentsDisableStatus()) { + printResult(oneDependentResult, indent); } } } @@ -163,9 +163,9 @@ public class DisablePluginCommand extends CLICommand { } /** - * Restart if this particular result of the disablement of a plugin and its dependant plugins (depending on the + * Restart if this particular result of the disablement of a plugin and its dependent plugins (depending on the * strategy used) has a plugin disablexd. - * @param oneResult the result of a plugin (and its dependants). + * @param oneResult the result of a plugin (and its dependents). * @return true if it end up in restarting jenkins. */ private boolean restartIfNecessary(PluginWrapper.PluginDisableResult oneResult) throws RestartNotSupportedException { @@ -175,9 +175,9 @@ public class DisablePluginCommand extends CLICommand { return true; } - if (oneResult.getDependantsDisableStatus().size() > 0) { - for (PluginWrapper.PluginDisableResult oneDependantResult : oneResult.getDependantsDisableStatus()) { - if (restartIfNecessary(oneDependantResult)) { + if (oneResult.getDependentsDisableStatus().size() > 0) { + for (PluginWrapper.PluginDisableResult oneDependentResult : oneResult.getDependentsDisableStatus()) { + if (restartIfNecessary(oneDependentResult)) { return true; } } @@ -191,7 +191,7 @@ public class DisablePluginCommand extends CLICommand { * Calculate the result code of the full process based in what went on during the process * @param results he list of results for the disablement of each plugin * @return the status code. 0 if all plugins disabled. {@link #RETURN_CODE_NOT_DISABLED_DEPENDANTS} if some - * dependant plugin is not disabled (with strategy NONE), {@link #RETURN_CODE_NO_SUCH_PLUGIN} if some passed + * dependent plugin is not disabled (with strategy NONE), {@link #RETURN_CODE_NO_SUCH_PLUGIN} if some passed * plugin doesn't exist. Whatever happens first. */ private int getResultCode(List results) { @@ -208,7 +208,7 @@ public class DisablePluginCommand extends CLICommand { /** * Calculate the result code of the disablement of one plugin based in what went on during the process of this one - * and its dependant plugins. + * and its dependent plugins. * @param result the result of the disablement of this plugin * @return the status code */ @@ -223,8 +223,8 @@ public class DisablePluginCommand extends CLICommand { } if (returnCode == 0) { - for (PluginWrapper.PluginDisableResult oneDependantResult : result.getDependantsDisableStatus()) { - returnCode = getResultCode(oneDependantResult); + for (PluginWrapper.PluginDisableResult oneDependentResult : result.getDependentsDisableStatus()) { + returnCode = getResultCode(oneDependentResult); if (returnCode != 0) { break; } diff --git a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java index ad003521d77a2025ad09eb5f9e16d6cc06e7f3bb..34da7d8cf6c704196dabc3006a6f38b9048d8d61 100644 --- a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java +++ b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java @@ -61,13 +61,12 @@ public class DisconnectNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet<>(); - hs.addAll(nodes); + final HashSet hs = new HashSet<>(nodes); List names = null; for (String node_s : hs) { - Computer computer = null; + Computer computer; try { computer = jenkins.getComputer(node_s); diff --git a/core/src/main/java/hudson/cli/GroovyCommand.java b/core/src/main/java/hudson/cli/GroovyCommand.java index 5e27f5478199f5f05c6d132e8b1f351e361cb7e6..f87ba80abc86cdbb45aff3923166ac53e557d8e9 100644 --- a/core/src/main/java/hudson/cli/GroovyCommand.java +++ b/core/src/main/java/hudson/cli/GroovyCommand.java @@ -25,11 +25,7 @@ package hudson.cli; import groovy.lang.GroovyShell; import groovy.lang.Binding; -import hudson.cli.util.ScriptLoader; -import hudson.model.AbstractProject; import jenkins.model.Jenkins; -import hudson.model.Item; -import hudson.model.Run; import hudson.Extension; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; @@ -52,7 +48,7 @@ public class GroovyCommand extends CLICommand { return Messages.GroovyCommand_ShortDescription(); } - @Argument(metaVar="SCRIPT",usage="Script to be executed. File, URL or '=' to represent stdin.") + @Argument(metaVar="SCRIPT",usage="Script to be executed. Only '=' (to represent stdin) is supported.") public String script; /** @@ -70,23 +66,9 @@ public class GroovyCommand extends CLICommand { binding.setProperty("stdin",stdin); binding.setProperty("stdout",stdout); binding.setProperty("stderr",stderr); - binding.setProperty("channel",channel); - - if (channel != null) { - String j = getClientEnvironmentVariable("JOB_NAME"); - if (j != null) { - Item job = Jenkins.getActiveInstance().getItemByFullName(j); - binding.setProperty("currentJob", job); - String b = getClientEnvironmentVariable("BUILD_NUMBER"); - if (b != null && job instanceof AbstractProject) { - Run r = ((AbstractProject) job).getBuildByNumber(Integer.parseInt(b)); - binding.setProperty("currentBuild", r); - } - } - } GroovyShell groovy = new GroovyShell(Jenkins.getActiveInstance().getPluginManager().uberClassLoader, binding); - groovy.run(loadScript(),"RemoteClass",remaining.toArray(new String[remaining.size()])); + groovy.run(loadScript(),"RemoteClass",remaining.toArray(new String[0])); return 0; } @@ -99,7 +81,8 @@ public class GroovyCommand extends CLICommand { if (script.equals("=")) return IOUtils.toString(stdin); - return checkChannel().call(new ScriptLoader(script)); + checkChannel(); + return null; // never called } } diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java index 6bd34dc6911db8932473be2aa8f02a9b55bc523a..1048bc53d75f0068adea8d5f5b91790a2ea0773d 100644 --- a/core/src/main/java/hudson/cli/GroovyshCommand.java +++ b/core/src/main/java/hudson/cli/GroovyshCommand.java @@ -25,7 +25,6 @@ package hudson.cli; import hudson.Extension; import jenkins.model.Jenkins; -import hudson.remoting.ChannelClosedException; import groovy.lang.Binding; import groovy.lang.Closure; import org.codehaus.groovy.tools.shell.Groovysh; @@ -110,25 +109,6 @@ public class GroovyshCommand extends CLICommand { }; Groovysh shell = new Groovysh(cl, binding, io, registrar); shell.getImports().add("hudson.model.*"); - - // defaultErrorHook doesn't re-throw IOException, so ShellRunner in - // Groovysh will keep looping forever if we don't terminate when the - // channel is closed - final Closure originalErrorHook = shell.getErrorHook(); - shell.setErrorHook(new Closure(shell, shell) { - private static final long serialVersionUID = 1L; - - @SuppressWarnings("unused") - @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS",justification="Closure invokes this via reflection") - public Object doCall(Object[] args) throws ChannelClosedException { - if (args.length == 1 && args[0] instanceof ChannelClosedException) { - throw (ChannelClosedException)args[0]; - } - - return originalErrorHook.call(args); - } - }); - return shell; } diff --git a/core/src/main/java/hudson/cli/InstallPluginCommand.java b/core/src/main/java/hudson/cli/InstallPluginCommand.java index 95f1e55f7782124bb2bc0b4692e8aa9d6455d19e..09cb54c95c85c3e178276a88c8b78146be264d5d 100644 --- a/core/src/main/java/hudson/cli/InstallPluginCommand.java +++ b/core/src/main/java/hudson/cli/InstallPluginCommand.java @@ -25,7 +25,6 @@ package hudson.cli; import hudson.AbortException; import hudson.Extension; -import hudson.FilePath; import hudson.PluginManager; import hudson.util.VersionNumber; import jenkins.model.Jenkins; @@ -38,10 +37,14 @@ import org.kohsuke.args4j.Option; import java.io.File; import java.net.URL; import java.net.MalformedURLException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.HashSet; import java.util.List; import java.util.ArrayList; import java.util.Set; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import org.apache.commons.io.FileUtils; /** @@ -52,21 +55,24 @@ import org.apache.commons.io.FileUtils; */ @Extension public class InstallPluginCommand extends CLICommand { + + @Override public String getShortDescription() { return Messages.InstallPluginCommand_ShortDescription(); } - @Argument(metaVar="SOURCE",required=true,usage="If this points to a local file (‘-remoting’ mode only), that file will be installed. " + + @Argument(metaVar="SOURCE",required=true,usage= "If this is an URL, Jenkins downloads the URL and installs that as a plugin. " + - "If it is the string ‘=’, the file will be read from standard input of the command, and ‘-name’ must be specified. " + + "If it is the string ‘=’, the file will be read from standard input of the command. " + "Otherwise the name is assumed to be the short name of the plugin in the existing update center (like ‘findbugs’), " + "and the plugin will be installed from the update center. If the short name includes a minimum version number " + "(like ‘findbugs:1.4’), and there are multiple update centers publishing different versions, the update centers " + "will be searched in order for the first one publishing a version that is at least the specified version.") - public List sources = new ArrayList(); + public List sources = new ArrayList<>(); - @Option(name="-name",usage="If specified, the plugin will be installed as this short name (whereas normally the name is inferred from the source name automatically).") - public String name; // TODO better to parse out Short-Name from the manifest and deprecate this option + @Deprecated + @Option(name = "-name", usage = "No longer used.") + public String name; @Option(name="-restart",usage="Restart Jenkins upon successful installation.") public boolean restart; @@ -74,61 +80,38 @@ public class InstallPluginCommand extends CLICommand { @Option(name="-deploy",usage="Deploy plugins right away without postponing them until the reboot.") public boolean dynamicLoad; + @Override protected int run() throws Exception { - Jenkins h = Jenkins.getActiveInstance(); + Jenkins h = Jenkins.get(); h.checkPermission(PluginManager.UPLOAD_PLUGINS); PluginManager pm = h.getPluginManager(); - if (sources.size() > 1 && name != null) { - throw new IllegalArgumentException("-name is incompatible with multiple sources"); + if (name != null) { + stderr.println("-name is deprecated; it is no longer necessary nor honored."); } for (String source : sources) { if (source.equals("=")) { - if (name == null) { - throw new IllegalArgumentException("-name required when using -source -"); - } stdout.println(Messages.InstallPluginCommand_InstallingPluginFromStdin()); - File f = getTargetFile(name); + File f = getTmpFile(); FileUtils.copyInputStreamToFile(stdin, f); + f = moveToFinalLocation(f); if (dynamicLoad) { pm.dynamicLoad(f); } continue; } - // is this a file? - if (channel!=null) { - FilePath f = new FilePath(channel, source); - if (f.exists()) { - stdout.println(Messages.InstallPluginCommand_InstallingPluginFromLocalFile(f)); - String n = name != null ? name : f.getBaseName(); - f.copyTo(getTargetFilePath(n)); - if (dynamicLoad) - pm.dynamicLoad(getTargetFile(n)); - continue; - } - } - // is this an URL? try { URL u = new URL(source); stdout.println(Messages.InstallPluginCommand_InstallingPluginFromUrl(u)); - String n; - if (name != null) { - n = name; - } else { - n = u.getPath(); - n = n.substring(n.lastIndexOf('/') + 1); - n = n.substring(n.lastIndexOf('\\') + 1); - int idx = n.lastIndexOf('.'); - if (idx > 0) { - n = n.substring(0, idx); - } + File f = getTmpFile(); + FileUtils.copyURLToFile(u, f); // TODO JENKINS-58248 proxy + f = moveToFinalLocation(f); + if (dynamicLoad) { + pm.dynamicLoad(f); } - getTargetFilePath(n).copyFrom(u); - if (dynamicLoad) - pm.dynamicLoad(getTargetFile(n)); continue; } catch (MalformedURLException e) { // not an URL @@ -185,11 +168,24 @@ public class InstallPluginCommand extends CLICommand { return 0; // all success } - private static FilePath getTargetFilePath(String name) { - return new FilePath(getTargetFile(name)); + private static File getTmpFile() throws Exception { + return File.createTempFile("download", ".jpi.tmp", Jenkins.get().getPluginManager().rootDir); } - private static File getTargetFile(String name) { - return new File(Jenkins.getActiveInstance().getPluginManager().rootDir,name+".jpi"); + private static File moveToFinalLocation(File tmpFile) throws Exception { + String pluginName; + try (JarFile jf = new JarFile(tmpFile)) { + Manifest mf = jf.getManifest(); + if (mf == null) { + throw new IllegalArgumentException("JAR lacks a manifest"); + } + pluginName = mf.getMainAttributes().getValue("Short-Name"); + } + if (pluginName == null) { + throw new IllegalArgumentException("JAR manifest lacks a Short-Name attribute and so does not look like a plugin"); + } + File target = new File(Jenkins.get().getPluginManager().rootDir, pluginName + ".jpi"); + Files.move(tmpFile.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + return target; } } diff --git a/core/src/main/java/hudson/cli/InstallToolCommand.java b/core/src/main/java/hudson/cli/InstallToolCommand.java deleted file mode 100644 index 4b06b51686bf04a2b30a98f030733435a0aaa352..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/InstallToolCommand.java +++ /dev/null @@ -1,150 +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. - */ -package hudson.cli; - -import hudson.Extension; -import hudson.AbortException; -import hudson.EnvVars; -import jenkins.model.Jenkins; -import hudson.model.AbstractProject; -import hudson.model.Run; -import hudson.model.Executor; -import hudson.model.Node; -import hudson.model.Item; -import hudson.util.EditDistance; -import hudson.util.StreamTaskListener; -import hudson.tools.ToolDescriptor; -import hudson.tools.ToolInstallation; - -import java.util.List; -import java.util.ArrayList; -import java.io.IOException; - -import jenkins.security.MasterToSlaveCallable; -import org.kohsuke.args4j.Argument; - -/** - * Performs automatic tool installation on demand. - * - * @author Kohsuke Kawaguchi - * @deprecated Limited to Remoting-based protocol. - */ -@Deprecated -@Extension -public class InstallToolCommand extends CLICommand { - @Argument(index=0,metaVar="KIND",usage="The type of the tool to install, such as 'Ant'") - public String toolType; - - @Argument(index=1,metaVar="NAME",usage="The name of the tool to install, as you've entered in the Jenkins system configuration") - public String toolName; - - public String getShortDescription() { - return Messages.InstallToolCommand_ShortDescription(); - } - - protected int run() throws Exception { - Jenkins h = Jenkins.getActiveInstance(); - h.checkPermission(Jenkins.READ); - - // where is this build running? - BuildIDs id = checkChannel().call(new BuildIDs()); - - if (!id.isComplete()) - throw new IllegalStateException("This command can be only invoked from a build executing inside Hudson"); - - AbstractProject p = h.getItemByFullName(id.job, AbstractProject.class); - if (p==null) - throw new IllegalStateException("No such job found: "+id.job); - p.checkPermission(Item.CONFIGURE); - - List toolTypes = new ArrayList<>(); - for (ToolDescriptor d : ToolInstallation.all()) { - toolTypes.add(d.getDisplayName()); - if (d.getDisplayName().equals(toolType)) { - List toolNames = new ArrayList<>(); - for (ToolInstallation t : d.getInstallations()) { - toolNames.add(t.getName()); - if (t.getName().equals(toolName)) - return install(t, id, p); - } - - // didn't find the right tool name - error(toolNames, toolName, "name"); - } - } - - // didn't find the tool type - error(toolTypes, toolType, "type"); - - // will never be here - throw new AssertionError(); - } - - private int error(List candidates, String given, String noun) throws AbortException { - if (given ==null) - throw new IllegalArgumentException("No tool "+ noun +" was specified. Valid values are "+candidates.toString()); - else - throw new IllegalArgumentException("Unrecognized tool "+noun+". Perhaps you meant '"+ EditDistance.findNearest(given,candidates)+"'?"); - } - - /** - * Performs an installation. - */ - private int install(ToolInstallation t, BuildIDs id, AbstractProject p) throws IOException, InterruptedException { - - Run b = p.getBuildByNumber(Integer.parseInt(id.number)); - if (b==null) - throw new IllegalStateException("No such build: "+id.number); - - Executor exec = b.getExecutor(); - if (exec==null) - throw new IllegalStateException(b.getFullDisplayName()+" is not building"); - - Node node = exec.getOwner().getNode(); - if (node == null) { - throw new IllegalStateException("The node " + exec.getOwner().getDisplayName() + " has been deleted"); - } - - t = t.translate(node, EnvVars.getRemote(checkChannel()), new StreamTaskListener(stderr)); - stdout.println(t.getHome()); - return 0; - } - - private static final class BuildIDs extends MasterToSlaveCallable { - String job,number,id; - - public BuildIDs call() throws IOException { - job = System.getenv("JOB_NAME"); - number = System.getenv("BUILD_NUMBER"); - id = System.getenv("BUILD_ID"); - return this; - } - - boolean isComplete() { - return job!=null && number!=null && id!=null; - } - - private static final long serialVersionUID = 1L; - } -} diff --git a/core/src/main/java/hudson/cli/ListPluginsCommand.java b/core/src/main/java/hudson/cli/ListPluginsCommand.java index faa35dfa678897d72a0ed5306d419dadfaf0e296..f637bf7a8436ba81daf3ad6daa79dc08ab4a5e18 100644 --- a/core/src/main/java/hudson/cli/ListPluginsCommand.java +++ b/core/src/main/java/hudson/cli/ListPluginsCommand.java @@ -47,7 +47,7 @@ public class ListPluginsCommand extends CLICommand { public String name; protected int run() { - Jenkins h = Jenkins.getInstance(); + Jenkins h = Jenkins.get(); h.checkPermission(Jenkins.ADMINISTER); PluginManager pluginManager = h.getPluginManager(); diff --git a/core/src/main/java/hudson/cli/LoginCommand.java b/core/src/main/java/hudson/cli/LoginCommand.java deleted file mode 100644 index 55efd9dbcbb93633a4787a9d6c06496695bedd07..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/LoginCommand.java +++ /dev/null @@ -1,54 +0,0 @@ -package hudson.cli; - -import hudson.Extension; -import java.io.PrintStream; -import jenkins.model.Jenkins; -import jenkins.security.SecurityListener; -import org.acegisecurity.Authentication; -import org.kohsuke.args4j.CmdLineException; - -/** - * Saves the current credential to allow future commands to run without explicit credential information. - * - * @author Kohsuke Kawaguchi - * @since 1.351 - * @deprecated Assumes Remoting, and vulnerable to JENKINS-12543. - */ -@Extension -@Deprecated -public class LoginCommand extends CLICommand { - @Override - public String getShortDescription() { - return Messages.LoginCommand_ShortDescription(); - } - - @Override - protected void printUsageSummary(PrintStream stderr) { - super.printUsageSummary(stderr); - stderr.println(Messages.LoginCommand_FullDescription()); - } - - /** - * If we use the stored authentication for the login command, login becomes no-op, which is clearly not what - * the user has intended. - */ - @Override - protected Authentication loadStoredAuthentication() throws InterruptedException { - return Jenkins.ANONYMOUS; - } - - @Override - protected int run() throws Exception { - Authentication a = Jenkins.getAuthentication(); - if (a== Jenkins.ANONYMOUS) - throw new CmdLineException("No credentials specified."); // this causes CLI to show the command line options. - - ClientAuthenticationCache store = new ClientAuthenticationCache(checkChannel()); - store.set(a); - - SecurityListener.fireLoggedIn(a.getName()); - - return 0; - } - -} diff --git a/core/src/main/java/hudson/cli/LogoutCommand.java b/core/src/main/java/hudson/cli/LogoutCommand.java deleted file mode 100644 index 8578a6eb0486a289674f787ec9fa8d838d1c565e..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/LogoutCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package hudson.cli; - -import hudson.Extension; -import jenkins.security.SecurityListener; -import org.acegisecurity.Authentication; - -import java.io.PrintStream; - -/** - * Deletes the credential stored with the login command. - * - * @author Kohsuke Kawaguchi - * @since 1.351 - * @deprecated See {@link LoginCommand}. - */ -@Deprecated -@Extension -public class LogoutCommand extends CLICommand { - @Override - public String getShortDescription() { - return Messages.LogoutCommand_ShortDescription(); - } - - @Override - protected void printUsageSummary(PrintStream stderr) { - super.printUsageSummary(stderr); - stderr.println(Messages.LogoutCommand_FullDescription()); - } - - @Override - protected int run() throws Exception { - ClientAuthenticationCache store = new ClientAuthenticationCache(checkChannel()); - - Authentication auth = store.get(); - - store.remove(); - - SecurityListener.fireLoggedOut(auth.getName()); - - return 0; - } -} diff --git a/core/src/main/java/hudson/cli/OnlineNodeCommand.java b/core/src/main/java/hudson/cli/OnlineNodeCommand.java index 0594d3770c9702a954f732d611dce90283ddfb87..b3ec4fc0c9930a1d0ca7ff0793883905f4070601 100644 --- a/core/src/main/java/hudson/cli/OnlineNodeCommand.java +++ b/core/src/main/java/hudson/cli/OnlineNodeCommand.java @@ -60,7 +60,7 @@ public class OnlineNodeCommand extends CLICommand { List names = null; for (String node_s : hs) { - Computer computer = null; + Computer computer; try { computer = jenkins.getComputer(node_s); diff --git a/core/src/main/java/hudson/cli/ReloadJobCommand.java b/core/src/main/java/hudson/cli/ReloadJobCommand.java index 3e42d64e68660dc69d4075bbac82cceb638b1201..f807476934a670fc51c489e12701bbedb69906b1 100644 --- a/core/src/main/java/hudson/cli/ReloadJobCommand.java +++ b/core/src/main/java/hudson/cli/ReloadJobCommand.java @@ -62,8 +62,7 @@ public class ReloadJobCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet<>(); - hs.addAll(jobs); + final HashSet hs = new HashSet<>(jobs); for (String job_s: hs) { AbstractItem job = null; diff --git a/core/src/main/java/hudson/cli/SetBuildParameterCommand.java b/core/src/main/java/hudson/cli/SetBuildParameterCommand.java deleted file mode 100644 index 2dfd0d6e8319d449a07db4344f93e5f67ea68af8..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/SetBuildParameterCommand.java +++ /dev/null @@ -1,50 +0,0 @@ -package hudson.cli; - -import hudson.Extension; -import hudson.model.ParametersAction; -import hudson.model.Run; -import hudson.model.StringParameterValue; -import org.kohsuke.args4j.Argument; - -import java.util.Collections; - -/** - * Used from the build to update the build variable. - * - * This allows one build step to affect the environment variables seen by later build steps. - * - * @author Kohsuke Kawaguchi - * @since 1.514 - * @deprecated Limited to Remoting-based protocol. - */ -@Deprecated -@Extension -public class SetBuildParameterCommand extends CommandDuringBuild { - @Argument(index=0, metaVar="NAME", required=true, usage="Name of the build variable") - public String name; - - @Argument(index=1, metaVar="VALUE", required=true, usage="Value of the build variable") - public String value; - - @Override - public String getShortDescription() { - return Messages.SetBuildParameterCommand_ShortDescription(); - } - - @Override - protected int run() throws Exception { - Run r = getCurrentlyBuilding(); - r.checkPermission(Run.UPDATE); - - StringParameterValue p = new StringParameterValue(name, value); - - ParametersAction a = r.getAction(ParametersAction.class); - if (a!=null) { - r.replaceAction(a.createUpdated(Collections.singleton(p))); - } else { - r.addAction(new ParametersAction(p)); - } - - return 0; - } -} diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java index e0dccb1553daef04c119b53f26526da553ee8031..f8363f32503f950b47e84d48496f1ae4dde534a8 100644 --- a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java +++ b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java @@ -35,7 +35,6 @@ import hudson.model.Hudson; import jenkins.ExtensionComponentSet; import jenkins.ExtensionRefreshException; import jenkins.model.Jenkins; -import hudson.security.CliAuthenticator; import org.acegisecurity.AccessDeniedException; import org.acegisecurity.Authentication; import org.acegisecurity.BadCredentialsException; @@ -43,7 +42,6 @@ import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.jvnet.hudson.annotation_indexer.Index; import org.jvnet.localizer.ResourceBundleHolder; -import org.kohsuke.args4j.ClassParser; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.CmdLineException; @@ -207,16 +205,10 @@ public class CLIRegisterer extends ExtensionFinder { SecurityContext sc = SecurityContextHolder.getContext(); Authentication old = sc.getAuthentication(); try { - // authentication - CliAuthenticator authenticator = Jenkins.get().getSecurityRealm().createCliAuthenticator(this); - new ClassParser().parse(authenticator, parser); - // fill up all the binders parser.parseArgument(args); - Authentication auth = authenticator.authenticate(); - if (auth == Jenkins.ANONYMOUS) - auth = loadStoredAuthentication(); + Authentication auth = getTransportAuthentication(); sc.setAuthentication(auth); // run the CLI with the right credential jenkins.checkPermission(Jenkins.READ); @@ -238,24 +230,24 @@ public class CLIRegisterer extends ExtensionFinder { sc.setAuthentication(old); // restore } } catch (CmdLineException e) { - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); printUsage(stderr, parser); return 2; } catch (IllegalStateException e) { - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 4; } catch (IllegalArgumentException e) { - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 3; } catch (AbortException e) { - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 5; } catch (AccessDeniedException e) { - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + e.getMessage()); return 6; } catch (BadCredentialsException e) { @@ -263,13 +255,13 @@ public class CLIRegisterer extends ExtensionFinder { // do that to the server log instead String id = UUID.randomUUID().toString(); LOGGER.log(Level.INFO, "CLI login attempt failed: " + id, e); - stderr.println(""); + stderr.println(); stderr.println("ERROR: Bad Credentials. Search the server log for " + id + " for more details."); return 7; } catch (Throwable e) { final String errorMsg = String.format("Unexpected exception occurred while performing %s command.", getName()); - stderr.println(""); + stderr.println(); stderr.println("ERROR: " + errorMsg); LOGGER.log(Level.WARNING, errorMsg, e); Functions.printStackTrace(e, stderr); diff --git a/core/src/main/java/hudson/cli/handlers/package-info.java b/core/src/main/java/hudson/cli/handlers/package-info.java index ee7dee536f7c8b302c186c2044c66f2c9cecd6d8..65a4bb7adf568d2e4a1ce51470edd73194df33ad 100644 --- a/core/src/main/java/hudson/cli/handlers/package-info.java +++ b/core/src/main/java/hudson/cli/handlers/package-info.java @@ -3,4 +3,3 @@ */ package hudson.cli.handlers; -import org.kohsuke.args4j.spi.OptionHandler; \ No newline at end of file diff --git a/core/src/main/java/hudson/cli/util/ScriptLoader.java b/core/src/main/java/hudson/cli/util/ScriptLoader.java deleted file mode 100644 index 2bdbf0253da1397a63fa1b9db17dd2fa58873908..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/util/ScriptLoader.java +++ /dev/null @@ -1,44 +0,0 @@ -package hudson.cli.util; - -import hudson.AbortException; -import jenkins.security.MasterToSlaveCallable; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -/** - * Reads a file (either a path or URL) over a channel. - * - * @author vjuranek - * @deprecated Specific to Remoting-based protocol. - */ -@Deprecated -public class ScriptLoader extends MasterToSlaveCallable { - - private final String script; - - public ScriptLoader(String script){ - this.script = script; - } - - public String call() throws IOException { - File f = new File(script); - if(f.exists()) - return FileUtils.readFileToString(f); - - URL url; - try { - url = new URL(script); - } catch (MalformedURLException e) { - throw new AbortException("Unable to find a script "+script); - } - try (InputStream s = url.openStream()) { - return IOUtils.toString(s); - } - } -} diff --git a/core/src/main/java/hudson/console/AnnotatedLargeText.java b/core/src/main/java/hudson/console/AnnotatedLargeText.java index 6acbdeff6e78e90b0ccbd0529e9bcd19e2b8bbec..c862d0c61b187ad53aa545b152064ed035e0fc56 100644 --- a/core/src/main/java/hudson/console/AnnotatedLargeText.java +++ b/core/src/main/java/hudson/console/AnnotatedLargeText.java @@ -25,7 +25,6 @@ */ package hudson.console; -import com.trilead.ssh2.crypto.Base64; import jenkins.model.Jenkins; import hudson.remoting.ObjectInputStreamEx; import java.util.concurrent.TimeUnit; @@ -48,10 +47,14 @@ import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Writer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + import com.jcraft.jzlib.GZIPInputStream; import com.jcraft.jzlib.GZIPOutputStream; import static java.lang.Math.abs; +import javax.annotation.CheckReturnValue; import org.jenkinsci.remoting.util.AnonymousClassWarnings; /** @@ -119,16 +122,15 @@ public class AnnotatedLargeText extends LargeText { if (base64!=null) { Cipher sym = PASSING_ANNOTATOR.decrypt(); - ObjectInputStream ois = new ObjectInputStreamEx(new GZIPInputStream( - new CipherInputStream(new ByteArrayInputStream(Base64.decode(base64.toCharArray())),sym)), - Jenkins.getInstance().pluginManager.uberClassLoader); - try { + try (ObjectInputStream ois = new ObjectInputStreamEx(new GZIPInputStream( + new CipherInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8))), sym)), + Jenkins.get().pluginManager.uberClassLoader)) { long timestamp = ois.readLong(); if (TimeUnit.HOURS.toMillis(1) > abs(System.currentTimeMillis()-timestamp)) // don't deserialize something too old to prevent a replay attack - return (ConsoleAnnotator)ois.readObject(); - } finally { - ois.close(); + return (ConsoleAnnotator) ois.readObject(); + } catch (RuntimeException ex) { + throw new IOException("Could not decode input", ex); } } } catch (ClassNotFoundException e) { @@ -138,6 +140,7 @@ public class AnnotatedLargeText extends LargeText { return ConsoleAnnotator.initial(context); } + @CheckReturnValue @Override public long writeLogTo(long start, Writer w) throws IOException { if (isHtml()) @@ -150,6 +153,7 @@ public class AnnotatedLargeText extends LargeText { * Strips annotations using a {@link PlainTextConsoleOutputStream}. * {@inheritDoc} */ + @CheckReturnValue @Override public long writeLogTo(long start, OutputStream out) throws IOException { return super.writeLogTo(start, new PlainTextConsoleOutputStream(out)); @@ -159,10 +163,12 @@ public class AnnotatedLargeText extends LargeText { * Calls {@link LargeText#writeLogTo(long, OutputStream)} without stripping annotations as {@link #writeLogTo(long, OutputStream)} would. * @since 1.577 */ + @CheckReturnValue public long writeRawLogTo(long start, OutputStream out) throws IOException { return super.writeLogTo(start, out); } + @CheckReturnValue public long writeHtmlTo(long start, Writer w) throws IOException { ConsoleAnnotationOutputStream caw = new ConsoleAnnotationOutputStream<>( w, createAnnotator(Stapler.getCurrentRequest()), context, charset); @@ -176,7 +182,7 @@ public class AnnotatedLargeText extends LargeText { oos.close(); StaplerResponse rsp = Stapler.getCurrentResponse(); if (rsp!=null) - rsp.setHeader("X-ConsoleAnnotator", new String(Base64.encode(baos.toByteArray()))); + rsp.setHeader("X-ConsoleAnnotator", new String(Base64.getEncoder().encode(baos.toByteArray()))); return r; } diff --git a/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java b/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java index 4a74ae8a9e8aea23b0d61bd8de1e0bfeb4c23384..2126cdc283913de68cbf201650cba57ce9fd2bda 100644 --- a/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java +++ b/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java @@ -92,6 +92,6 @@ public abstract class ConsoleAnnotationDescriptor extends Descriptor,ConsoleAnnotationDescriptor> all() { - return (DescriptorExtensionList) Jenkins.getInstance().getDescriptorList(ConsoleNote.class); + return (DescriptorExtensionList) Jenkins.get().getDescriptorList(ConsoleNote.class); } } diff --git a/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java b/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java index f3a22c875cbdc9041424a7ab71070c9e5d8c5472..00a84b6f46127f356a8a301ca59329f434a3d833 100644 --- a/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java +++ b/core/src/main/java/hudson/console/ConsoleAnnotationOutputStream.java @@ -24,9 +24,6 @@ package hudson.console; import hudson.MarkupText; -import org.apache.commons.io.output.ProxyWriter; -import org.kohsuke.stapler.framework.io.WriterOutputStream; - import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; @@ -38,6 +35,9 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import org.apache.commons.io.output.ProxyWriter; +import org.apache.commons.lang.StringEscapeUtils; +import org.kohsuke.stapler.framework.io.WriterOutputStream; /** * Used to convert plain text console output (as byte sequence) + embedded annotations into HTML (as char sequence), @@ -49,7 +49,7 @@ import java.util.logging.Logger; * @since 1.349 */ public class ConsoleAnnotationOutputStream extends LineTransformationOutputStream { - private final Writer out; + private final Writer out; // not an OutputStream so cannot use LineTransformationOutputStream.Delegating private final T context; private ConsoleAnnotator ann; @@ -119,11 +119,9 @@ public class ConsoleAnnotationOutputStream extends LineTransformationOutputSt } }); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { // if we failed to resurrect an annotation, ignore it. - LOGGER.log(Level.FINE,"Failed to resurrect annotation",e); - } catch (ClassNotFoundException e) { - LOGGER.log(Level.FINE,"Failed to resurrect annotation",e); + LOGGER.log(Level.FINE, "Failed to resurrect annotation from \"" + StringEscapeUtils.escapeJava(new String(in, next, rest)) + "\"", e); } int bytesUsed = rest - b.available(); // bytes consumed by annotations diff --git a/core/src/main/java/hudson/console/ConsoleLogFilter.java b/core/src/main/java/hudson/console/ConsoleLogFilter.java index ec1b0c05920c03375d7c434c79595b9b5e041f7c..99fe50eca374fd6117a6db2eeb969f2b59902f39 100644 --- a/core/src/main/java/hudson/console/ConsoleLogFilter.java +++ b/core/src/main/java/hudson/console/ConsoleLogFilter.java @@ -36,6 +36,8 @@ import hudson.util.ArgumentListBuilder; import javax.annotation.Nonnull; import java.io.IOException; import java.io.OutputStream; +import java.io.Serializable; +import jenkins.util.JenkinsJVM; /** * A hook to allow filtering of information that is written to the console log. @@ -43,6 +45,10 @@ import java.io.OutputStream; * direct access to the underlying {@link OutputStream} so it's possible to suppress * data, which isn't possible from the other interfaces. * ({@link ArgumentListBuilder#add(String, boolean)} is a simpler way to suppress a single password.) + *

    Implementations which are {@link Serializable} may be sent to an agent JVM for processing. + * In particular, this happens under JEP-210. + * In this case, the implementation should not assume that {@link JenkinsJVM#isJenkinsJVM}, + * and if generating {@link ConsoleNote}s will need to encode them on the master side first. * @author dty * @since 1.383 * @see BuildWrapper#decorateLogger diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java index b4f67cf3d6a7e6bd3df599e6481d769db022a32a..6625812e0e0e535c7c373c45d30ee9a3e7a17b5c 100644 --- a/core/src/main/java/hudson/console/ConsoleNote.java +++ b/core/src/main/java/hudson/console/ConsoleNote.java @@ -110,6 +110,17 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; * is also important, although {@link ConsoleNote}s that failed to deserialize will be simply ignored, so the * worst thing that can happen is that you just lose some notes. * + *

    + * Note that {@link #encode}, {@link #encodeTo(OutputStream)}, and {@link #encodeTo(Writer)} + * should be called on the Jenkins master. + * If called from an agent JVM, a signature will be missing and so as per + * SECURITY-382 + * the console note will be ignored. + * This may happen, in particular, if the note was generated by a {@link ConsoleLogFilter} sent to the agent. + * Alternative solutions include using a {@link ConsoleAnnotatorFactory} where practical; + * or generating the encoded form of the note on the master side and sending it to the agent, + * for example by saving that form as instance fields in a {@link ConsoleLogFilter} implementation. + * *

    Behaviour, JavaScript, and CSS

    *

    * {@link ConsoleNote} can have associated {@code script.js} and {@code style.css} (put them @@ -154,7 +165,7 @@ public abstract class ConsoleNote implements Serializable, Describable implements Serializable, Describable implements Serializable, Describable implements Serializable, Describable implements Serializable, Describable removeNotes(Collection logLines) { - List r = new ArrayList(logLines.size()); + List r = new ArrayList<>(logLines.size()); for (String l : logLines) r.add(removeNotes(l)); return r; diff --git a/core/src/main/java/hudson/console/HyperlinkNote.java b/core/src/main/java/hudson/console/HyperlinkNote.java index 3f8ccfda1d89ab00a50e809cafb4b443c31d8e76..8d0e13d70bada17908d4ee72083858430b3e8676 100644 --- a/core/src/main/java/hudson/console/HyperlinkNote.java +++ b/core/src/main/java/hudson/console/HyperlinkNote.java @@ -66,7 +66,7 @@ public class HyperlinkNote extends ConsoleNote { url = req.getContextPath()+url; } else { // otherwise presumably this is rendered for e-mails and other non-HTTP stuff - url = Jenkins.getInstance().getRootUrl()+url.substring(1); + url = Jenkins.get().getRootUrl()+url.substring(1); } } text.addMarkup(charPos, charPos + length, "", ""); diff --git a/core/src/main/java/hudson/console/LineTransformationOutputStream.java b/core/src/main/java/hudson/console/LineTransformationOutputStream.java index 95bb0baf6989be6e0fc990821aa5e782b8d591ca..37f760df20885177ab66c13ea13ddfa38221a3df 100644 --- a/core/src/main/java/hudson/console/LineTransformationOutputStream.java +++ b/core/src/main/java/hudson/console/LineTransformationOutputStream.java @@ -32,7 +32,7 @@ import java.io.OutputStream; * Filtering {@link OutputStream} that buffers text by line, so that the derived class * can perform some manipulation based on the contents of the whole line. * - * TODO: Mac is supposed to be CR-only. This class needs to handle that. + *

    Subclass {@link Delegating} in the typical case that you are decorating an underlying stream. * * @author Kohsuke Kawaguchi * @since 1.349 @@ -110,4 +110,32 @@ public abstract class LineTransformationOutputStream extends OutputStream { } private static final int LF = 0x0A; + + /** + * Convenience subclass for cases where you wish to process lines being sent to an underlying stream. + * {@link #eol} will typically {@link OutputStream#write(byte[], int, int)} to {@link #out}. + * Flushing or closing the decorated stream will behave properly. + * @since FIXME + */ + public static abstract class Delegating extends LineTransformationOutputStream { + + protected final OutputStream out; + + protected Delegating(OutputStream out) { + this.out = out; + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + super.close(); + out.close(); + } + + } + } diff --git a/core/src/main/java/hudson/console/ModelHyperlinkNote.java b/core/src/main/java/hudson/console/ModelHyperlinkNote.java index 127911cc76fa848ac5dee822634739d815960c65..c389dfa3839830eb87c9180301a80a5f36562bde 100644 --- a/core/src/main/java/hudson/console/ModelHyperlinkNote.java +++ b/core/src/main/java/hudson/console/ModelHyperlinkNote.java @@ -50,7 +50,7 @@ public class ModelHyperlinkNote extends HyperlinkNote { if (c != null) { return encodeTo("/" + c.getUrl(), node.getDisplayName()); } - String nodePath = node == Jenkins.getInstance() ? "(master)" : node.getNodeName(); + String nodePath = node == Jenkins.get() ? "(master)" : node.getNodeName(); return encodeTo("/computer/" + nodePath, node.getDisplayName()); } diff --git a/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java b/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java index ee02d177d48c4fbd7d0aadc9e422b02aa3d02421..6eb4c9f4eab60df1bde369d503eaa6771955a46a 100644 --- a/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java +++ b/core/src/main/java/hudson/console/PlainTextConsoleOutputStream.java @@ -28,21 +28,19 @@ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.logging.Logger; /** * Filters out console notes. * * @author Kohsuke Kawaguchi */ -public class PlainTextConsoleOutputStream extends LineTransformationOutputStream { - private final OutputStream out; +public class PlainTextConsoleOutputStream extends LineTransformationOutputStream.Delegating { /** * */ public PlainTextConsoleOutputStream(OutputStream out) { - this.out = out; + super(out); } /** @@ -77,17 +75,4 @@ public class PlainTextConsoleOutputStream extends LineTransformationOutputStream out.write(in,written,sz-written); } - @Override - public void flush() throws IOException { - out.flush(); - } - - @Override - public void close() throws IOException { - super.close(); - out.close(); - } - - - private static final Logger LOGGER = Logger.getLogger(PlainTextConsoleOutputStream.class.getName()); } diff --git a/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java b/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java index 56eaf7d941a0f281dd45a3f219204fce04719353..586a244b7a3d92f1180a01de357c295d8f75a191 100644 --- a/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java +++ b/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java @@ -43,8 +43,8 @@ public class HudsonHomeDiskUsageChecker extends PeriodicWork { } protected void doRun() { - long free = Jenkins.getInstance().getRootDir().getUsableSpace(); - long total = Jenkins.getInstance().getRootDir().getTotalSpace(); + long free = Jenkins.get().getRootDir().getUsableSpace(); + long total = Jenkins.get().getRootDir().getTotalSpace(); if(free<=0 || total<=0) { // information unavailable. pointless to try. LOGGER.info("JENKINS_HOME disk usage information isn't available. aborting to monitor"); diff --git a/core/src/main/java/hudson/diagnosis/MemoryUsageMonitor.java b/core/src/main/java/hudson/diagnosis/MemoryUsageMonitor.java index 1ba648eb38fe142462d98cea457473130c77e4eb..eba9991aff141cffc5cf104c7f017c488d014d31 100644 --- a/core/src/main/java/hudson/diagnosis/MemoryUsageMonitor.java +++ b/core/src/main/java/hudson/diagnosis/MemoryUsageMonitor.java @@ -53,7 +53,7 @@ public final class MemoryUsageMonitor extends PeriodicWork { * A memory group is conceptually a set of memory pools. */ public final class MemoryGroup { - private final List pools = new ArrayList(); + private final List pools = new ArrayList<>(); /** * Trend of the memory usage, after GCs. diff --git a/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java b/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java index 8b3778262ef5650e2aa00f03e63f2fcb07c589c5..5663a32d967a75eee283bfecc2a28d7731a51dee 100644 --- a/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java +++ b/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java @@ -55,7 +55,7 @@ public class NullIdDescriptorMonitor extends AdministrativeMonitor { return Messages.NullIdDescriptorMonitor_DisplayName(); } - private final List problems = new ArrayList(); + private final List problems = new ArrayList<>(); @Override public boolean isActivated() { @@ -68,7 +68,7 @@ public class NullIdDescriptorMonitor extends AdministrativeMonitor { @Initializer(after=EXTENSIONS_AUGMENTED) public void verify() { - Jenkins h = Jenkins.getInstance(); + Jenkins h = Jenkins.get(); for (Descriptor d : h.getExtensionList(Descriptor.class)) { PluginWrapper p = h.getPluginManager().whichPlugin(d.getClass()); String id; diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java index 7fc185737c1563f8fb109ce4525fd1ee6d66cc0e..ea4c484f23ea5b0e0b483cf72ed3eb774790af58 100644 --- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java +++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java @@ -78,7 +78,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST; public class OldDataMonitor extends AdministrativeMonitor { private static final Logger LOGGER = Logger.getLogger(OldDataMonitor.class.getName()); - private ConcurrentMap data = new ConcurrentHashMap(); + private ConcurrentMap data = new ConcurrentHashMap<>(); /** * Gets instance of the monitor. @@ -106,7 +106,7 @@ public class OldDataMonitor extends AdministrativeMonitor { } public Map getData() { - Map r = new HashMap(); + Map r = new HashMap<>(); for (Map.Entry entry : this.data.entrySet()) { Saveable s = entry.getKey().get(); if (s != null) { @@ -117,7 +117,7 @@ public class OldDataMonitor extends AdministrativeMonitor { } private static void remove(Saveable obj, boolean isDelete) { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); OldDataMonitor odm = get(j); SecurityContext oldContext = ACL.impersonate(ACL.SYSTEM); try { @@ -166,7 +166,7 @@ public class OldDataMonitor extends AdministrativeMonitor { * @param version Hudson release when the data structure changed. */ public static void report(Saveable obj, String version) { - OldDataMonitor odm = get(Jenkins.getInstance()); + OldDataMonitor odm = get(Jenkins.get()); try { SaveableReference ref = referTo(obj); while (true) { @@ -395,7 +395,7 @@ public class OldDataMonitor extends AdministrativeMonitor { private static SaveableReference referTo(Saveable s) { if (s instanceof Run) { Job parent = ((Run) s).getParent(); - if (Jenkins.getInstance().getItemByFullName(parent.getFullName()) == parent) { + if (Jenkins.get().getItemByFullName(parent.getFullName()) == parent) { return new RunSaveableReference((Run) s); } } diff --git a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java index 858f79ddaad89b84974b90d1113f5b0e88b269c7..70c4c211bdc376c87fc6c0fd3382caea088bf70a 100644 --- a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java +++ b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java @@ -64,7 +64,7 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor { public HttpResponse doTest() { String referer = Stapler.getCurrentRequest().getReferer(); - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); // May need to send an absolute URL, since handling of HttpRedirect with a relative URL does not currently honor X-Forwarded-Proto/Port at all. String redirect = j.getRootUrl() + "administrativeMonitor/" + id + "/testForReverseProxySetup/" + (referer != null ? Util.rawEncode(referer) : "NO-REFERER") + "/"; LOGGER.log(Level.FINE, "coming from {0} and redirecting to {1}", new Object[] {referer, redirect}); @@ -73,7 +73,7 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor { @StaplerDispatchable public void getTestForReverseProxySetup(String rest) { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); String inferred = j.getRootUrlFromRequest() + "manage"; // TODO this could also verify that j.getRootUrl() has been properly configured, and send a different message if not if (rest.startsWith(inferred)) { // not using equals due to JENKINS-24014 diff --git a/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java b/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java index 7f5c02c0e35d131e3fb92f854b16c16e1bd2a0e9..56c98854150c661fa2c99da4f75740f3d82d984f 100644 --- a/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java +++ b/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java @@ -50,7 +50,7 @@ public class TooManyJobsButNoView extends AdministrativeMonitor { } public boolean isActivated() { - Jenkins h = Jenkins.getInstance(); + Jenkins h = Jenkins.get(); return h.getViews().size()==1 && h.getItemMap().size()> THRESHOLD; } diff --git a/core/src/main/java/hudson/fsp/WorkspaceSnapshotSCM.java b/core/src/main/java/hudson/fsp/WorkspaceSnapshotSCM.java index 956f4c946412a704feb491dd7b289b45a0236b43..e8c19e62a896c4e717b5a57869aa7ab7739130af 100644 --- a/core/src/main/java/hudson/fsp/WorkspaceSnapshotSCM.java +++ b/core/src/main/java/hudson/fsp/WorkspaceSnapshotSCM.java @@ -94,7 +94,7 @@ public class WorkspaceSnapshotSCM extends SCM { * @return never null. */ public Snapshot resolve() throws ResolvedFailedException { - Jenkins h = Jenkins.getInstance(); + Jenkins h = Jenkins.get(); AbstractProject job = h.getItemByFullName(jobName, AbstractProject.class); if(job==null) { if(h.getItemByFullName(jobName)==null) { diff --git a/core/src/main/java/hudson/init/InitStrategy.java b/core/src/main/java/hudson/init/InitStrategy.java index 9d7d287c4310237183984b3287f7536867d76cde..c6c5795e546cba5e3c1162eec3b0565e18513eb0 100644 --- a/core/src/main/java/hudson/init/InitStrategy.java +++ b/core/src/main/java/hudson/init/InitStrategy.java @@ -45,7 +45,7 @@ public class InitStrategy { * and when that happens, Jenkins will ignore all but the first one in the list. */ public List listPluginArchives(PluginManager pm) throws IOException { - List r = new ArrayList(); + List r = new ArrayList<>(); // the ordering makes sure that during the debugging we get proper precedence among duplicates. // for example, while doing "mvn jpi:run" or "mvn hpi:run" on a plugin that's bundled with Jenkins, we want to the diff --git a/core/src/main/java/hudson/init/TaskMethodFinder.java b/core/src/main/java/hudson/init/TaskMethodFinder.java index 0e09bfe267821a22fc01f26296477880a4d046eb..b8370294c83a3785c121e003ce3f3ccd43ebe1b2 100644 --- a/core/src/main/java/hudson/init/TaskMethodFinder.java +++ b/core/src/main/java/hudson/init/TaskMethodFinder.java @@ -32,7 +32,7 @@ import static java.util.logging.Level.WARNING; abstract class TaskMethodFinder extends TaskBuilder { private static final Logger LOGGER = Logger.getLogger(TaskMethodFinder.class.getName()); protected final ClassLoader cl; - private final Set discovered = new HashSet(); + private final Set discovered = new HashSet<>(); private final Class type; private final Class milestoneType; @@ -52,7 +52,7 @@ abstract class TaskMethodFinder extends TaskBuilder { protected abstract boolean fatalOf(T i); public Collection discoverTasks(Reactor session) throws IOException { - List result = new ArrayList(); + List result = new ArrayList<>(); for (Method e : Index.list(type, cl, Method.class)) { if (filter(e)) continue; // already reported once @@ -115,7 +115,7 @@ abstract class TaskMethodFinder extends TaskBuilder { * Determines the parameter injection of the initialization method. */ private Object lookUp(Class type) { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); assert j != null : "This method is only invoked after the Jenkins singleton instance has been set"; if (type==Jenkins.class || type==Hudson.class) return j; @@ -180,7 +180,7 @@ abstract class TaskMethodFinder extends TaskBuilder { } private Collection toMilestones(String[] tokens, Milestone m) { - List r = new ArrayList(); + List r = new ArrayList<>(); for (String s : tokens) { try { r.add((Milestone)Enum.valueOf(milestoneType,s)); diff --git a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java index a092a5123a2b206a89398c9e61ec4e55f451ad85..a8b73db22ef46dbb15d3114f1e77e8389edfa191 100644 --- a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java +++ b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java @@ -1,22 +1,18 @@ package hudson.init.impl; import hudson.init.Initializer; -import java.io.EOFException; import jenkins.model.Jenkins; +import jenkins.telemetry.impl.java11.MissingClassTelemetry; +import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.WebApp; import org.kohsuke.stapler.compression.CompressionFilter; -import org.kohsuke.stapler.compression.UncaughtExceptionHandler; -import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import java.io.EOFException; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; -import org.kohsuke.stapler.Stapler; - /** * Deals with exceptions that get thrown all the way up to the Stapler rendering layer. */ @@ -33,6 +29,10 @@ public class InstallUncaughtExceptionHandler { } req.setAttribute("javax.servlet.error.exception",e); try { + // If we have an exception, let's see if it's related with missing classes on Java 11. We reach + // here with a ClassNotFoundException in an action, for example. Setting the report here is the only + // way to catch the missing classes when the plugin uses Thread.currentThread().getContextClassLoader().loadClass + MissingClassTelemetry.reportExceptionInside(e); WebApp.get(j.servletContext).getSomeStapler().invoke(req, rsp, j, "/oops"); } catch (ServletException | IOException x) { if (!Stapler.isSocketException(x)) { @@ -46,10 +46,10 @@ public class InstallUncaughtExceptionHandler { } catch (SecurityException ex) { LOGGER.log(Level.SEVERE, - "Failed to set the default UncaughtExceptionHandler. " + + "Failed to set the default UncaughtExceptionHandler. " + "If any threads die due to unhandled coding errors then there will be no logging of this information. " + - "The lack of this diagnostic information will make it harder to track down issues which will reduce the supportability of Jenkins. " + - "It is highly recommended that you consult the documentation that comes with you servlet container on how to allow the " + + "The lack of this diagnostic information will make it harder to track down issues which will reduce the supportability of Jenkins. " + + "It is highly recommended that you consult the documentation that comes with you servlet container on how to allow the " + "`setDefaultUncaughtExceptionHandler` permission and enable it.", ex); } } @@ -74,10 +74,14 @@ public class InstallUncaughtExceptionHandler { "A thread (" + t.getName() + '/' + t.getId() + ") died unexpectedly due to an uncaught exception, this may leave your Jenkins in a bad way and is usually indicative of a bug in the code.", ex); + + // If we have an exception, let's see if it's related with missing classes on Java 11. We reach + // here with a ClassNotFoundException in an action, for example. Setting the report here is the only + // way to catch the missing classes when the plugin uses Thread.currentThread().getContextClassLoader().loadClass + MissingClassTelemetry.reportExceptionInside(ex); } } private InstallUncaughtExceptionHandler() {} - } diff --git a/core/src/main/java/hudson/init/package-info.java b/core/src/main/java/hudson/init/package-info.java index 5f66063ca3b2ef245b9491cdf4488513d8421b9d..57dbab8fd5c82002b8861bf5efcbabd304fd331b 100644 --- a/core/src/main/java/hudson/init/package-info.java +++ b/core/src/main/java/hudson/init/package-info.java @@ -44,4 +44,3 @@ */ package hudson.init; -import org.jvnet.hudson.reactor.Task; \ No newline at end of file diff --git a/core/src/main/java/hudson/lifecycle/Lifecycle.java b/core/src/main/java/hudson/lifecycle/Lifecycle.java index 4c52cdd87877e8e91f588831cdfbb244dba9c2d4..8df00fb23b079144d9a4018b1c313363a78477e8 100644 --- a/core/src/main/java/hudson/lifecycle/Lifecycle.java +++ b/core/src/main/java/hudson/lifecycle/Lifecycle.java @@ -61,7 +61,7 @@ public abstract class Lifecycle implements ExtensionPoint { String p = SystemProperties.getString("hudson.lifecycle"); if(p!=null) { try { - ClassLoader cl = Jenkins.getInstance().getPluginManager().uberClassLoader; + ClassLoader cl = Jenkins.get().getPluginManager().uberClassLoader; instance = (Lifecycle)cl.loadClass(p).newInstance(); } catch (InstantiationException e) { InstantiationError x = new InstantiationError(e.getMessage()); diff --git a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java index 1af9c131558926666e3f2693d9ff14ae2311fa0f..a17c898ab365f6c7b148cfd86a777981be18d011 100644 --- a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java @@ -56,11 +56,8 @@ public class UnixLifecycle extends Lifecycle { // if we are running as daemon, don't fork into background one more time during restart args.remove("--daemon"); - } catch (UnsupportedOperationException e) { - // can't restart - failedToObtainArgs = e; - } catch (LinkageError e) { - // see HUDSON-3875 + } catch (UnsupportedOperationException | LinkageError e) { + // can't restart / see HUDSON-3875 failedToObtainArgs = e; } } @@ -86,7 +83,7 @@ public class UnixLifecycle extends Lifecycle { // exec to self String exe = args.get(0); - LIBC.execvp(exe, new StringArray(args.toArray(new String[args.size()]))); + LIBC.execvp(exe, new StringArray(args.toArray(new String[0]))); throw new IOException("Failed to exec '"+exe+"' "+LIBC.strerror(Native.getLastError())); } diff --git a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java index b218d5964077bb45df6909a644e0892276561b18..67d7e24c9b7a98e9cbb55311ee5a5c1a4cf658f3 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java +++ b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java @@ -54,7 +54,6 @@ import org.kohsuke.stapler.interceptor.RequirePOST; import javax.servlet.ServletException; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.util.logging.Logger; import java.util.logging.Level; @@ -112,7 +111,7 @@ public class WindowsInstallerLink extends ManagementLink { */ @RequirePOST public void doDoInstall(StaplerRequest req, StaplerResponse rsp, @QueryParameter("dir") String _dir) throws IOException, ServletException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); if(installationDir!=null) { // installation already complete @@ -174,7 +173,7 @@ public class WindowsInstallerLink extends ManagementLink { @RequirePOST public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); if(installationDir==null) { // if the user reloads the page after Hudson has restarted, @@ -185,7 +184,7 @@ public class WindowsInstallerLink extends ManagementLink { } rsp.forward(this,"_restart",req); - final File oldRoot = Jenkins.getInstance().getRootDir(); + final File oldRoot = Jenkins.get().getRootDir(); // initiate an orderly shutdown after we finished serving this request new Thread("terminator") { @@ -216,9 +215,7 @@ public class WindowsInstallerLink extends ManagementLink { int r = runElevated( new File(installationDir, "jenkins.exe"), "start", task, installationDir); task.getLogger().println(r==0?"Successfully started":"start service failed. Exit code="+r); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { + } catch (IOException | InterruptedException e) { e.printStackTrace(); } } @@ -231,7 +228,7 @@ public class WindowsInstallerLink extends ManagementLink { } }); - Jenkins.getInstance().cleanUp(); + Jenkins.get().cleanUp(); System.exit(0); } catch (InterruptedException e) { e.printStackTrace(); @@ -250,7 +247,7 @@ public class WindowsInstallerLink extends ManagementLink { protected final void sendError(String message, StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException { req.setAttribute("message",message); req.setAttribute("pre",true); - rsp.forward(Jenkins.getInstance(),"error",req); + rsp.forward(Jenkins.get(),"error",req); } /** @@ -270,11 +267,9 @@ public class WindowsInstallerLink extends ManagementLink { if(war!=null && new File(war).exists()) { WindowsInstallerLink link = new WindowsInstallerLink(new File(war)); - // in certain situations where we know the user is just trying Jenkins (like when Jenkins is launched - // from JNLP), also put this link on the navigation bar to increase - // visibility + // TODO possibly now unused (JNLP installation mode is long gone): if(SystemProperties.getString(WindowsInstallerLink.class.getName()+".prominent")!=null) - Jenkins.getInstance().getActions().add(link); + Jenkins.get().getActions().add(link); return link; } @@ -292,9 +287,7 @@ public class WindowsInstallerLink extends ManagementLink { try { return new LocalLauncher(out).launch().cmds(jenkinsExe, command).stdout(out).pwd(pwd).join(); } catch (IOException e) { - if (e.getMessage().contains("CreateProcess") && e.getMessage().contains("=740")) { - // fall through - } else { + if (!e.getMessage().contains("CreateProcess") || !e.getMessage().contains("=740")) { throw e; } } diff --git a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java index ad3940b4a4feab58375654f6a0f0bb870c18ad1b..92c155fbeb3693b7b645cb54ef6052feecb44aef 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java @@ -145,7 +145,7 @@ public class WindowsServiceLifecycle extends Lifecycle { throw new IOException(baos.toString()); } - private static final File getBaseDir() { + private static File getBaseDir() { File baseDir; String baseEnv = System.getenv("BASE"); @@ -153,7 +153,7 @@ public class WindowsServiceLifecycle extends Lifecycle { baseDir = new File(baseEnv); } else { LOGGER.log(Level.WARNING, "Could not find environment variable 'BASE' for Jenkins base directory. Falling back to JENKINS_HOME"); - baseDir = Jenkins.getInstance().getRootDir(); + baseDir = Jenkins.get().getRootDir(); } return baseDir; } diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java index 51549a3bd34d8453945b08d344ed40b2f930239b..dfeb1d633164ac1ede60638d1b73df3c884bc7f0 100644 --- a/core/src/main/java/hudson/logging/LogRecorder.java +++ b/core/src/main/java/hudson/logging/LogRecorder.java @@ -75,7 +75,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; public class LogRecorder extends AbstractModelObject implements Saveable { private volatile String name; - public final CopyOnWriteList targets = new CopyOnWriteList(); + public final CopyOnWriteList targets = new CopyOnWriteList<>(); private final static TargetComparator TARGET_COMPARATOR = new TargetComparator(); @Restricted(NoExternalUse.class) @@ -156,7 +156,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable { continue; } - if (match.booleanValue()) { + if (match) { // most specific logger matches, so publish super.publish(record); } @@ -215,14 +215,14 @@ public class LogRecorder extends AbstractModelObject implements Saveable { public Boolean matches(LogRecord r) { boolean levelSufficient = r.getLevel().intValue() >= level; if (name.length() == 0) { - return Boolean.valueOf(levelSufficient); // include if level matches + return levelSufficient; // include if level matches } String logName = r.getLoggerName(); if(logName==null || !logName.startsWith(name)) return null; // not in the domain of this logger String rest = logName.substring(name.length()); if (rest.startsWith(".") || rest.length()==0) { - return Boolean.valueOf(levelSufficient); // include if level matches + return levelSufficient; // include if level matches } return null; } @@ -275,7 +275,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable { return null; } void broadcast() { - for (Computer c : Jenkins.getInstance().getComputers()) { + for (Computer c : Jenkins.get().getComputers()) { if (c.getName().length() > 0) { // i.e. not master VirtualChannel ch = c.getChannel(); if (ch != null) { @@ -292,7 +292,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable { @Extension @Restricted(NoExternalUse.class) public static final class ComputerLogInitializer extends ComputerListener { @Override public void preOnline(Computer c, Channel channel, FilePath root, TaskListener listener) throws IOException, InterruptedException { - for (LogRecorder recorder : Jenkins.getInstance().getLog().logRecorders.values()) { + for (LogRecorder recorder : Jenkins.get().getLog().logRecorders.values()) { for (Target t : recorder.targets) { channel.call(new SetLevel(t.name, t.getLevel())); } @@ -320,7 +320,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable { } public LogRecorderManager getParent() { - return Jenkins.getInstance().getLog(); + return Jenkins.get().getLog(); } /** @@ -426,7 +426,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable { return COLL.compare(c1.getDisplayName(), c2.getDisplayName()); } }); - for (Computer c : Jenkins.getInstance().getComputers()) { + for (Computer c : Jenkins.get().getComputers()) { if (c.getName().length() == 0) { continue; // master } @@ -466,5 +466,5 @@ public class LogRecorder extends AbstractModelObject implements Saveable { * Log levels that can be configured for {@link Target}. */ public static List LEVELS = - Arrays.asList(Level.ALL, Level.FINEST, Level.FINER, Level.FINE, Level.CONFIG, Level.INFO, Level.WARNING, Level.SEVERE); + Arrays.asList(Level.ALL, Level.FINEST, Level.FINER, Level.FINE, Level.CONFIG, Level.INFO, Level.WARNING, Level.SEVERE, Level.OFF); } diff --git a/core/src/main/java/hudson/logging/LogRecorderManager.java b/core/src/main/java/hudson/logging/LogRecorderManager.java index 765253341bc13b4d9fb501320ddf32bb7db82e20..485a31ab48d4481395bda2145bf54293f34b0a0e 100644 --- a/core/src/main/java/hudson/logging/LogRecorderManager.java +++ b/core/src/main/java/hudson/logging/LogRecorderManager.java @@ -25,7 +25,6 @@ package hudson.logging; import hudson.FeedAdapter; import hudson.Functions; -import hudson.PluginManager; import hudson.init.Initializer; import static hudson.init.InitMilestone.PLUGINS_PREPARED; import hudson.model.AbstractModelObject; @@ -69,7 +68,7 @@ public class LogRecorderManager extends AbstractModelObject implements ModelObje /** * {@link LogRecorder}s keyed by their {@linkplain LogRecorder#name name}. */ - public transient final Map logRecorders = new CopyOnWriteMap.Tree(); + public transient final Map logRecorders = new CopyOnWriteMap.Tree<>(); public String getDisplayName() { return Messages.LogRecorderManager_DisplayName(); @@ -88,7 +87,7 @@ public class LogRecorderManager extends AbstractModelObject implements ModelObje } static File configDir() { - return new File(Jenkins.getInstance().getRootDir(), "log"); + return new File(Jenkins.get().getRootDir(), "log"); } /** @@ -136,7 +135,7 @@ public class LogRecorderManager extends AbstractModelObject implements ModelObje @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE") @RequirePOST public HttpResponse doConfigLogger(@QueryParameter String name, @QueryParameter String level) { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); Level lv; if(level.equals("inherit")) lv = null; @@ -161,7 +160,7 @@ public class LogRecorderManager extends AbstractModelObject implements ModelObje String level = req.getParameter("level"); if(level!=null) { Level threshold = Level.parse(level); - List filtered = new ArrayList(); + List filtered = new ArrayList<>(); for (LogRecord r : logs) { if(r.getLevel().intValue() >= threshold.intValue()) filtered.add(r); @@ -207,7 +206,7 @@ public class LogRecorderManager extends AbstractModelObject implements ModelObje @Restricted(NoExternalUse.class) public Object getTarget() { if (!SKIP_PERMISSION_CHECK) { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); } return this; } diff --git a/core/src/main/java/hudson/logging/WeakLogHandler.java b/core/src/main/java/hudson/logging/WeakLogHandler.java index 339c50ef64111b7b0671dca4a5f6bcc8f54d50ec..9a51f279de28e7eb70b8c13fb98ae1b8a87c670c 100644 --- a/core/src/main/java/hudson/logging/WeakLogHandler.java +++ b/core/src/main/java/hudson/logging/WeakLogHandler.java @@ -44,7 +44,7 @@ public final class WeakLogHandler extends Handler { public WeakLogHandler(Handler target, Logger logger) { this.logger = logger; logger.addHandler(this); - this.target = new WeakReference(target); + this.target = new WeakReference<>(target); } public void publish(LogRecord record) { diff --git a/core/src/main/java/hudson/markup/MarkupFormatterDescriptor.java b/core/src/main/java/hudson/markup/MarkupFormatterDescriptor.java index 133c0fe19da0429656b14a6adce47555a78c4aba..cd7c108bd7aca7f82703f4b73ff075e13c9a1a23 100644 --- a/core/src/main/java/hudson/markup/MarkupFormatterDescriptor.java +++ b/core/src/main/java/hudson/markup/MarkupFormatterDescriptor.java @@ -38,7 +38,7 @@ public abstract class MarkupFormatterDescriptor extends Descriptor all() { - return Jenkins.getInstance(). - getDescriptorList(MarkupFormatter.class); + return Jenkins.get(). + getDescriptorList(MarkupFormatter.class); } } diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 2268f3dd9967c69df673fede3e6bf159c59a28dd..28c2b926b9eefae92c964a0f58db1844d07bffab 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -207,9 +207,9 @@ public abstract class AbstractBuild

    ,R extends Abs */ public @CheckForNull Node getBuiltOn() { if (builtOn==null || builtOn.equals("")) - return Jenkins.getInstance(); + return Jenkins.get(); else - return Jenkins.getInstance().getNode(builtOn); + return Jenkins.get().getNode(builtOn); } /** @@ -460,12 +460,12 @@ public abstract class AbstractBuild

    ,R extends Abs this.listener = listener; launcher = createLauncher(listener); - if (!Jenkins.getInstance().getNodes().isEmpty()) { + if (!Jenkins.get().getNodes().isEmpty()) { if (node instanceof Jenkins) { listener.getLogger().print(Messages.AbstractBuild_BuildingOnMaster()); } else { listener.getLogger().print(Messages.AbstractBuild_BuildingRemotely(ModelHyperlinkNote.encodeTo("/computer/" + builtOn, node.getDisplayName()))); - Set assignedLabels = new HashSet(node.getAssignedLabels()); + Set assignedLabels = new HashSet<>(node.getAssignedLabels()); assignedLabels.remove(node.getSelfLabel()); if (!assignedLabels.isEmpty()) { boolean first = true; @@ -536,7 +536,7 @@ public abstract class AbstractBuild

    ,R extends Abs l = bw.decorateLauncher(AbstractBuild.this,l,listener); } - buildEnvironments = new ArrayList(); + buildEnvironments = new ArrayList<>(); for (RunListener rl: RunListener.all()) { Environment environment = rl.setUpEnvironment(AbstractBuild.this, l, listener); @@ -545,7 +545,7 @@ public abstract class AbstractBuild

    ,R extends Abs } } - for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) { + for (NodeProperty nodeProperty: Jenkins.get().getGlobalNodeProperties()) { Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener); if (environment != null) { buildEnvironments.add(environment); @@ -583,7 +583,7 @@ public abstract class AbstractBuild

    ,R extends Abs } build.scm = scm.createChangeLogParser(); - build.changeSet = new WeakReference>(build.calcChangeSet()); + build.changeSet = new WeakReference<>(build.calcChangeSet()); for (SCMListener l : SCMListener.all()) try { @@ -635,7 +635,7 @@ public abstract class AbstractBuild

    ,R extends Abs post2(listener); } finally { // update the culprit list - HashSet r = new HashSet(); + HashSet r = new HashSet<>(); for (User u : getCulprits()) r.add(u.getId()); culprits = ImmutableSortedSet.copyOf(r); @@ -694,10 +694,7 @@ public abstract class AbstractBuild

    ,R extends Abs setResult(Result.FAILURE); } } - } catch (Exception e) { - reportError(bs, e, listener, phase); - r = false; - } catch (LinkageError e) { + } catch (Exception | LinkageError e) { reportError(bs, e, listener, phase); r = false; } @@ -742,10 +739,7 @@ public abstract class AbstractBuild

    ,R extends Abs try { canContinue = mon.perform(bs, AbstractBuild.this, launcher, listener); - } catch (RequestAbortedException ex) { - // Channel is closed, do not continue - reportBrokenChannel(listener); - } catch (ChannelClosedException ex) { + } catch (RequestAbortedException | ChannelClosedException ex) { // Channel is closed, do not continue reportBrokenChannel(listener); } catch (RuntimeException ex) { @@ -834,7 +828,7 @@ public abstract class AbstractBuild

    ,R extends Abs if (cs==null) cs = ChangeLogSet.createEmpty(this); - changeSet = new WeakReference>(cs); + changeSet = new WeakReference<>(cs); return cs; } @@ -859,9 +853,7 @@ public abstract class AbstractBuild

    ,R extends Abs try { return scm.parse(this,changelogFile); - } catch (IOException e) { - LOGGER.log(WARNING, "Failed to parse "+changelogFile,e); - } catch (SAXException e) { + } catch (IOException | SAXException e) { LOGGER.log(WARNING, "Failed to parse "+changelogFile,e); } return ChangeLogSet.createEmpty(this); @@ -903,11 +895,11 @@ public abstract class AbstractBuild

    ,R extends Abs public EnvironmentList getEnvironments() { Executor e = Executor.currentExecutor(); if (e!=null && e.getCurrentExecutable()==this) { - if (buildEnvironments==null) buildEnvironments = new ArrayList(); + if (buildEnvironments==null) buildEnvironments = new ArrayList<>(); return new EnvironmentList(buildEnvironments); } - return new EnvironmentList(buildEnvironments==null ? Collections.emptyList() : ImmutableList.copyOf(buildEnvironments)); + return new EnvironmentList(buildEnvironments==null ? Collections.emptyList() : ImmutableList.copyOf(buildEnvironments)); } public Calendar due() { @@ -936,7 +928,7 @@ public abstract class AbstractBuild

    ,R extends Abs * @since 1.378 */ public Set getSensitiveBuildVariables() { - Set s = new HashSet(); + Set s = new HashSet<>(); ParametersAction parameters = getAction(ParametersAction.class); if (parameters != null) { @@ -973,7 +965,7 @@ public abstract class AbstractBuild

    ,R extends Abs * The returned map is mutable so that subtypes can put more values. */ public Map getBuildVariables() { - Map r = new HashMap(); + Map r = new HashMap<>(); ParametersAction parameters = getAction(ParametersAction.class); if (parameters!=null) { @@ -1000,7 +992,7 @@ public abstract class AbstractBuild

    ,R extends Abs * Creates {@link VariableResolver} backed by {@link #getBuildVariables()}. */ public final VariableResolver getBuildVariableResolver() { - return new VariableResolver.ByMap(getBuildVariables()); + return new VariableResolver.ByMap<>(getBuildVariables()); } /** @@ -1009,7 +1001,7 @@ public abstract class AbstractBuild

    ,R extends Abs @Deprecated public Action getTestResultAction() { try { - return getAction(Jenkins.getInstance().getPluginManager().uberClassLoader.loadClass("hudson.tasks.test.AbstractTestResultAction").asSubclass(Action.class)); + return getAction(Jenkins.get().getPluginManager().uberClassLoader.loadClass("hudson.tasks.test.AbstractTestResultAction").asSubclass(Action.class)); } catch (ClassNotFoundException x) { return null; } @@ -1021,7 +1013,7 @@ public abstract class AbstractBuild

    ,R extends Abs @Deprecated public Action getAggregatedTestResultAction() { try { - return getAction(Jenkins.getInstance().getPluginManager().uberClassLoader.loadClass("hudson.tasks.test.AggregatedTestResultAction").asSubclass(Action.class)); + return getAction(Jenkins.get().getPluginManager().uberClassLoader.loadClass("hudson.tasks.test.AggregatedTestResultAction").asSubclass(Action.class)); } catch (ClassNotFoundException x) { return null; } @@ -1173,7 +1165,7 @@ public abstract class AbstractBuild

    ,R extends Abs * of builds (which can be empty if no build uses the artifact from this build or downstream is not {@link AbstractProject#isFingerprintConfigured}.) */ public Map getDownstreamBuilds() { - Map r = new HashMap(); + Map r = new HashMap<>(); for (AbstractProject p : getParent().getDownstreamProjects()) { if (p.isFingerprintConfigured()) r.put(p,getDownstreamRelationship(p)); @@ -1200,7 +1192,7 @@ public abstract class AbstractBuild

    ,R extends Abs } private Map _getUpstreamBuilds(Collection projects) { - Map r = new HashMap(); + Map r = new HashMap<>(); for (AbstractProject p : projects) { int n = getUpstreamRelationship(p); if (n>=0) @@ -1222,7 +1214,7 @@ public abstract class AbstractBuild

    ,R extends Abs Map ndep = n.getDependencies(true); Map odep = o.getDependencies(true); - Map r = new HashMap(); + Map r = new HashMap<>(); for (Map.Entry entry : odep.entrySet()) { AbstractProject p = entry.getKey(); @@ -1274,7 +1266,7 @@ public abstract class AbstractBuild

    ,R extends Abs * of IDs, but due to log rotations, some builds may be already unavailable. */ public List getBuilds() { - List r = new ArrayList(); + List r = new ArrayList<>(); AbstractBuild b = project.getNearestBuild(fromId); if (b!=null && b.getNumber()==fromId) diff --git a/core/src/main/java/hudson/model/AbstractCIBase.java b/core/src/main/java/hudson/model/AbstractCIBase.java index d82afd6488d03d5e52f48abc3347332106d7f3b8..aec90b19a383ec6cd047ef1523087d7163619250 100644 --- a/core/src/main/java/hudson/model/AbstractCIBase.java +++ b/core/src/main/java/hudson/model/AbstractCIBase.java @@ -33,13 +33,14 @@ import hudson.slaves.RetentionStrategy; import jenkins.model.Jenkins; import org.kohsuke.stapler.StaplerFallback; import org.kohsuke.stapler.StaplerProxy; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; import jenkins.model.Configuration; @@ -94,6 +95,11 @@ public abstract class AbstractCIBase extends Node implements ItemGroup disabledAdministrativeMonitors = new CopyOnWriteArraySet(); + @Restricted(NoExternalUse.class) + public CopyOnWriteArraySet getDisabledAdministrativeMonitors(){ + return disabledAdministrativeMonitors; + } + /* ================================================================================================================= * Implementation provided * ============================================================================================================== */ @@ -125,7 +131,7 @@ public abstract class AbstractCIBase extends Node implements ItemGroup0 || n==Jenkins.getInstance()) { + if(n.getNumExecutors()>0 || n==Jenkins.get()) { try { c = n.createComputer(); } catch(RuntimeException ex) { // Just in case there is a bogus extension diff --git a/core/src/main/java/hudson/model/AbstractDescribableImpl.java b/core/src/main/java/hudson/model/AbstractDescribableImpl.java index 07ed386a930885d755b29f9b68c1d184d1bc7a18..b1ac6dd8477f2333e4adce210b726ed7e1805385 100644 --- a/core/src/main/java/hudson/model/AbstractDescribableImpl.java +++ b/core/src/main/java/hudson/model/AbstractDescribableImpl.java @@ -39,7 +39,7 @@ public abstract class AbstractDescribableImpl getDescriptor() { - return Jenkins.getInstance().getDescriptorOrDie(getClass()); + return Jenkins.get().getDescriptorOrDie(getClass()); } } diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 1374429381f550e0e8bddfa991431c794eb4d56b..eae1e64e4014a60906e50ade8b52bd67b931de7d 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -38,7 +38,6 @@ import hudson.model.queue.Tasks; import hudson.model.queue.WorkUnit; import hudson.security.ACLContext; import hudson.security.AccessControlled; -import hudson.security.Permission; import hudson.security.ACL; import hudson.util.AlternativeUiTextProvider; import hudson.util.AlternativeUiTextProvider.Message; @@ -276,6 +275,11 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet */ @Restricted(NoExternalUse.class) public @Nonnull FormValidation doCheckNewName(@QueryParameter String newName) { + + if (!isNameEditable()) { + return FormValidation.error("Trying to rename an item that does not support this operation."); + } + // TODO: Create an Item.RENAME permission to use here, see JENKINS-18649. if (!hasPermission(Item.CONFIGURE)) { if (parent instanceof AccessControlled) { @@ -352,6 +356,11 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet * you can use this method. */ protected void renameTo(final String newName) throws IOException { + + if (!isNameEditable()) { + throw new IOException("Trying to rename an item that does not support this operation."); + } + // always synchronize from bigger objects first final ItemGroup parent = getParent(); String oldName = this.name; @@ -590,7 +599,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet * Returns the {@link ACL} for this object. */ public ACL getACL() { - return Jenkins.getInstance().getAuthorizationStrategy().getACL(this); + return Jenkins.get().getAuthorizationStrategy().getACL(this); } /** @@ -714,7 +723,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet // the 15 second delay for every child item). This happens after queue cancellation, so will be // a complete set of builds in flight Map buildsInProgress = new LinkedHashMap<>(); - for (Computer c : Jenkins.getInstance().getComputers()) { + for (Computer c : Jenkins.get().getComputers()) { for (Executor e : c.getAllExecutors()) { final WorkUnit workUnit = e.getCurrentWorkUnit(); final Executable executable = workUnit != null ? workUnit.getExecutable() : null; @@ -781,7 +790,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet } } getParent().onDeleted(AbstractItem.this); - Jenkins.getInstance().rebuildDependencyGraphAsync(); + Jenkins.get().rebuildDependencyGraphAsync(); } /** @@ -883,7 +892,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet return null; } }); - Jenkins.getInstance().rebuildDependencyGraphAsync(); + Jenkins.get().rebuildDependencyGraphAsync(); // if everything went well, commit this new version out.commit(); @@ -916,7 +925,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet return null; } }); - Jenkins.getInstance().rebuildDependencyGraphAsync(); + Jenkins.get().rebuildDependencyGraphAsync(); SaveableListener.fireOnChange(this, getConfigFile()); } @@ -962,9 +971,9 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet public static AbstractItem resolveForCLI( @Argument(required=true,metaVar="NAME",usage="Item name") String name) throws CmdLineException { // TODO can this (and its pseudo-override in AbstractProject) share code with GenericItemOptionHandler, used for explicit CLICommand’s rather than CLIMethod’s? - AbstractItem item = Jenkins.getInstance().getItemByFullName(name, AbstractItem.class); + AbstractItem item = Jenkins.get().getItemByFullName(name, AbstractItem.class); if (item==null) { - AbstractItem project = Items.findNearest(AbstractItem.class, name, Jenkins.getInstance()); + AbstractItem project = Items.findNearest(AbstractItem.class, name, Jenkins.get()); throw new CmdLineException(null, project == null ? Messages.AbstractItem_NoSuchJobExistsWithoutSuggestion(name) : Messages.AbstractItem_NoSuchJobExists(name, project.getFullName())); } diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 3b60e8618519d41c5dc5b6a23a357377296d1d6a..d9870c2134bbfc4e1f3758801258f5a723b0b888 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -259,7 +259,7 @@ public abstract class AbstractProject

    ,R extends A buildMixIn = createBuildMixIn(); builds = buildMixIn.getRunMap(); - final Jenkins j = Jenkins.getInstance(); + final Jenkins j = Jenkins.get(); final List nodes = j != null ? j.getNodes() : null; if(nodes!=null && !nodes.isEmpty()) { // if a new job is configured with Hudson that already has agent nodes @@ -385,8 +385,8 @@ public abstract class AbstractProject

    ,R extends A return null; if(assignedNode==null) - return Jenkins.getInstance().getSelfLabel(); - return Jenkins.getInstance().getLabel(assignedNode); + return Jenkins.get().getSelfLabel(); + return Jenkins.get().getLabel(assignedNode); } /** @@ -428,7 +428,7 @@ public abstract class AbstractProject

    ,R extends A assignedNode = null; } else { canRoam = false; - if(l== Jenkins.getInstance().getSelfLabel()) assignedNode = null; + if(l== Jenkins.get().getSelfLabel()) assignedNode = null; else assignedNode = l.getExpression(); } save(); @@ -602,7 +602,7 @@ public abstract class AbstractProject

    ,R extends A } public int getQuietPeriod() { - return quietPeriod!=null ? quietPeriod : Jenkins.getInstance().getQuietPeriod(); + return quietPeriod!=null ? quietPeriod : Jenkins.get().getQuietPeriod(); } public SCMCheckoutStrategy getScmCheckoutStrategy() { @@ -616,7 +616,7 @@ public abstract class AbstractProject

    ,R extends A public int getScmCheckoutRetryCount() { - return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Jenkins.getInstance().getScmCheckoutRetryCount(); + return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Jenkins.get().getScmCheckoutRetryCount(); } // ugly name because of EL @@ -774,10 +774,10 @@ public abstract class AbstractProject

    ,R extends A updateTransientActions(); // notify the queue as the project might be now tied to different node - Jenkins.getInstance().getQueue().scheduleMaintenance(); + Jenkins.get().getQueue().scheduleMaintenance(); // this is to reflect the upstream build adjustments done above - Jenkins.getInstance().rebuildDependencyGraphAsync(); + Jenkins.get().rebuildDependencyGraphAsync(); } /** @@ -822,7 +822,7 @@ public abstract class AbstractProject

    ,R extends A if (c != null) { queueActions.add(new CauseAction(c)); } - return scheduleBuild2(quietPeriod, queueActions.toArray(new Action[queueActions.size()])); + return scheduleBuild2(quietPeriod, queueActions.toArray(new Action[0])); } /** @@ -870,19 +870,19 @@ public abstract class AbstractProject

    ,R extends A */ @Override public boolean isInQueue() { - return Jenkins.getInstance().getQueue().contains(this); + return Jenkins.get().getQueue().contains(this); } @Override public Queue.Item getQueueItem() { - return Jenkins.getInstance().getQueue().getItem(this); + return Jenkins.get().getQueue().getItem(this); } /** * Gets the JDK that this project is configured with, or null. */ public JDK getJDK() { - return Jenkins.getInstance().getJDK(jdk); + return Jenkins.get().getJDK(jdk); } /** @@ -1101,7 +1101,7 @@ public abstract class AbstractProject

    ,R extends A * the given project (provided that all builds went smoothly.) */ public AbstractProject getBuildingDownstream() { - Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); + Set unblockedTasks = Jenkins.get().getQueue().getUnblockedTasks(); for (AbstractProject tup : getTransitiveDownstreamProjects()) { if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) @@ -1118,7 +1118,7 @@ public abstract class AbstractProject

    ,R extends A * the given project (provided that all builds went smoothly.) */ public AbstractProject getBuildingUpstream() { - Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); + Set unblockedTasks = Jenkins.get().getQueue().getUnblockedTasks(); for (AbstractProject tup : getTransitiveUpstreamProjects()) { if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) @@ -1340,7 +1340,7 @@ public abstract class AbstractProject

    ,R extends A // because otherwise there's no way we can detect changes. // However, first there are some conditions in which we do not want to do so. // give time for agents to come online if we are right after reconnection (JENKINS-8408) - long running = Jenkins.getInstance().getInjector().getInstance(Uptime.class).getUptime(); + long running = Jenkins.get().getInjector().getInstance(Uptime.class).getUptime(); long remaining = TimeUnit.MINUTES.toMillis(10)-running; if (remaining>0 && /* this logic breaks tests of polling */!Functions.getIsUnitTest()) { listener.getLogger().print(Messages.AbstractProject_AwaitingWorkspaceToComeOnline(remaining/1000)); @@ -1431,7 +1431,7 @@ public abstract class AbstractProject

    ,R extends A */ private boolean isAllSuitableNodesOffline(R build) { Label label = getAssignedLabel(); - List allNodes = Jenkins.getInstance().getNodes(); + List allNodes = Jenkins.get().getNodes(); if (label != null) { //Invalid label. Put in queue to make administrator fix @@ -1442,7 +1442,7 @@ public abstract class AbstractProject

    ,R extends A return label.isOffline(); } else { if(canRoam) { - for (Node n : Jenkins.getInstance().getNodes()) { + for (Node n : Jenkins.get().getNodes()) { Computer c = n.toComputer(); if (c != null && c.isOnline() && c.isAcceptingTasks() && n.getMode() == Mode.NORMAL) { // Some executor is online that is ready and this job can run anywhere @@ -1450,11 +1450,7 @@ public abstract class AbstractProject

    ,R extends A } } //We can roam, check that the master is set to be used as much as possible, and not tied jobs only. - if(Jenkins.getInstance().getMode() == Mode.EXCLUSIVE) { - return true; - } else { - return false; - } + return Jenkins.get().getMode() == Mode.EXCLUSIVE; } } return true; @@ -1465,7 +1461,7 @@ public abstract class AbstractProject

    ,R extends A Label label = getAssignedLabel(); if (isAllSuitableNodesOffline(build)) { - Collection applicableClouds = label == null ? Jenkins.getInstance().clouds : label.getClouds(); + Collection applicableClouds = label == null ? Jenkins.get().clouds : label.getClouds(); return applicableClouds.isEmpty() ? WorkspaceOfflineReason.all_suitable_nodes_are_offline : WorkspaceOfflineReason.use_ondemand_slave; } @@ -1573,7 +1569,7 @@ public abstract class AbstractProject

    ,R extends A * when a build of this project is completed. */ public final List getDownstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getDownstream(this); + return Jenkins.get().getDependencyGraph().getDownstream(this); } @Exported(name="downstreamProjects") @@ -1589,7 +1585,7 @@ public abstract class AbstractProject

    ,R extends A } public final List getUpstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getUpstream(this); + return Jenkins.get().getDependencyGraph().getUpstream(this); } @Exported(name="upstreamProjects") @@ -1627,7 +1623,7 @@ public abstract class AbstractProject

    ,R extends A * @since 1.138 */ public final Set getTransitiveUpstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this); + return Jenkins.get().getDependencyGraph().getTransitiveUpstream(this); } /** @@ -1636,7 +1632,7 @@ public abstract class AbstractProject

    ,R extends A * @since 1.138 */ public final Set getTransitiveDownstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this); + return Jenkins.get().getDependencyGraph().getTransitiveDownstream(this); } /** @@ -1938,7 +1934,7 @@ public abstract class AbstractProject

    ,R extends A return FormValidation.error(e, Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage())); } - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); Label l = j.getLabel(value); if (l.isEmpty()) { for (LabelAtom a : l.listAtoms()) { @@ -1972,7 +1968,7 @@ public abstract class AbstractProject

    ,R extends A public AutoCompletionCandidates doAutoCompleteUpstreamProjects(@QueryParameter String value) { AutoCompletionCandidates candidates = new AutoCompletionCandidates(); - List jobs = Jenkins.getInstance().getItems(Job.class); + List jobs = Jenkins.get().getItems(Job.class); for (Job job: jobs) { if (job.getFullName().startsWith(value)) { if (job.hasPermission(Item.READ)) { @@ -1993,7 +1989,7 @@ public abstract class AbstractProject

    ,R extends A public AutoCompletionCandidates doAutoCompleteLabel(@QueryParameter String value) { AutoCompletionCandidates c = new AutoCompletionCandidates(); - Set

    ,R extends A * @see Items#findNearest */ public static @CheckForNull AbstractProject findNearest(String name) { - return findNearest(name,Jenkins.getInstance()); + return findNearest(name,Jenkins.get()); } /** @@ -2096,7 +2092,7 @@ public abstract class AbstractProject

    ,R extends A @CLIResolver public static AbstractProject resolveForCLI( @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException { - AbstractProject item = Jenkins.getInstance().getItemByFullName(name, AbstractProject.class); + AbstractProject item = Jenkins.get().getItemByFullName(name, AbstractProject.class); if (item==null) { AbstractProject project = AbstractProject.findNearest(name); throw new CmdLineException(null, project == null ? Messages.AbstractItem_NoSuchJobExistsWithoutSuggestion(name) diff --git a/core/src/main/java/hudson/model/Actionable.java b/core/src/main/java/hudson/model/Actionable.java index 96eeda813ab218d96f8603ecc48c651b89d8ccdc..496c2de2866c77fcb1fa0f9c72a4a2a5596e0f7a 100644 --- a/core/src/main/java/hudson/model/Actionable.java +++ b/core/src/main/java/hudson/model/Actionable.java @@ -78,7 +78,7 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj if (actions == null) { synchronized (this) { if (actions == null) { - actions = new CopyOnWriteArrayList(); + actions = new CopyOnWriteArrayList<>(); } } } @@ -196,7 +196,7 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj throw new IllegalArgumentException("Action must be non-null"); } // CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way: - List old = new ArrayList(1); + List old = new ArrayList<>(1); List current = getActions(); boolean found = false; for (Action a2 : current) { @@ -255,7 +255,7 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj throw new IllegalArgumentException("Action type must be non-null"); } // CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way: - List old = new ArrayList(); + List old = new ArrayList<>(); List current = getActions(); for (Action a : current) { if (clazz.isInstance(a)) { @@ -290,7 +290,7 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj throw new IllegalArgumentException("Action must be non-null"); } // CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way: - List old = new ArrayList(); + List old = new ArrayList<>(); List current = getActions(); boolean found = false; for (Action a1 : current) { diff --git a/core/src/main/java/hudson/model/AdministrativeMonitor.java b/core/src/main/java/hudson/model/AdministrativeMonitor.java index 2121c3e5991faad2661f1c9536dd6848793b890c..e0f0a63ff991353a3f726d4a178db588ba2026b0 100644 --- a/core/src/main/java/hudson/model/AdministrativeMonitor.java +++ b/core/src/main/java/hudson/model/AdministrativeMonitor.java @@ -117,7 +117,7 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen */ public void disable(boolean value) throws IOException { AbstractCIBase jenkins = Jenkins.get(); - Set set = jenkins.disabledAdministrativeMonitors; + Set set = jenkins.getDisabledAdministrativeMonitors(); if(value) set.add(id); else set.remove(id); jenkins.save(); @@ -131,7 +131,7 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen * he wants to ignore. */ public boolean isEnabled() { - return !((AbstractCIBase)Jenkins.get()).disabledAdministrativeMonitors.contains(id); + return !Jenkins.get().getDisabledAdministrativeMonitors().contains(id); } /** diff --git a/core/src/main/java/hudson/model/AperiodicWork.java b/core/src/main/java/hudson/model/AperiodicWork.java index 3c5385c07791a0c547edd8611c3038014763f137..dad7bbe5731f162776434ebf65c17d196c447700 100644 --- a/core/src/main/java/hudson/model/AperiodicWork.java +++ b/core/src/main/java/hudson/model/AperiodicWork.java @@ -126,9 +126,7 @@ public abstract class AperiodicWork extends SafeTimerTask implements ExtensionPo private final Set registered = new HashSet<>(); AperiodicWorkExtensionListListener(ExtensionList initiallyRegistered) { - for (AperiodicWork p : initiallyRegistered) { - registered.add(p); - } + registered.addAll(initiallyRegistered); } @Override diff --git a/core/src/main/java/hudson/model/Api.java b/core/src/main/java/hudson/model/Api.java index 024d66869d4591afe4d28a31c5294e00b2fa8032..5fe6cdd56ae3d93fd245f8db99fae0d50b6372e7 100644 --- a/core/src/main/java/hudson/model/Api.java +++ b/core/src/main/java/hudson/model/Api.java @@ -50,6 +50,7 @@ import java.io.OutputStream; import java.io.StringReader; import java.io.StringWriter; import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -188,7 +189,7 @@ public class Api extends AbstractModelObject { // simple output allowed rsp.setContentType("text/plain;charset=UTF-8"); String text = result instanceof CharacterData ? ((CharacterData) result).getText() : result.toString(); - o.write(text.getBytes("UTF-8")); + o.write(text.getBytes(StandardCharsets.UTF_8)); return; } diff --git a/core/src/main/java/hudson/model/AutoCompletionCandidates.java b/core/src/main/java/hudson/model/AutoCompletionCandidates.java index 29ce16f4ca59e486771629b9ac0f82b9064d5ab7..735a9b80e8a1f9043c13ba09f52a58b585c62ef4 100644 --- a/core/src/main/java/hudson/model/AutoCompletionCandidates.java +++ b/core/src/main/java/hudson/model/AutoCompletionCandidates.java @@ -48,7 +48,7 @@ import org.apache.commons.lang.StringUtils; * @author Kohsuke Kawaguchi */ public class AutoCompletionCandidates implements HttpResponse { - private final List values = new ArrayList(); + private final List values = new ArrayList<>(); public AutoCompletionCandidates add(String v) { values.add(v); @@ -154,12 +154,12 @@ public class AutoCompletionCandidates implements HttpResponse { } } - if (container==null || container==Jenkins.getInstance()) { - new Visitor("").onItemGroup(Jenkins.getInstance()); + if (container==null || container==Jenkins.get()) { + new Visitor("").onItemGroup(Jenkins.get()); } else { new Visitor("").onItemGroup(container); if (value.startsWith("/")) - new Visitor("/").onItemGroup(Jenkins.getInstance()); + new Visitor("/").onItemGroup(Jenkins.get()); for ( String p="../"; value.startsWith(p); p+="../") { container = ((Item)container).getParent(); diff --git a/core/src/main/java/hudson/model/BallColor.java b/core/src/main/java/hudson/model/BallColor.java index 450b216cfb83a45c32bc798bc5051a6d0a205f00..84897187d756b9fc3a4a654e16e5a92810a6725a 100644 --- a/core/src/main/java/hudson/model/BallColor.java +++ b/core/src/main/java/hudson/model/BallColor.java @@ -26,6 +26,7 @@ package hudson.model; import hudson.util.ColorPalette; import jenkins.model.Jenkins; import org.jenkins.ui.icon.Icon; +import org.jenkins.ui.icon.IconSet; import org.jvnet.localizer.LocaleProvider; import org.jvnet.localizer.Localizable; import org.kohsuke.stapler.Stapler; @@ -61,15 +62,15 @@ public enum BallColor implements StatusIcon { BLUE("blue",Messages._BallColor_Success(), ColorPalette.BLUE), BLUE_ANIME("blue_anime",Messages._BallColor_InProgress(), ColorPalette.BLUE), // for historical reasons they are called grey. - GREY("grey",Messages._BallColor_Pending(), ColorPalette.GREY), + GREY("grey",Messages._BallColor_Disabled(), ColorPalette.GREY), GREY_ANIME("grey_anime",Messages._BallColor_InProgress(), ColorPalette.GREY), DISABLED("disabled",Messages._BallColor_Disabled(), ColorPalette.GREY), DISABLED_ANIME("disabled_anime",Messages._BallColor_InProgress(), ColorPalette.GREY), - ABORTED("aborted",Messages._BallColor_Aborted(), ColorPalette.GREY), - ABORTED_ANIME("aborted_anime",Messages._BallColor_InProgress(), ColorPalette.GREY), - NOTBUILT("nobuilt",Messages._BallColor_NotBuilt(), ColorPalette.GREY), - NOTBUILT_ANIME("nobuilt_anime",Messages._BallColor_InProgress(), ColorPalette.GREY), + ABORTED("aborted",Messages._BallColor_Aborted(), ColorPalette.DARK_GREY), + ABORTED_ANIME("aborted_anime",Messages._BallColor_InProgress(), ColorPalette.DARK_GREY), + NOTBUILT("nobuilt",Messages._BallColor_NotBuilt(), ColorPalette.LIGHT_GREY), + NOTBUILT_ANIME("nobuilt_anime",Messages._BallColor_InProgress(), ColorPalette.LIGHT_GREY), ; private final Localizable description; diff --git a/core/src/main/java/hudson/model/BooleanParameterValue.java b/core/src/main/java/hudson/model/BooleanParameterValue.java index 9b7b94046ba5fef161ed2e795c37a429b35fdbfb..7d69a903d7e49f6699c89dbad98a3e05ca65c6f6 100644 --- a/core/src/main/java/hudson/model/BooleanParameterValue.java +++ b/core/src/main/java/hudson/model/BooleanParameterValue.java @@ -79,9 +79,7 @@ public class BooleanParameterValue extends ParameterValue { BooleanParameterValue that = (BooleanParameterValue) o; - if (value != that.value) return false; - - return true; + return value == that.value; } @Override diff --git a/core/src/main/java/hudson/model/Build.java b/core/src/main/java/hudson/model/Build.java index 15aed23050bcfbcb43655a7cf5576b397d13778e..00f9c21d4eb469bb9d6ce60bb391d7b41cf046d0 100644 --- a/core/src/main/java/hudson/model/Build.java +++ b/core/src/main/java/hudson/model/Build.java @@ -147,7 +147,7 @@ public abstract class Build

    ,B extends Build> Result r = null; try { - List wrappers = new ArrayList(project.getBuildWrappers().values()); + List wrappers = new ArrayList<>(project.getBuildWrappers().values()); ParametersAction parameters = getAction(ParametersAction.class); if (parameters != null) diff --git a/core/src/main/java/hudson/model/BuildAuthorizationToken.java b/core/src/main/java/hudson/model/BuildAuthorizationToken.java index 8aa53eb429da444c1a435e99abc9cd72e35f21b1..dd54ffb30756967d756d03781c3164de8d2f4cc9 100644 --- a/core/src/main/java/hudson/model/BuildAuthorizationToken.java +++ b/core/src/main/java/hudson/model/BuildAuthorizationToken.java @@ -64,12 +64,11 @@ public final class BuildAuthorizationToken { } @Deprecated public static void checkPermission(AbstractProject project, BuildAuthorizationToken token, StaplerRequest req, StaplerResponse rsp) throws IOException { - Job j = project; - checkPermission(j, token, req, rsp); + checkPermission((Job) project, token, req, rsp); } public static void checkPermission(Job project, BuildAuthorizationToken token, StaplerRequest req, StaplerResponse rsp) throws IOException { - if (!Jenkins.getInstance().isUseSecurity()) + if (!Jenkins.get().isUseSecurity()) return; // everyone is authorized if(token!=null && token.token != null) { diff --git a/core/src/main/java/hudson/model/BuildStepListener.java b/core/src/main/java/hudson/model/BuildStepListener.java index e0ee3efe29ce12313b1ff81abfbc11c51d7d0905..20bd9e1a235ab62c6c82974f8669a008d02019fe 100644 --- a/core/src/main/java/hudson/model/BuildStepListener.java +++ b/core/src/main/java/hudson/model/BuildStepListener.java @@ -25,7 +25,7 @@ public abstract class BuildStepListener implements ExtensionPoint { * Returns all the registered {@link BuildStepListener}s. */ public static ExtensionList all() { - // TODO should have a null-safe version when Jenkins.getInstance() is null; would require changes in ExtensionList + // TODO should have a null-safe version when Jenkins.getInstanceOrNull() is null; would require changes in ExtensionList return ExtensionList.lookup(BuildStepListener.class); } } diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index c0286bc55a8556d88df25c7db3e968938c2ce292..b6f120e21180801051d7de1c62b2d7fe2e94638a 100644 --- a/core/src/main/java/hudson/model/Cause.java +++ b/core/src/main/java/hudson/model/Cause.java @@ -25,7 +25,6 @@ package hudson.model; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import hudson.console.ModelHyperlinkNote; @@ -176,8 +175,8 @@ public abstract class Cause { upstreamBuild = up.getNumber(); upstreamProject = up.getParent().getFullName(); upstreamUrl = up.getParent().getUrl(); - upstreamCauses = new ArrayList(); - Set traversed = new HashSet(); + upstreamCauses = new ArrayList<>(); + Set traversed = new HashSet<>(); for (Cause c : up.getCauses()) { upstreamCauses.add(trim(c, MAX_DEPTH, traversed)); } @@ -192,8 +191,8 @@ public abstract class Cause { @Override public void onLoad(@Nonnull Job _job, int _buildNumber) { - Item i = Jenkins.getInstance().getItemByFullName(this.upstreamProject); - if (i == null || !(i instanceof Job)) { + Item i = Jenkins.get().getItemByFullName(this.upstreamProject); + if (!(i instanceof Job)) { // cannot initialize upstream causes return; } @@ -235,7 +234,7 @@ public abstract class Cause { return c; } UpstreamCause uc = (UpstreamCause) c; - List cs = new ArrayList(); + List cs = new ArrayList<>(); if (depth > 0) { if (traversed.add(uc.upstreamUrl + uc.upstreamBuild)) { for (Cause c2 : uc.upstreamCauses) { @@ -276,7 +275,7 @@ public abstract class Cause { * @since 1.505 */ public @CheckForNull Run getUpstreamRun() { - Job job = Jenkins.getInstance().getItemByFullName(upstreamProject, Job.class); + Job job = Jenkins.get().getItemByFullName(upstreamProject, Job.class); return job != null ? job.getBuildByNumber(upstreamBuild) : null; } @@ -334,7 +333,7 @@ public abstract class Cause { public ConverterImpl(XStream2 xstream) { super(xstream); } @Override protected void callback(UpstreamCause uc, UnmarshallingContext context) { if (uc.upstreamCause != null) { - if (uc.upstreamCauses == null) uc.upstreamCauses = new ArrayList(); + if (uc.upstreamCauses == null) uc.upstreamCauses = new ArrayList<>(); uc.upstreamCauses.add(uc.upstreamCause); uc.upstreamCause = null; OldDataMonitor.report(context, "1.288"); @@ -487,7 +486,7 @@ public abstract class Cause { public String getShortDescription() { if(note != null) { try { - return Messages.Cause_RemoteCause_ShortDescriptionWithNote(addr, Jenkins.getInstance().getMarkupFormatter().translate(note)); + return Messages.Cause_RemoteCause_ShortDescriptionWithNote(addr, Jenkins.get().getMarkupFormatter().translate(note)); } catch (IOException x) { // ignore } diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java index b065715db852731b41653b85f987e2a57847f12b..9881a8009a387a3e32aaf6ae66204c8b797fe21b 100644 --- a/core/src/main/java/hudson/model/CauseAction.java +++ b/core/src/main/java/hudson/model/CauseAction.java @@ -56,8 +56,8 @@ public class CauseAction implements FoldableAction, RunAction2 { private Map causeBag = new LinkedHashMap<>(); public CauseAction(Cause c) { - this.causeBag.put(c, 1); - } + this.causeBag.put(c, 1); + } private void addCause(Cause c) { synchronized (causeBag) { @@ -72,16 +72,17 @@ public class CauseAction implements FoldableAction, RunAction2 { } public CauseAction(Cause... c) { - this(Arrays.asList(c)); - } + this(Arrays.asList(c)); + } + @SuppressWarnings("CopyConstructorMissesField") // does not initialize the deprecated #cause field public CauseAction(Collection causes) { - addCauses(causes); - } + addCauses(causes); + } - public CauseAction(CauseAction ca) { - addCauses(ca.getCauses()); - } + public CauseAction(CauseAction ca) { + addCauses(ca.getCauses()); + } /** * Lists all causes of this build. @@ -90,14 +91,14 @@ public class CauseAction implements FoldableAction, RunAction2 { * to create an action with multiple causes use either of the constructors that support this; * to append causes retroactively to a build you must create a new {@link CauseAction} and replace the old */ - @Exported(visibility=2) - public List getCauses() { - List r = new ArrayList<>(); + @Exported(visibility=2) + public List getCauses() { + List r = new ArrayList<>(); for (Map.Entry entry : causeBag.entrySet()) { r.addAll(Collections.nCopies(entry.getValue(), entry.getKey())); } return Collections.unmodifiableList(r); - } + } /** * Finds the cause of the specific type. @@ -108,19 +109,19 @@ public class CauseAction implements FoldableAction, RunAction2 { return type.cast(c); return null; } - - public String getDisplayName() { - return "Cause"; - } - public String getIconFileName() { - // no icon - return null; - } + public String getDisplayName() { + return "Cause"; + } - public String getUrlName() { - return "cause"; - } + public String getIconFileName() { + // no icon + return null; + } + + public String getUrlName() { + return "cause"; + } /** * Get list of causes with duplicates combined into counters. diff --git a/core/src/main/java/hudson/model/ChoiceParameterDefinition.java b/core/src/main/java/hudson/model/ChoiceParameterDefinition.java index 9ea14153624d8aa3d1d13b2728dec4ccd274a2d6..ffe11cb3fdabf8804a3b936add3687be8291409b 100644 --- a/core/src/main/java/hudson/model/ChoiceParameterDefinition.java +++ b/core/src/main/java/hudson/model/ChoiceParameterDefinition.java @@ -45,7 +45,7 @@ public class ChoiceParameterDefinition extends SimpleParameterDefinition { public ChoiceParameterDefinition(String name, String[] choices, String description) { super(name, description); - this.choices = new ArrayList(Arrays.asList(choices)); + this.choices = new ArrayList<>(Arrays.asList(choices)); defaultValue = null; } diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 3a9fde57dc5ef80f73bd2550053652e0479486ec..c3dce4798dbbfe8a3626e8b56a2be2a8cd2249ce 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -31,7 +31,6 @@ import hudson.Extension; import hudson.Launcher.ProcStarter; import hudson.slaves.Cloud; import jenkins.security.stapler.StaplerDispatchable; -import jenkins.util.SystemProperties; import hudson.Util; import hudson.cli.declarative.CLIResolver; import hudson.console.AnnotatedLargeText; @@ -151,9 +150,9 @@ import static javax.servlet.http.HttpServletResponse.*; @ExportedBean public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener, DescriptorByNameOwner { - private final CopyOnWriteArrayList executors = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList executors = new CopyOnWriteArrayList<>(); // TODO: - private final CopyOnWriteArrayList oneOffExecutors = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList oneOffExecutors = new CopyOnWriteArrayList<>(); private int numExecutors; @@ -199,8 +198,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * @since 1.607 * @see Executor#resetWorkUnit(String) */ - private transient final List terminatedBy = Collections.synchronizedList(new ArrayList - ()); + private transient final List terminatedBy = Collections.synchronizedList(new ArrayList<>()); /** * This method captures the information of a request to terminate a computer instance. Method is public as @@ -242,7 +240,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * @since 1.607 */ public List getTerminatedBy() { - return new ArrayList(terminatedBy); + return new ArrayList<>(terminatedBy); } public Computer(Node node) { @@ -261,15 +259,14 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ @SuppressWarnings("deprecation") public List getActions() { - List result = new ArrayList(); - result.addAll(super.getActions()); - synchronized (this) { - if (transientActions == null) { - transientActions = TransientComputerActionFactory.createAllFor(this); - } - result.addAll(transientActions); - } - return Collections.unmodifiableList(result); + List result = new ArrayList<>(super.getActions()); + synchronized (this) { + if (transientActions == null) { + transientActions = TransientComputerActionFactory.createAllFor(this); + } + result.addAll(transientActions); + } + return Collections.unmodifiableList(result); } @SuppressWarnings({"ConstantConditions","deprecation"}) @@ -302,7 +299,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * @since 1.613 */ protected @Nonnull File getLogDir() { - File dir = new File(Jenkins.getInstance().getRootDir(),"logs/slaves/"+nodeName); + File dir = new File(Jenkins.get().getRootDir(),"logs/slaves/"+nodeName); if (!dir.exists() && !dir.mkdirs()) { LOGGER.severe("Failed to create agent log directory " + dir.getAbsolutePath()); } @@ -328,11 +325,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ public AnnotatedLargeText getLogText() { checkPermission(CONNECT); - return new AnnotatedLargeText(getLogFile(), Charset.defaultCharset(), false, this); + return new AnnotatedLargeText<>(getLogFile(), Charset.defaultCharset(), false, this); } public ACL getACL() { - return Jenkins.getInstance().getAuthorizationStrategy().getACL(this); + return Jenkins.get().getAuthorizationStrategy().getACL(this); } /** @@ -425,8 +422,8 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * make much sense because as soon as {@link Computer} is connected it can be disconnected by some other threads.) */ public final Future connect(boolean forceReconnect) { - connectTime = System.currentTimeMillis(); - return _connect(forceReconnect); + connectTime = System.currentTimeMillis(); + return _connect(forceReconnect); } /** @@ -468,7 +465,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * @return The time in ms since epoch when this computer last connected. */ public final long getConnectTime() { - return connectTime; + return connectTime; } /** @@ -595,7 +592,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces @Exported public LoadStatistics getLoadStatistics() { - return LabelAtom.get(nodeName != null ? nodeName : Jenkins.getInstance().getSelfLabel().toString()).loadStatistics; + return LabelAtom.get(nodeName != null ? nodeName : Jenkins.get().getSelfLabel().toString()).loadStatistics; } public BuildTimelineWidget getTimeline() { @@ -651,7 +648,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces public abstract boolean isConnecting(); /** - * Returns true if this computer is supposed to be launched via JNLP. + * Returns true if this computer is supposed to be launched via inbound protocol. * @deprecated since 2008-05-18. * See {@linkplain #isLaunchSupported()} and {@linkplain ComputerLauncher} */ @@ -665,7 +662,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * Returns true if this computer can be launched by Hudson proactively and automatically. * *

    - * For example, JNLP agents return {@code false} from this, because the launch process + * For example, inbound agents return {@code false} from this, because the launch process * needs to be initiated from the agent side. */ @Exported @@ -727,9 +724,9 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces /** * Returns the icon for this computer. - * + * * It is both the recommended and default implementation to serve different icons based on {@link #isOffline} - * + * * @see #getIconClassName() */ @Exported @@ -742,13 +739,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces /** * Returns the class name that will be used to lookup the icon. - * + * * This class name will be added as a class tag to the html img tags where the icon should * show up followed by a size specifier given by {@link Icon#toNormalizedIconSizeClass(String)} * The conversion of class tag to src tag is registered through {@link IconSet#addIcon(Icon)} - * + * * It is both the recommended and default implementation to serve different icons based on {@link #isOffline} - * + * * @see #getIcon() */ @Exported @@ -794,7 +791,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } public RunList getBuilds() { - return RunList.fromJobs((Iterable)Jenkins.getInstance().allItems(Job.class)).node(getNode()); + return RunList.fromJobs((Iterable)Jenkins.get().allItems(Job.class)).node(getNode()); } /** @@ -911,7 +908,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces if (Jenkins.getInstanceOrNull() == null) { return; } - Set availableNumbers = new HashSet(); + Set availableNumbers = new HashSet<>(); for (int i = 0; i < numExecutors; i++) availableNumbers.add(i); @@ -966,7 +963,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces @Exported @StaplerDispatchable public List getExecutors() { - return new ArrayList(executors); + return new ArrayList<>(executors); } /** @@ -975,7 +972,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces @Exported @StaplerDispatchable public List getOneOffExecutors() { - return new ArrayList(oneOffExecutors); + return new ArrayList<>(oneOffExecutors); } /** @@ -999,7 +996,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces @Restricted(NoExternalUse.class) public List getDisplayExecutors() { // The size may change while we are populating, but let's start with a reasonable guess to minimize resizing - List result = new ArrayList(executors.size()+oneOffExecutors.size()); + List result = new ArrayList<>(executors.size() + oneOffExecutors.size()); int index = 0; for (Executor e: executors) { if (e.isDisplayCell()) { @@ -1067,7 +1064,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ public final long getDemandStartMilliseconds() { long firstDemand = Long.MAX_VALUE; - for (Queue.BuildableItem item : Jenkins.getInstance().getQueue().getBuildableItems(this)) { + for (Queue.BuildableItem item : Jenkins.get().getQueue().getBuildableItems(this)) { firstDemand = Math.min(item.buildableStartMilliseconds, firstDemand); } return firstDemand; @@ -1154,7 +1151,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ @Exported(inline=true) public Map getMonitorData() { - Map r = new HashMap(); + Map r = new HashMap<>(); if (hasPermission(CONNECT)) { for (NodeMonitor monitor : NodeMonitor.getAll()) r.put(monitor.getClass().getName(), monitor.data(this)); @@ -1206,7 +1203,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces Node node = getNode(); if (node==null) return env; // bail out - for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) { + for (NodeProperty nodeProperty: Jenkins.get().getGlobalNodeProperties()) { nodeProperty.buildEnvVars(env,listener); } @@ -1215,7 +1212,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } // TODO: hmm, they don't really belong - String rootUrl = Jenkins.getInstance().getRootUrl(); + String rootUrl = Jenkins.get().getRootUrl(); if(rootUrl!=null) { env.put("HUDSON_URL", rootUrl); // Legacy. env.put("JENKINS_URL", rootUrl); @@ -1245,7 +1242,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * *

    * Since it's possible that the agent is not reachable from the master (it may be behind a firewall, - * connecting to master via JNLP), this method may return null. + * connecting to master via inbound protocol), this method may return null. * * It's surprisingly tricky for a machine to know a name that other systems can get to, * especially between things like DNS search suffix, the hosts file, and YP. @@ -1323,9 +1320,9 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * other classes from being transferred over remoting. */ private static final Logger LOGGER = Logger.getLogger(ListPossibleNames.class.getName()); - + public List call() throws IOException { - List names = new ArrayList(); + List names = new ArrayList<>(); Enumeration nis = NetworkInterface.getNetworkInterfaces(); while (nis.hasMoreElements()) { @@ -1490,7 +1487,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces } Node result = node.reconfigure(req, req.getSubmittedForm()); - Jenkins.getInstance().getNodesObject().replaceNode(this.getNode(), result); + Jenkins.get().getNodesObject().replaceNode(this.getNode(), result); // take the user back to the agent top page. rsp.sendRedirect2("../" + result.getNodeName() + '/'); @@ -1532,7 +1529,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces public void updateByXml(final InputStream source) throws IOException, ServletException { checkPermission(CONFIGURE); Node result = (Node)Jenkins.XSTREAM2.fromXML(source); - Jenkins.getInstance().getNodesObject().replaceNode(this.getNode(), result); + Jenkins.get().getNodesObject().replaceNode(this.getNode(), result); } /** @@ -1543,9 +1540,9 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces checkPermission(DELETE); Node node = getNode(); if (node != null) { - Jenkins.getInstance().removeNode(node); + Jenkins.get().removeNode(node); } else { - AbstractCIBase app = Jenkins.getInstance(); + AbstractCIBase app = Jenkins.get(); app.removeComputer(this); } return new HttpRedirect(".."); @@ -1606,7 +1603,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces @CLIResolver public static Computer resolveForCLI( @Argument(required=true,metaVar="NAME",usage="Agent name, or empty string for master") String name) throws CmdLineException { - Jenkins h = Jenkins.getInstance(); + Jenkins h = Jenkins.get(); Computer item = h.getComputer(name); if (item==null) { List names = ComputerSet.getComputerNames(); @@ -1628,7 +1625,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ @Initializer public static void relocateOldLogs() { - relocateOldLogs(Jenkins.getInstance().getRootDir()); + relocateOldLogs(Jenkins.get().getRootDir()); } /*package*/ static void relocateOldLogs(File dir) { @@ -1697,12 +1694,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces @Override public String toString() { - final StringBuilder sb = new StringBuilder("DisplayExecutor{"); - sb.append("displayName='").append(displayName).append('\''); - sb.append(", url='").append(url).append('\''); - sb.append(", executor=").append(executor); - sb.append('}'); - return sb.toString(); + String sb = "DisplayExecutor{" + "displayName='" + displayName + '\'' + + ", url='" + url + '\'' + + ", executor=" + executor + + '}'; + return sb; } @Override diff --git a/core/src/main/java/hudson/model/ComputerPanelBox.java b/core/src/main/java/hudson/model/ComputerPanelBox.java index 94ee1aaa3ca938aa8412999f65cbae7c2cbf7337..d5e7e45cba50ee9779a3c55ce782fa101834cebe 100644 --- a/core/src/main/java/hudson/model/ComputerPanelBox.java +++ b/core/src/main/java/hudson/model/ComputerPanelBox.java @@ -37,7 +37,7 @@ public abstract class ComputerPanelBox implements ExtensionPoint{ * List of all the registered {@link ComputerPanelBox}s. */ public static List all(Computer computer) { - List boxs = new ArrayList(); + List boxs = new ArrayList<>(); for(ComputerPanelBox box: ExtensionList.lookup(ComputerPanelBox.class)){ box.setComputer(computer); boxs.add(box); diff --git a/core/src/main/java/hudson/model/ComputerPinger.java b/core/src/main/java/hudson/model/ComputerPinger.java index ecf81abceb7a430c81e1bfa61dccd8ce413652cd..1cf4114a0022863c5c52d782431a301e7bfd179e 100644 --- a/core/src/main/java/hudson/model/ComputerPinger.java +++ b/core/src/main/java/hudson/model/ComputerPinger.java @@ -6,6 +6,7 @@ import hudson.ExtensionPoint; import java.io.IOException; import java.net.InetAddress; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** @@ -57,7 +58,7 @@ public abstract class ComputerPinger implements ExtensionPoint { public static class BuiltInComputerPinger extends ComputerPinger { @Override public boolean isReachable(InetAddress ia, int timeout) throws IOException { - return ia.isReachable(timeout * 1000); + return ia.isReachable((int)TimeUnit.SECONDS.toMillis(timeout)); } } diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java index 5dbadb921955bd8418cc75b701de50aaafb457dd..059ed21291822905e04034858107e39f3b862d7f 100644 --- a/core/src/main/java/hudson/model/ComputerSet.java +++ b/core/src/main/java/hudson/model/ComputerSet.java @@ -85,7 +85,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl }; private static final DescribableList> monitors - = new DescribableList>(MONITORS_OWNER); + = new DescribableList<>(MONITORS_OWNER); @Exported public String getDisplayName() { @@ -103,7 +103,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl @Exported(name="computer",inline=true) public Computer[] get_all() { - return Jenkins.getInstance().getComputers(); + return Jenkins.get().getComputers(); } public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception { @@ -129,7 +129,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl * Returns a subset pf {@link #getMonitors()} that are {@linkplain NodeMonitor#isIgnored() not ignored}. */ public static Map,NodeMonitor> getNonIgnoredMonitors() { - Map,NodeMonitor> r = new HashMap, NodeMonitor>(); + Map,NodeMonitor> r = new HashMap<>(); for (NodeMonitor m : monitors) { if(!m.isIgnored()) r.put(m.getDescriptor(),m); @@ -142,7 +142,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl */ public List get_slaveNames() { return new AbstractList() { - final List nodes = Jenkins.getInstance().getNodes(); + final List nodes = Jenkins.get().getNodes(); public String get(int index) { return nodes.get(index).getNodeName(); @@ -198,12 +198,12 @@ public final class ComputerSet extends AbstractModelObject implements Describabl } public Computer getDynamic(String token, StaplerRequest req, StaplerResponse rsp) { - return Jenkins.getInstance().getComputer(token); + return Jenkins.get().getComputer(token); } @RequirePOST public void do_launchAll(StaplerRequest req, StaplerResponse rsp) throws IOException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); for(Computer c : get_all()) { if(c.isLaunchSupported()) @@ -219,7 +219,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl */ @RequirePOST public void doUpdateNow( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); for (NodeMonitor nodeMonitor : NodeMonitor.getAll()) { Thread t = nodeMonitor.triggerUpdate(); @@ -238,7 +238,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl public synchronized void doCreateItem( StaplerRequest req, StaplerResponse rsp, @QueryParameter String name, @QueryParameter String mode, @QueryParameter String from ) throws IOException, ServletException { - final Jenkins app = Jenkins.getInstance(); + final Jenkins app = Jenkins.get(); app.checkPermission(Computer.CREATE); if(mode!=null && mode.equals("copy")) { @@ -288,7 +288,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl public synchronized void doDoCreateItem( StaplerRequest req, StaplerResponse rsp, @QueryParameter String name, @QueryParameter String type ) throws IOException, ServletException, FormException { - final Jenkins app = Jenkins.getInstance(); + final Jenkins app = Jenkins.get(); app.checkPermission(Computer.CREATE); String fixedName = Util.fixEmptyAndTrim(name); checkName(fixedName); @@ -315,7 +315,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl name = name.trim(); Jenkins.checkGoodName(name); - if(Jenkins.getInstance().getNode(name)!=null) + if(Jenkins.get().getNode(name)!=null) throw new Failure(Messages.ComputerSet_SlaveAlreadyExists(name)); // looks good @@ -326,7 +326,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl * Makes sure that the given name is good as an agent name. */ public FormValidation doCheckName(@QueryParameter String value) throws IOException, ServletException { - Jenkins.getInstance().checkPermission(Computer.CREATE); + Jenkins.get().checkPermission(Computer.CREATE); if(Util.fixEmpty(value)==null) return FormValidation.ok(); @@ -346,7 +346,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl public synchronized HttpResponse doConfigSubmit( StaplerRequest req) throws IOException, ServletException, FormException { BulkChange bc = new BulkChange(MONITORS_OWNER); try { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); monitors.rebuild(req,req.getSubmittedForm(),getNodeMonitorDescriptors()); // add in the rest of instances are ignored instances @@ -372,7 +372,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl * {@link NodeMonitor}s are persisted in this file. */ private static XmlFile getConfigFile() { - return new XmlFile(new File(Jenkins.getInstance().getRootDir(),"nodeMonitors.xml")); + return new XmlFile(new File(Jenkins.get().getRootDir(),"nodeMonitors.xml")); } public Api getApi() { @@ -380,7 +380,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl } public Descriptor getDescriptor() { - return Jenkins.getInstance().getDescriptorOrDie(ComputerSet.class); + return Jenkins.get().getDescriptorOrDie(ComputerSet.class); } @Extension @@ -391,7 +391,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl public AutoCompletionCandidates doAutoCompleteCopyNewItemFrom(@QueryParameter final String value) { final AutoCompletionCandidates r = new AutoCompletionCandidates(); - for (Node n : Jenkins.getInstance().getNodes()) { + for (Node n : Jenkins.get().getNodes()) { if (n.getNodeName().startsWith(value)) r.add(n.getNodeName()); } @@ -421,8 +421,8 @@ public final class ComputerSet extends AbstractModelObject implements Describabl */ @Nonnull public static List getComputerNames() { - final ArrayList names = new ArrayList(); - for (Computer c : Jenkins.getInstance().getComputers()) { + final ArrayList names = new ArrayList<>(); + for (Computer c : Jenkins.get().getComputers()) { if (!c.getName().isEmpty()) { names.add(c.getName()); } @@ -435,14 +435,14 @@ public final class ComputerSet extends AbstractModelObject implements Describabl static { try { DescribableList> r - = new DescribableList>(Saveable.NOOP); + = new DescribableList<>(Saveable.NOOP); // load persisted monitors XmlFile xf = getConfigFile(); if(xf.exists()) { DescribableList> persisted = (DescribableList>) xf.read(); - List sanitized = new ArrayList(); + List sanitized = new ArrayList<>(); for (NodeMonitor nm : persisted) { try { nm.getDescriptor(); diff --git a/core/src/main/java/hudson/model/DependencyGraph.java b/core/src/main/java/hudson/model/DependencyGraph.java index 237eba1b40f50ad180a722999aca36a9559b94f9..2588c8994b2e2d0a3caecac843abf4a68eabf9e9 100644 --- a/core/src/main/java/hudson/model/DependencyGraph.java +++ b/core/src/main/java/hudson/model/DependencyGraph.java @@ -70,8 +70,8 @@ import java.util.Stack; */ public class DependencyGraph implements Comparator { - private Map> forward = new HashMap>(); - private Map> backward = new HashMap>(); + private Map> forward = new HashMap<>(); + private Map> backward = new HashMap<>(); private transient Map, Object> computationalData; @@ -90,8 +90,8 @@ public class DependencyGraph implements Comparator { // Set full privileges while computing to avoid missing any projects the current user cannot see. SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM); try { - this.computationalData = new HashMap, Object>(); - for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) ) + this.computationalData = new HashMap<>(); + for( AbstractProject p : Jenkins.get().allItems(AbstractProject.class) ) p.buildDependencyGraph(this); forward = finalize(forward); @@ -113,7 +113,7 @@ public class DependencyGraph implements Comparator { DirectedGraph g = new DirectedGraph() { @Override protected Collection nodes() { - final Set nodes = new HashSet(); + final Set nodes = new HashSet<>(); nodes.addAll(forward.keySet()); nodes.addAll(backward.keySet()); return nodes; @@ -127,8 +127,8 @@ public class DependencyGraph implements Comparator { List> sccs = g.getStronglyConnectedComponents(); - final Map topoOrder = new HashMap(); - topologicallySorted = new ArrayList>(); + final Map topoOrder = new HashMap<>(); + topologicallySorted = new ArrayList<>(); int idx=0; for (SCC scc : sccs) { for (AbstractProject n : scc) { @@ -196,7 +196,7 @@ public class DependencyGraph implements Comparator { private List get(Map> map, AbstractProject src, boolean up) { List v = map.get(src); if(v==null) return Collections.emptyList(); - List result = new ArrayList(v.size()); + List result = new ArrayList<>(v.size()); for (DependencyGroup d : v) result.add(up ? d.getUpstreamProject() : d.getDownstreamProject()); return result; } @@ -284,8 +284,8 @@ public class DependencyGraph implements Comparator { * where the length is greater than 1. */ public boolean hasIndirectDependencies(AbstractProject src, AbstractProject dst) { - Set visited = new HashSet(); - Stack queue = new Stack(); + Set visited = new HashSet<>(); + Stack queue = new Stack<>(); queue.addAll(getDownstream(src)); queue.remove(dst); @@ -316,8 +316,8 @@ public class DependencyGraph implements Comparator { } private Set getTransitive(Map> direction, AbstractProject src, boolean up) { - Set visited = new HashSet(); - Stack queue = new Stack(); + Set visited = new HashSet<>(); + Stack queue = new Stack<>(); queue.add(src); @@ -334,15 +334,10 @@ public class DependencyGraph implements Comparator { } private void add(Map> map, AbstractProject key, Dependency dep) { - List set = map.get(key); - if(set==null) { - set = new ArrayList(); - map.put(key,set); - } - for (ListIterator it = set.listIterator(); it.hasNext();) { - DependencyGroup d = it.next(); + List set = map.computeIfAbsent(key, k -> new ArrayList<>()); + for (DependencyGroup d : set) { // Check for existing edge that connects the same two projects: - if (d.getUpstreamProject()==dep.getUpstreamProject() && d.getDownstreamProject()==dep.getDownstreamProject()) { + if (d.getUpstreamProject() == dep.getUpstreamProject() && d.getDownstreamProject() == dep.getDownstreamProject()) { d.add(dep); return; } @@ -353,7 +348,7 @@ public class DependencyGraph implements Comparator { private Map> finalize(Map> m) { for (Entry> e : m.entrySet()) { - Collections.sort( e.getValue(), NAME_COMPARATOR ); + e.getValue().sort(NAME_COMPARATOR); e.setValue( Collections.unmodifiableList(e.getValue()) ); } return Collections.unmodifiableMap(m); @@ -458,7 +453,7 @@ public class DependencyGraph implements Comparator { * Collect multiple dependencies between the same two projects. */ private static class DependencyGroup { - private Set group = new LinkedHashSet(); + private Set group = new LinkedHashSet<>(); DependencyGroup(Dependency first) { this.upstream = first.getUpstreamProject(); diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java index b7fc8097e8950523b3bd10cced0d698a5aea7dd4..d282abbd80497655986fb3df47bb2987a1bbb981 100644 --- a/core/src/main/java/hudson/model/Descriptor.java +++ b/core/src/main/java/hudson/model/Descriptor.java @@ -53,7 +53,6 @@ import org.apache.commons.io.IOUtils; import static hudson.util.QuotedStringTokenizer.*; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; -import javax.annotation.PostConstruct; import javax.servlet.ServletException; import javax.servlet.RequestDispatcher; import java.io.File; @@ -138,7 +137,7 @@ public abstract class Descriptor> implements Saveable, */ public transient final Class clazz; - private transient final Map checkMethods = new ConcurrentHashMap(2); + private transient final Map checkMethods = new ConcurrentHashMap<>(2); /** * Lazily computed list of properties on {@link #clazz} and on the descriptor itself. @@ -200,7 +199,7 @@ public abstract class Descriptor> implements Saveable, * Returns {@link Descriptor} whose 'clazz' is the same as {@link #getItemType() the item type}. */ public Descriptor getItemTypeDescriptor() { - return Jenkins.getInstance().getDescriptor(getItemType()); + return Jenkins.get().getDescriptor(getItemType()); } public Descriptor getItemTypeDescriptorOrDie() { @@ -208,7 +207,7 @@ public abstract class Descriptor> implements Saveable, if (it == null) { throw new AssertionError(clazz + " is not an array/collection type in " + displayName + ". See https://jenkins.io/redirect/developer/class-is-missing-descriptor"); } - Descriptor d = Jenkins.getInstance().getDescriptor(it); + Descriptor d = Jenkins.get().getDescriptor(it); if (d==null) throw new AssertionError(it +" is missing its descriptor in "+displayName+". See https://jenkins.io/redirect/developer/class-is-missing-descriptor"); return d; @@ -218,14 +217,14 @@ public abstract class Descriptor> implements Saveable, * Returns all the descriptors that produce types assignable to the property type. */ public List getApplicableDescriptors() { - return Jenkins.getInstance().getDescriptorList(clazz); + return Jenkins.get().getDescriptorList(clazz); } /** * Returns all the descriptors that produce types assignable to the item type for a collection property. */ public List getApplicableItemDescriptors() { - return Jenkins.getInstance().getDescriptorList(getItemType()); + return Jenkins.get().getDescriptorList(getItemType()); } } @@ -234,7 +233,7 @@ public abstract class Descriptor> implements Saveable, * * @see #getHelpFile(String) */ - private transient final Map helpRedirect = new HashMap(2); + private transient final Map helpRedirect = new HashMap<>(2); private static class HelpRedirect { private final Class owner; @@ -247,7 +246,7 @@ public abstract class Descriptor> implements Saveable, private String resolve() { // the resolution has to be deferred to avoid ordering issue among descriptor registrations. - return Jenkins.getInstance().getDescriptor(owner).getHelpFile(fieldNameToRedirectTo); + return Jenkins.get().getDescriptor(owner).getHelpFile(fieldNameToRedirectTo); } } @@ -417,7 +416,7 @@ public abstract class Descriptor> implements Saveable, throw new IllegalStateException(String.format("%s doesn't have the %s method for filling a drop-down list", getClass(), methodName)); // build query parameter line by figuring out what should be submitted - List depends = buildFillDependencies(method, new ArrayList()); + List depends = buildFillDependencies(method, new ArrayList<>()); if (!depends.isEmpty()) attributes.put("fillDependsOn",Util.join(depends," ")); @@ -507,7 +506,7 @@ public abstract class Descriptor> implements Saveable, * Given the class, list up its {@link PropertyType}s from its public fields/getters. */ private Map buildPropertyTypes(Class clazz) { - Map r = new HashMap(); + Map r = new HashMap<>(); for (Field f : clazz.getFields()) r.put(f.getName(),new PropertyType(f)); @@ -856,8 +855,8 @@ public abstract class Descriptor> implements Saveable, } protected List getPossibleViewNames(String baseName) { - List names = new ArrayList(); - for (Facet f : WebApp.get(Jenkins.getInstance().servletContext).facets) { + List names = new ArrayList<>(); + for (Facet f : WebApp.get(Jenkins.get().servletContext).facets) { if (f instanceof JellyCompatibleFacet) { JellyCompatibleFacet jcf = (JellyCompatibleFacet) f; for (String ext : jcf.getScriptExtensions()) @@ -902,7 +901,7 @@ public abstract class Descriptor> implements Saveable, } protected XmlFile getConfigFile() { - return new XmlFile(new File(Jenkins.getInstance().getRootDir(),getId()+".xml")); + return new XmlFile(new File(Jenkins.get().getRootDir(),getId()+".xml")); } /** @@ -912,7 +911,7 @@ public abstract class Descriptor> implements Saveable, * null to indicate that this descriptor came from the core. */ protected PluginWrapper getPlugin() { - return Jenkins.getInstance().getPluginManager().whichPlugin(clazz); + return Jenkins.get().getPluginManager().whichPlugin(clazz); } /** @@ -982,12 +981,12 @@ public abstract class Descriptor> implements Saveable, } public static List toList( T... values ) { - return new ArrayList(Arrays.asList(values)); + return new ArrayList<>(Arrays.asList(values)); } public static > Map,T> toMap(Iterable describables) { - Map,T> m = new LinkedHashMap,T>(); + Map,T> m = new LinkedHashMap<>(); for (T d : describables) { Descriptor descriptor; try { @@ -1026,7 +1025,7 @@ public abstract class Descriptor> implements Saveable, List newInstancesFromHeteroList(StaplerRequest req, Object formData, Collection> descriptors) throws FormException { - List items = new ArrayList(); + List items = new ArrayList<>(); if (formData!=null) { for (Object o : JSONArray.fromObject(formData)) { diff --git a/core/src/main/java/hudson/model/DescriptorByNameOwner.java b/core/src/main/java/hudson/model/DescriptorByNameOwner.java index 26414ca6a155ad4c8d3feb76548ce920a8d7fe66..e36f61df4d0033073544ae7148932e572c9976f2 100644 --- a/core/src/main/java/hudson/model/DescriptorByNameOwner.java +++ b/core/src/main/java/hudson/model/DescriptorByNameOwner.java @@ -49,6 +49,6 @@ public interface DescriptorByNameOwner extends ModelObject { * Either {@link Descriptor#getId()} (recommended) or the short name. */ default Descriptor getDescriptorByName(String id) { - return Jenkins.getInstance().getDescriptorByName(id); + return Jenkins.get().getDescriptorByName(id); } } diff --git a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java index e449569500d8afee5de19199c97d83762af5b3f4..ef099a64dc77ff013567e4269652567a61584df7 100644 --- a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java +++ b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java @@ -65,7 +65,7 @@ public abstract class DescriptorVisibilityFilter implements ExtensionPoint { public static List apply(Object context, Iterable source) { ExtensionList filters = all(); - List r = new ArrayList(); + List r = new ArrayList<>(); Class contextClass = context == null ? null : context.getClass(); if (source == null) { @@ -110,7 +110,7 @@ public abstract class DescriptorVisibilityFilter implements ExtensionPoint { public static List applyType(Class contextClass, Iterable source) { ExtensionList filters = all(); - List r = new ArrayList(); + List r = new ArrayList<>(); OUTER: for (T d : source) { diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java index 4a9217ffcfda4c8b788e6286b4cb0c9909e19a9e..aa92c9c1e83ef29a147d544106b69229bfa145d8 100644 --- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java +++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java @@ -31,6 +31,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; @@ -39,8 +40,10 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.GregorianCalendar; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.StringTokenizer; import java.util.logging.Level; @@ -59,7 +62,6 @@ import org.apache.tools.zip.ZipOutputStream; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -75,7 +77,7 @@ import org.kohsuke.stapler.StaplerResponse; public final class DirectoryBrowserSupport implements HttpResponse { // escape hatch for SECURITY-904 to keep legacy behavior @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") - public static boolean ALLOW_SYMLINK_ESCAPE = Boolean.getBoolean(DirectoryBrowserSupport.class.getName() + ".allowSymlinkEscape"); + public static boolean ALLOW_SYMLINK_ESCAPE = SystemProperties.getBoolean(DirectoryBrowserSupport.class.getName() + ".allowSymlinkEscape"); public final ModelObject owner; @@ -243,7 +245,7 @@ public final class DirectoryBrowserSupport implements HttpResponse { rsp.setContentType("text/plain;charset=UTF-8"); try (OutputStream os = rsp.getOutputStream()) { for (VirtualFile kid : baseFile.list()) { - os.write(kid.getName().getBytes("UTF-8")); + os.write(kid.getName().getBytes(StandardCharsets.UTF_8)); if (kid.isDirectory()) { os.write('/'); } @@ -308,7 +310,7 @@ public final class DirectoryBrowserSupport implements HttpResponse { if(rest.equals("*fingerprint*")) { InputStream fingerprintInput = baseFile.open(); try { - rsp.forward(Jenkins.getInstance().getFingerprint(Util.getDigestOf(fingerprintInput)), "/", req); + rsp.forward(Jenkins.get().getFingerprint(Util.getDigestOf(fingerprintInput)), "/", req); } finally { fingerprintInput.close(); } @@ -436,33 +438,79 @@ public final class DirectoryBrowserSupport implements HttpResponse { try (ZipOutputStream zos = new ZipOutputStream(outputStream)) { zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter // TODO consider using run(Callable) here - for (String n : dir.list(glob.isEmpty() ? "**" : glob, null, /* TODO what is the user expectation? */true)) { - String relativePath; - if (glob.length() == 0) { - // JENKINS-19947: traditional behavior is to prepend the directory name - relativePath = dir.getName() + '/' + n; - } else { - relativePath = n; - } - String targetFile = dir.toString().substring(root.toString().length()) + n; - if (!ALLOW_SYMLINK_ESCAPE && root.supportIsDescendant() && !root.isDescendant(targetFile)) { - LOGGER.log(Level.INFO, "Trying to access a file outside of the directory: " + root + ", illicit target: " + targetFile); - } else { - // In ZIP archives "All slashes MUST be forward slashes" (http://pkware.com/documents/casestudies/APPNOTE.TXT) - // TODO On Linux file names can contain backslashes which should not treated as file separators. - // Unfortunately, only the file separator char of the master is known (File.separatorChar) - // but not the file separator char of the (maybe remote) "dir". - ZipEntry e = new ZipEntry(relativePath.replace('\\', '/')); - VirtualFile f = dir.child(n); - e.setTime(f.lastModified()); - zos.putNextEntry(e); - try (InputStream in = f.open()) { - IOUtils.copy(in, zos); - } - zos.closeEntry(); + if (glob.isEmpty()) { + if (!root.supportsQuickRecursiveListing()) { + // avoid slow listing when the Glob can do a quicker job + glob = "**"; } } + + if (glob.isEmpty()) { + Map nameToVirtualFiles = collectRecursivelyAllLegalChildren(dir); + sendZipUsingMap(zos, dir, nameToVirtualFiles); + } else { + Collection listOfFile = dir.list(glob, null, /* TODO what is the user expectation? */true); + sendZipUsingListOfNames(zos, dir, listOfFile); + } + } + } + + private static void sendZipUsingMap(ZipOutputStream zos, VirtualFile dir, Map nameToVirtualFiles) throws IOException { + for (Map.Entry entry : nameToVirtualFiles.entrySet()) { + String n = entry.getKey(); + + // JENKINS-19947: traditional behavior is to prepend the directory name + String relativePath = dir.getName() + '/' + n; + + VirtualFile f = entry.getValue(); + sendOneZipEntry(zos, f, relativePath); + } + } + + private static void sendZipUsingListOfNames(ZipOutputStream zos, VirtualFile dir, Collection listOfFileNames) throws IOException { + for (String relativePath : listOfFileNames) { + VirtualFile f = dir.child(relativePath); + sendOneZipEntry(zos, f, relativePath); + } + } + + private static void sendOneZipEntry(ZipOutputStream zos, VirtualFile vf, String relativePath) throws IOException { + // In ZIP archives "All slashes MUST be forward slashes" (http://pkware.com/documents/casestudies/APPNOTE.TXT) + // TODO On Linux file names can contain backslashes which should not treated as file separators. + // Unfortunately, only the file separator char of the master is known (File.separatorChar) + // but not the file separator char of the (maybe remote) "dir". + ZipEntry e = new ZipEntry(relativePath.replace('\\', '/')); + + e.setTime(vf.lastModified()); + zos.putNextEntry(e); + try (InputStream in = vf.open()) { + IOUtils.copy(in, zos); + } + finally { + zos.closeEntry(); + } + } + + private static Map collectRecursivelyAllLegalChildren(VirtualFile dir) throws IOException { + Map nameToFiles = new LinkedHashMap<>(); + collectRecursivelyAllLegalChildren(dir, "", nameToFiles); + return nameToFiles; + } + + private static void collectRecursivelyAllLegalChildren(VirtualFile currentDir, String currentPrefix, Map nameToFiles) throws IOException { + if (currentDir.isFile()) { + if (currentDir.isDescendant("")) { + nameToFiles.put(currentPrefix, currentDir); + } + } else { + if (!currentPrefix.isEmpty()) { + currentPrefix += "/"; + } + List children = currentDir.listOnlyDescendants(); + for (VirtualFile child : children) { + collectRecursivelyAllLegalChildren(child, currentPrefix + child.getName(), nameToFiles); + } } } diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java index ada651bbe4c6b723538606f9d67ed173a7724d88..c187dc0940507ee74ccf8e31012b295c3b342aac 100644 --- a/core/src/main/java/hudson/model/DownloadService.java +++ b/core/src/main/java/hudson/model/DownloadService.java @@ -46,11 +46,11 @@ import java.net.URLConnection; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.DownloadSettings; import jenkins.model.Jenkins; -import jenkins.util.JSONSignatureValidator; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; @@ -89,10 +89,10 @@ public class DownloadService extends PageDecorator { if (doesNotSupportPostMessage()) return ""; StringBuilder buf = new StringBuilder(); - if(Jenkins.getInstance().hasPermission(Jenkins.READ)) { + if(Jenkins.get().hasPermission(Jenkins.READ)) { long now = System.currentTimeMillis(); for (Downloadable d : Downloadable.all()) { - if(d.getDue()") .append("Behaviour.addLoadEvent(function() {") .append(" downloadService.download(") @@ -100,7 +100,7 @@ public class DownloadService extends PageDecorator { .append(',') .append(QuotedStringTokenizer.quote(mapHttps(d.getUrl()))) .append(',') - .append("{version:"+QuotedStringTokenizer.quote(Jenkins.VERSION)+'}') + .append("{version:").append(QuotedStringTokenizer.quote(Jenkins.VERSION)).append('}') .append(',') .append(QuotedStringTokenizer.quote(Stapler.getCurrentRequest().getContextPath()+'/'+getUrl()+"/byId/"+d.getId()+"/postBack")) .append(',') @@ -302,14 +302,14 @@ public class DownloadService extends PageDecorator { * URL to download. */ public String getUrl() { - return Jenkins.getInstance().getUpdateCenter().getDefaultBaseUrl()+"updates/"+url; + return Jenkins.get().getUpdateCenter().getDefaultBaseUrl()+"updates/"+url; } /** * URLs to download from. */ public List getUrls() { - List updateSites = new ArrayList(); + List updateSites = new ArrayList<>(); for (UpdateSite site : Jenkins.getActiveInstance().getUpdateCenter().getSiteList()) { String siteUrl = site.getUrl(); int baseUrlEnd = siteUrl.indexOf("update-center.json"); @@ -337,7 +337,7 @@ public class DownloadService extends PageDecorator { * This is where the retrieved file will be stored. */ public TextFile getDataFile() { - return new TextFile(new File(Jenkins.getInstance().getRootDir(),"updates/"+id)); + return new TextFile(new File(Jenkins.get().getRootDir(),"updates/"+id)); } /** diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java index 5f90ad1bfcf37b9db0046b5be6f48f453052fa92..b466779e1ac08106ed2cb1860a23e67984d6ab51 100644 --- a/core/src/main/java/hudson/model/Executor.java +++ b/core/src/main/java/hudson/model/Executor.java @@ -139,7 +139,7 @@ public class Executor extends Thread implements ModelObject { * Cause of interruption. Access needs to be synchronized. */ @GuardedBy("lock") - private final List causes = new Vector(); + private final List causes = new Vector<>(); public Executor(@Nonnull Computer owner, int n) { super("Executor #"+n+" for "+owner.getDisplayName()); @@ -271,7 +271,7 @@ public class Executor extends Thread implements ModelObject { lock.writeLock().lock(); try { if (causes.isEmpty()) return; - r = new ArrayList(causes); + r = new ArrayList<>(causes); causes.clear(); } finally { lock.writeLock().unlock(); @@ -961,7 +961,7 @@ public class Executor extends Thread implements ModelObject { * Mechanism to allow threads (in particular the channel request handling threads) to * run on behalf of {@link Executor}. */ - private static final ThreadLocal IMPERSONATION = new ThreadLocal(); + private static final ThreadLocal IMPERSONATION = new ThreadLocal<>(); private static final Logger LOGGER = Logger.getLogger(Executor.class.getName()); } diff --git a/core/src/main/java/hudson/model/Failure.java b/core/src/main/java/hudson/model/Failure.java index 37fc2696b54291acebf80ece0a18af4ddcd6d83f..4fb00809f02379e24ea5f9634c71a3054d2ff62e 100644 --- a/core/src/main/java/hudson/model/Failure.java +++ b/core/src/main/java/hudson/model/Failure.java @@ -67,8 +67,8 @@ public class Failure extends RuntimeException implements HttpResponse { if(pre) req.setAttribute("pre",true); if (node instanceof AbstractItem) // Maintain ancestors - rsp.forward(Jenkins.getInstance(), ((AbstractItem)node).getUrl() + "error", req); + rsp.forward(Jenkins.get(), ((AbstractItem)node).getUrl() + "error", req); else - rsp.forward(node instanceof AbstractModelObject ? node : Jenkins.getInstance() ,"error", req); + rsp.forward(node instanceof AbstractModelObject ? node : Jenkins.get() ,"error", req); } } diff --git a/core/src/main/java/hudson/model/FileParameterDefinition.java b/core/src/main/java/hudson/model/FileParameterDefinition.java index e7160796cdf7faf757a9247b24306b74b2725099..5175364665b778eedd8e0d7c56800be74a259469 100644 --- a/core/src/main/java/hudson/model/FileParameterDefinition.java +++ b/core/src/main/java/hudson/model/FileParameterDefinition.java @@ -24,7 +24,6 @@ package hudson.model; import hudson.Extension; -import hudson.FilePath; import hudson.cli.CLICommand; import java.io.File; import java.io.IOException; @@ -109,9 +108,8 @@ public class FileParameterDefinition extends ParameterDefinition { FileUtils.copyInputStreamToFile(command.stdin, local); name = "stdin"; } else { - FilePath src = new FilePath(command.checkChannel(), value); - src.copyTo(new FilePath(local)); - name = src.getName(); + command.checkChannel(); + return null; // never called } FileParameterValue p = new FileParameterValue(getName(), local, name); diff --git a/core/src/main/java/hudson/model/FileParameterValue.java b/core/src/main/java/hudson/model/FileParameterValue.java index f911927285ae55af04f93d234a7d2e709de54a6f..33336cd6b2f36ad009ec0fe8abd1337b5eff6aa1 100644 --- a/core/src/main/java/hudson/model/FileParameterValue.java +++ b/core/src/main/java/hudson/model/FileParameterValue.java @@ -37,6 +37,7 @@ import java.io.UnsupportedEncodingException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.regex.Pattern; import javax.servlet.ServletException; import org.apache.commons.fileupload.FileItem; @@ -65,6 +66,7 @@ import org.kohsuke.stapler.StaplerResponse; */ public class FileParameterValue extends ParameterValue { private static final String FOLDER_NAME = "fileParameters"; + private static final Pattern PROHIBITED_DOUBLE_DOT = Pattern.compile(".*[\\\\/]\\.\\.[\\\\/].*"); /** * Escape hatch for SECURITY-1074, fileParameter used to escape their expected folder. @@ -162,7 +164,7 @@ public class FileParameterValue extends ParameterValue { if (ws == null) { throw new IllegalStateException("The workspace should be created when setUp method is called"); } - if (!ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE && !ws.isDescendant(location)) { + if (!ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE && (PROHIBITED_DOUBLE_DOT.matcher(location).matches() || !ws.isDescendant(location))) { listener.error("Rejecting file path escaping base directory with relative path: " + location); // force the build to fail return null; diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index e94790857676e28cca1c6def7f4b0d7f1ad30ca3..31945050270e8421eeccc817488107475d29d314 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -129,7 +129,7 @@ public class Fingerprint implements ModelObject, Saveable { private boolean hasPermissionToDiscoverBuild() { // We expose the data to Jenkins administrators in order to // let them manage the data for deleted jobs (also works for SYSTEM) - final Jenkins instance = Jenkins.getInstance(); + final Jenkins instance = Jenkins.get(); if (instance.hasPermission(Jenkins.ADMINISTER)) { return true; } @@ -149,7 +149,7 @@ public class Fingerprint implements ModelObject, Saveable { */ @WithBridgeMethods(value=AbstractProject.class, castRequired=true) public Job getJob() { - return Jenkins.getInstance().getItemByFullName(name, Job.class); + return Jenkins.get().getItemByFullName(name, Job.class); } /** @@ -201,7 +201,7 @@ public class Fingerprint implements ModelObject, Saveable { * belongs to MavenModuleSet. */ public boolean belongsTo(Job job) { - Item p = Jenkins.getInstance().getItemByFullName(name); + Item p = Jenkins.get().getItemByFullName(name); while(p!=null) { if(p==job) return true; @@ -349,7 +349,7 @@ public class Fingerprint implements ModelObject, Saveable { private final List ranges; public RangeSet() { - this(new ArrayList()); + this(new ArrayList<>()); } private RangeSet(List data) { @@ -417,7 +417,7 @@ public class Fingerprint implements ModelObject, Saveable { */ @Exported public synchronized List getRanges() { - return new ArrayList(ranges); + return new ArrayList<>(ranges); } /** @@ -515,7 +515,7 @@ public class Fingerprint implements ModelObject, Saveable { * @return true if this range set was modified as a result. */ public synchronized boolean retainAll(RangeSet that) { - List intersection = new ArrayList(); + List intersection = new ArrayList<>(); int lhs=0,rhs=0; while(lhs sub = new ArrayList(); + List sub = new ArrayList<>(); int lhs=0,rhs=0; while(lhs builds = p.getBuilds(); for (Run build : builds) { @@ -866,9 +866,9 @@ public class Fingerprint implements ModelObject, Saveable { /** * Range of builds that use this file keyed by a job full name. */ - private final Hashtable usages = new Hashtable(); + private Hashtable usages = new Hashtable<>(); - PersistedList facets = new PersistedList(this); + PersistedList facets = new PersistedList<>(this); /** * Lazily computed immutable {@link FingerprintFacet}s created from {@link TransientFingerprintFacetFactory}. @@ -967,13 +967,12 @@ public class Fingerprint implements ModelObject, Saveable { * Gets the sorted list of job names where this jar is used. */ public @Nonnull List getJobs() { - List r = new ArrayList(); - r.addAll(usages.keySet()); + List r = new ArrayList<>(usages.keySet()); Collections.sort(r); return r; } - public @Nonnull Hashtable getUsages() { + public @CheckForNull Hashtable getUsages() { return usages; } @@ -993,8 +992,8 @@ public class Fingerprint implements ModelObject, Saveable { // this is for remote API @Exported(name="usage") public @Nonnull List _getUsages() { - List r = new ArrayList(); - final Jenkins instance = Jenkins.getInstance(); + List r = new ArrayList<>(); + final Jenkins instance = Jenkins.get(); for (Entry e : usages.entrySet()) { final String itemName = e.getKey(); if (instance.hasPermission(Jenkins.ADMINISTER) || canDiscoverItem(itemName)) { @@ -1009,7 +1008,7 @@ public class Fingerprint implements ModelObject, Saveable { */ @Deprecated public synchronized void add(@Nonnull AbstractBuild b) throws IOException { - addFor((Run) b); + addFor(b); } /** @@ -1029,6 +1028,14 @@ public class Fingerprint implements ModelObject, Saveable { save(); } + // JENKINS-49588 + protected Object readResolve() { + if (usages == null) { + usages = new Hashtable<>(); + } + return this; + } + void addWithoutSaving(@Nonnull String jobFullName, int n) { synchronized(usages) { // TODO why not synchronized (this) like some, though not all, other accesses? RangeSet r = usages.get(jobFullName); @@ -1053,7 +1060,7 @@ public class Fingerprint implements ModelObject, Saveable { return true; for (Entry e : usages.entrySet()) { - Job j = Jenkins.getInstance().getItemByFullName(e.getKey(),Job.class); + Job j = Jenkins.get().getItemByFullName(e.getKey(),Job.class); if(j==null) continue; @@ -1079,8 +1086,8 @@ public class Fingerprint implements ModelObject, Saveable { public synchronized boolean trim() throws IOException { boolean modified = false; - for (Entry e : new Hashtable(usages).entrySet()) {// copy because we mutate - Job j = Jenkins.getInstance().getItemByFullName(e.getKey(),Job.class); + for (Entry e : new Hashtable<>(usages).entrySet()) {// copy because we mutate + Job j = Jenkins.get().getItemByFullName(e.getKey(),Job.class); if(j==null) {// no such job any more. recycle the record modified = true; usages.remove(e.getKey()); @@ -1150,7 +1157,7 @@ public class Fingerprint implements ModelObject, Saveable { */ public @Nonnull Collection getFacets() { if (transientFacets==null) { - List transientFacets = new ArrayList(); + List transientFacets = new ArrayList<>(); for (TransientFingerprintFacetFactory fff : TransientFingerprintFacetFactory.all()) { fff.createFor(this,transientFacets); } @@ -1191,13 +1198,13 @@ public class Fingerprint implements ModelObject, Saveable { * @return Sorted list of {@link FingerprintFacet}s */ public @Nonnull Collection getSortedFacets() { - List r = new ArrayList(getFacets()); - Collections.sort(r,new Comparator() { + List r = new ArrayList<>(getFacets()); + r.sort(new Comparator() { public int compare(FingerprintFacet o1, FingerprintFacet o2) { long a = o1.getTimestamp(); long b = o2.getTimestamp(); - if (a getActions() { - List r = new ArrayList(); + List r = new ArrayList<>(); for (FingerprintFacet ff : getFacets()) ff.createActions(r); return Collections.unmodifiableList(r); @@ -1342,7 +1349,7 @@ public class Fingerprint implements ModelObject, Saveable { */ private static @Nonnull File getFingerprintFile(@Nonnull byte[] md5sum) { assert md5sum.length==16; - return new File( Jenkins.getInstance().getRootDir(), + return new File( Jenkins.get().getRootDir(), "fingerprints/"+ Util.toHexString(md5sum,0,1)+'/'+Util.toHexString(md5sum,1,1)+'/'+Util.toHexString(md5sum,2,md5sum.length-2)+".xml"); } @@ -1373,7 +1380,7 @@ public class Fingerprint implements ModelObject, Saveable { if(logger.isLoggable(Level.FINE)) logger.fine("Loading fingerprint "+file+" took "+(System.currentTimeMillis()-start)+"ms"); if (f.facets==null) - f.facets = new PersistedList(f); + f.facets = new PersistedList<>(f); for (FingerprintFacet facet : f.facets) facet._setOwner(f); return f; @@ -1412,7 +1419,7 @@ public class Fingerprint implements ModelObject, Saveable { } @Override public String toString() { - return "Fingerprint[original=" + original + ",hash=" + getHashString() + ",fileName=" + fileName + ",timestamp=" + DATE_CONVERTER.toString(timestamp) + ",usages=" + new TreeMap(usages) + ",facets=" + facets + "]"; + return "Fingerprint[original=" + original + ",hash=" + getHashString() + ",fileName=" + fileName + ",timestamp=" + DATE_CONVERTER.toString(timestamp) + ",usages=" + ((usages == null) ? "null" : new TreeMap<>(getUsages())) + ",facets=" + facets + "]"; } /** @@ -1422,7 +1429,7 @@ public class Fingerprint implements ModelObject, Saveable { * @return {@code true} if the user can discover the item */ private static boolean canDiscoverItem(@Nonnull final String fullName) { - final Jenkins jenkins = Jenkins.getInstance(); + final Jenkins jenkins = Jenkins.get(); // Fast check to avoid security context switches Item item = null; diff --git a/core/src/main/java/hudson/model/FingerprintMap.java b/core/src/main/java/hudson/model/FingerprintMap.java index 7760bd0ada91fcf10695fb59fa8d7ced5e6d238c..2727d22174a6973f9b07f7ab7cb0bd286a52dff0 100644 --- a/core/src/main/java/hudson/model/FingerprintMap.java +++ b/core/src/main/java/hudson/model/FingerprintMap.java @@ -50,7 +50,7 @@ public final class FingerprintMap extends KeyedDataStorage i } public DescriptorImpl getDescriptor() { - return (DescriptorImpl)Jenkins.getInstance().getDescriptorOrDie(getClass()); + return (DescriptorImpl)Jenkins.get().getDescriptorOrDie(getClass()); } /** diff --git a/core/src/main/java/hudson/model/FullDuplexHttpChannel.java b/core/src/main/java/hudson/model/FullDuplexHttpChannel.java index 70e8c269f228d71e4fe189ce3ae52b4e3a79dca3..95cea4abf938e49ced939ac28ebd9cc3bb1a8c32 100644 --- a/core/src/main/java/hudson/model/FullDuplexHttpChannel.java +++ b/core/src/main/java/hudson/model/FullDuplexHttpChannel.java @@ -38,7 +38,9 @@ import jenkins.util.FullDuplexHttpService; * Builds a {@link Channel} on top of two HTTP streams (one used for each direction.) * * @author Kohsuke Kawaguchi + * @deprecated Unused. */ +@Deprecated abstract public class FullDuplexHttpChannel extends FullDuplexHttpService { private Channel channel; private final boolean restricted; diff --git a/core/src/main/java/hudson/model/HealthReport.java b/core/src/main/java/hudson/model/HealthReport.java index ddcc1f9decebdd48a6fad4f6bd126655b43c1349..7b675382723c64ae5051bd016bbcb9706f78cde5 100644 --- a/core/src/main/java/hudson/model/HealthReport.java +++ b/core/src/main/java/hudson/model/HealthReport.java @@ -62,7 +62,7 @@ public class HealthReport implements Serializable, Comparable { private static final String HEALTH_0_TO_20_IMG = "health-00to19.png"; private static final String HEALTH_UNKNOWN_IMG = "empty.png"; - private static final Map iconIMGToClassMap = new HashMap(); + private static final Map iconIMGToClassMap = new HashMap<>(); static { iconIMGToClassMap.put(HEALTH_OVER_80_IMG, HEALTH_OVER_80); iconIMGToClassMap.put(HEALTH_61_TO_80_IMG, HEALTH_61_TO_80); diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java index cf72f3b3366fa62e5d0436a857ec2f24d8765852..b20ddd349f59ca6bd73416826022667c564ae210 100644 --- a/core/src/main/java/hudson/model/Hudson.java +++ b/core/src/main/java/hudson/model/Hudson.java @@ -74,7 +74,7 @@ public class Hudson extends Jenkins { @CLIResolver @Nullable public static Hudson getInstance() { - return (Hudson)Jenkins.getInstance(); + return (Hudson)Jenkins.get(); } public Hudson(File root, ServletContext context) throws IOException, InterruptedException, ReactorException { @@ -305,7 +305,7 @@ public class Hudson extends Jenkins { */ @Deprecated public static boolean isAdmin() { - return Jenkins.getInstance().getACL().hasPermission(ADMINISTER); + return Jenkins.get().getACL().hasPermission(ADMINISTER); } /** diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java index be8a5495e16d886a8d543f4caf9f0951955bb05e..7f545652ad9018deaffeccb408ed6c967a1e59dc 100644 --- a/core/src/main/java/hudson/model/Item.java +++ b/core/src/main/java/hudson/model/Item.java @@ -201,7 +201,7 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont */ @Deprecated default String getAbsoluteUrl() { - String r = Jenkins.getInstance().getRootUrl(); + String r = Jenkins.get().getRootUrl(); if(r==null) throw new IllegalStateException("Root URL isn't configured yet. Cannot compute absolute URL."); return Util.encode(r+getUrl()); diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 195d4d8c0ce95322284d279d8b6176286cd24958..6678385907711924a7da6f21d9aa70dd5ec98989 100644 --- a/core/src/main/java/hudson/model/ItemGroupMixIn.java +++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java @@ -107,7 +107,7 @@ public abstract class ItemGroupMixIn { return child.isDirectory(); } }); - CopyOnWriteMap.Tree configurations = new CopyOnWriteMap.Tree(); + CopyOnWriteMap.Tree configurations = new CopyOnWriteMap.Tree<>(); for (File subdir : subdirs) { try { // Try to retain the identity of an existing child object if we can. @@ -175,7 +175,7 @@ public abstract class ItemGroupMixIn { String from = req.getParameter("from"); // resolve a name to Item - Item src = Jenkins.getInstance().getItem(from, parent); + Item src = Jenkins.get().getItem(from, parent); if(src==null) { if(Util.fixEmpty(from)==null) throw new Failure("Specify which job to copy"); @@ -255,7 +255,7 @@ public abstract class ItemGroupMixIn { add(result); ItemListener.fireOnCopied(src,result); - Jenkins.getInstance().rebuildDependencyGraphAsync(); + Jenkins.get().rebuildDependencyGraphAsync(); return result; } @@ -263,7 +263,7 @@ public abstract class ItemGroupMixIn { public synchronized TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException { acl.checkPermission(Item.CREATE); - Jenkins.getInstance().getProjectNamingStrategy().checkName(name); + Jenkins.get().getProjectNamingStrategy().checkName(name); Items.verifyItemDoesNotAlreadyExist(parent, name, null); // place it as config.xml @@ -272,7 +272,7 @@ public abstract class ItemGroupMixIn { dir.mkdirs(); boolean success = false; try { - XMLUtils.safeTransform((Source)new StreamSource(xml), new StreamResult(configXml)); + XMLUtils.safeTransform(new StreamSource(xml), new StreamResult(configXml)); // load it TopLevelItem result = Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable() { @@ -287,19 +287,13 @@ public abstract class ItemGroupMixIn { add(result); ItemListener.fireOnCreated(result); - Jenkins.getInstance().rebuildDependencyGraphAsync(); + Jenkins.get().rebuildDependencyGraphAsync(); return result; - } catch (TransformerException e) { + } catch (TransformerException | SAXException e) { success = false; throw new IOException("Failed to persist config.xml", e); - } catch (SAXException e) { - success = false; - throw new IOException("Failed to persist config.xml", e); - } catch (IOException e) { - success = false; - throw e; - } catch (RuntimeException e) { + } catch (IOException | RuntimeException e) { success = false; throw e; } finally { @@ -316,14 +310,14 @@ public abstract class ItemGroupMixIn { type.checkApplicableIn(parent); acl.getACL().checkCreatePermission(parent, type); - Jenkins.getInstance().getProjectNamingStrategy().checkName(name); + Jenkins.get().getProjectNamingStrategy().checkName(name); Items.verifyItemDoesNotAlreadyExist(parent, name, null); TopLevelItem item = type.newInstance(parent, name); item.onCreatedFromScratch(); item.save(); add(item); - Jenkins.getInstance().rebuildDependencyGraphAsync(); + Jenkins.get().rebuildDependencyGraphAsync(); if (notify) ItemListener.fireOnCreated(item); diff --git a/core/src/main/java/hudson/model/ItemVisitor.java b/core/src/main/java/hudson/model/ItemVisitor.java index bfd2bbc64d04c2a4e9d6fd0202ef54f49bbd73f7..e33a6849b0eda62066717fa8a0bc9c0a1344fafb 100644 --- a/core/src/main/java/hudson/model/ItemVisitor.java +++ b/core/src/main/java/hudson/model/ItemVisitor.java @@ -56,6 +56,6 @@ public abstract class ItemVisitor { * To walk a subtree, call {@link #onItemGroup(ItemGroup)} or {@link #onItem(Item)} */ public final void walk() { - onItemGroup(Jenkins.getInstance()); + onItemGroup(Jenkins.get()); } } diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java index d0a26238a3d3f71a650c631ab5ca2caec3a901b3..b2d3a0e7d7bd5e1a581e969c9ba5c1cb6eec8378 100644 --- a/core/src/main/java/hudson/model/Items.java +++ b/core/src/main/java/hudson/model/Items.java @@ -69,7 +69,7 @@ public class Items { * Use {@link #all()} for read access and {@link Extension} for registration. */ @Deprecated - public static final List LIST = (List)new DescriptorList(TopLevelItem.class); + public static final List LIST = (List) new DescriptorList<>(TopLevelItem.class); /** * Used to behave differently when loading posted configuration as opposed to persisted configuration. @@ -153,7 +153,7 @@ public class Items { * Returns all the registered {@link TopLevelItemDescriptor}s. */ public static DescriptorExtensionList all() { - return Jenkins.getInstance().getDescriptorList(TopLevelItem.class); + return Jenkins.get().getDescriptorList(TopLevelItem.class); } /** @@ -173,13 +173,13 @@ public class Items { * @since 1.607 */ public static List all(Authentication a, ItemGroup c) { - List result = new ArrayList(); + List result = new ArrayList<>(); ACL acl; if (c instanceof AccessControlled) { acl = ((AccessControlled) c).getACL(); } else { // fall back to root - acl = Jenkins.getInstance().getACL(); + acl = Jenkins.get().getACL(); } for (TopLevelItemDescriptor d: all()) { if (acl.hasCreatePermission(a, c, d) && d.isApplicableIn(c)) { @@ -222,9 +222,9 @@ public class Items { * Does the opposite of {@link #toNameList(Collection)}. */ public static List fromNameList(ItemGroup context, @Nonnull String list, @Nonnull Class type) { - final Jenkins jenkins = Jenkins.getInstance(); + final Jenkins jenkins = Jenkins.get(); - List r = new ArrayList(); + List r = new ArrayList<>(); if (jenkins == null) { return r; } @@ -250,7 +250,7 @@ public class Items { String[] c = context.getFullName().split("/"); String[] p = path.split("/"); - Stack name = new Stack(); + Stack name = new Stack<>(); for (int i=0; i newValue = new ArrayList(); + List newValue = new ArrayList<>(); while(tokens.hasMoreTokens()) { String relativeName = tokens.nextToken().trim(); String canonicalName = getCanonicalName(context, relativeName); @@ -400,14 +400,14 @@ public class Items { * @since 1.512 */ public static List getAllItems(final ItemGroup root, Class type) { - List r = new ArrayList(); + List r = new ArrayList<>(); getAllItems(root, type, r); return r; } private static void getAllItems(final ItemGroup root, Class type, List r) { - List items = new ArrayList(((ItemGroup) root).getItems()); + List items = new ArrayList<>(((ItemGroup) root).getItems()); // because we add items depth first, we can use the quicker BY_NAME comparison - Collections.sort(items, BY_NAME); + items.sort(BY_NAME); for (Item i : items) { if (type.isInstance(i)) { if (i.hasPermission(Item.READ)) { @@ -466,11 +466,11 @@ public class Items { */ public static @CheckForNull T findNearest(Class type, String name, ItemGroup context) { List names = new ArrayList<>(); - for (T item: Jenkins.getInstance().allItems(type)) { + for (T item: Jenkins.get().allItems(type)) { names.add(item.getRelativeNameFrom(context)); } String nearest = EditDistance.findNearest(name, names); - return Jenkins.getInstance().getItem(nearest, context, type); + return Jenkins.get().getItem(nearest, context, type); } /** diff --git a/core/src/main/java/hudson/model/JDK.java b/core/src/main/java/hudson/model/JDK.java index abcb1a9f74d039af0fce62acac8e52b89da2effe..2aaf9463d55334cf8259f9838dc5e35675fe6d96 100644 --- a/core/src/main/java/hudson/model/JDK.java +++ b/core/src/main/java/hudson/model/JDK.java @@ -39,7 +39,6 @@ import hudson.util.XStream2; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.List; import java.util.Arrays; @@ -82,7 +81,7 @@ public final class JDK extends ToolInstallation implements NodeSpecific, En private transient String javaHome; public JDK(String name, String javaHome) { - super(name, javaHome, Collections.>emptyList()); + super(name, javaHome, Collections.emptyList()); } @DataBoundConstructor @@ -164,9 +163,7 @@ public final class JDK extends ToolInstallation implements NodeSpecific, En TaskListener listener = new StreamTaskListener(new NullStream()); Launcher launcher = n.createLauncher(listener); return launcher.launch().cmds("java","-fullversion").stdout(listener).join()==0; - } catch (IOException e) { - return false; - } catch (InterruptedException e) { + } catch (IOException | InterruptedException e) { return false; } } @@ -179,17 +176,17 @@ public final class JDK extends ToolInstallation implements NodeSpecific, En } public @Override JDK[] getInstallations() { - return Jenkins.getInstance().getJDKs().toArray(new JDK[0]); + return Jenkins.get().getJDKs().toArray(new JDK[0]); } public @Override void setInstallations(JDK... jdks) { - Jenkins.getInstance().setJDKs(Arrays.asList(jdks)); + Jenkins.get().setJDKs(Arrays.asList(jdks)); } @Override public List getDefaultInstallers() { try { - Class jdkInstallerClass = Jenkins.getInstance().getPluginManager() + Class jdkInstallerClass = Jenkins.get().getPluginManager() .uberClassLoader.loadClass("hudson.tools.JDKInstaller").asSubclass(ToolInstaller.class); Constructor constructor = jdkInstallerClass.getConstructor(String.class, boolean.class); return Collections.singletonList(constructor.newInstance(null, false)); diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 4b149034bd825bf448e8b6aad5416a8afd748b12..90fb09fac61fe82ac16c5610786d386aff275a9e 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -29,7 +29,6 @@ import hudson.EnvVars; import hudson.Extension; import hudson.ExtensionPoint; import hudson.FeedAdapter; -import hudson.FilePath; import hudson.PermalinkList; import hudson.Util; import hudson.cli.declarative.CLIResolver; @@ -67,7 +66,6 @@ import java.awt.Color; import java.awt.Paint; import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -177,7 +175,7 @@ public abstract class Job, RunT extends Run> properties = new CopyOnWriteList>(); + protected CopyOnWriteList> properties = new CopyOnWriteList<>(); @Restricted(NoExternalUse.class) public transient RunIdMigrator runIdMigrator; @@ -205,7 +203,7 @@ public abstract class Job, RunT extends Run, RunT extends Run, RunT extends Run>(); + properties = new CopyOnWriteList<>(); for (JobProperty p : properties) p.setOwner(this); @@ -597,14 +596,14 @@ public abstract class Job, RunT extends Run getOverrides() { - List r = new ArrayList(); + List r = new ArrayList<>(); for (JobProperty p : properties) r.addAll(p.getJobOverrides()); return r; } public List getWidgets() { - ArrayList r = new ArrayList(); + ArrayList r = new ArrayList<>(); r.add(createHistoryWidget()); return r; } @@ -683,7 +682,7 @@ public abstract class Job, RunT extends Run, RunT extends Run getBuilds() { - return RunList.fromRuns(_getRuns().values()); + return RunList.fromRuns(_getRuns().values()); } /** @@ -743,7 +742,7 @@ public abstract class Job, RunT extends Run getBuilds(RangeSet rs) { - List builds = new LinkedList(); + List builds = new LinkedList<>(); for (Range r : rs.getRanges()) { for (RunT b = getNearestBuild(r.start); b!=null && b.getNumber(), RunT extends Run getBuildsAsMap() { - return Collections.unmodifiableSortedMap(_getRuns()); + return Collections.unmodifiableSortedMap(_getRuns()); } /** @@ -999,7 +998,7 @@ public abstract class Job, RunT extends Run getLastBuildsOverThreshold(int numberOfBuilds, Result threshold) { - List result = new ArrayList(numberOfBuilds); + List result = new ArrayList<>(numberOfBuilds); RunT r = getLastBuild(); while (r != null && result.size() < numberOfBuilds) { @@ -1023,7 +1022,7 @@ public abstract class Job, RunT extends Run getEstimatedDurationCandidates() { - List candidates = new ArrayList(3); + List candidates = new ArrayList<>(3); RunT lastSuccessful = getLastSuccessfulBuild(); int lastSuccessfulNumber = -1; if (lastSuccessful != null) { @@ -1033,7 +1032,7 @@ public abstract class Job, RunT extends Run fallbackCandidates = new ArrayList(3); + List fallbackCandidates = new ArrayList<>(3); while (r != null && candidates.size() < 3 && i < 6) { if (!r.isBuilding() && r.getResult() != null && r.getNumber() != lastSuccessfulNumber) { Result result = r.getResult(); @@ -1105,7 +1104,7 @@ public abstract class Job, RunT extends Run entries = new ArrayList(); + List entries = new ArrayList<>(); String scmDisplayName = ""; if (this instanceof SCMTriggerItem) { SCMTriggerItem scmItem = (SCMTriggerItem) this; @@ -1201,7 +1200,7 @@ public abstract class Job, RunT extends Run getBuildHealthReports() { - List reports = new ArrayList(); + List reports = new ArrayList<>(); RunT lastBuild = getLastBuild(); if (lastBuild != null && lastBuild.isBuilding()) { @@ -1214,7 +1213,7 @@ public abstract class Job, RunT extends Run, RunT extends Run(reports); + cachedBuildHealthReports = new ArrayList<>(reports); } return reports; @@ -1334,7 +1333,7 @@ public abstract class Job, RunT extends Run, JobPropertyDescriptor> t = new DescribableList, JobPropertyDescriptor>(NOOP,getAllProperties()); + DescribableList, JobPropertyDescriptor> t = new DescribableList<>(NOOP, getAllProperties()); JSONObject jsonProperties = json.optJSONObject("properties"); if (jsonProperties != null) { t.rebuild(req,jsonProperties,JobPropertyDescriptor.getPropertyDescriptors(Job.this.getClass())); @@ -1352,7 +1351,7 @@ public abstract class Job, RunT extends Run, RunT extends Run data = new DataSetBuilder(); + DataSetBuilder data = new DataSetBuilder<>(); for (Run r : getNewBuilds()) { if (r.isBuilding()) continue; @@ -1604,7 +1603,7 @@ public abstract class Job, RunT extends Run> implements ReconfigurableD */ @Override public JobPropertyDescriptor getDescriptor() { - return (JobPropertyDescriptor) Jenkins.getInstance().getDescriptorOrDie(getClass()); + return (JobPropertyDescriptor) Jenkins.get().getDescriptorOrDie(getClass()); } /** diff --git a/core/src/main/java/hudson/model/JobPropertyDescriptor.java b/core/src/main/java/hudson/model/JobPropertyDescriptor.java index 6955dff091ccc1c5ad12a9a5c9d536b17694a81b..b024f56865e9451e63a102649df0829e0be35bcf 100644 --- a/core/src/main/java/hudson/model/JobPropertyDescriptor.java +++ b/core/src/main/java/hudson/model/JobPropertyDescriptor.java @@ -99,7 +99,7 @@ public abstract class JobPropertyDescriptor extends Descriptor> { * Gets the {@link JobPropertyDescriptor}s applicable for a given job type. */ public static List getPropertyDescriptors(Class clazz) { - List r = new ArrayList(); + List r = new ArrayList<>(); for (JobPropertyDescriptor p : all()) if(p.isApplicable(clazz)) r.add(p); @@ -107,6 +107,6 @@ public abstract class JobPropertyDescriptor extends Descriptor> { } public static Collection all() { - return (Collection) Jenkins.getInstance().getDescriptorList(JobProperty.class); + return (Collection) Jenkins.get().getDescriptorList(JobProperty.class); } } diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index 606068104e3f3ecf925343c70973908d0183eb53..41c55dc11094d8ca7ad75aba88654e820524bf0f 100644 --- a/core/src/main/java/hudson/model/Label.java +++ b/core/src/main/java/hudson/model/Label.java @@ -110,7 +110,7 @@ public abstract class Label extends Actionable implements Comparable