diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab12568a0a3b58e3b1789651e0d0f0d3343556df..4311a35ac21abf137bfc69b9415f3f62bb49f8d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ This page provides information about contributing code to the Jenkins core codebase. -:exclamation: There's a lot more to the Jenkins project than just code. For information on contributing to the Jenkins project overall, check out https://jenkins.io/participate/. +:exclamation: There's a lot more to the Jenkins project than just code. For information on contributing to the Jenkins project overall, check out [Participate]. ## Getting started @@ -10,32 +10,32 @@ This page provides information about contributing code to the Jenkins core codeb 2. Clone the forked repository to your machine 3. Install the development tools. In order to develop Jenkins, you need the following tools: * Java Development Kit (JDK) 8. - - In Jenkins project we usually use [OpenJDK](http://openjdk.java.net/), + - In Jenkins project we usually use [OpenJDK], but you can use other JDKs as well. - - Java 9 is **not supported** in Jenkins. - * Maven 3.5.3 or above. You can download it [here](https://maven.apache.org/download.cgi) - * Any IDE which supports importing Maven projects -4. Setup your development environment as described in [Preparing for Plugin Development](https://jenkins.io/doc/developer/tutorial/prepare/) + - Java 9+ is **not supported** in Jenkins. + * Maven 3.5.3 or above. You can [download maven]. + * Any IDE which supports importing Maven projects. +4. Setup your development environment as described in [Preparing for Plugin Development] If you want to contribute to Jenkins or just learn about the project, you can start by fixing some easier issues. In the Jenkins issue tracker we mark such issues as `newbie-friendly`. You can find them -using [this query](https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly)). +using this query for [newbie friendly issues]. ## Building and Debugging The build flow for Jenkins core is built around Maven. -Building and debugging process is described [here](https://jenkins.io/doc/developer/building/). +There is a description of the [building and debugging process]. If you want simply to have the `jenkins.war` file as fast as possible without tests, run: mvn clean package -pl war -am -DskipTests -Dfindbugs.skip The WAR file will be created in `war/target/jenkins.war`. -After that you can start Jenkins using Java CLI ([guide](https://wiki.jenkins.io/display/JENKINS/Starting+and+Accessing+Jenkins)). +After that you can start Jenkins using Java CLI ([guide]). If you want to debug this WAR file without using Maven plugins, -You can just start the executable with [Remote Debug Flags](https://stackoverflow.com/questions/975271/remote-debugging-a-java-application) +You can just start the executable with [Remote Debug Flags] and then attach IDE Debugger to it. ## Testing changes @@ -53,12 +53,12 @@ There are 3 profiles for tests: * `all-tests` - Runs all tests, with re-run (default) In addition to the included tests, you can also find extra integration and UI -tests in the [Acceptance Test Harness (ATH)](https://github.com/jenkinsci/acceptance-test-harness) repository. +tests in the [Acceptance Test Harness (ATH)] repository. If you propose complex UI changes, you should create new ATH tests for them. ## Proposing Changes -The Jenkins project source code repositories are hosted GitHub. +The Jenkins project source code repositories are hosted at GitHub. All proposed changes are submitted and code reviewed using the _GitHub Pull Request_ process. To submit a pull request: @@ -69,8 +69,8 @@ It is a good practice is to create branches instead of pushing to master. 3. Select `jenkinsci` as _base fork_ and `master` as `base`, then click _Create Pull Request_ * We integrate all changes into the master branch towards the Weekly releases * After that the changes may be backported to the current LTS baseline by the LTS Team. - The backporting process is described [here](https://jenkins.io/download/lts/). -4. Fill in the Pull Request description according to the [proposed template](.github/PULL_REQUEST_TEMPLATE.md). + Read more about the [backporting process] +4. Fill in the Pull Request description according to the [proposed template]. 5. Click _Create Pull Request_ 6. Wait for CI results/reviews, process the feedback. * If you do not get feedback after 3 days, feel free to ping `@jenkinsci/code-reviewers` to CC. @@ -83,25 +83,25 @@ There is no additional action required from pull request authors at this point. ## Copyright -Jenkins core is licensed under [MIT license](./LICENSE.txt), with a few exceptions in bundled classes. +Jenkins core is licensed under [MIT license], with a few exceptions in bundled classes. We consider all contributions as MIT unless it's explicitly stated otherwise. MIT-incompatible code contributions will be rejected. Contributions under MIT-compatible licenses may be also rejected if they are not ultimately necessary. -We **Do NOT** require pull request submitters to sign the [contributor agreement](https://wiki.jenkins.io/display/JENKINS/Copyright+on+source+code) +We **Do NOT** require pull request submitters to sign the [contributor agreement] as long as the code is licensed under MIT and merged by one of the contributors with the signed agreement. We still encourage people to sign the contributor agreement if they intend to submit more than a few pull requests. Signing is also a mandatory prerequisite for getting merge/push permissions to core repositories -and for joining teams like [Jenkins Security Team](https://jenkins.io/security/#team). +and for joining teams like [Jenkins Security Team]. ## Continuous Integration The Jenkins project has a Continuous Integration server... powered by Jenkins, of course. -It is located at [ci.jenkins.io](https://ci.jenkins.io/). +It is located at [ci.jenkins.io]. -The Jenkins project uses [Jenkins Pipeline](https://jenkins.io/doc/book/pipeline/) to run builds. -The code for the core build flow is stored in the [Jenkinsfile](./Jenkinsfile) in the repository root. +The Jenkins project uses [Jenkins Pipeline] to run builds. +The code for the core build flow is stored in the [Jenkinsfile] in the repository root. If you want to update that build flow (e.g. "add more checks"), just submit a pull request. @@ -112,4 +112,20 @@ just submit a pull request. * [Beginners Guide To Contributing](https://wiki.jenkins.io/display/JENKINS/Beginners+Guide+to+Contributing) * [List of newbie-friendly issues in the core](https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly)) - +[download maven]: https://maven.apache.org/download.cgi +[Preparing for Plugin Development]: https://jenkins.io/doc/developer/tutorial/prepare/ +[newbie friendly issues]: https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly) +[OpenJDK]: http://openjdk.java.net/ +[Participate]: https://jenkins.io/participate/ +[building and debugging process]: https://jenkins.io/doc/developer/building/ +[guide]: https://wiki.jenkins.io/display/JENKINS/Starting+and+Accessing+Jenkins +[Remote Debug Flags]: https://stackoverflow.com/questions/975271/remote-debugging-a-java-application +[Acceptance Test Harness (ATH)]: https://github.com/jenkinsci/acceptance-test-harness +[backporting process]: https://jenkins.io/download/lts/ +[proposed template]: .github/PULL_REQUEST_TEMPLATE.md +[MIT license]: ./LICENSE.txt +[contributor agreement]: https://wiki.jenkins.io/display/JENKINS/Copyright+on+source+code +[Jenkins Security Team]: https://jenkins.io/security/#team +[ci.jenkins.io]: https://ci.jenkins.io/ +[Jenkins Pipeline]: https://jenkins.io/doc/book/pipeline/ +[Jenkinsfile]: ./Jenkinsfile \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index f6eb3e2c0cfdc06c586ea5c1a1a46689b9811957..f6b139aaec9d2ce37672dc1a977fda58decf8ab5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,11 +13,11 @@ 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'] -def jdks = [8] +def jdks = [8, 11] def builds = [:] for(i = 0; i < buildTypes.size(); i++) { @@ -49,7 +49,7 @@ for(j = 0; j < jdks.size(); j++) { if(isUnix()) { sh mvnCmd - sh 'test `git status --short | tee /dev/stderr | wc --bytes` -eq 0' + sh 'git add . && git diff --exit-code HEAD' } else { bat mvnCmd } @@ -61,12 +61,13 @@ for(j = 0; j < jdks.size(); j++) { stage("${buildType} Publishing") { if (runTests) { 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*", - excludes: '**/*.lastUpdated,**/jenkins-test/', + excludes: '**/*.lastUpdated,**/jenkins-test*/', allowEmptyArchive: true, // in case we forgot to reincrementalify fingerprint: true } diff --git a/README.md b/README.md index 6dc6c47ecef715c771fcfdeb9ccd83cc45cf4962..7ef6c08a8ec40120d755931ce679e5ca870c5bda 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Non-source downloads such as WAR files and several Linux packages can be found o Our latest and greatest source of Jenkins can be found on [GitHub]. Fork us! # Contributing to Jenkins -Follow [contributing](CONTRIBUTING.md) file. +Follow the [contributing](CONTRIBUTING.md) file. # News and Website All information about Jenkins can be found on our [website]. Follow us on Twitter [@jenkinsci]. diff --git a/cli/pom.xml b/cli/pom.xml index 670e416a26dd5c5f5bc6943ad046e9f8c4c41c00..b02df3123ec17c21c9e7c5d8e12d44a9e6eec3ad 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -58,7 +58,7 @@ org.jvnet.localizer localizer - 1.24 + 1.26 org.apache.sshd @@ -66,6 +66,12 @@ 1.7.0 true + + + net.i2p.crypto + eddsa + 0.3.0 + org.slf4j slf4j-jdk14 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..64a91d5ca56382027ae6b0259e00bf769d4a6223 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\ + \ 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/test/java/hudson/cli/ConnectionMockTest.java b/cli/src/test/java/hudson/cli/ConnectionMockTest.java deleted file mode 100644 index 77b319a4d249ee6856bdc60b66e5bd217c3829af..0000000000000000000000000000000000000000 --- a/cli/src/test/java/hudson/cli/ConnectionMockTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2013 Ericsson - * - * 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.FastPipedInputStream; -import hudson.remoting.FastPipedOutputStream; - -import java.io.DataInputStream; -import java.io.IOException; - -import static org.junit.Assert.*; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.powermock.api.mockito.PowerMockito.*; - -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -/** - * @author marco.miller@ericsson.com - */ -@RunWith(PowerMockRunner.class) -@PrepareForTest({Connection.class}) -public class ConnectionMockTest { - - @Test - public void shouldTolerateEmptyByteArrayUponStreamZeroValue() throws IOException { - DataInputStream din = mock(DataInputStream.class); - //when(din.readInt()).thenReturn(0) does not work; mock always return 0 for some TBD reason - Connection c = new Connection(din, new FastPipedOutputStream(new FastPipedInputStream())); - assertTrue(c.readByteArray().length == 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/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/pom.xml b/core/pom.xml index cbbf3f30616771fbd074ee27f7f709e815ef576c..388f0a7930733d97e6c564d601511bcc3e01f9ca 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,7 +39,7 @@ THE SOFTWARE. true - 1.255 + 1.256 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 @@ -119,7 +119,7 @@ THE SOFTWARE. org.jenkins-ci trilead-ssh2 - build-217-jenkins-11 + build-217-jenkins-14 org.kohsuke.stapler @@ -177,6 +177,11 @@ THE SOFTWARE. tests test + + io.jenkins.stapler + jenkins-stapler-support + 1.0 + org.hamcrest hamcrest-library @@ -221,7 +226,7 @@ THE SOFTWARE. org.jvnet.localizer localizer - 1.24 + 1.26 antlr @@ -641,7 +646,6 @@ THE SOFTWARE. org.codehaus.mojo build-helper-maven-plugin - 1.7 add-source @@ -774,7 +778,7 @@ THE SOFTWARE. com.sun.winsw winsw - 2.1.2 + 2.2.0 bin exe ${project.build.outputDirectory}/windows-service 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..1d93d3fb249cce72a88725bab80d4b9ef523d768 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. */ @@ -201,7 +192,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 +218,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(",")) { @@ -266,32 +257,16 @@ public class ClassicPluginStrategy implements PluginStrategy { if (jenkinsVersion==null) jenkinsVersion = atts.getValue("Hudson-Version"); - optionalDependencies.addAll(getImpliedDependencies(pluginName, jenkinsVersion)); + optionalDependencies.addAll(DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion)); } - + /** - * 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 +294,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. * @@ -483,7 +324,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 +336,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 +578,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 +644,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 +705,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..fcb3fe33bb05fdffb9bfd7cf54adde74f0e6e51b 100644 --- a/core/src/main/java/hudson/DNSMultiCast.java +++ b/core/src/main/java/hudson/DNSMultiCast.java @@ -32,7 +32,7 @@ public class DNSMultiCast implements Closeable { try { jmdns = JmDNS.create(); - Map props = new HashMap(); + Map props = new HashMap<>(); String rootURL = jenkins.getRootUrl(); if (rootURL==null) return null; diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java index dd334db858be47f551cf25a897d37b11d095fae5..5e9d35f7ec4a3c87f0400bd9737361a7dda6cf7e 100644 --- a/core/src/main/java/hudson/DescriptorExtensionList.java +++ b/core/src/main/java/hudson/DescriptorExtensionList.java @@ -76,7 +76,7 @@ public class DescriptorExtensionList, D extends Descrip if (describableType == (Class) Publisher.class) { return (DescriptorExtensionList) new Publisher.DescriptorExtensionListImpl(jenkins); } - return new DescriptorExtensionList(jenkins,describableType); + return new DescriptorExtensionList<>(jenkins, describableType); } /** @@ -161,13 +161,13 @@ public class DescriptorExtensionList, D extends Descrip @Override public boolean add(D d) { boolean r = super.add(d); - hudson.getExtensionList(Descriptor.class).add(d); + getDescriptorExtensionList().add(d); return r; } @Override public boolean remove(Object o) { - hudson.getExtensionList(Descriptor.class).remove(o); + getDescriptorExtensionList().remove(o); return super.remove(o); } @@ -176,7 +176,8 @@ public class DescriptorExtensionList, D extends Descrip */ @Override protected Object getLoadLock() { - return this; + // Get a lock for the singleton extension list to prevent deadlocks (JENKINS-55361) + return getDescriptorExtensionList().getLoadLock(); } /** @@ -189,7 +190,7 @@ public class DescriptorExtensionList, D extends Descrip LOGGER.log(Level.WARNING, "Cannot load extension components, because Jenkins instance has not been assigned yet"); return Collections.emptyList(); } - return _load(jenkins.getExtensionList(Descriptor.class).getComponents()); + return _load(getDescriptorExtensionList().getComponents()); } @Override @@ -198,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 { @@ -211,6 +212,10 @@ public class DescriptorExtensionList, D extends Descrip return r; } + private ExtensionList getDescriptorExtensionList() { + return ExtensionList.lookup(Descriptor.class); + } + /** * Stores manually registered Descriptor instances. Keyed by the {@link Describable} type. */ diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java index cd553f3aba01ea325c58912b990d88aab9cb5e66..8caaebf31290c23a676ceadc9cee171106b86650 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, @@ -88,7 +89,7 @@ public class EnvVars extends TreeMap { /** * Gets the platform for which these env vars targeted. - * @since TODO + * @since 2.144 * @return The platform. */ public @CheckForNull Platform getPlatform() { @@ -97,7 +98,7 @@ public class EnvVars extends TreeMap { /** * Sets the platform for which these env vars target. - * @since TODO + * @since 2.144 * @param platform the platform to set. */ public void setPlatform(@Nonnull Platform platform) { @@ -199,7 +200,7 @@ public class EnvVars extends TreeMap { } public void clear() { - referredVariables = new TreeSet(comparator); + referredVariables = new TreeSet<>(comparator); } public String resolve(String name) { @@ -225,8 +226,8 @@ public class EnvVars extends TreeMap { } return refereeSetMap.get(n); } - }; - + } + private final Comparator comparator; @Nonnull @@ -287,8 +288,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 +327,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..9e9c18f63b53749471149199d9d5003b41b9b26b 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; 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/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 9452064e8105e124c0e62328f3310c03765b4ff7..e2a4b30b8cd76c6c68f5018d0161273a540ab540 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -59,7 +59,6 @@ import hudson.util.NamingThreadFactory; import hudson.util.io.Archiver; import hudson.util.io.ArchiverFactory; -import static java.util.logging.Level.FINE; import java.io.BufferedOutputStream; import java.io.File; @@ -80,9 +79,10 @@ 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.InvalidPathException; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.LinkOption; import java.nio.file.StandardCopyOption; @@ -132,13 +132,12 @@ import org.jenkinsci.remoting.RoleChecker; import org.jenkinsci.remoting.RoleSensitive; 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.deleteFile; import static hudson.Util.fileToPath; import static hudson.Util.fixEmpty; -import static hudson.Util.isSymlink; import java.util.Collections; import org.apache.tools.ant.BuildException; @@ -326,7 +325,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); @@ -387,7 +386,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("\\"); } /** @@ -638,14 +637,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()); } @@ -1063,7 +1061,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); } @@ -1138,7 +1136,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); } @@ -1269,7 +1267,7 @@ public final class FilePath implements Serializable { private static final long serialVersionUID = 1L; @Override public Void invoke(File f, VirtualChannel channel) throws IOException { - deleteRecursive(deleting(f)); + Util.deleteRecursive(fileToPath(f), path -> deleting(path.toFile())); return null; } } @@ -1284,34 +1282,11 @@ public final class FilePath implements Serializable { private static final long serialVersionUID = 1L; @Override public Void invoke(File f, VirtualChannel channel) throws IOException { - deleteContentsRecursive(f); + Util.deleteContentsRecursive(fileToPath(f), path -> deleting(path.toFile())); return null; } } - private void deleteRecursive(File dir) throws IOException { - if(!isSymlink(dir)) - deleteContentsRecursive(dir); - try { - deleteFile(deleting(dir)); - } catch (IOException e) { - // if some of the child directories are big, it might take long enough to delete that - // it allows others to create new files, causing problems like JENKINS-10113 - // so give it one more attempt before we give up. - if(!isSymlink(dir)) - deleteContentsRecursive(dir); - deleteFile(deleting(dir)); - } - } - - private void deleteContentsRecursive(File file) throws IOException { - File[] files = file.listFiles(); - if(files==null) - return; // the directory didn't exist in the first place - for (File child : files) - deleteRecursive(child); - } - /** * Gets the file name portion except the extension. * @@ -1621,11 +1596,7 @@ public final class FilePath implements Serializable { @Override public Void invoke(File f, VirtualChannel channel) throws IOException { if(!f.exists()) { - try { - Files.newOutputStream(creating(f).toPath()).close(); - } catch (InvalidPathException e) { - throw new IOException(e); - } + Files.newOutputStream(fileToPath(creating(f))).close(); } if(!stating(f).setLastModified(timestamp)) throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp); @@ -1868,7 +1839,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)); @@ -1959,8 +1930,7 @@ public final class FilePath implements Serializable { } catch (BuildException x) { throw new IOException(x.getMessage()); } - String[] files = ds.getIncludedFiles(); - return files; + return ds.getIncludedFiles(); } /** @@ -1968,11 +1938,7 @@ public final class FilePath implements Serializable { */ public InputStream read() throws IOException, InterruptedException { if(channel==null) { - try { - return Files.newInputStream(reading(new File(remote)).toPath()); - } catch (InvalidPathException e) { - throw new IOException(e); - } + return Files.newInputStream(fileToPath(reading(new File(remote)))); } final Pipe p = Pipe.createRemoteToLocal(); @@ -1988,11 +1954,9 @@ public final class FilePath implements Serializable { } @Override public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - try (InputStream fis = Files.newInputStream(reading(f).toPath()); + try (InputStream fis = Files.newInputStream(fileToPath(reading(f))); OutputStream out = p.getOut()) { org.apache.commons.io.IOUtils.copy(fis, out); - } catch (InvalidPathException e) { - p.error(new IOException(e)); } catch (Exception x) { p.error(x); } @@ -2063,11 +2027,16 @@ public final class FilePath implements Serializable { } /** - * Reads this file into a string, by using the current system encoding. + * Reads this file into a string, by using the current system encoding on the remote machine. */ public String readToString() throws IOException, InterruptedException { - try (InputStream in = read()) { - return org.apache.commons.io.IOUtils.toString(in); + return act(new ReadToString()); + } + private final class ReadToString extends SecureFileCallable { + private static final long serialVersionUID = 1L; + @Override + public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { + return new String(Files.readAllBytes(fileToPath(reading(f)))); } } @@ -2090,11 +2059,7 @@ public final class FilePath implements Serializable { if(channel==null) { File f = new File(remote).getAbsoluteFile(); mkdirs(f.getParentFile()); - try { - return Files.newOutputStream(writing(f).toPath()); - } catch (InvalidPathException e) { - throw new IOException(e); - } + return Files.newOutputStream(fileToPath(writing(f))); } return act(new WritePipe()); @@ -2105,12 +2070,7 @@ public final class FilePath implements Serializable { public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { f = f.getAbsoluteFile(); mkdirs(f.getParentFile()); - try { - OutputStream fos = Files.newOutputStream(writing(f).toPath()); - return new RemoteOutputStream(fos); - } catch (InvalidPathException e) { - throw new IOException(e); - } + return new RemoteOutputStream(Files.newOutputStream(fileToPath(writing(f)))); } } @@ -2118,7 +2078,7 @@ public final class FilePath implements Serializable { * Overwrites this file by placing the given String as the content. * * @param encoding - * Null to use the platform default encoding. + * Null to use the platform default encoding on the remote machine. * @since 1.105 */ public void write(final String content, final String encoding) throws IOException, InterruptedException { @@ -2135,11 +2095,9 @@ public final class FilePath implements Serializable { @Override public Void invoke(File f, VirtualChannel channel) throws IOException { mkdirs(f.getParentFile()); - try (OutputStream fos = Files.newOutputStream(writing(f).toPath()); + try (OutputStream fos = Files.newOutputStream(fileToPath(writing(f))); Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) { w.write(content); - } catch (InvalidPathException e) { - throw new IOException(e); } return null; } @@ -2285,11 +2243,9 @@ public final class FilePath implements Serializable { } @Override public Void invoke(File f, VirtualChannel channel) throws IOException { - try (InputStream fis = Files.newInputStream(reading(f).toPath())) { + try (InputStream fis = Files.newInputStream(fileToPath(reading(f)))) { org.apache.commons.io.IOUtils.copy(fis, out); return null; - } catch (InvalidPathException e) { - throw new IOException(e); } finally { out.close(); } @@ -2405,12 +2361,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 @@ -2435,15 +2386,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; @@ -3303,36 +3259,91 @@ public final class FilePath implements Serializable { if (new File(potentialChildRelativePath).isAbsolute()) { throw new IllegalArgumentException("Only a relative path is supported, the given path is absolute: " + potentialChildRelativePath); } + + Path parentAbsolutePath = Util.fileToPath(parentFile.getAbsoluteFile()); + Path parentRealPath; + try { + if (Functions.isWindows()) { + parentRealPath = this.windowsToRealPath(parentAbsolutePath); + } else { + parentRealPath = parentAbsolutePath.toRealPath(); + } + } + catch (NoSuchFileException e) { + LOGGER.log(Level.FINE, String.format("Cannot find the real path to the parentFile: %s", parentAbsolutePath), e); + return false; + } - Path parent = parentFile.getAbsoluteFile().toPath().normalize(); - + // example: "a/b/c" that will become "b/c" then just "c", and finally an empty string String remainingPath = potentialChildRelativePath; - File currentFile = parentFile; + + Path currentFilePath = parentFile.toPath(); while (!remainingPath.isEmpty()) { - File directChild = this.getDirectChild(currentFile, remainingPath); - File childUsingFullPath = new File(currentFile, remainingPath); - remainingPath = childUsingFullPath.getAbsolutePath().substring(directChild.getAbsolutePath().length()); - - File childFileSymbolic = Util.resolveSymlinkToFile(directChild); + Path directChild = this.getDirectChild(currentFilePath, remainingPath); + Path childUsingFullPath = currentFilePath.resolve(remainingPath); + String childUsingFullPathAbs = childUsingFullPath.toAbsolutePath().toString(); + String directChildAbs = directChild.toAbsolutePath().toString(); + + if (childUsingFullPathAbs.length() == directChildAbs.length()) { + remainingPath = ""; + } else { + // +1 to avoid the last slash + remainingPath = childUsingFullPathAbs.substring(directChildAbs.length() + 1); + } + + File childFileSymbolic = Util.resolveSymlinkToFile(directChild.toFile()); if (childFileSymbolic == null) { - currentFile = directChild; + currentFilePath = directChild; } else { - currentFile = childFileSymbolic; + currentFilePath = childFileSymbolic.toPath(); + } + + Path currentFileAbsolutePath = currentFilePath.toAbsolutePath(); + 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 / 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; } } - //TODO could be refactored using Util#isDescendant(File, File) from 2.80+ - Path child = currentFile.getAbsoluteFile().toPath().normalize(); - return child.startsWith(parent); + return true; } - private @CheckForNull File getDirectChild(File parentFile, String childPath){ - File current = new File(parentFile, childPath); - while (current != null && !parentFile.equals(current.getParentFile())) { - current = current.getParentFile(); + private @CheckForNull Path getDirectChild(Path parentPath, String childPath){ + Path current = parentPath.resolve(childPath); + while (current != null && !parentPath.equals(current.getParent())) { + current = current.getParent(); } 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..901ff79a6aaa61e36cea879ce645250b27a1365a 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; diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index ef2a7671a3f777f5f6a4b6c849b40799857ae636..67e9398c231dae3367b4bcc9eba15fd1625922a2 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -465,7 +465,7 @@ public class Functions { } public static Map getSystemProperties() { - return new TreeMap(System.getProperties()); + return new TreeMap<>(System.getProperties()); } /** @@ -479,7 +479,7 @@ public class Functions { } public static Map getEnvVars() { - return new TreeMap(EnvVars.masterEnvVars); + return new TreeMap<>(EnvVars.masterEnvVars); } public static boolean isWindows() { @@ -544,7 +544,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; } @@ -1556,30 +1556,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"; } @@ -1779,6 +1762,10 @@ public class Functions { return SimplePageDecorator.first(); } + public static List getSimplePageDecorators() { + return SimplePageDecorator.all(); + } + public static List> getCloudDescriptors() { return Cloud.all(); } @@ -1827,7 +1814,7 @@ public class Functions { * from {@link ConsoleAnnotatorFactory}s and {@link ConsoleAnnotationDescriptor}s. */ public static String generateConsoleAnnotationScriptAndStylesheet() { - String cp = Stapler.getCurrentRequest().getContextPath(); + String cp = Stapler.getCurrentRequest().getContextPath() + Jenkins.RESOURCE_PATH; StringBuilder buf = new StringBuilder(); for (ConsoleAnnotatorFactory f : ConsoleAnnotatorFactory.all()) { String path = cp + "/extensionList/" + ConsoleAnnotatorFactory.class.getName() + "/" + f.getClass().getName(); @@ -2002,7 +1989,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"; @@ -2067,14 +2054,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..c6e39a27d3e8f73991f4ff2857b5202d14bbf125 100644 --- a/core/src/main/java/hudson/Launcher.java +++ b/core/src/main/java/hudson/Launcher.java @@ -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; } @@ -774,7 +774,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; } 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..107c8fbdfc1f292e3a97dcd872c10b19bfd03832 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. diff --git a/core/src/main/java/hudson/PermalinkList.java b/core/src/main/java/hudson/PermalinkList.java index 0c4a3085a6303614d78976a3daed211d082442c5..caffd8baf0fe75df22b730b559a0469e220f2f0f 100644 --- a/core/src/main/java/hudson/PermalinkList.java +++ b/core/src/main/java/hudson/PermalinkList.java @@ -60,7 +60,7 @@ public final class PermalinkList extends ArrayList { * 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/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 576c2b96af3b72c566c3337d22b8fbf99a29fa50..62c21991b3c8947b0810c4e9b0c0811c8a428b17 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -49,6 +49,7 @@ import hudson.util.CyclicGraphDetector; import hudson.util.CyclicGraphDetector.CycleDetectedException; import hudson.util.FormValidation; import hudson.util.PersistedList; +import hudson.util.Retrier; import hudson.util.Service; import hudson.util.VersionNumber; import hudson.util.XStream2; @@ -61,6 +62,7 @@ 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.util.SystemProperties; import jenkins.util.io.OnMaster; @@ -177,6 +179,31 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas /** Custom plugin manager system property or context param. */ public static final String CUSTOM_PLUGIN_MANAGER = PluginManager.class.getName() + ".className"; + private static final Logger LOGGER = Logger.getLogger(PluginManager.class.getName()); + + /** + * Time elapsed between retries to check the updates sites. It's kind of constant, but let it so for tests + */ + /* private final */ static int CHECK_UPDATE_SLEEP_TIME_MILLIS; + + /** + * Number of attempts to check the updates sites. It's kind of constant, but let it so for tests + */ + /* private final */ static int CHECK_UPDATE_ATTEMPTS; + + static { + try { + // Secure initialization + CHECK_UPDATE_SLEEP_TIME_MILLIS = SystemProperties.getInteger(PluginManager.class.getName() + ".checkUpdateSleepTimeMillis", 1000); + CHECK_UPDATE_ATTEMPTS = SystemProperties.getInteger(PluginManager.class.getName() + ".checkUpdateAttempts", 1); + } catch(Exception e) { + LOGGER.warning(String.format("There was an error initializing the PluginManager. Exception: %s", e)); + } finally { + CHECK_UPDATE_ATTEMPTS = CHECK_UPDATE_ATTEMPTS > 0 ? CHECK_UPDATE_ATTEMPTS : 1; + CHECK_UPDATE_SLEEP_TIME_MILLIS = CHECK_UPDATE_SLEEP_TIME_MILLIS > 0 ? CHECK_UPDATE_SLEEP_TIME_MILLIS : 1000; + } + } + /** Accepted constructors for custom plugin manager, in the order they are tried. */ private enum PMConstructor { JENKINS { @@ -257,15 +284,21 @@ 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. */ public final File rootDir; + /** + * Hold the status of the last try to check update centers. Consumed from the check.jelly to show an + * error message if the last attempt failed. + */ + private String lastErrorCheckUpdateCenters = null; + /** * If non-null, the base directory for all exploded .hpi/.jpi plugins. Controlled by the system property / servlet * context parameter {@literal hudson.PluginManager.workDir}. @@ -389,7 +422,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() { @@ -432,7 +465,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; @@ -504,7 +537,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas 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 { @@ -527,7 +560,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)) { @@ -558,10 +591,10 @@ 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(); } }); }}); @@ -693,7 +726,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 @@ -702,7 +735,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. @@ -715,7 +748,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; } @@ -731,9 +764,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)) { @@ -748,7 +781,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; } @@ -915,7 +948,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } // Redo who depends on who. - resolveDependantPlugins(); + resolveDependentPlugins(); try { Jenkins.get().refreshExtensions(); @@ -927,40 +960,40 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } @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); } } @@ -1012,8 +1045,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()); @@ -1209,7 +1241,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); @@ -1232,7 +1264,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); @@ -1513,7 +1545,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } // Fire a one-off thread to wait for the plugins to be deployed and then - // refresh the dependant plugins list. + // refresh the dependent plugins list. new Thread() { @Override public void run() { @@ -1531,7 +1563,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } } // All the plugins are installed. It's now safe to refresh. - resolveDependantPlugins(); + resolveDependentPlugins(); break; } } @@ -1654,34 +1686,93 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Restricted(NoExternalUse.class) @RequirePOST public HttpResponse doCheckUpdatesServer() throws IOException { Jenkins.getInstance().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<>( + // the action to perform + this::checkUpdatesServer, + + // the way we know whether this attempt was right or wrong + (currentAttempt, result) -> result.kind == FormValidation.Kind.OK, + + // the action name we are trying to perform + "check updates server") + + // the number of attempts to try + .withAttempts(CHECK_UPDATE_ATTEMPTS) + + // the delay between attempts + .withDelay(CHECK_UPDATE_SLEEP_TIME_MILLIS) + + // whatever exception raised is considered as a fail attempt (all exceptions), not a failure + .withDuringActionExceptions(new Class[] {Exception.class}) + + // what we do with a failed attempt due to an allowed exception, return an FormValidation.error with the message + .withDuringActionExceptionListener( (attempt, e) -> FormValidation.errorWithMarkup(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage())) + + // lets get our retrier object + .build(); + try { - for (UpdateSite site : Jenkins.getInstance().getUpdateCenter().getSites()) { - FormValidation v = site.updateDirectlyNow(DownloadService.signatureCheck); - if (v.kind != FormValidation.Kind.OK) { - // TODO crude but enough for now - return v; + // Begin the process + FormValidation result = updateServerRetrier.start(); + + // Check how it went + if (!FormValidation.Kind.OK.equals(result.kind)) { + LOGGER.log(Level.SEVERE, Messages.PluginManager_UpdateSiteError(CHECK_UPDATE_ATTEMPTS, result.getMessage())); + if (CHECK_UPDATE_ATTEMPTS > 1 && !Logger.getLogger(Retrier.class.getName()).isLoggable(Level.WARNING)) { + LOGGER.log(Level.SEVERE, Messages.PluginManager_UpdateSiteChangeLogLevel(Retrier.class.getName())); } + + lastErrorCheckUpdateCenters = Messages.PluginManager_CheckUpdateServerError(result.getMessage()); + } else { + lastErrorCheckUpdateCenters = null; } - for (DownloadService.Downloadable d : DownloadService.Downloadable.all()) { - FormValidation v = d.updateNow(); - if (v.kind != FormValidation.Kind.OK) { - return v; - } + + } catch (Exception e) { + // It's never going to be reached because we declared all Exceptions in the withDuringActionExceptions, so + // whatever exception is considered a expected failed attempt and the retries continue + LOGGER.log(Level.WARNING, Messages.PluginManager_UnexpectedException(), e); + + // In order to leave this method as it was, rethrow as IOException + throw new IOException(e); + } + + // Stay in the same page in any case + return HttpResponses.forwardToPreviousPage(); + } + + private FormValidation checkUpdatesServer() throws Exception { + for (UpdateSite site : Jenkins.get().getUpdateCenter().getSites()) { + FormValidation v = site.updateDirectlyNow(DownloadService.signatureCheck); + if (v.kind != FormValidation.Kind.OK) { + // Stop with an error + return v; + } + } + for (DownloadService.Downloadable d : DownloadService.Downloadable.all()) { + FormValidation v = d.updateNow(); + if (v.kind != FormValidation.Kind.OK) { + // Stop with an error + return v; } - return HttpResponses.forwardToPreviousPage(); - } catch(RuntimeException ex) { - throw new IOException("Unhandled exception during updates server check", ex); } + return FormValidation.ok(); + } + + /** + * Returns the last error raised during the update sites checking. + * @return the last error message + */ + public String getLastErrorCheckUpdateCenters() { + return lastErrorCheckUpdateCenters; } 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); @@ -1713,13 +1804,13 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas */ public List> prevalidateConfig(InputStream configXml) throws IOException { Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); - List> jobs = new ArrayList>(); + List> jobs = new ArrayList<>(); UpdateCenter uc = Jenkins.getInstance().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()); if (pw == null) { // install new - UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey()); + UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey(), requestedPlugin.getValue()); if (toInstall == null) { LOGGER.log(WARNING, "No such plugin {0} to install", requestedPlugin.getKey()); continue; @@ -1730,9 +1821,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas if (toInstall.isForNewerHudson()) { LOGGER.log(WARNING, "{0}@{1} was built for a newer Jenkins", new Object[] {toInstall.name, toInstall.version}); } + if (toInstall.isForNewerJava()) { + LOGGER.log(WARNING, "{0}@{1} was built for a newer Java", new Object[] {toInstall.name, toInstall.version}); + } jobs.add(toInstall.deploy(true)); } else if (pw.isOlderThan(requestedPlugin.getValue())) { // upgrade - UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey()); + UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey(), requestedPlugin.getValue()); if (toInstall == null) { LOGGER.log(WARNING, "No such plugin {0} to upgrade", requestedPlugin.getKey()); continue; @@ -1747,6 +1841,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas if (toInstall.isForNewerHudson()) { LOGGER.log(WARNING, "{0}@{1} was built for a newer Jenkins", new Object[] {toInstall.name, toInstall.version}); } + if (toInstall.isForNewerJava()) { + LOGGER.log(WARNING, "{0}@{1} was built for a newer Java", new Object[] {toInstall.name, toInstall.version}); + } if (!toInstall.isCompatibleWithInstalledVersion()) { LOGGER.log(WARNING, "{0}@{1} is incompatible with the installed @{2}", new Object[] {toInstall.name, toInstall.version, pw.getVersion()}); } @@ -1801,7 +1898,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 { @@ -1841,10 +1938,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 { @@ -1881,16 +1978,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 @@ -1971,7 +2068,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))); @@ -1990,9 +2087,6 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas return "classLoader " + getClass().getName(); } } - - private static final Logger LOGGER = Logger.getLogger(PluginManager.class.getName()); - public static boolean FAST_LOOKUP = !SystemProperties.getBoolean(PluginManager.class.getName()+".noFastLookup"); public static final Permission UPLOAD_PLUGINS = new Permission(Jenkins.PERMISSIONS, "UploadPlugins", Messages._PluginManager_UploadPluginsPermission_Description(),Jenkins.ADMINISTER,PermissionScope.JENKINS); diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index e282fd365ba81506605d6c786858b9841bdb5b04..be78e2d192bf33edf03108c42bbf2b68682c8e08 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -33,8 +33,10 @@ 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; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.kohsuke.accmod.Restricted; @@ -206,12 +208,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 @@ -221,37 +223,69 @@ 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 setOptionalDependants(@Nonnull Set optionalDependants) { - this.optionalDependants = optionalDependants; + public void setOptionalDependents(@Nonnull Set optionalDependents) { + this.optionalDependents = optionalDependents; + } + + /** + * @deprecated Please use {@link setOptionalDependents}. + */ + @Deprecated + public void setOptionalDependants(@Nonnull Set optionalDependents) { + setOptionalDependents(dependents); } /** * Get the list of components that depend on this plugin. * @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(); + } + /** * @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(); } /** @@ -259,8 +293,16 @@ public class PluginWrapper implements Comparable, ModelObject { * @return {@code true} if something (Jenkins core, or another plugin) depends on this * plugin, otherwise {@code false}. */ + public boolean hasDependents() { + return (isBundled || !dependents.isEmpty()); + } + + /** + * @deprecated Please use {@link hasDependents}. + */ + @Deprecated public boolean hasDependants() { - return (isBundled || !dependants.isEmpty()); + return hasDependents(); } /** @@ -268,10 +310,17 @@ 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. @@ -495,6 +544,20 @@ public class PluginWrapper implements Comparable, ModelObject { return null; } + /** + * Returns the minimum Java version of this plugin, as specified in the plugin metadata. + * Generally coming from the java.level extracted as MANIFEST's metadata with + * this addition on the plugins' parent pom. + * + * @see maven-hpi-plugin#PR-75. + * + * @since TODO + */ + @Exported + public @CheckForNull String getMinimumJavaVersion() { + return manifest.getMainAttributes().getValue("Minimum-Java-Version"); + } + /** * Returns the version number of this plugin */ @@ -581,7 +644,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); @@ -592,55 +655,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)); @@ -649,30 +712,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; } /** @@ -745,6 +808,14 @@ public class PluginWrapper implements Comparable, ModelObject { versionDependencyError(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion), Jenkins.getVersion().toString(), requiredCoreVersion); } } + + String minimumJavaVersion = getMinimumJavaVersion(); + if (minimumJavaVersion != null) { + JavaSpecificationVersion actualVersion = JavaUtils.getCurrentJavaRuntimeVersionNumber(); + if (actualVersion.isOlderThan(new JavaSpecificationVersion(minimumJavaVersion))) { + versionDependencyError(Messages.PluginWrapper_obsoleteJava(actualVersion.toString(), minimumJavaVersion), actualVersion.toString(), minimumJavaVersion); + } + } } // make sure dependencies exist for (Dependency d : dependencies) { @@ -832,7 +903,7 @@ public class PluginWrapper implements Comparable, ModelObject { */ public UpdateSite.Plugin getUpdateInfo() { UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); - UpdateSite.Plugin p = uc.getPlugin(getShortName()); + UpdateSite.Plugin p = uc.getPlugin(getShortName(), getVersionNumber()); if(p!=null && p.isNewerThan(getVersion())) return p; return null; } @@ -842,6 +913,8 @@ public class PluginWrapper implements Comparable, ModelObject { */ public UpdateSite.Plugin getInfo() { UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); + UpdateSite.Plugin p = uc.getPlugin(getShortName(), getVersionNumber()); + if (p != null) return p; return uc.getPlugin(getShortName()); } @@ -975,13 +1048,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; @@ -1026,12 +1099,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); } } @@ -1106,7 +1179,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..b218017522ec54823ed881355725ff79e9c94389 100644 --- a/core/src/main/java/hudson/Proc.java +++ b/core/src/main/java/hudson/Proc.java @@ -325,8 +325,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 b8ba5bdf3f2c55626bad094350771ff59561aedc..f6560c6a053328124536f5a06276fdacded6d16b 100644 --- a/core/src/main/java/hudson/ProxyConfiguration.java +++ b/core/src/main/java/hudson/ProxyConfiguration.java @@ -47,9 +47,11 @@ 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; +import jenkins.security.stapler.StaplerAccessibleType; import jenkins.util.JenkinsJVM; import jenkins.util.SystemProperties; import org.apache.commons.httpclient.Credentials; @@ -78,12 +80,13 @@ import org.kohsuke.stapler.interceptor.RequirePOST; * * @see jenkins.model.Jenkins#proxy */ +@StaplerAccessibleType public final class ProxyConfiguration extends AbstractDescribableImpl implements Saveable, Serializable { /** * Holds a default TCP connect timeout set on all connections returned from this class, * note this is value is in milliseconds, it's passed directly to {@link URLConnection#setConnectTimeout(int)} */ - private static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = SystemProperties.getInteger("hudson.ProxyConfiguration.DEFAULT_CONNECT_TIMEOUT_MILLIS", 20 * 1000); + private static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = SystemProperties.getInteger("hudson.ProxyConfiguration.DEFAULT_CONNECT_TIMEOUT_MILLIS", (int)TimeUnit.SECONDS.toMillis(20)); public final String name; public final int port; @@ -110,6 +113,10 @@ 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 : TimeUnit.SECONDS.toMillis(30)); HttpClient client = new HttpClient(); if (Util.fixEmptyAndTrim(name) != null && !isNoProxyHost(host, noProxyHost)) { diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index f0a9c037002dbbce2b2dabfaaf7ae07283b032e6..62f66b54714eea4d6a241bd9154d679a368c4004 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -28,12 +28,15 @@ 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; import jenkins.slaves.RemotingVersionInfo; import jenkins.util.SystemProperties; import hudson.slaves.OfflineCause; @@ -82,6 +85,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; * @author Kohsuke Kawaguchi * @see AgentProtocol */ +@StaplerAccessibleType public final class TcpSlaveAgentListener extends Thread { private final ServerSocketChannel serverSocket; @@ -150,6 +154,14 @@ public final class TcpSlaveAgentListener extends Thread { return StringUtils.join(Jenkins.getInstance().getAgentProtocols(), ", "); } + /** + * Gets Remoting minimum supported version to prevent unsupported agents from connecting + * @since 2.171 + */ + public VersionNumber getRemotingMinimumVersion() { + return RemotingVersionInfo.getMinimumSupportedVersion(); + } + @Override public void run() { try { @@ -234,7 +246,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 @@ -289,9 +301,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"); @@ -301,7 +312,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 { @@ -356,11 +367,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); } /** @@ -412,8 +419,10 @@ 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(response, 0, responseLength, "UTF-8") + new String(ping, StandardCharsets.UTF_8), + responseLength > 0 && responseLength <= response.length ? + new String(response, 0, responseLength, StandardCharsets.UTF_8) : + "bad response length " + responseLength }); return false; } 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 8007200a6293ead38d91608592412fe13cae2d9c..dc3de475bf37f2162c7fc5b42cd3513d85b1a760 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -23,14 +23,13 @@ */ package hudson; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import hudson.model.TaskListener; import jenkins.util.MemoryReductionUtil; import hudson.util.QuotedStringTokenizer; import hudson.util.VariableResolver; import jenkins.util.SystemProperties; +import jenkins.util.io.PathRemover; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; @@ -62,13 +61,11 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.DosFileAttributes; +import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.security.DigestInputStream; import java.security.MessageDigest; @@ -94,6 +91,7 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.io.FileUtils; +import org.kohsuke.stapler.StaplerRequest; /** * Various utility methods that don't have more proper home. @@ -116,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)); @@ -146,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)); } /** @@ -247,16 +245,18 @@ public class Util { * if the operation fails. */ public static void deleteContentsRecursive(@Nonnull File file) throws IOException { - for( int numberOfAttempts=1 ; ; numberOfAttempts++ ) { - try { - tryOnceDeleteContentsRecursive(file); - break; // success - } catch (IOException ex) { - boolean threadWasInterrupted = pauseBetweenDeletes(numberOfAttempts); - if( numberOfAttempts>= DELETION_MAX || threadWasInterrupted) - throw new IOException(deleteFailExceptionMessage(file, numberOfAttempts, threadWasInterrupted), ex); - } - } + deleteContentsRecursive(fileToPath(file), PathRemover.PathChecker.ALLOW_ALL); + } + + /** + * Deletes the given directory contents (but not the directory itself) recursively using a PathChecker. + * @param path a directory to delete + * @param pathChecker a security check to validate a path before deleting + * @throws IOException if the operation fails + */ + @Restricted(NoExternalUse.class) + public static void deleteContentsRecursive(@Nonnull Path path, @Nonnull PathRemover.PathChecker pathChecker) throws IOException { + newPathRemover(pathChecker).forceRemoveDirectoryContents(path); } /** @@ -267,88 +267,7 @@ public class Util { * @throws IOException if it exists but could not be successfully deleted */ public static void deleteFile(@Nonnull File f) throws IOException { - for( int numberOfAttempts=1 ; ; numberOfAttempts++ ) { - try { - tryOnceDeleteFile(f); - break; // success - } catch (IOException ex) { - boolean threadWasInterrupted = pauseBetweenDeletes(numberOfAttempts); - if( numberOfAttempts>= DELETION_MAX || threadWasInterrupted) - throw new IOException(deleteFailExceptionMessage(f, numberOfAttempts, threadWasInterrupted), ex); - } - } - } - - /** - * Deletes this file, working around most problems which might make - * this difficult. - * - * @param f - * What to delete. If a directory, it'll need to be empty. - * @throws IOException if it exists but could not be successfully deleted, - * or if it represents an invalid {@link Path}. - */ - private static void tryOnceDeleteFile(File f) throws IOException { - Path path = fileToPath(f); - try { - Files.deleteIfExists(path); - } catch (IOException e) { - // perhaps this file is read-only? - makeWritable(path); - /* - on Unix both the file and the directory that contains it has to be writable - for a file deletion to be successful. (Confirmed on Solaris 9) - - $ ls -la - total 6 - dr-xr-sr-x 2 hudson hudson 512 Apr 18 14:41 . - dr-xr-sr-x 3 hudson hudson 512 Apr 17 19:36 .. - -r--r--r-- 1 hudson hudson 469 Apr 17 19:36 manager.xml - -rw-r--r-- 1 hudson hudson 0 Apr 18 14:41 x - $ rm x - rm: x not removed: Permission denied - */ - Path parent = path.getParent(); - if (parent != null) { - makeWritable(parent); - } - try { - Files.deleteIfExists(path); - } catch (IOException e2) { - // see https://java.net/projects/hudson/lists/users/archive/2008-05/message/357 - // I suspect other processes putting files in this directory - File[] files = f.listFiles(); - if(files!=null && files.length>0) - throw new IOException("Unable to delete " + f.getPath()+" - files in dir: "+Arrays.asList(files), e2); - throw e2; - } - } - } - - /** - * Makes the file at the given path writable by any means possible. - */ - private static void makeWritable(@Nonnull Path path) throws IOException { - if (!Functions.isWindows()) { - try { - PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class); - Set newPermissions = attrs.permissions(); - newPermissions.add(PosixFilePermission.OWNER_WRITE); - Files.setPosixFilePermissions(path, newPermissions); - return; - } catch (NoSuchFileException e) { - return; - } catch (UnsupportedOperationException e) { - // PosixFileAttributes not supported, fall back to old IO. - } - } - - /** - * We intentionally do not check the return code of setWritable, because if it - * is false we prefer to rethrow the exception thrown by Files.deleteIfExists, - * which will have a more useful message than something we make up here. - */ - path.toFile().setWritable(true); + newPathRemover(PathRemover.PathChecker.ALLOW_ALL).forceRemoveFile(fileToPath(f)); } /** @@ -360,137 +279,18 @@ public class Util { * if the operation fails. */ public static void deleteRecursive(@Nonnull File dir) throws IOException { - for( int numberOfAttempts=1 ; ; numberOfAttempts++ ) { - try { - tryOnceDeleteRecursive(dir); - break; // success - } catch (IOException ex) { - boolean threadWasInterrupted = pauseBetweenDeletes(numberOfAttempts); - if( numberOfAttempts>= DELETION_MAX || threadWasInterrupted) - throw new IOException(deleteFailExceptionMessage(dir, numberOfAttempts, threadWasInterrupted), ex); - } - } + deleteRecursive(fileToPath(dir), PathRemover.PathChecker.ALLOW_ALL); } /** - * Deletes a file or folder, throwing the first exception encountered, but - * having a go at deleting everything. i.e. it does not stop on the - * first exception, but tries (to delete) everything once. - * - * @param dir - * What to delete. If a directory, the contents will be deleted - * too. - * @throws The first exception encountered. + * Deletes the given directory and contents recursively using a filter. + * @param dir a directory to delete + * @param pathChecker a security check to validate a path before deleting + * @throws IOException if the operation fails */ - private static void tryOnceDeleteRecursive(File dir) throws IOException { - if(!isSymlink(dir)) - tryOnceDeleteContentsRecursive(dir); - tryOnceDeleteFile(dir); - } - - /** - * Deletes a folder's contents, throwing the first exception encountered, - * but having a go at deleting everything. i.e. it does not stop - * on the first exception, but tries (to delete) everything once. - * - * @param directory - * The directory whose contents will be deleted. - * @throws The first exception encountered. - */ - private static void tryOnceDeleteContentsRecursive(File directory) throws IOException { - File[] directoryContents = directory.listFiles(); - if(directoryContents==null) - return; // the directory didn't exist in the first place - IOException firstCaught = null; - for (File child : directoryContents) { - try { - tryOnceDeleteRecursive(child); - } catch (IOException justCaught) { - if( firstCaught==null) { - firstCaught = justCaught; - } - } - } - if( firstCaught!=null ) - throw firstCaught; - } - - /** - * Pauses between delete attempts, and says if it's ok to try again. - * This does not wait if the wait time is zero or if we have tried - * too many times already. - *

- * See {@link #WAIT_BETWEEN_DELETION_RETRIES} for details of - * the pause duration.
- * See {@link #GC_AFTER_FAILED_DELETE} for when {@link System#gc()} is called. - * - * @return false if it is ok to continue trying to delete things, true if - * we were interrupted (and should stop now). - */ - @SuppressFBWarnings(value = "DM_GC", justification = "Garbage collection happens only when " - + "GC_AFTER_FAILED_DELETE is true. It's an experimental feature in Jenkins.") - private static boolean pauseBetweenDeletes(int numberOfAttemptsSoFar) { - long delayInMs; - if( numberOfAttemptsSoFar>=DELETION_MAX ) return false; - /* If the Jenkins process had the file open earlier, and it has not - * closed it then Windows won't let us delete it until the Java object - * with the open stream is Garbage Collected, which can result in builds - * failing due to "file in use" on Windows despite working perfectly - * well on other OSs. */ - if (GC_AFTER_FAILED_DELETE) { - System.gc(); - } - if (WAIT_BETWEEN_DELETION_RETRIES>=0) { - delayInMs = WAIT_BETWEEN_DELETION_RETRIES; - } else { - delayInMs = -numberOfAttemptsSoFar*WAIT_BETWEEN_DELETION_RETRIES; - } - if (delayInMs<=0) - return Thread.interrupted(); - try { - Thread.sleep(delayInMs); - return false; - } catch (InterruptedException e) { - return true; - } - } - - /** - * Creates a "couldn't delete file" message that explains how hard we tried. - * See {@link #DELETION_MAX}, {@link #WAIT_BETWEEN_DELETION_RETRIES} - * and {@link #GC_AFTER_FAILED_DELETE} for more details. - */ - private static String deleteFailExceptionMessage(File whatWeWereTryingToRemove, int retryCount, boolean wasInterrupted) { - StringBuilder sb = new StringBuilder(); - sb.append("Unable to delete '"); - sb.append(whatWeWereTryingToRemove); - sb.append("'. Tried "); - sb.append(retryCount); - sb.append(" time"); - if( retryCount!=1 ) sb.append('s'); - if( DELETION_MAX>1 ) { - sb.append(" (of a maximum of "); - sb.append(DELETION_MAX); - sb.append(')'); - if( GC_AFTER_FAILED_DELETE ) - sb.append(" garbage-collecting"); - if( WAIT_BETWEEN_DELETION_RETRIES!=0 && GC_AFTER_FAILED_DELETE ) - sb.append(" and"); - if( WAIT_BETWEEN_DELETION_RETRIES!=0 ) { - sb.append(" waiting "); - sb.append(getTimeSpanString(Math.abs(WAIT_BETWEEN_DELETION_RETRIES))); - if( WAIT_BETWEEN_DELETION_RETRIES<0 ) { - sb.append("-"); - sb.append(getTimeSpanString(Math.abs(WAIT_BETWEEN_DELETION_RETRIES)*DELETION_MAX)); - } - } - if( WAIT_BETWEEN_DELETION_RETRIES!=0 || GC_AFTER_FAILED_DELETE) - sb.append(" between attempts"); - } - if( wasInterrupted ) - sb.append(". The delete operation was interrupted before it completed successfully"); - sb.append('.'); - return sb.toString(); + @Restricted(NoExternalUse.class) + public static void deleteRecursive(@Nonnull Path dir, @Nonnull PathRemover.PathChecker pathChecker) throws IOException { + newPathRemover(pathChecker).forceRemoveRecursive(dir); } /* @@ -509,9 +309,16 @@ public class Util { * limitations under the License. */ /** - * Checks if the given file represents a symlink. + * Checks if the given file represents a symlink. Unlike {@link Files#isSymbolicLink(Path)}, this method also + * considers NTFS junction points as symbolic + * links. */ public static boolean isSymlink(@Nonnull File file) throws IOException { + return isSymlink(fileToPath(file)); + } + + @Restricted(NoExternalUse.class) + public static boolean isSymlink(@Nonnull Path path) { /* * Windows Directory Junctions are effectively the same as Linux symlinks to directories. * Unfortunately, the Java 7 NIO2 API function isSymbolicLink does not treat them as such. @@ -525,20 +332,9 @@ public class Util { * calling readAttributes. */ try { - Path path = fileToPath(file); BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); - if (attrs.isSymbolicLink()) { - return true; - } else if (attrs instanceof DosFileAttributes) { - /* Returns true for non-symbolic link reparse points and devices. We could call - * WindowsFileAttributes#isReparsePoint with reflection instead to exclude devices, - * but as mentioned in the above comment this does not appear to be an issue. - */ - return attrs.isOther(); - } else { - return false; - } - } catch (NoSuchFileException e) { + return attrs.isSymbolicLink() || (attrs instanceof DosFileAttributes && attrs.isOther()); + } catch (IOException ignored) { return false; } } @@ -987,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)); @@ -1201,7 +997,7 @@ public class Util { /** * Convert {@code null} to a default value. * @param defaultValue Default value. It may be immutable or not, depending on the implementation. - * @since TODO + * @since 2.144 */ @Nonnull public static T fixNull(@CheckForNull T s, @Nonnull T defaultValue) { @@ -1321,7 +1117,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; @@ -1667,9 +1463,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; } @@ -1729,6 +1525,19 @@ public class Util { return Math.max(0, daysBetween(date, new Date())); } + /** + * Find the specific ancestor, or throw an exception. + * Useful for an ancestor we know is inside the URL to ease readability + */ + @Restricted(NoExternalUse.class) + public static @Nonnull T getNearestAncestorOfTypeOrThrow(@Nonnull StaplerRequest request, @Nonnull Class clazz) { + T t = request.findAncestorObject(clazz); + if (t == null) { + throw new IllegalArgumentException("No ancestor of type " + clazz.getName() + " in the request"); + } + return t; + } + public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT")); // Note: RFC822 dates must not be localized! @@ -1759,7 +1568,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 @@ -1771,7 +1580,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 @@ -1787,7 +1596,7 @@ public class Util { * Warning: This should only ever be used if you find that your builds are * failing because Jenkins is unable to delete files, that this failure is * because Jenkins itself has those files locked "open", and even then it - * should only be used on slaves with relatively few executors (because the + * should only be used on agents with relatively few executors (because the * garbage collection can impact the performance of all job executors on * that slave).
* i.e. Setting this flag is a act of last resort - it is not @@ -1797,6 +1606,10 @@ public class Util { @Restricted(value = NoExternalUse.class) static boolean GC_AFTER_FAILED_DELETE = SystemProperties.getBoolean(Util.class.getName() + ".performGCOnFailedDelete"); + private static PathRemover newPathRemover(@Nonnull PathRemover.PathChecker pathChecker) { + return PathRemover.newFilteredRobustRemover(pathChecker, DELETION_MAX - 1, GC_AFTER_FAILED_DELETE, WAIT_BETWEEN_DELETION_RETRIES); + } + /** * If this flag is true, native implementations of {@link FilePath#chmod} * and {@link hudson.util.IOUtils#mode} are used instead of NIO. diff --git a/core/src/main/java/hudson/cli/BuildCommand.java b/core/src/main/java/hudson/cli/BuildCommand.java index cd6a13efa1dbae62a404f3e50a73a332b02394a2..8cd09281c52c1eb930db6dede5900c1731d42f81 100644 --- a/core/src/main/java/hudson/cli/BuildCommand.java +++ b/core/src/main/java/hudson/cli/BuildCommand.java @@ -89,7 +89,7 @@ public class BuildCommand extends CLICommand { public boolean checkSCM = false; @Option(name="-p",usage="Specify the build parameters in the key=value format.") - public Map parameters = new HashMap(); + public Map parameters = new HashMap<>(); @Option(name="-v",usage="Prints out the console output of the build. Use with -s") public boolean consoleOutput = false; @@ -109,7 +109,7 @@ public class BuildCommand extends CLICommand { throw new IllegalStateException(job.getFullDisplayName()+" is not parameterized but the -p option was specified."); //TODO: switch to type annotations after the migration to Java 1.8 - List values = new ArrayList(); + List values = new ArrayList<>(); for (Entry e : parameters.entrySet()) { String name = e.getKey(); diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java index 2c37db46cc0ed3e52184fe7d9ef9cb264bbca21b..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.getInstance().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 91d88118d1dcf99ca57a3bdc407c010e509b3d74..e9c8f5765f86be4ed56b8f89a38416bed01d0e67 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()}); @@ -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,13 +530,7 @@ 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(); + private static final ThreadLocal CURRENT_COMMAND = new ThreadLocal<>(); /*package*/ static CLICommand setCurrent(CLICommand cmd) { CLICommand old = getCurrent(); @@ -656,7 +548,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { static { // register option handlers that are defined ClassLoaders cls = new ClassLoaders(); - Jenkins j = Jenkins.getActiveInstance(); + Jenkins j = Jenkins.getInstanceOrNull(); if (j!=null) {// only when running on the master cls.put(j.getPluginManager().uberClassLoader); @@ -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/ClearQueueCommand.java b/core/src/main/java/hudson/cli/ClearQueueCommand.java index 8e76a477aa12af83cf315b4c35a0e7253401a223..52cd7c4063880db362f7b6ea7df9580389fec70b 100644 --- a/core/src/main/java/hudson/cli/ClearQueueCommand.java +++ b/core/src/main/java/hudson/cli/ClearQueueCommand.java @@ -26,7 +26,6 @@ package hudson.cli; import hudson.Extension; import jenkins.model.Jenkins; -import org.acegisecurity.AccessDeniedException; import java.util.logging.Logger; 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 750a38d210af7cb8f267086346715b3981129b50..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.current().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 2cd45a77766f373035938f234f3ec47215114b84..0000000000000000000000000000000000000000 --- a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java +++ /dev/null @@ -1,156 +0,0 @@ -package hudson.cli; - -import com.google.common.annotations.VisibleForTesting; -import hudson.FilePath; -import hudson.remoting.Channel; -import hudson.util.Secret; -import jenkins.model.Jenkins; -import jenkins.security.MasterToSlaveCallable; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -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.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; -import jenkins.security.HMACConfidentialKey; - -import javax.annotation.Nonnull; - -/** - * 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()); - - /** - * 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() { - Jenkins h = Jenkins.getActiveInstance(); - 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(':'); - if (idx == -1) { - LOGGER.log(Level.FINE, "Ignoring malformed stored CLI authentication: {0}", val); - return Jenkins.ANONYMOUS; - } - String username = val.substring(0, idx); - if (!MAC.checkMac(username, val.substring(idx + 1))) { - LOGGER.log(Level.FINE, "Ignoring stored CLI authentication due to MAC mismatch: {0}", val); - return Jenkins.ANONYMOUS; - } - try { - UserDetails u = h.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(); - props.setProperty(getPropertyKey(), username + ":" + MAC.mac(username)); - - save(); - } - - /** - * 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 ae20f5e60848507503cca9f3ff0d3a21cb9c53a5..189d2b5406376a2e88c6411308aa991238d5339e 100644 --- a/core/src/main/java/hudson/cli/ConnectNodeCommand.java +++ b/core/src/main/java/hudson/cli/ConnectNodeCommand.java @@ -62,7 +62,7 @@ public class ConnectNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(nodes); List names = null; diff --git a/core/src/main/java/hudson/cli/CreateNodeCommand.java b/core/src/main/java/hudson/cli/CreateNodeCommand.java index 6a7f6dee7698cf6f5f4dddeb2167dd5df003a51f..edab3f7e71ec2eda866dd7c12b32c1920d902c81 100644 --- a/core/src/main/java/hudson/cli/CreateNodeCommand.java +++ b/core/src/main/java/hudson/cli/CreateNodeCommand.java @@ -32,7 +32,6 @@ import hudson.model.User; import jenkins.model.Jenkins; import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.CmdLineException; /** * @author ogondza diff --git a/core/src/main/java/hudson/cli/DeleteBuildsCommand.java b/core/src/main/java/hudson/cli/DeleteBuildsCommand.java index bd1768786cd4c5bfd6e9df37547eda439b6a8632..b7c2531446b0c21462ab81f0863da28d6592a648 100644 --- a/core/src/main/java/hudson/cli/DeleteBuildsCommand.java +++ b/core/src/main/java/hudson/cli/DeleteBuildsCommand.java @@ -57,7 +57,7 @@ public class DeleteBuildsCommand extends RunRangeCommand { protected int act(List> builds) throws IOException { job.checkPermission(Run.DELETE); - final HashSet hsBuilds = new HashSet(); + final HashSet hsBuilds = new HashSet<>(); for (Run build : builds) { if (!hsBuilds.contains(build.number)) { diff --git a/core/src/main/java/hudson/cli/DeleteJobCommand.java b/core/src/main/java/hudson/cli/DeleteJobCommand.java index 199296f87fc48d09a14d289ef4c36cea8f05901f..03e63e8579da3f9b631577120b30bb873ab6d8ae 100644 --- a/core/src/main/java/hudson/cli/DeleteJobCommand.java +++ b/core/src/main/java/hudson/cli/DeleteJobCommand.java @@ -31,7 +31,6 @@ import org.kohsuke.args4j.Argument; import java.util.List; import java.util.HashSet; -import java.util.logging.Logger; /** * CLI command, which deletes a job or multiple jobs. @@ -56,7 +55,7 @@ public class DeleteJobCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(jobs); for (String job_s: hs) { diff --git a/core/src/main/java/hudson/cli/DeleteNodeCommand.java b/core/src/main/java/hudson/cli/DeleteNodeCommand.java index 03001fccc3a10886fb164632952e426d31862ba7..60a8821e0f7134b396344ce4fc6078dd4c5eac24 100644 --- a/core/src/main/java/hudson/cli/DeleteNodeCommand.java +++ b/core/src/main/java/hudson/cli/DeleteNodeCommand.java @@ -31,7 +31,6 @@ import org.kohsuke.args4j.Argument; import java.util.HashSet; import java.util.List; -import java.util.logging.Logger; /** * CLI command, which deletes Jenkins nodes. @@ -56,7 +55,7 @@ public class DeleteNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(nodes); for (String node_s : hs) { diff --git a/core/src/main/java/hudson/cli/DeleteViewCommand.java b/core/src/main/java/hudson/cli/DeleteViewCommand.java index 894978e683fcac696a634fcad383c199e5dec840..955a476ad96c009dac15484c864e180b8f337ce0 100644 --- a/core/src/main/java/hudson/cli/DeleteViewCommand.java +++ b/core/src/main/java/hudson/cli/DeleteViewCommand.java @@ -33,7 +33,6 @@ import org.kohsuke.args4j.Argument; import java.util.HashSet; import java.util.List; -import java.util.logging.Logger; /** * @author ogondza, pjanouse @@ -57,7 +56,7 @@ public class DeleteViewCommand extends CLICommand { boolean errorOccurred = false; // Remove duplicates - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(views); ViewOptionHandler voh = new ViewOptionHandler(null, null, null); diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index 36d4b6bd8ca4b0ec0e60cfe1d66bd49bbc3afbeb..101576e90442b3b20c901563bfb356b02364ac83 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -36,7 +36,7 @@ import java.util.List; /** * Disable one or more installed plugins. - * @since TODO + * @since 2.151 */ @Extension public class DisablePluginCommand extends CLICommand { @@ -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 65c95106eb5f8653007b8d62bf8aa5c8c5e987e0..ad003521d77a2025ad09eb5f9e16d6cc06e7f3bb 100644 --- a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java +++ b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java @@ -61,7 +61,7 @@ public class DisconnectNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(nodes); List names = null; diff --git a/core/src/main/java/hudson/cli/GroovyCommand.java b/core/src/main/java/hudson/cli/GroovyCommand.java index e7b247951650711c23cffb22d1bc0273dc78e0f1..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,14 +48,14 @@ 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; /** * Remaining arguments. */ @Argument(metaVar="ARGUMENTS", index=1, usage="Command line arguments to pass into script.") - public List remaining = new ArrayList(); + public List remaining = new ArrayList<>(); protected int run() throws Exception { // this allows the caller to manipulate the JVM state, so require the execute script privilege. @@ -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 31f998d0aa5be777ef55f2d4c1bfbf3aab4fe459..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; @@ -56,7 +55,7 @@ public class GroovyshCommand extends CLICommand { return Messages.GroovyshCommand_ShortDescription(); } - @Argument(metaVar="ARGS") public List args = new ArrayList(); + @Argument(metaVar="ARGS") public List args = new ArrayList<>(); @Override protected int run() { @@ -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/HelpCommand.java b/core/src/main/java/hudson/cli/HelpCommand.java index 7b73c0b31dfef2993e39d773cfc47ad959cc3b06..071bce6a7fc49d9f400dfec4198d4a2acf04a796 100644 --- a/core/src/main/java/hudson/cli/HelpCommand.java +++ b/core/src/main/java/hudson/cli/HelpCommand.java @@ -65,7 +65,7 @@ public class HelpCommand extends CLICommand { } private int showAllCommands() { - Map commands = new TreeMap(); + Map commands = new TreeMap<>(); for (CLICommand c : CLICommand.all()) commands.put(c.getName(),c); diff --git a/core/src/main/java/hudson/cli/InstallPluginCommand.java b/core/src/main/java/hudson/cli/InstallPluginCommand.java index e04032893625736e4544ece5ba787a09172b0e17..5279a7fd3a3fd14dc110319697d894e42d786204 100644 --- a/core/src/main/java/hudson/cli/InstallPluginCommand.java +++ b/core/src/main/java/hudson/cli/InstallPluginCommand.java @@ -27,6 +27,7 @@ import hudson.AbortException; import hudson.Extension; import hudson.FilePath; import hudson.PluginManager; +import hudson.util.VersionNumber; import jenkins.model.Jenkins; import hudson.model.UpdateSite; import hudson.model.UpdateSite.Data; @@ -35,7 +36,6 @@ import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; import java.io.File; -import java.io.IOException; import java.net.URL; import java.net.MalformedURLException; import java.util.HashSet; @@ -56,12 +56,14 @@ public class InstallPluginCommand extends CLICommand { 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. " + "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.") - public List sources = new ArrayList(); + "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<>(); @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 @@ -95,19 +97,6 @@ public class InstallPluginCommand extends CLICommand { 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); @@ -133,7 +122,18 @@ public class InstallPluginCommand extends CLICommand { } // is this a plugin the update center? - UpdateSite.Plugin p = h.getUpdateCenter().getPlugin(source); + int index = source.lastIndexOf(':'); + UpdateSite.Plugin p; + if (index == -1) { + p = h.getUpdateCenter().getPlugin(source); + } else { + // try to find matching min version number + VersionNumber version = new VersionNumber(source.substring(index + 1)); + p = h.getUpdateCenter().getPlugin(source.substring(0,index), version); + if (p == null) { + p = h.getUpdateCenter().getPlugin(source); + } + } if (p!=null) { stdout.println(Messages.InstallPluginCommand_InstallingFromUpdateCenter(source)); Throwable e = p.deploy(dynamicLoad).get().getError(); @@ -152,7 +152,7 @@ public class InstallPluginCommand extends CLICommand { if (h.getUpdateCenter().getSites().isEmpty()) { stdout.println(Messages.InstallPluginCommand_NoUpdateCenterDefined()); } else { - Set candidates = new HashSet(); + Set candidates = new HashSet<>(); for (UpdateSite s : h.getUpdateCenter().getSites()) { Data dt = s.getData(); if (dt==null) 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 ef8db698f99df0f20da2a91c5092fc8804870d93..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/ListJobsCommand.java b/core/src/main/java/hudson/cli/ListJobsCommand.java index 68c30dd0152d4af2f791a1a389d39ceb5bceb6a5..8dae08e67470d1584384990287a8f34304a5aeff 100644 --- a/core/src/main/java/hudson/cli/ListJobsCommand.java +++ b/core/src/main/java/hudson/cli/ListJobsCommand.java @@ -26,7 +26,6 @@ package hudson.cli; import java.util.Collection; import hudson.model.Item; -import hudson.model.Items; import hudson.model.TopLevelItem; import hudson.model.View; import hudson.Extension; @@ -66,7 +65,8 @@ public class ListJobsCommand extends CLICommand { // If item group was found use it's jobs. if (item instanceof ModifiableTopLevelItemGroup) { - jobs = ((ModifiableTopLevelItemGroup) item).getAllItems(TopLevelItem.class); + jobs = ((ModifiableTopLevelItemGroup) item).getItems(); + } // No view and no item group with the given name found. else { 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/OfflineNodeCommand.java b/core/src/main/java/hudson/cli/OfflineNodeCommand.java index e003a633b28ce80895a5257e74973918c7ee9a2b..39270918dcb8446506a24386a5497c42eef98350 100644 --- a/core/src/main/java/hudson/cli/OfflineNodeCommand.java +++ b/core/src/main/java/hudson/cli/OfflineNodeCommand.java @@ -34,7 +34,6 @@ import jenkins.model.Jenkins; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -60,8 +59,8 @@ public class OfflineNodeCommand extends CLICommand { @Override protected int run() throws Exception { boolean errorOccurred = false; - final Jenkins jenkins = Jenkins.getInstance(); - final HashSet hs = new HashSet(nodes); + final Jenkins jenkins = Jenkins.get(); + final HashSet hs = new HashSet<>(nodes); List names = null; for (String node_s : hs) { diff --git a/core/src/main/java/hudson/cli/OnlineNodeCommand.java b/core/src/main/java/hudson/cli/OnlineNodeCommand.java index 5cb190fcd8ccf6bdb2c7f4e3e4c7e55888ad6505..0594d3770c9702a954f732d611dce90283ddfb87 100644 --- a/core/src/main/java/hudson/cli/OnlineNodeCommand.java +++ b/core/src/main/java/hudson/cli/OnlineNodeCommand.java @@ -56,7 +56,7 @@ public class OnlineNodeCommand extends CLICommand { protected int run() throws Exception { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(nodes); + final HashSet hs = new HashSet<>(nodes); List names = null; for (String node_s : hs) { diff --git a/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java b/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java index adb61e12c6d2fc17d32415acff65262dd9042553..6b29dcf52904c09ff60c91ab014b395caeb8ab83 100644 --- a/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java +++ b/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java @@ -46,7 +46,7 @@ public class ReloadConfigurationCommand extends CLICommand { @Override protected int run() throws Exception { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); // Or perhaps simpler to inline the thread body of doReload? j.doReload(); Object app; diff --git a/core/src/main/java/hudson/cli/ReloadJobCommand.java b/core/src/main/java/hudson/cli/ReloadJobCommand.java index c8849a317f4832ef5c8411f8fc3d29fffe3217ff..3e42d64e68660dc69d4075bbac82cceb638b1201 100644 --- a/core/src/main/java/hudson/cli/ReloadJobCommand.java +++ b/core/src/main/java/hudson/cli/ReloadJobCommand.java @@ -26,11 +26,9 @@ package hudson.cli; import hudson.AbortException; import hudson.Extension; import hudson.model.AbstractItem; -import hudson.model.AbstractProject; import hudson.model.Item; import hudson.model.Items; -import hudson.model.TopLevelItem; import jenkins.model.Jenkins; import org.kohsuke.args4j.Argument; @@ -64,7 +62,7 @@ public class ReloadJobCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(jobs); for (String job_s: hs) { diff --git a/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java b/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java index 786f33d21e1ee5fc1cb0d68cec7bcb8605816d40..5a543c91c53a399d398ed43f0cf8dfe92e9105b4 100644 --- a/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java +++ b/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java @@ -31,7 +31,6 @@ import hudson.model.DirectlyModifiableView; import hudson.model.View; import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.CmdLineException; /** * @author ogondza 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 5868d62b9ad68a8f31a7980496a638b0ef611472..d5a9031eb5d6a24697fa713a98beabd104d94d07 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,10 +42,10 @@ 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; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; @@ -90,7 +89,7 @@ public class CLIRegisterer extends ExtensionFinder { * Finds a resolved method annotated with {@link CLIResolver}. */ private Method findResolver(Class type) throws IOException { - List resolvers = Util.filter(Index.list(CLIResolver.class, Jenkins.getInstance().getPluginManager().uberClassLoader), Method.class); + List resolvers = Util.filter(Index.list(CLIResolver.class, Jenkins.get().getPluginManager().uberClassLoader), Method.class); for ( ; type!=null; type=type.getSuperclass()) for (Method m : resolvers) if (m.getReturnType()==type) @@ -98,12 +97,12 @@ public class CLIRegisterer extends ExtensionFinder { return null; } - private List> discover(final Jenkins hudson) { + private List> discover(@Nonnull final Jenkins jenkins) { LOGGER.fine("Listing up @CLIMethod"); - List> r = new ArrayList>(); + List> r = new ArrayList<>(); try { - for ( final Method m : Util.filter(Index.list(CLIMethod.class, hudson.getPluginManager().uberClassLoader),Method.class)) { + for ( final Method m : Util.filter(Index.list(CLIMethod.class, jenkins.getPluginManager().uberClassLoader),Method.class)) { try { // command name final String name = m.getAnnotation(CLIMethod.class).name(); @@ -111,7 +110,7 @@ public class CLIRegisterer extends ExtensionFinder { final ResourceBundleHolder res = loadMessageBundle(m); res.format("CLI."+name+".shortDescription"); // make sure we have the resource, to fail early - r.add(new ExtensionComponent(new CloneableCLICommand() { + r.add(new ExtensionComponent<>(new CloneableCLICommand() { @Override public String getName() { return name; @@ -120,12 +119,12 @@ public class CLIRegisterer extends ExtensionFinder { @Override public String getShortDescription() { // format by using the right locale - return res.format("CLI."+name+".shortDescription"); + return res.format("CLI." + name + ".shortDescription"); } @Override protected CmdLineParser getCmdLineParser() { - return bindMethod(new ArrayList()); + return bindMethod(new ArrayList<>()); } private CmdLineParser bindMethod(List binders) { @@ -134,7 +133,7 @@ public class CLIRegisterer extends ExtensionFinder { CmdLineParser parser = new CmdLineParser(null); // build up the call sequence - Stack chains = new Stack(); + Stack chains = new Stack<>(); Method method = m; while (true) { chains.push(method); @@ -146,15 +145,15 @@ public class CLIRegisterer extends ExtensionFinder { try { method = findResolver(type); } catch (IOException ex) { - throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for "+type, ex); + throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for " + type, ex); } - if (method==null) { - throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for "+type); + if (method == null) { + throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for " + type); } } while (!chains.isEmpty()) - binders.add(new MethodBinder(chains.pop(),this,parser)); + binders.add(new MethodBinder(chains.pop(), this, parser)); return parser; } @@ -199,25 +198,19 @@ public class CLIRegisterer extends ExtensionFinder { this.stderr = stderr; this.locale = locale; - List binders = new ArrayList(); + List binders = new ArrayList<>(); CmdLineParser parser = bindMethod(binders); try { SecurityContext sc = SecurityContextHolder.getContext(); Authentication old = sc.getAuthentication(); try { - // authentication - CliAuthenticator authenticator = Jenkins.getInstance().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 - hudson.checkPermission(Jenkins.READ); + jenkins.checkPermission(Jenkins.READ); // resolve them Object instance = null; diff --git a/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java index 0648f057a04b28ab7aff06b283e6159b36a93054..5bd9834b485b625ffd089f50f6ff9dee64105c65 100644 --- a/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java +++ b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java @@ -57,7 +57,7 @@ public abstract class GenericItemOptionHandler extends OptionHan protected abstract Class type(); @Override public int parseArguments(Parameters params) throws CmdLineException { - final Jenkins j = Jenkins.getInstance(); + final Jenkins j = Jenkins.get(); final String src = params.getParameter(0); T s = j.getItemByFullName(src, type()); if (s == null) { diff --git a/core/src/main/java/hudson/cli/handlers/NodeOptionHandler.java b/core/src/main/java/hudson/cli/handlers/NodeOptionHandler.java index 90905d5348968191b2d75e61ceeb01b3ca670b5f..a90199e8c1185630b152e35ddc5153dc1cd19d42 100644 --- a/core/src/main/java/hudson/cli/handlers/NodeOptionHandler.java +++ b/core/src/main/java/hudson/cli/handlers/NodeOptionHandler.java @@ -53,7 +53,7 @@ public class NodeOptionHandler extends OptionHandler { String nodeName = params.getParameter(0); - final Node node = Jenkins.getInstance().getNode(nodeName); + final Node node = Jenkins.get().getNode(nodeName); if (node == null) throw new IllegalArgumentException("No such node '" + nodeName + "'"); setter.addValue(node); 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..f29430b1b2a9fb5654bd0f20efda960f340a09f2 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.getInstance().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/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 a572a966b614a2b8f10b451ecd8ae696eab6cbc2..2b646cab4b502700156d3e8b269604002dff40c6 100644 --- a/core/src/main/java/hudson/console/ConsoleNote.java +++ b/core/src/main/java/hudson/console/ConsoleNote.java @@ -53,8 +53,11 @@ import com.jcraft.jzlib.GZIPInputStream; import com.jcraft.jzlib.GZIPOutputStream; import hudson.remoting.ClassFilter; import jenkins.security.HMACConfidentialKey; +import jenkins.util.JenkinsJVM; import jenkins.util.SystemProperties; import org.jenkinsci.remoting.util.AnonymousClassWarnings; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Data that hangs off from a console output. @@ -107,6 +110,17 @@ import org.jenkinsci.remoting.util.AnonymousClassWarnings; * 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 @@ -130,7 +144,8 @@ 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/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/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/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..27a5685f1dcc29e28469d058119e288bbde819b0 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() { diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java index 6bbeed6cf5b7c6dfd55d97c8a617443e2ab2b966..c4317b4816aff7af6da1fb3687fd757b1919c02c 100644 --- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java +++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java @@ -26,6 +26,7 @@ package hudson.diagnosis; import com.google.common.base.Predicate; import com.thoughtworks.xstream.converters.UnmarshallingContext; import hudson.Extension; +import hudson.ExtensionList; import hudson.XmlFile; import hudson.model.AdministrativeMonitor; import hudson.model.Item; @@ -52,6 +53,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import jenkins.model.Jenkins; import org.acegisecurity.context.SecurityContext; @@ -76,10 +78,18 @@ 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<>(); - static OldDataMonitor get(Jenkins j) { - return (OldDataMonitor) j.getAdministrativeMonitor("OldData"); + /** + * Gets instance of the monitor. + * @param j Jenkins instance + * @return Monitor instance + * @throws IllegalStateException Monitor not found. + * It should never happen since the monitor is located in the core. + */ + @Nonnull + static OldDataMonitor get(Jenkins j) throws IllegalStateException { + return ExtensionList.lookupSingleton(OldDataMonitor.class); } public OldDataMonitor() { @@ -96,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) { diff --git a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java index 5e3563f3a4461fb57b60dd0f9a1b7e83bd9487d9..858f79ddaad89b84974b90d1113f5b0e88b269c7 100644 --- a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java +++ b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java @@ -26,6 +26,7 @@ package hudson.diagnosis; import hudson.Extension; import hudson.Util; import hudson.model.AdministrativeMonitor; +import jenkins.security.stapler.StaplerDispatchable; import org.jenkinsci.Symbol; import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.HttpResponse; @@ -70,6 +71,7 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor { return new HttpRedirect(redirect); } + @StaplerDispatchable public void getTestForReverseProxySetup(String rest) { Jenkins j = Jenkins.getInstance(); String inferred = j.getRootUrlFromRequest() + "manage"; 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..bbd60a61bfe0e089ec0f59e3cebe53f9bba562a0 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 @@ -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 f15dd429ba7d4d07f1d377b74241857246d67399..454af18cb3cf0a9df7e75d25abcb81c64b3530d1 100644 --- a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java +++ b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java @@ -5,12 +5,8 @@ import java.io.EOFException; import jenkins.model.Jenkins; 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.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -33,7 +29,7 @@ public class InstallUncaughtExceptionHandler { } req.setAttribute("javax.servlet.error.exception",e); try { - WebApp.get(j.servletContext).getSomeStapler().invoke(req, rsp, Jenkins.get(), "/oops"); + WebApp.get(j.servletContext).getSomeStapler().invoke(req, rsp, j, "/oops"); } catch (ServletException | IOException x) { if (!Stapler.isSocketException(x)) { throw x; 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/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..414d39b4e6d7396ade0c8bde59b38a4676312a2b 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; @@ -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(); } } diff --git a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java index ad3940b4a4feab58375654f6a0f0bb870c18ad1b..378caf460b37657dcf2e06829f04638677142469 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"); diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java index 51549a3bd34d8453945b08d344ed40b2f930239b..85de03238709fad466c4a42f8591faa17dbce4a8 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; } @@ -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..a572900a5597fb15c4b908af84b6ddfe029297e6 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(); @@ -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); 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/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 2268f3dd9967c69df673fede3e6bf159c59a28dd..3c2c1b441235bf4abf72110eeaf2f12135c74f01 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -465,7 +465,7 @@ public abstract class AbstractBuild

    ,R extends Abs 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); @@ -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,7 +895,7 @@ 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); } @@ -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()); } /** @@ -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..073099e548733e09388b118ba6462cdf9a104351 100644 --- a/core/src/main/java/hudson/model/AbstractCIBase.java +++ b/core/src/main/java/hudson/model/AbstractCIBase.java @@ -39,7 +39,6 @@ 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; diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 1374429381f550e0e8bddfa991431c794eb4d56b..b52c646a746540c8bf2039c089b2683a64e54096 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; diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 3b60e8618519d41c5dc5b6a23a357377296d1d6a..f86aed606dd3a5facdc02d7ab52219dac3c17739 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -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])); } /** @@ -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.getInstance().getMode() == Mode.EXCLUSIVE; } } return true; 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 16b3fbb69774c938aaf531c3d3cc53df353d5ab3..2121c3e5991faad2661f1c9536dd6848793b890c 100644 --- a/core/src/main/java/hudson/model/AdministrativeMonitor.java +++ b/core/src/main/java/hudson/model/AdministrativeMonitor.java @@ -116,11 +116,11 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen * Mark this monitor as disabled, to prevent this from showing up in the UI. */ public void disable(boolean value) throws IOException { - AbstractCIBase hudson = Jenkins.getInstance(); - Set set = hudson.disabledAdministrativeMonitors; + AbstractCIBase jenkins = Jenkins.get(); + Set set = jenkins.disabledAdministrativeMonitors; if(value) set.add(id); else set.remove(id); - hudson.save(); + jenkins.save(); } /** @@ -131,7 +131,7 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen * he wants to ignore. */ public boolean isEnabled() { - return !((AbstractCIBase)Jenkins.getInstance()).disabledAdministrativeMonitors.contains(id); + return !((AbstractCIBase)Jenkins.get()).disabledAdministrativeMonitors.contains(id); } /** @@ -158,7 +158,7 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen */ @Restricted(NoExternalUse.class) public Object getTarget() { - Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); return this; } 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..e8e64b8e4833c67063da82d5aa3fced24b315e03 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); 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..1d5456a8f903891229ce79ccc1c30f32ffbcdba9 100644 --- a/core/src/main/java/hudson/model/BuildAuthorizationToken.java +++ b/core/src/main/java/hudson/model/BuildAuthorizationToken.java @@ -64,8 +64,7 @@ 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 { diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index c0286bc55a8556d88df25c7db3e968938c2ce292..4758f17dd06cf77fba46a333060ab34b4a0eae30 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)); } @@ -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) { @@ -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"); 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 641d82fc27ea758fd2087abd86a19205adff31f7..dacf3d8fa1fdccb3d261e679355257d15f6cb3ef 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -30,6 +30,7 @@ import hudson.EnvVars; import hudson.Extension; import hudson.Launcher.ProcStarter; import hudson.slaves.Cloud; +import jenkins.security.stapler.StaplerDispatchable; import hudson.Util; import hudson.cli.declarative.CLIResolver; import hudson.console.AnnotatedLargeText; @@ -149,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; @@ -197,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 @@ -240,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) { @@ -259,7 +259,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ @SuppressWarnings("deprecation") public List getActions() { - List result = new ArrayList(); + List result = new ArrayList<>(); result.addAll(super.getActions()); synchronized (this) { if (transientActions == null) { @@ -326,7 +326,7 @@ 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() { @@ -909,7 +909,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); @@ -962,16 +962,18 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * Gets the read-only snapshot view of all {@link Executor}s. */ @Exported + @StaplerDispatchable public List getExecutors() { - return new ArrayList(executors); + return new ArrayList<>(executors); } /** * Gets the read-only snapshot view of all {@link OneOffExecutor}s. */ @Exported + @StaplerDispatchable public List getOneOffExecutors() { - return new ArrayList(oneOffExecutors); + return new ArrayList<>(oneOffExecutors); } /** @@ -995,7 +997,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()) { @@ -1150,7 +1152,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)); @@ -1321,7 +1323,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces 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()) { 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..1b618d0d508f1d06a7eea8feb56b8ba5e1ce138d 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() { @@ -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); @@ -421,7 +421,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl */ @Nonnull public static List getComputerNames() { - final ArrayList names = new ArrayList(); + final ArrayList names = new ArrayList<>(); for (Computer c : Jenkins.getInstance().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..076b25dee8957b45220ad0853f1d70acd69620ee 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,7 +90,7 @@ 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>(); + this.computationalData = new HashMap<>(); for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) ) p.buildDependencyGraph(this); @@ -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..b00b60e4fb564e9b680816f54dd7c8a8e6ae8c92 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. @@ -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; @@ -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,7 +855,7 @@ public abstract class Descriptor> implements Saveable, } protected List getPossibleViewNames(String baseName) { - List names = new ArrayList(); + List names = new ArrayList<>(); for (Facet f : WebApp.get(Jenkins.getInstance().servletContext).facets) { if (f instanceof JellyCompatibleFacet) { JellyCompatibleFacet jcf = (JellyCompatibleFacet) f; @@ -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/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 e89f28c8a5785c38230cf04b03fb1c0df701566d..3325fe1b5270e88086767866036210306f8732c4 100644 --- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java +++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java @@ -23,6 +23,7 @@ */ package hudson.model; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.FilePath; import hudson.Util; import java.io.IOException; @@ -30,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; @@ -38,11 +40,16 @@ 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; import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; @@ -55,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; @@ -69,6 +75,9 @@ import org.kohsuke.stapler.StaplerResponse; * @author Kohsuke Kawaguchi */ 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 = SystemProperties.getBoolean(DirectoryBrowserSupport.class.getName() + ".allowSymlinkEscape"); public final ModelObject owner; @@ -217,20 +226,26 @@ public final class DirectoryBrowserSupport implements HttpResponse { String base = _base.toString(); String rest = _rest.toString(); + if(!ALLOW_SYMLINK_ESCAPE && (root.supportIsDescendant() && !root.isDescendant(base))){ + LOGGER.log(Level.WARNING, "Trying to access a file outside of the directory, target: "+ base); + rsp.sendError(HttpServletResponse.SC_FORBIDDEN, "Trying to access a file outside of the directory, target: " + base); + return; + } + // this is the base file/directory VirtualFile baseFile = base.isEmpty() ? root : root.child(base); if(baseFile.isDirectory()) { if(zip) { rsp.setContentType("application/zip"); - zip(rsp.getOutputStream(), baseFile, rest); + zip(rsp, root, baseFile, rest); return; } if (plain) { 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('/'); } @@ -251,8 +266,8 @@ public final class DirectoryBrowserSupport implements HttpResponse { } List> glob = null; - - if(rest.length()>0) { + boolean patternUsed = rest.length() > 0; + if(patternUsed) { // the rest is Ant glob pattern glob = patternScan(baseFile, rest, createBackRef(restSize)); } else @@ -262,13 +277,15 @@ public final class DirectoryBrowserSupport implements HttpResponse { } if(glob!=null) { + List> filteredGlob = keepReadabilityOnlyOnDescendants(baseFile, patternUsed, glob); + // serve glob req.setAttribute("it", this); List parentPaths = buildParentPath(base,restSize); req.setAttribute("parentPath",parentPaths); req.setAttribute("backPath", createBackRef(restSize)); req.setAttribute("topPath", createBackRef(parentPaths.size()+restSize)); - req.setAttribute("files", glob); + req.setAttribute("files", filteredGlob); req.setAttribute("icon", icon); req.setAttribute("path", path); req.setAttribute("pattern",rest); @@ -332,6 +349,57 @@ public final class DirectoryBrowserSupport implements HttpResponse { rsp.serveFile(req, in, lastModified, -1, length, baseFile.getName() ); } } + + private List> keepReadabilityOnlyOnDescendants(VirtualFile root, boolean patternUsed, List> pathFragmentsList){ + Stream> pathFragmentsStream = pathFragmentsList.stream().map((List pathFragments) -> { + List mappedFragments = new ArrayList<>(pathFragments.size()); + String relativePath = ""; + for (int i = 0; i < pathFragments.size(); i++) { + Path current = pathFragments.get(i); + if (i == 0) { + relativePath = current.title; + } else { + relativePath += "/" + current.title; + } + + if (!current.isReadable) { + if (patternUsed) { + // we do not want to leak information about existence of folders / files satisfying the pattern inside that folder + return null; + } + mappedFragments.add(current); + return mappedFragments; + } else { + if (isDescendant(root, relativePath)) { + mappedFragments.add(current); + } else { + if (patternUsed) { + // we do not want to leak information about existence of folders / files satisfying the pattern inside that folder + return null; + } + mappedFragments.add(Path.createNotReadableVersionOf(current)); + return mappedFragments; + } + } + } + return mappedFragments; + }); + + if (patternUsed) { + pathFragmentsStream = pathFragmentsStream.filter(Objects::nonNull); + } + + return pathFragmentsStream.collect(Collectors.toList()); + } + + private boolean isDescendant(VirtualFile root, String relativePath){ + try { + return ALLOW_SYMLINK_ESCAPE || !root.supportIsDescendant() || root.isDescendant(relativePath); + } + catch (IOException e) { + return false; + } + } private String getPath(StaplerRequest req) { String path = req.getRestOfPath(); @@ -365,30 +433,83 @@ public final class DirectoryBrowserSupport implements HttpResponse { return buf.toString(); } - private static void zip(OutputStream outputStream, VirtualFile dir, String glob) throws IOException { + private static void zip(StaplerResponse rsp, VirtualFile root, VirtualFile dir, String glob) throws IOException, InterruptedException { + OutputStream outputStream = rsp.getOutputStream(); 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; - } - // 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); + + if (glob.isEmpty()) { + if (!root.supportsQuickRecursiveListing()) { + // avoid slow listing when the Glob can do a quicker job + glob = "**"; } - zos.closeEntry(); + } + + 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); } } } @@ -497,6 +618,10 @@ public final class DirectoryBrowserSupport implements HttpResponse { return cal; } + public static Path createNotReadableVersionOf(Path that){ + return new Path(that.href, that.title, that.isFolder, that.size, false); + } + private static final long serialVersionUID = 1L; } diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java index ada651bbe4c6b723538606f9d67ed173a7724d88..1b0c4088fb83978e9f14cb044aae88d1aa8d6126 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; @@ -92,7 +92,7 @@ public class DownloadService extends PageDecorator { if(Jenkins.getInstance().hasPermission(Jenkins.READ)) { long now = System.currentTimeMillis(); for (Downloadable d : Downloadable.all()) { - if(d.getDue()") .append("Behaviour.addLoadEvent(function() {") .append(" downloadService.download(") @@ -309,7 +309,7 @@ public class DownloadService extends PageDecorator { * 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"); 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/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/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index e94790857676e28cca1c6def7f4b0d7f1ad30ca3..37e46769c3919d9b69898aee5a7d562bfb8ce23f 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -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 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,13 @@ 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(); + List r = new ArrayList<>(); r.addAll(usages.keySet()); Collections.sort(r); return r; } - public @Nonnull Hashtable getUsages() { + public @CheckForNull Hashtable getUsages() { return usages; } @@ -993,7 +993,7 @@ public class Fingerprint implements ModelObject, Saveable { // this is for remote API @Exported(name="usage") public @Nonnull List _getUsages() { - List r = new ArrayList(); + List r = new ArrayList<>(); final Jenkins instance = Jenkins.getInstance(); for (Entry e : usages.entrySet()) { final String itemName = e.getKey(); @@ -1029,6 +1029,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); @@ -1079,7 +1087,7 @@ 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 + for (Entry e : new Hashtable<>(usages).entrySet()) {// copy because we mutate Job j = Jenkins.getInstance().getItemByFullName(e.getKey(),Job.class); if(j==null) {// no such job any more. recycle the record modified = true; @@ -1150,7 +1158,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 +1199,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); @@ -1373,7 +1381,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 +1420,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 + "]"; } /** 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/InvisibleAction.java b/core/src/main/java/hudson/model/InvisibleAction.java index 25242c332afb358e663522527d01ddda9e9922c8..bd0ef82b522b6d49dca9f6cdc616187c4518da99 100644 --- a/core/src/main/java/hudson/model/InvisibleAction.java +++ b/core/src/main/java/hudson/model/InvisibleAction.java @@ -24,12 +24,16 @@ package hudson.model; /** - * Partial {@link Action} implementation that doesn't have any UI presence. + * Partial {@link Action} implementation that doesn't have any UI presence (unless the {@link #getUrlName()} is overrided). * *

    * This class can be used as a convenient base class, when you use * {@link Action} for just storing data associated with a build. * + *

    + * It could also be used to reduce the amount of code required to just create an accessible url for tests + * by overriding the {@link #getUrlName()} method. + * * @author Kohsuke Kawaguchi * @since 1.188 */ @@ -42,7 +46,7 @@ public abstract class InvisibleAction implements Action { return null; } - public final String getUrlName() { + public String getUrlName() { return null; } } diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 195d4d8c0ce95322284d279d8b6176286cd24958..b5cf8251f1817e0ff95c6ab7b8930f00944bf945 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. @@ -290,16 +290,10 @@ public abstract class ItemGroupMixIn { Jenkins.getInstance().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 { diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java index d0a26238a3d3f71a650c631ab5ca2caec3a901b3..fb812e7a653ade50c2190896d463d15b5091290e 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. @@ -173,7 +173,7 @@ 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(); @@ -224,7 +224,7 @@ public class Items { public static List fromNameList(ItemGroup context, @Nonnull String list, @Nonnull Class type) { final Jenkins jenkins = Jenkins.getInstance(); - 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)) { diff --git a/core/src/main/java/hudson/model/JDK.java b/core/src/main/java/hudson/model/JDK.java index abcb1a9f74d039af0fce62acac8e52b89da2effe..b7181ab45b21eff3ae9124820eddb51f0ee380fa 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; @@ -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; } } diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 4b149034bd825bf448e8b6aad5416a8afd748b12..820c85e8aea35c0a3a53fc4fb516707da60bb9c7 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; @@ -234,7 +232,7 @@ public abstract class Job, RunT extends Run>(); + properties = new CopyOnWriteList<>(); for (JobProperty p : properties) p.setOwner(this); @@ -597,14 +595,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; } @@ -743,7 +741,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 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 +1021,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 +1031,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 +1103,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 +1199,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 +1212,7 @@ public abstract class Job, RunT extends Run, RunT extends Run(reports); + cachedBuildHealthReports = new ArrayList<>(reports); } return reports; @@ -1334,7 +1332,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())); @@ -1471,7 +1469,7 @@ public abstract class Job, RunT extends Run data = new DataSetBuilder(); + DataSetBuilder data = new DataSetBuilder<>(); for (Run r : getNewBuilds()) { if (r.isBuilding()) continue; diff --git a/core/src/main/java/hudson/model/JobPropertyDescriptor.java b/core/src/main/java/hudson/model/JobPropertyDescriptor.java index 6955dff091ccc1c5ad12a9a5c9d536b17694a81b..1a90cedf85dd3ccb1e5dce75ffc2c76367789826 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); diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index 606068104e3f3ecf925343c70973908d0183eb53..0cb65eea38cbf25f41df19631dda91e53798f44a 100644 --- a/core/src/main/java/hudson/model/Label.java +++ b/core/src/main/java/hudson/model/Label.java @@ -241,7 +241,7 @@ public abstract class Label extends Actionable implements Comparable