diff --git a/.gitignore b/.gitignore
index 60ba89065c90d09f0cc1ea8935b0bf08b691b0e9..5c8b1b7ac8b0be6f49db372e82de07e26bdac765 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,9 @@ out
.project
build
+# VSCode workspace file
+*.code-workspace
+
# vim
*~
*.swp
@@ -49,4 +52,5 @@ jenkins_*.changes
*.zip
push-build.sh
war/node_modules/
+war/yarn-error.log
.java-version
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a2d496cc2b211139d6f9b2eee27cb0be380ec8f5
--- /dev/null
+++ b/.mvn/extensions.xml
@@ -0,0 +1,7 @@
+
+
+ io.jenkins.tools.incrementals
+ git-changelist-maven-extension
+ 1.0-beta-4
+
+
diff --git a/.mvn/maven.config b/.mvn/maven.config
new file mode 100644
index 0000000000000000000000000000000000000000..e54fdacfe62468e4e85ebe51890384926933b476
--- /dev/null
+++ b/.mvn/maven.config
@@ -0,0 +1 @@
+-Pmight-produce-incrementals
diff --git a/BUILDING.TXT b/BUILDING.TXT
deleted file mode 100644
index bde5cadf353210259628baa34b1813f0acddc5b1..0000000000000000000000000000000000000000
--- a/BUILDING.TXT
+++ /dev/null
@@ -1,11 +0,0 @@
-If you want simply to have the jenkins.war as fast as possible (without test
-execution), run:
-
- mvn clean install -pl war -am -DskipTests
-
-The WAR file will be in war/target/jenkins.war (you can play with it)
-
-For more information on building Jenkins, visit
-https://wiki.jenkins-ci.org/display/JENKINS/Building+Jenkins
-
-Have Fun !!
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 01de1fea2cc3acf1ea8e561dcb74edb6cdd64aba..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.3.9 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
+ 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
@@ -46,13 +46,19 @@ Functional tests (`test` module) take a while even on server-grade machines.
Most of the tests will be launched by the continuous integration instance,
so there is no strict need to run full test suites before proposing a pull request.
+There are 3 profiles for tests:
+
+* `light-test` - only unit tests, no functional tests
+* `smoke-test` - run unit tests + a number of functional 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:
@@ -63,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.
@@ -77,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.
@@ -106,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/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d4541bdfcbaa7904cfc9264f6a823d80d1b55992
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,26 @@
+# This is a Dockerfile definition for Experimental Docker builds.
+# DockerHub: https://hub.docker.com/r/jenkins/jenkins-experimental/
+# If you are looking for official images, see https://github.com/jenkinsci/docker
+FROM maven:3.5.4-jdk-8 as builder
+
+COPY .mvn/ /jenkins/src/.mvn/
+COPY cli/ /jenkins/src/cli/
+COPY core/ /jenkins/src/core/
+COPY src/ /jenkins/src/src/
+COPY test/ /jenkins/src/test/
+COPY war/ /jenkins/src/war/
+COPY *.xml /jenkins/src/
+COPY LICENSE.txt /jenkins/src/LICENSE.txt
+COPY licenseCompleter.groovy /jenkins/src/licenseCompleter.groovy
+COPY show-pom-version.rb /jenkins/src/show-pom-version.rb
+
+WORKDIR /jenkins/src/
+RUN mvn clean install --batch-mode -Plight-test
+
+# The image is based on the previous weekly, new changes in jenkinci/docker are not applied
+FROM jenkins/jenkins:latest
+
+LABEL Description="This is an experimental image for the master branch of the Jenkins core" Vendor="Jenkins Project"
+
+COPY --from=builder /jenkins/src/war/target/jenkins.war /usr/share/jenkins/jenkins.war
+ENTRYPOINT ["tini", "--", "/usr/local/bin/jenkins.sh"]
diff --git a/Jenkinsfile b/Jenkinsfile
index 3599b383fcffd0d8de8485726eb0346f8c949d02..355e20ebc51d538975cf656b4df8ba00066760eb 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -13,15 +13,18 @@
def runTests = true
def failFast = false
-properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '20'))])
+properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '20')), 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, 11]
def builds = [:]
for(i = 0; i < buildTypes.size(); i++) {
+for(j = 0; j < jdks.size(); j++) {
def buildType = buildTypes[i]
- builds[buildType] = {
+ def jdk = jdks[j]
+ builds["${buildType}-jdk${jdk}"] = {
node(buildType.toLowerCase()) {
timestamps {
// First stage is actually checking out the source. Since we're using Multibranch
@@ -30,19 +33,23 @@ for(i = 0; i < buildTypes.size(); i++) {
checkout scm
}
+ def changelistF = "${pwd tmp: true}/changelist"
+ def m2repo = "${pwd tmp: true}/m2repo"
+
// Now run the actual build.
stage("${buildType} Build / Test") {
timeout(time: 180, unit: 'MINUTES') {
// See below for what this method does - we're passing an arbitrary environment
// variable to it so that JAVA_OPTS and MAVEN_OPTS are set correctly.
withMavenEnv(["JAVA_OPTS=-Xmx1536m -Xms512m",
- "MAVEN_OPTS=-Xmx1536m -Xms512m"]) {
+ "MAVEN_OPTS=-Xmx1536m -Xms512m"], jdk) {
// Actually run Maven!
// -Dmaven.repo.local=… tells Maven to create a subdir in the temporary directory for the local Maven repository
- def mvnCmd = "mvn -Pdebug -U javadoc:javadoc clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -Dmaven.repo.local=${pwd tmp: true}/m2repo -s settings-azure.xml -e"
+ def mvnCmd = "mvn -Pdebug -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -Dmaven.repo.local=$m2repo -s settings-azure.xml -e"
+
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
}
@@ -51,34 +58,64 @@ for(i = 0; i < buildTypes.size(); i++) {
}
// Once we've built, archive the artifacts and the test results.
- stage("${buildType} Archive Artifacts / Test Results") {
- def files = findFiles(glob: '**/target/*.jar, **/target/*.war, **/target/*.hpi')
- renameFiles(files, buildType.toLowerCase())
-
- archiveArtifacts artifacts: '**/target/*.jar, **/target/*.war, **/target/*.hpi',
- fingerprint: true
+ stage("${buildType} Publishing") {
if (runTests) {
- junit healthScaleFactor: 20.0, testResults: '**/target/surefire-reports/*.xml'
+ junit healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/*.xml'
+ archiveArtifacts allowEmptyArchive: true, artifacts: '**/target/surefire-reports/*.dumpstream'
+ }
+ if (buildType == 'Linux') {
+ def changelist = readFile(changelistF)
+ dir(m2repo) {
+ archiveArtifacts artifacts: "**/*$changelist/*$changelist*",
+ excludes: '**/*.lastUpdated,**/jenkins-test/',
+ allowEmptyArchive: true, // in case we forgot to reincrementalify
+ fingerprint: true
+ }
}
}
}
}
}
+}}
+
+// TODO: ATH flow now supports Java 8 only, it needs to be reworked (INFRA-1690)
+builds.ath = {
+ node("docker&&highmem") {
+ // Just to be safe
+ deleteDir()
+ def fileUri
+ def metadataPath
+ dir("sources") {
+ checkout scm
+ withMavenEnv(["JAVA_OPTS=-Xmx1536m -Xms512m",
+ "MAVEN_OPTS=-Xmx1536m -Xms512m"], 8) {
+ sh "mvn --batch-mode --show-version -DskipTests -am -pl war package -Dmaven.repo.local=${pwd tmp: true}/m2repo -s settings-azure.xml"
+ }
+ dir("war/target") {
+ fileUri = "file://" + pwd() + "/jenkins.war"
+ }
+ metadataPath = pwd() + "/essentials.yml"
+ }
+ dir("ath") {
+ runATH jenkins: fileUri, metadataFile: metadataPath
+ }
+ }
}
builds.failFast = failFast
parallel builds
+infra.maybePublishIncrementals()
// This method sets up the Maven and JDK tools, puts them in the environment along
// with whatever other arbitrary environment variables we passed in, and runs the
// body we passed in within that environment.
-void withMavenEnv(List envVars = [], def body) {
+void withMavenEnv(List envVars = [], def javaVersion, def body) {
// The names here are currently hardcoded for my test environment. This needs
// to be made more flexible.
// Using the "tool" Workflow call automatically installs those tools on the
// node.
String mvntool = tool name: "mvn", type: 'hudson.tasks.Maven$MavenInstallation'
- String jdktool = tool name: "jdk8", type: 'hudson.model.JDK'
+ String jdktool = tool name: "jdk${javaVersion}", type: 'hudson.model.JDK'
// Set JAVA_HOME, MAVEN_HOME and special PATH variables for the tools we're
// using.
@@ -92,15 +129,3 @@ void withMavenEnv(List envVars = [], def body) {
body.call()
}
}
-
-void renameFiles(def files, String prefix) {
- for(i = 0; i < files.length; i++) {
- def newPath = files[i].path.replace(files[i].name, "${prefix}-${files[i].name}")
- def rename = "${files[i].path} ${newPath}"
- if(isUnix()) {
- sh "mv ${rename}"
- } else {
- bat "move ${rename}"
- }
- }
-}
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 133939ecdecf25f4dab809e4eddcc5d45e7157f5..0a5aa6a5c328e6072e7a164f4bf974656b676cb0 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -4,8 +4,8 @@
org.jenkins-ci.main
- pom
- 2.91-SNAPSHOT
+ jenkins-parent
+ ${revision}${changelist}cli
@@ -13,6 +13,11 @@
Jenkins cliCommand line interface for Jenkins
+
+
+ Medium
+
+
org.powermock
@@ -21,13 +26,17 @@
org.powermock
- powermock-api-mockito
+ powermock-api-mockito2testorg.kohsukeaccess-modifier-annotation
+
+ org.jenkins-ci
+ annotation-indexer
+ commons-codeccommons-codec
@@ -54,9 +63,15 @@
org.apache.sshdsshd-core
- 1.6.0
+ 1.7.0true
+
+
+ net.i2p.crypto
+ eddsa
+ 0.3.0
+ org.slf4jslf4j-jdk14
@@ -67,6 +82,15 @@
trilead-ssh2build214-jenkins-1
+
+ com.google.code.findbugs
+ annotations
+ provided
+
+
+ commons-lang
+ commons-lang
+
@@ -89,7 +113,7 @@
hudson.cli.CLI
- ${build.version}
+ ${project.version}
diff --git a/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties b/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties
index 39b91895a20a85254399d1f4f0206c6af45ff094..defbd48204e4784090fe0e17e28fe5bc395abf47 100644
--- a/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties
+++ b/cli/src/filter/resources/jenkins/cli/jenkins-cli-version.properties
@@ -1 +1 @@
-version=${build.version}
+version=${project.version}
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index cbd3acd04458b6fe1416089e1660784f54bf6686..0ffef53f9305a6e9d260826e092e805fe9b8465e 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -77,6 +77,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
/**
* CLI entry point to Jenkins.
@@ -452,6 +453,10 @@ public class CLI implements AutoCloseable {
String user = null;
String auth = null;
+
+ String userIdEnv = System.getenv("JENKINS_USER_ID");
+ String tokenEnv = System.getenv("JENKINS_API_TOKEN");
+
boolean strictHostKey = false;
while(!args.isEmpty()) {
@@ -563,6 +568,17 @@ public class CLI implements AutoCloseable {
return -1;
}
+ if (auth == null) {
+ // -auth option not set
+ if (StringUtils.isNotBlank(userIdEnv) && StringUtils.isNotBlank(tokenEnv)) {
+ auth = StringUtils.defaultString(userIdEnv).concat(":").concat(StringUtils.defaultString(tokenEnv));
+ } else if (StringUtils.isNotBlank(userIdEnv) || StringUtils.isNotBlank(tokenEnv)) {
+ printUsage(Messages.CLI_BadAuth());
+ return -1;
+ } // Otherwise, none credentials were set
+
+ }
+
if (!url.endsWith("/")) {
url += '/';
}
@@ -689,13 +705,13 @@ public class CLI implements AutoCloseable {
connection.sendLocale(Locale.getDefault().toString());
connection.sendStart();
connection.begin();
- final OutputStream stdin = connection.streamStdin();
new Thread("input reader") {
@Override
public void run() {
try {
+ final OutputStream stdin = connection.streamStdin();
int c;
- while ((c = System.in.read()) != -1) { // TODO use InputStream.available
+ while (!connection.complete && (c = System.in.read()) != -1) {
stdin.write(c);
}
connection.sendEndStdin();
@@ -809,9 +825,14 @@ public class CLI implements AutoCloseable {
return authenticate(Collections.singleton(key));
}
+ /** For access from {@code HelpCommand}. */
+ static String usage() {
+ return Messages.CLI_Usage();
+ }
+
private static void printUsage(String msg) {
if(msg!=null) System.out.println(msg);
- System.err.println(Messages.CLI_Usage());
+ System.err.println(usage());
}
static final Logger LOGGER = Logger.getLogger(CLI.class.getName());
diff --git a/cli/src/main/java/hudson/cli/Connection.java b/cli/src/main/java/hudson/cli/Connection.java
index 017051a64a646a76b4ecfe81966381e3874adbca..eacc567a7e5d822afc9e4d1b20afc5473cbb0b3b 100644
--- a/cli/src/main/java/hudson/cli/Connection.java
+++ b/cli/src/main/java/hudson/cli/Connection.java
@@ -55,6 +55,7 @@ import java.security.Signature;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
+import org.jenkinsci.remoting.util.AnonymousClassWarnings;
/**
* Used by Jenkins core only in deprecated Remoting-based CLI.
@@ -102,7 +103,7 @@ public class Connection {
* Sends a serializable object.
*/
public void writeObject(Object o) throws IOException {
- ObjectOutputStream oos = new ObjectOutputStream(out);
+ ObjectOutputStream oos = AnonymousClassWarnings.checkingObjectOutputStream(out);
oos.writeObject(o);
// don't close oss, which will close the underlying stream
// no need to flush either, given the way oos is implemented
diff --git a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
index d7753a750498aff78325fc04326eb63b7e45209d..9f87dd4a7b38a174229ccfc02b04779b51002951 100644
--- a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
+++ b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
@@ -71,7 +71,7 @@ public class PrivateKeyProvider {
/**
* Read keys from default keyFiles
*
- * .ssh/id_rsa, .ssh/id_dsa and .ssh/identity.
+ * {@code .ssh/id_rsa}, {@code .ssh/id_dsa} and {@code .ssh/identity}.
*
* @return true if some key was read successfully.
*/
diff --git a/cli/src/main/java/hudson/cli/SSHCLI.java b/cli/src/main/java/hudson/cli/SSHCLI.java
index bdd6c2beb50e5e696af3467e5bae7598f41eb5ef..39dc9210a3e1615bac888932c689d97ad4dd1b4d 100644
--- a/cli/src/main/java/hudson/cli/SSHCLI.java
+++ b/cli/src/main/java/hudson/cli/SSHCLI.java
@@ -51,6 +51,8 @@ import org.apache.sshd.common.util.io.NoCloseInputStream;
import org.apache.sshd.common.util.io.NoCloseOutputStream;
import org.apache.sshd.common.util.security.SecurityUtils;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
/**
* Implements SSH connection mode of {@link CLI}.
* In a separate class to avoid any class loading of {@code sshd-core} when not using {@code -ssh} mode.
@@ -59,6 +61,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
*/
class SSHCLI {
+ @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", justification = "Due to whatever reason FindBugs reports it fot try-with-resources")
static int sshConnection(String jenkinsUrl, String user, List args, PrivateKeyProvider provider, final boolean strictHostKey) throws IOException {
Logger.getLogger(SecurityUtils.class.getName()).setLevel(Level.WARNING); // suppress: BouncyCastle not registered, using the default JCE provider
URL url = new URL(jenkinsUrl + "login");
diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties
index 921fe67a211da560ac75fc355179a117b3c00dc2..2e7fde44cb075a87813b4cced7eb48f2952c85cd 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -1,22 +1,25 @@
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\
- -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\
- -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\
- -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\
+ \ -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\
+ \ -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\
+ \ -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\
+ \ -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\
+ \ 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\n\
+ The available commands depend on the server. Run the 'help' command to \
see the list.
CLI.NoURL=Neither -s nor the JENKINS_URL env var is specified.
CLI.VersionMismatch=Version mismatch. This CLI cannot work with this Jenkins server.
CLI.NoSuchFileExists=No such file exists: {0}
+CLI.BadAuth=The JENKINS_USER_ID and JENKINS_API_TOKEN env vars should be both set or left empty.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_es.properties b/cli/src/main/resources/hudson/cli/client/Messages_es.properties
index f9979a5619831289dfb4952ffe328e4d6d6b9798..b8ff42b3c23b707bdd8b105f53a624e034ebcc7d 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_es.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_es.properties
@@ -1,10 +1,24 @@
CLI.Usage=Jenkins CLI\n\
- Usar: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
- Options:\n\
- \ -s URL : direccin web (por defecto se usa la variable JENKINS_URL)\n\
+ Uso: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
+ Opciones:\n\
+ -s URL : direcci\u00f3n web (por defecto se usa la variable JENKINS_URL)\n\
+ -http : usa un protocolo plano sobre HTTP(S) (es el uso por defecto; excluyente con -ssh y -remoting)\n\
+ -ssh : usa el protocolo SSH (requiere -user; el puerto SSH debe estar abierto en el servidor y el usuario debe tener registrada su clave publica)\n\
+ -remoting : usa el protocolo deprecado de Remoting (siempre que est\u00e9 habilitado en el servidor; s\u00f3lo para compatibilidad con comandos heredados o legacy)\n\
+ -i KEY : clave privada SSH usada para autenticaci\u00f3n (usado con -ssh o -remoting)\n\
+ -p HOST:PORT : host y puerto para el uso de proxy HTTPS. Ver https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ -noCertificateCheck : elude por completo la verificaci\u00f3n del certificado HTTPS. Usar con precauci\u00f3n\n\
+ -noKeyAuth : intenta no cargar la clave privada de autenticaci\u00f3n SSH. Presenta conflicto con -i\n\
+ -user : especifica el usuario (se usa con -ssh)\n\
+ -strictHostKey : solicita la comprobaci\u00f3n estricta de la clave del host (se usa con -ssh)\n\
+ -logger FINE : habilita logs detallados en el cliente\n\
+ -auth [ USER:SECRET | @FILE ] : especifica el usuario y contrase\u00f1a o API token (o los carga de un fichero);\n\
+ se usa con -http o -remoting pero s\u00f3lo si el puerto del agente JNLP est\u00e1 deshabilitado.\n\
+ No es necesario si las variables de entorno JENKINS_USER_ID y JENKINS_API_TOKEN se encuentran configuradas.\n\
\n\
- La lista completa de comandos disponibles depende del servidor. Ejecuta\n\
- el comando ''help'' para ver la lista.
-CLI.NoURL=No se ha especificado el parmetro -s ni la variable JENKINS_URL
-CLI.VersionMismatch=La versin no coincide. Esta consola "CLI" no se puede usar en este Jenkins
+ La lista de comandos disponibles depende del servidor. Ejecuta\n\
+ el comando ''help'' para ver la lista completa.
+CLI.NoURL=No se ha especificado el par\u00e1metro -s ni la variable JENKINS_URL
+CLI.VersionMismatch=La versi\u00f3n no coincide. Esta consola "CLI" no se puede usar en este Jenkins
CLI.NoSuchFileExists=El fichero {0} no existe.
+CLI.BadAuth=Ambas variables de entorno JENKINS_USER_ID y JENKINS_API_TOKEN deber\u00edan estar configuradas o mantenerse vac\u00edas.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties
new file mode 100644
index 0000000000000000000000000000000000000000..16fa41c48c9a4aad3d63e9f40e2d017b3bd9fb77
--- /dev/null
+++ b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties
@@ -0,0 +1,44 @@
+# The MIT License
+#
+# Copyright (c) 2013, Sun Microsystems, Inc., Kohsuke Kawaguchi, Pei-Tang Huang,
+# Chunghwa Telecom Co., Ltd., and a number of other of contributers
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+CLI.Usage=Jenkins CLI\n\
+ \u7528\u6cd5: java -jar jenkins-cli.jar [-s url] \u547d\u4ee4 [\u9009\u9879...] \u53c2\u6570...\n\
+ \u9009\u9879:\n\
+ -s URL : \u670d\u52a1\u5668url \uff08\u9ed8\u8ba4\u662fjenkins_url\u73af\u5883\u53d8\u91cf\uff09\n\
+ -http : \u5728http%28s%29\u4e0a\u4f7f\u7528\u539f\u59cb\u7684cli\u534f\u8bae\uff08\u9ed8\u8ba4\u503c%3b \u4e0e -ssh \u548c -remoting \u4e92\u65a5\uff09\n\
+ -ssh : \u4f7f\u7528ssh\u534f\u8bae\uff08\u9700\u8981 -user%3b \u670d\u52a1\u5668\u7684ssh\u7aef\u53e3\u5fc5\u987b\u6253\u5f00\uff0c\u4e14\u7528\u6237\u5fc5\u987b\u5df2\u6ce8\u518c\u516c\u94a5\u3002\uff09\n\
+ -remoting : \u4f7f\u7528\u4e0d\u63a8\u8350\u7684\u8fdc\u7a0b\u4fe1\u9053\u534f\u8bae \uff08\u5982\u679c\u670d\u52a1\u5668\u4e0a\u662f\u6253\u5f00\u7684\uff1b\u4ec5\u7528\u4e8e\u517c\u5bb9\u9057\u7559\u547d\u4ee4\u6216\u547d\u4ee4\u6a21\u5f0f\uff09\n\
+ -i KEY : \u7528\u4e8e\u8ba4\u8bc1\u7684ssh\u79c1\u94a5\u6587\u4ef6\uff08\u4e0e -ssh \u6216 -remoting \u4e00\u8d77\u4f7f\u7528\uff09\n\
+ -p HOST:PORT : \u7528\u6237https\u4ee3\u7406\u96a7\u9053\u7684http\u4ee3\u7406\u4e3b\u673a\u548c\u7aef\u53e3\u3002\u53c2\u89c1 https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ -noCertificateCheck : \u5b8c\u5168\u5ffd\u7565https\u8bc1\u4e66\u8ba4\u8bc1\u3002\u8c28\u614e\u4f7f\u7528\n\
+ -noKeyAuth : \u65e0\u9700\u5c1d\u8bd5\u52a0\u8f7dssh\u8ba4\u8bc1\u79c1\u94a5\u3002\u4e0e -i \u76f8\u53cd\n\
+ -user : \u6307\u5b9a\u7528\u6237\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\
+ -strictHostKey : \u8981\u6c42\u9a8c\u8bc1\u4e3b\u673akey\u68c0\u67e5\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\
+ -logger FINE : \u5141\u8bb8\u5ba2\u6237\u7aef\u8be6\u7ec6\u65e5\u5fd7\u8bb0\u5f55\n\
+ -auth [ USER:SECRET | @FILE ] : \u6307\u5b9a\u7528\u6237\u540d\u4e0e\u5bc6\u7801\u6216\u7528\u6237\u540d\u4e0eapi token\uff08\u6216\u8005\u4ece\u6587\u4ef6\u52a0\u8f7d\uff09\uff1b\n\
+ \u4e0e -http \u4e00\u8d77\u4f7f\u7528\uff0c\u6216\u8005\u53ea\u5728jnlp\u4ee3\u7406\u7aef\u53e3\u7981\u7528\u65f6\u4e0e -remoting \u4e00\u8d77\u4f7f\u7528\n\
+ \n\
+ \u53ef\u7528\u7684\u547d\u4ee4\u53d6\u51b3\u4e8e\u670d\u52a1\u5668\u3002\u6267\u884c 'help' \u547d\u4ee4\u53ef\u4ee5\u67e5\u770b\u5b8c\u6574\u6e05\u5355\u3002
+CLI.NoURL=\u6c92\u6709\u6307\u5b9a -s \u53c2\u6570\u6216\u8005 jenkins_url \u73af\u5883\u53d8\u91cf\u3002
+CLI.VersionMismatch=\u7248\u672c\u4e0d\u5339\u914d\u3002cli \u65e0\u6cd5\u5728\u6b64 jenkins \u670d\u52a1\u5668\u4e0a\u8fd0\u884c\u3002
+CLI.NoSuchFileExists=\u6587\u4ef6\u4e0d\u5b58\u5728: {0}
diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
index 86970265089b4bf41cb7bc4aae7d6e320447d21a..a29af91e6eb818245140e987dda1c323786a3fd8 100644
--- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
+++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
@@ -47,7 +47,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
-@PrepareForTest(CLI.class) // When mocking new operator caller has to be @PreparedForTest, not class itself
+@PrepareForTest({CLI.class, CLIConnectionFactory.class}) // When mocking new operator caller has to be @PreparedForTest, not class itself
public class PrivateKeyProviderTest {
@Test
@@ -113,15 +113,9 @@ public class PrivateKeyProviderTest {
private Iterable withKeyPairs(final KeyPair... expected) {
return Mockito.argThat(new ArgumentMatcher>() {
- @Override public void describeTo(Description description) {
- description.appendText(Arrays.asList(expected).toString());
- }
-
- @Override public boolean matches(Object argument) {
- if (!(argument instanceof Iterable)) throw new IllegalArgumentException("Not an instance of Iterrable");
- @SuppressWarnings("unchecked")
- final Iterable actual = (Iterable) argument;
+ @Override
+ public boolean matches(Iterable actual) {
int i = 0;
for (KeyPair akp: actual) {
if (!eq(expected[i].getPublic(), akp.getPublic())) return false;
diff --git a/core/pom.xml b/core/pom.xml
index 4ad829605c56596d4488f69755f34696ac1e082c..4ba0498ae9a876d95afc42bad9b0b84eb47eda0a 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -28,8 +28,8 @@ THE SOFTWARE.
org.jenkins-ci.main
- pom
- 2.91-SNAPSHOT
+ jenkins-parent
+ ${revision}${changelist}jenkins-core
@@ -39,11 +39,9 @@ THE SOFTWARE.
true
- 1.253
+ 1.2562.5.6.SEC03
- 2.4.11
-
- true
+ 2.4.12
@@ -111,7 +109,7 @@ THE SOFTWARE.
com.github.jnrjnr-posix
- 3.0.41
+ 3.0.45org.kohsuke
@@ -121,7 +119,7 @@ THE SOFTWARE.
org.jenkins-citrilead-ssh2
- build-217-jenkins-11
+ build-217-jenkins-14org.kohsuke.stapler
@@ -179,6 +177,17 @@ THE SOFTWARE.
teststest
+
+ io.jenkins.stapler
+ jenkins-stapler-support
+ 1.0
+
+
+ org.hamcrest
+ hamcrest-library
+ 1.3
+ test
+ com.infradna.tool
@@ -203,17 +212,16 @@ THE SOFTWARE.
org.jenkins-ciannotation-indexer
- 1.12org.jenkins-cibytecode-compatibility-transformer
- 1.8
+ 2.0-beta-2org.jenkins-citask-reactor
- 1.4
+ 1.5org.jvnet.localizer
@@ -240,6 +248,20 @@ THE SOFTWARE.
+
+
+ xpp3
+ xpp3
+ 1.1.4c
+
+
+ net.sf.kxml
+ kxml2
+ 2.3.0
+ jfreejfreechart
@@ -262,7 +284,6 @@ THE SOFTWARE.
commons-langcommons-lang
- 2.6commons-digester
@@ -446,11 +467,6 @@ THE SOFTWARE.
spring-aop${spring.version}
-
- xpp3
- xpp3
- 1.1.4c
- junitjunit
@@ -468,7 +484,7 @@ THE SOFTWARE.
org.powermock
- powermock-api-mockito
+ powermock-api-mockito2test
@@ -502,7 +518,7 @@ THE SOFTWARE.
org.jvnet.winpwinp
- 1.25
+ 1.27org.jenkins-ci
@@ -522,7 +538,7 @@ THE SOFTWARE.
net.java.dev.jnajna
- 4.2.1
+ 4.5.2org.kohsuke
@@ -532,7 +548,7 @@ THE SOFTWARE.
org.kohsukelibpam4j
- 1.8
+ 1.11org.kohsuke
@@ -547,7 +563,7 @@ THE SOFTWARE.
net.java.sezpozsezpoz
- 1.12
+ 1.13org.kohsuke.jinterop
@@ -557,8 +573,7 @@ THE SOFTWARE.
org.kohsuke.metainf-servicesmetainf-services
- 1.4
- provided
+ 1.8true
@@ -585,7 +600,6 @@ THE SOFTWARE.
com.google.code.findbugsannotations
- 3.0.0provided
@@ -764,7 +778,7 @@ THE SOFTWARE.
com.sun.winswwinsw
- 2.1.2
+ 2.2.0binexe${project.build.outputDirectory}/windows-service
@@ -796,25 +810,6 @@ THE SOFTWARE.
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
-
- testCompile
-
-
-
-
-
- org.codehaus.groovy
- groovy-all
- ${groovy.version}
-
-
- org.codehaus.mojofindbugs-maven-plugin
@@ -889,41 +884,5 @@ THE SOFTWARE.
true
-
-
- cobertura
-
-
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
-
- test
-
- execute
-
-
-
- ${project.basedir}/src/build-script
-
-
-
-
-
-
-
-
- maven-surefire-plugin
-
-
- true
-
-
-
-
-
diff --git a/core/src/build-script/Cobertura.groovy b/core/src/build-script/Cobertura.groovy
deleted file mode 100644
index 49b773b49ba17e79fa74975cf8a0b20d42153143..0000000000000000000000000000000000000000
--- a/core/src/build-script/Cobertura.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-import org.apache.maven.project.MavenProject;
-
-/**
- * Cobertura invoker.
- */
-public class Cobertura {
- private final MavenProject project;
- // maven helper
- private def maven;
- // ant builder
- private def ant;
- /**
- * Cobertura data file.
- */
- private final File ser;
-
- def Cobertura(project, maven, ant, ser) {
- this.project = maven.project;
- this.maven = maven;
- this.ant = ant;
- this.ser =ser;
-
- // define cobertura tasks
- ant.taskdef(resource:"tasks.properties")
- }
-
- // function that ensures that the given directory exists
- private String dir(String dir) {
- new File(project.basedir,dir).mkdirs();
- return dir;
- }
-
- /**
- * Instruments the given class dirs/jars by cobertura
- *
- * @param files
- * List of jar files and class dirs to instrument.
- */
- def instrument(files) {
- ant."cobertura-instrument"(todir:dir("target/cobertura-classes"),datafile:ser) {
- fileset(dir:"target/classes");
- files.each{ fileset(file:it) }
- }
- }
-
- def runTests() {
- ant.junit(fork:true, forkMode:"once", failureproperty:"failed", printsummary:true) {
- classpath {
- junitClasspath()
- }
- batchtest(todir:dir("target/surefire-reports")) {
- fileset(dir:"target/test-classes") {
- include(name:"**/*Test.class")
- }
- formatter(type:"xml")
- }
- sysproperty(key:"net.sourceforge.cobertura.datafile",value:ser)
- sysproperty(key:"hudson.ClassicPluginStrategy.useAntClassLoader",value:"true")
- }
- }
-
- def junitClasspath() {
- ant.pathelement(path: "target/cobertura-classes") // put the instrumented classes first
- ant.fileset(dir:"target/cobertura-classes",includes:"*.jar") // instrumented jar files
- ant.pathelement(path: maven.resolveArtifact("net.sourceforge.cobertura:cobertura:1.9")) // cobertura runtime
- project.getTestClasspathElements().each { ant.pathelement(path: it) } // the rest of the dependencies
- }
-
- def report(dirs) {
- maven.attachArtifact(ser,"ser","cobertura")
- ["html","xml"].each { format ->
- ant."cobertura-report"(format:format,datafile:ser,destdir:dir("target/cobertura-reports"),srcdir:"src/main/java") {
- dirs.each{ fileset(dir:it) }
- }
- }
- }
-
- def makeBuildFailIfTestFail() {
- if(ant.project.getProperty("failed")!=null && !Boolean.getBoolean("testFailureIgnore"))
- throw new Exception("Some unit tests failed");
- }
-}
diff --git a/core/src/build-script/readme.txt b/core/src/build-script/readme.txt
deleted file mode 100644
index 6c53a01026b7921e493e1c42f7463c0857c17c69..0000000000000000000000000000000000000000
--- a/core/src/build-script/readme.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Parts of the build scripts that are written in Groovy.
-
-IntelliJ needs Groovy files to be in its source directory to provide completion and such,
-so we need this to be in its own directory.
\ No newline at end of file
diff --git a/core/src/build-script/unitTest.groovy b/core/src/build-script/unitTest.groovy
deleted file mode 100644
index 653391aad7f15bbc6cdea2366f5f143bd4a0ae2b..0000000000000000000000000000000000000000
--- a/core/src/build-script/unitTest.groovy
+++ /dev/null
@@ -1,11 +0,0 @@
-// run unit tests
-
-ant.project.setBaseDir(project.basedir)
-ser=new File(project.basedir,"target/cobertura.ser"); // store cobertura data file in a module-specific location
-
-cob = new Cobertura(project,maven,ant,ser);
-
-cob.instrument([])
-cob.runTests()
-cob.report([])
-cob.makeBuildFailIfTestFail();
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_zh_CN.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_zh_CN.properties
deleted file mode 100644
index 0ef79b530b974daaaed8ae9bc346ed86fb71a2bc..0000000000000000000000000000000000000000
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_zh_CN.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file is under the MIT License by authors
-
-NewVersionAvailable=Jenkins\u65B0\u7248\u672C ({0})\u53EF\u70B9\u51FB download (\u53D8\u66F4\u8BF4\u660E)\u4E0B\u8F7D\u3002
-Or\ Upgrade\ Automatically=\u6216 \u81EA\u52A8\u5347\u7EA7\u7248\u672C
-UpgradeComplete=Jenkins {0} \u7248\u672C\u5347\u7EA7\u5DF2\u5B8C\u6210,\u7B49\u5F85\u91CD\u542F\u4E2D.
-UpgradeCompleteRestartNotSupported=Jenkins {0} \u7248\u672C\u5347\u7EA7\u5DF2\u5B8C\u6210,\u7B49\u5F85\u91CD\u542F\u4E2D.
diff --git a/core/src/filter/resources/hudson/model/hudson-version.properties b/core/src/filter/resources/hudson/model/hudson-version.properties
index 9b9343d10d659357b7acccbcf34bc2aece573625..defbd48204e4784090fe0e17e28fe5bc395abf47 100644
--- a/core/src/filter/resources/hudson/model/hudson-version.properties
+++ b/core/src/filter/resources/hudson/model/hudson-version.properties
@@ -1 +1 @@
-version=${build.version}
\ No newline at end of file
+version=${project.version}
diff --git a/core/src/filter/resources/jenkins/model/jenkins-version.properties b/core/src/filter/resources/jenkins/model/jenkins-version.properties
index 9b9343d10d659357b7acccbcf34bc2aece573625..defbd48204e4784090fe0e17e28fe5bc395abf47 100644
--- a/core/src/filter/resources/jenkins/model/jenkins-version.properties
+++ b/core/src/filter/resources/jenkins/model/jenkins-version.properties
@@ -1 +1 @@
-version=${build.version}
\ No newline at end of file
+version=${project.version}
diff --git a/core/src/filter/resources/jenkins/slaves/remoting-info.properties b/core/src/filter/resources/jenkins/slaves/remoting-info.properties
new file mode 100644
index 0000000000000000000000000000000000000000..6ac78f4ad43e96e9f1277206b67049f098979a21
--- /dev/null
+++ b/core/src/filter/resources/jenkins/slaves/remoting-info.properties
@@ -0,0 +1,6 @@
+# Remoting version, which is embedded into the core
+# This version MAY differ from what is really classloaded (see the "Pluggable Core Components", JENKINS-41196)
+remoting.embedded.version=${remoting.version}
+
+# Minimum Remoting version on external agents which is supported by the core
+remoting.minimum.supported.version=${remoting.minimum.supported.version}
diff --git a/core/src/main/groovy/hudson/util/LoadMonitor.groovy b/core/src/main/groovy/hudson/util/LoadMonitor.groovy
deleted file mode 100644
index 97d701816199c011e67c6fcc39e539563442f225..0000000000000000000000000000000000000000
--- a/core/src/main/groovy/hudson/util/LoadMonitor.groovy
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
- *
- * 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.util
-
-import hudson.model.Computer
-import jenkins.util.Timer
-import jenkins.model.Jenkins
-import hudson.model.Label
-import hudson.model.Queue.BlockedItem
-import hudson.model.Queue.BuildableItem
-import hudson.model.Queue.WaitingItem
-import hudson.triggers.SafeTimerTask
-import java.text.DateFormat
-import java.util.concurrent.TimeUnit
-
-/**
- * Spits out the load information.
- *
- *
- * I'm using this code to design the auto scaling feature.
- * In future this might be useful data to expose to the UI.
- *
- * @author Kohsuke Kawaguchi
- */
-public class LoadMonitorImpl extends SafeTimerTask {
-
- private final File dataFile;
- private List labels;
-
- public LoadMonitorImpl(File dataFile) {
- this.dataFile = dataFile;
- labels = Jenkins.getInstance().labels*.name;
- printHeaders();
- Timer.get().scheduleAtFixedRate(this,0,10*1000, TimeUnit.MILLISECONDS);
- }
-
- private String quote(Object s) { "\"${s}\""; }
-
- protected void printHeaders() {
- def headers = ["# of executors","# of busy executors","BuildableItems in Q","BuildableItem avg wait time"];
- def data = ["timestamp"];
- data += headers;
- data += ["WaitingItems in Q","BlockedItems in Q"];
-
- for( String label : labels)
- data += headers.collect { "${it} (${label}}" }
-
- dataFile.append(data.collect({ quote(it) }).join(",")+"\n");
- }
-
- @Override
- protected void doRun() {
- def now = new Date();
- def data = [];
- data.add(quote(FORMATTER.format(now)));
-
- def h = Jenkins.getInstance();
-
- def items = h.queue.items;
- def filterByType = {Class type -> items.findAll { type.isInstance(it) } }
-
- def builder = {List cs, Closure itemFilter ->
- // number of total executor, number of busy executor
- data.add(cs.sum { it.isOffline() ? 0 : it.numExecutors });
- data.add(cs.sum {Computer c ->
- c.executors.findAll { !it.isIdle() }.size()
- });
-
- // queue statistics
- def is = filterByType(BuildableItem).findAll(itemFilter);
- data.add(is.size());
- data.add(is.sum {BuildableItem bi -> now.time - bi.buildableStartMilliseconds }?:0 / Math.max(1,is.size()) );
- };
-
-
- // for the whole thing
- builder(Arrays.asList(h.computers),{ it->true });
-
- data.add(filterByType(WaitingItem).size());
- data.add(filterByType(BlockedItem).size());
-
- // per label stats
- for (String label : labels) {
- Label l = h.getLabel(label)
- builder(l.nodes.collect { it.toComputer() }) { BuildableItem bi -> bi.task.assignedLabel==l };
- }
-
- dataFile.append(data.join(",")+"\n");
- }
-
- private static final DateFormat FORMATTER = DateFormat.getDateTimeInstance();
-}
-
-new LoadMonitorImpl(new File("/files/hudson/load.txt"));
-
diff --git a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy b/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy
deleted file mode 100644
index 08cb1c23a3da552cc531cf3b655b0a915b43aee6..0000000000000000000000000000000000000000
--- a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-package jenkins.util.groovy
-
-import lib.FormTagLib
-import lib.LayoutTagLib
-import org.kohsuke.stapler.jelly.groovy.JellyBuilder
-import org.kohsuke.stapler.jelly.groovy.Namespace
-import lib.JenkinsTagLib
-
-/**
- * Base class for utility classes for Groovy view scripts
- *
- * Usage from script of a subclass, say ViewHelper:
- *
- * new ViewHelper(delegate).method();
- *
- * see ModularizeViewScript in ui-samples for an example how to use this class.
- *
- * @author Stefan Wolf (wolfs)
- */
-abstract class AbstractGroovyViewModule {
- JellyBuilder builder
- FormTagLib f
- LayoutTagLib l
- JenkinsTagLib t
- Namespace st
-
- public AbstractGroovyViewModule(JellyBuilder b) {
- builder = b
- f= builder.namespace(FormTagLib)
- l=builder.namespace(LayoutTagLib)
- t=builder.namespace(JenkinsTagLib)
- st=builder.namespace("jelly:stapler")
-
- }
-
- def methodMissing(String name, args) {
- builder.invokeMethod(name,args)
- }
-
- def propertyMissing(String name) {
- builder.getProperty(name)
- }
-
- def propertyMissing(String name, value) {
- builder.setProperty(name, value)
- }
-}
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index bc00b2a0c9a68ac9412d4d7934a4afc445a84b3d..5d173c3839022d162265f7b2a2779c436e30791a 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -25,10 +25,13 @@ 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;
@@ -110,11 +113,20 @@ public class ClassicPluginStrategy implements PluginStrategy {
@Override public String getShortName(File archive) throws IOException {
Manifest manifest;
+ if (!archive.exists()) {
+ throw new FileNotFoundException("Failed to load " + archive + ". The file does not exist");
+ } else if (!archive.isFile()) {
+ throw new FileNotFoundException("Failed to load " + archive + ". It is not a file");
+ }
+
if (isLinked(archive)) {
manifest = loadLinkedManifest(archive);
} else {
try (JarFile jf = new JarFile(archive, false)) {
manifest = jf.getManifest();
+ } catch (IOException ex) {
+ // Mention file name in the exception
+ throw new IOException("Failed to load " + archive, ex);
}
}
return PluginWrapper.computeShortName(manifest, archive.getName());
@@ -767,19 +779,20 @@ public class ClassicPluginStrategy implements PluginStrategy {
Class> c = ClassLoaderReflectionToolkit._findLoadedClass(pw.classLoader, name);
if (c!=null) return c;
return ClassLoaderReflectionToolkit._findClass(pw.classLoader, name);
- } catch (ClassNotFoundException e) {
+ } catch (ClassNotFoundException ignored) {
//not found. try next
}
}
} else {
for (Dependency dep : dependencies) {
PluginWrapper p = pluginManager.getPlugin(dep.shortName);
- if(p!=null)
+ if(p!=null) {
try {
return p.classLoader.loadClass(name);
- } catch (ClassNotFoundException _) {
- // try next
+ } catch (ClassNotFoundException ignored) {
+ // OK, try next
}
+ }
}
}
@@ -787,6 +800,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
@Override
+ @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();
diff --git a/core/src/main/java/hudson/CopyOnWrite.java b/core/src/main/java/hudson/CopyOnWrite.java
index efe21357c4405e122651a284c0cb4c30157fce62..891cbf04bb9ffef6d6f657d05eb4708d94fa0f5e 100644
--- a/core/src/main/java/hudson/CopyOnWrite.java
+++ b/core/src/main/java/hudson/CopyOnWrite.java
@@ -43,7 +43,7 @@ import java.lang.annotation.Target;
*
*
* The field marked with this annotation usually needs to be marked as
- * volatile.
+ * {@code volatile}.
*
* @author Kohsuke Kawaguchi
*/
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index 1849ecbf66aebaf6ca6a4df5ce197b5b825c8b36..a24976b219169c690bba916faa467f4c0a1d4bdf 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -44,6 +44,7 @@ import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
+import javax.annotation.CheckForNull;
/**
* Environment variables.
@@ -54,7 +55,7 @@ import javax.annotation.Nonnull;
* but case insensitive way (that is, cmd.exe can get both FOO and foo as environment variables
* when it's launched, and the "set" command will display it accordingly, but "echo %foo%" results in
* echoing the value of "FOO", not "foo" — this is presumably caused by the behavior of the underlying
- * Win32 API GetEnvironmentVariable acting in case insensitive way.) Windows users are also
+ * Win32 API {@code GetEnvironmentVariable} acting in case insensitive way.) Windows users are also
* used to write environment variable case-insensitively (like %Path% vs %PATH%), and you can see many
* documents on the web that claims Windows environment variables are case insensitive.
*
@@ -65,10 +66,10 @@ import javax.annotation.Nonnull;
*
* In Jenkins, often we need to build up "environment variable overrides"
* on master, then to execute the process on agents. This causes a problem
- * when working with variables like PATH. So to make this work,
- * we introduce a special convention PATH+FOO — all entries
- * that starts with PATH+ are merged and prepended to the inherited
- * PATH variable, on the process where a new process is executed.
+ * when working with variables like {@code PATH}. So to make this work,
+ * we introduce a special convention {@code PATH+FOO} — all entries
+ * that starts with {@code PATH+} are merged and prepended to the inherited
+ * {@code PATH} variable, on the process where a new process is executed.
*
* @author Kohsuke Kawaguchi
*/
@@ -84,7 +85,24 @@ public class EnvVars extends TreeMap {
* So this property remembers that information.
*/
private Platform platform;
+
+ /**
+ * Gets the platform for which these env vars targeted.
+ * @since 2.144
+ * @return The platform.
+ */
+ public @CheckForNull Platform getPlatform() {
+ return platform;
+ }
+ /**
+ * Sets the platform for which these env vars target.
+ * @since 2.144
+ * @param platform the platform to set.
+ */
+ public void setPlatform(@Nonnull Platform platform) {
+ this.platform = platform;
+ }
public EnvVars() {
super(CaseInsensitiveComparator.INSTANCE);
}
@@ -107,7 +125,7 @@ public class EnvVars extends TreeMap {
}
/**
- * Builds an environment variables from an array of the form "key","value","key","value"...
+ * Builds an environment variables from an array of the form {@code "key","value","key","value"...}
*/
public EnvVars(String... keyValuePairs) {
this();
@@ -121,7 +139,7 @@ public class EnvVars extends TreeMap {
* Overrides the current entry by the given entry.
*
*
* If you access this field from agents, then this is the environment
- * variable of the agent agent.
+ * variable of the agent.
*/
public static final Map masterEnvVars = initMaster();
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index 6208f6cec8322a0145f2fc4436519000d07e0ce6..1ff0638c10d6b578927ea90757d5acb8bdbeff6b 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -23,6 +23,7 @@
*/
package hudson;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -35,11 +36,13 @@ import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
+import com.google.inject.matcher.Matchers;
import com.google.inject.name.Names;
-import com.google.common.collect.ImmutableList;
+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;
@@ -49,20 +52,25 @@ import net.java.sezpoz.Index;
import net.java.sezpoz.IndexItem;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.springframework.util.ClassUtils;
+import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.logging.Logger;
+import java.util.Set;
import java.util.logging.Level;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
+import java.util.logging.Logger;
/**
* Discovers the implementations of an extension point.
@@ -254,12 +262,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
private Map,GuiceExtensionAnnotation>> extensionAnnotations = Maps.newHashMap();
public GuiceFinder() {
- for (ExtensionComponent ec : moduleFinder.find(GuiceExtensionAnnotation.class, Hudson.getInstance())) {
- GuiceExtensionAnnotation gea = ec.getInstance();
- extensionAnnotations.put(gea.annotationType,gea);
- }
+ refreshExtensionAnnotations();
- sezpozIndex = loadSezpozIndices(Jenkins.getInstance().getPluginManager().uberClassLoader);
+ SezpozModule extensions = new SezpozModule(loadSezpozIndices(Jenkins.getInstance().getPluginManager().uberClassLoader));
List modules = new ArrayList<>();
modules.add(new AbstractModule() {
@@ -270,7 +275,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
bind(PluginManager.class).toInstance(j.getPluginManager());
}
});
- modules.add(new SezpozModule(sezpozIndex));
+ modules.add(extensions);
for (ExtensionComponent ec : moduleFinder.find(Module.class, Hudson.getInstance())) {
modules.add(ec.getInstance());
@@ -278,6 +283,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
try {
container = Guice.createInjector(modules);
+ sezpozIndex = extensions.getLoadedIndex();
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, "Failed to create Guice container from all the plugins",e);
// failing to load all bindings are disastrous, so recover by creating minimum that works
@@ -293,6 +299,13 @@ public abstract class ExtensionFinder implements ExtensionPoint {
});
}
+ private void refreshExtensionAnnotations() {
+ for (ExtensionComponent ec : moduleFinder.find(GuiceExtensionAnnotation.class, Hudson.getInstance())) {
+ GuiceExtensionAnnotation gea = ec.getInstance();
+ extensionAnnotations.put(gea.annotationType,gea);
+ }
+ }
+
private ImmutableList> loadSezpozIndices(ClassLoader classLoader) {
List> indices = Lists.newArrayList();
for (GuiceExtensionAnnotation> gea : extensionAnnotations.values()) {
@@ -315,17 +328,17 @@ public abstract class ExtensionFinder implements ExtensionPoint {
*/
@Override
public synchronized ExtensionComponentSet refresh() throws ExtensionRefreshException {
+ refreshExtensionAnnotations();
// figure out newly discovered sezpoz components
List> delta = Lists.newArrayList();
for (Class extends Annotation> annotationType : extensionAnnotations.keySet()) {
delta.addAll(Sezpoz.listDelta(annotationType,sezpozIndex));
}
- List> l = Lists.newArrayList(sezpozIndex);
- l.addAll(delta);
- sezpozIndex = l;
+
+ SezpozModule deltaExtensions = new SezpozModule(delta);
List modules = new ArrayList<>();
- modules.add(new SezpozModule(delta));
+ modules.add(deltaExtensions);
for (ExtensionComponent ec : moduleFinder.refresh().find(Module.class)) {
modules.add(ec.getInstance());
}
@@ -333,6 +346,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
try {
final Injector child = container.createChildInjector(modules);
container = child;
+ List> l = Lists.newArrayList(sezpozIndex);
+ l.addAll(deltaExtensions.getLoadedIndex());
+ sezpozIndex = l;
return new ExtensionComponentSet() {
@Override
@@ -446,11 +462,13 @@ public abstract class ExtensionFinder implements ExtensionPoint {
* Instead of using SezPoz to instantiate, we'll instantiate them by using Guice,
* so that we can take advantage of dependency injection.
*/
- private class SezpozModule extends AbstractModule {
+ private class SezpozModule extends AbstractModule implements ProvisionListener {
private final List> index;
+ private final List> loadedIndex;
public SezpozModule(List> index) {
this.index = index;
+ this.loadedIndex = new ArrayList<>();
}
/**
@@ -493,6 +511,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
@SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"})
@Override
protected void configure() {
+
+ bindListener(Matchers.any(), this);
+
for (final IndexItem,Object> item : index) {
boolean optional = isOptional(item.annotation());
try {
@@ -527,6 +548,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
}
}).in(scope);
}
+ loadedIndex.add(item);
} catch (Exception|LinkageError e) {
// sometimes the instantiation fails in an indirect classloading failure,
// which results in a LinkageError
@@ -535,9 +557,68 @@ public abstract class ExtensionFinder implements ExtensionPoint {
}
}
}
+
+ public List> getLoadedIndex() {
+ return Collections.unmodifiableList(loadedIndex);
+ }
+
+ @Override
+ public void onProvision(ProvisionInvocation provision) {
+ final T instance = provision.provision();
+ if (instance == null) return;
+ List methods = new LinkedList<>();
+ Class c = instance.getClass();
+
+ // find PostConstruct methods in class hierarchy, the one from parent class being first in list
+ // so that we invoke them before derived class one. This isn't specified in JSR-250 but implemented
+ // this way in Spring and what most developers would expect to happen.
+
+ final Set interfaces = ClassUtils.getAllInterfacesAsSet(instance);
+
+ while (c != Object.class) {
+ Arrays.stream(c.getDeclaredMethods())
+ .map(m -> getMethodAndInterfaceDeclarations(m, interfaces))
+ .flatMap(Collection::stream)
+ .filter(m -> m.getAnnotation(PostConstruct.class) != null)
+ .findFirst()
+ .ifPresent(method -> methods.add(0, method));
+ c = c.getSuperclass();
+ }
+
+ for (Method postConstruct : methods) {
+ try {
+ postConstruct.setAccessible(true);
+ postConstruct.invoke(instance);
+ } catch (final Exception e) {
+ throw new RuntimeException(String.format("@PostConstruct %s", postConstruct), e);
+ }
+ }
+ }
}
}
+ /**
+ * Returns initial {@link Method} as well as all matching ones found in interfaces.
+ * This allows to introspect metadata for a method which is both declared in parent class and in implemented
+ * interface(s). interfaces typically is obtained by {@link ClassUtils#getAllInterfacesAsSet}
+ */
+ Collection getMethodAndInterfaceDeclarations(Method method, Collection interfaces) {
+ final List methods = new ArrayList<>();
+ methods.add(method);
+
+ // we search for matching method by iteration and comparison vs getMethod to avoid repeated NoSuchMethodException
+ // being thrown, while interface typically only define a few set of methods to check.
+ interfaces.stream()
+ .map(Class::getMethods)
+ .flatMap(Arrays::stream)
+ .filter(m -> m.getName().equals(method.getName()) && Arrays.equals(m.getParameterTypes(), method.getParameterTypes()))
+ .findFirst()
+ .ifPresent(methods::add);
+
+ return methods;
+ }
+
+
/**
* The bootstrap implementation that looks for the {@link Extension} marker.
*
diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java
index 2420f05057463381ca99ba188209ef78847756d6..566e3de404911db2fbc418bab933a4bdd8261c79 100644
--- a/core/src/main/java/hudson/ExtensionList.java
+++ b/core/src/main/java/hudson/ExtensionList.java
@@ -145,15 +145,29 @@ public class ExtensionList extends AbstractList implements OnMaster {
* Looks for the extension instance of the given type (subclasses excluded),
* or return null.
*/
- public @CheckForNull U get(Class type) {
+ public @CheckForNull U get(@Nonnull Class type) {
for (T ext : this)
if(ext.getClass()==type)
return type.cast(ext);
return null;
}
+ /**
+ * Looks for the extension instance of the given type (subclasses excluded),
+ * or throws an IllegalStateException.
+ *
+ * Meant to simplify call inside @Extension annotated class to retrieve their own instance.
+ */
+ public @Nonnull U getInstance(@Nonnull Class type) throws IllegalStateException {
+ for (T ext : this)
+ if(ext.getClass()==type)
+ return type.cast(ext);
+
+ throw new IllegalStateException("The class " + type.getName() + " was not found, potentially not yet loaded");
+ }
+
@Override
- public Iterator iterator() {
+ public @Nonnull Iterator iterator() {
// we need to intercept mutation, so for now don't allow Iterator.remove
return new AdaptedIterator,T>(Iterators.readOnly(ensureLoaded().iterator())) {
protected T adapt(ExtensionComponent item) {
@@ -428,7 +442,7 @@ public class ExtensionList extends AbstractList implements OnMaster {
* @return the singleton instance of the given type in its list.
* @throws IllegalStateException if there are no instances, or more than one
*
- * @since TODO
+ * @since 2.87
*/
public static @Nonnull U lookupSingleton(Class type) {
ExtensionList all = lookup(type);
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index c9fbc439840613b2c8f8e656ffe43cf9aba81014..9cb3835dc911553c96438bde2fbb0acecb7fe34d 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -58,6 +58,8 @@ import hudson.util.IOUtils;
import hudson.util.NamingThreadFactory;
import hudson.util.io.Archiver;
import hudson.util.io.ArchiverFactory;
+
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
@@ -77,11 +79,19 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+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;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
@@ -111,6 +121,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.input.CountingInputStream;
+import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
@@ -124,10 +135,11 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
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;
/**
* {@link File} like object with remoting support.
@@ -214,9 +226,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* This is used to determine whether we are running on the master or the agent.
*/
private transient VirtualChannel channel;
-
- // since the platform of the agent might be different, can't use java.io.File
- private final String remote;
+
+ /**
+ * Represent the path to the file in the master or the agent
+ * Since the platform of the agent might be different, can't use java.io.File
+ *
+ * The field could not be final since it's modified in {@link #readResolve()}
+ */
+ private /*final*/ String remote;
/**
* If this {@link FilePath} is deserialized to handle file access request from a remote computer,
@@ -264,6 +281,11 @@ public final class FilePath implements SerializableOnlyOverRemoting {
this.remote = normalize(resolvePathIfRelative(base, rel));
}
+ private Object readResolve() {
+ this.remote = normalize(this.remote);
+ return this;
+ }
+
private String resolvePathIfRelative(@Nonnull FilePath base, @Nonnull String rel) {
if(isAbsolute(rel)) return rel;
if(base.isUnix()) {
@@ -291,7 +313,8 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
* so remove them.
*/
- private static String normalize(@Nonnull String path) {
+ @Restricted(NoExternalUse.class)
+ public static String normalize(@Nonnull String path) {
StringBuilder buf = new StringBuilder();
// Check for prefix designating absolute path
Matcher m = ABSOLUTE_PREFIX_PATTERN.matcher(path);
@@ -455,7 +478,18 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*/
public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner) throws IOException, InterruptedException {
final OutputStream out = (channel!=null)?new RemoteOutputStream(os):os;
- return act(new SecureFileCallable() {
+ return act(new Archive(factory, out, scanner));
+ }
+ private class Archive extends SecureFileCallable {
+ private final ArchiverFactory factory;
+ private final OutputStream out;
+ private final DirScanner scanner;
+ Archive(ArchiverFactory factory, OutputStream out, DirScanner scanner) {
+ this.factory = factory;
+ this.out = out;
+ this.scanner = scanner;
+ }
+ @Override
public Integer invoke(File f, VirtualChannel channel) throws IOException {
Archiver a = factory.create(out);
try {
@@ -467,7 +501,6 @@ public final class FilePath implements SerializableOnlyOverRemoting {
}
private static final long serialVersionUID = 1L;
- });
}
public int archive(final ArchiverFactory factory, OutputStream os, final FileFilter filter) throws IOException, InterruptedException {
@@ -490,27 +523,32 @@ public final class FilePath implements SerializableOnlyOverRemoting {
// TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream
if (this.channel!=target.channel) {// local -> remote or remote->local
final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY);
- target.act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- unzip(dir, in);
- return null;
- }
-
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UnzipRemote(in));
} else {// local -> local or remote->remote
- target.act(new SecureFileCallable() {
-
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- assert !FilePath.this.isRemote(); // this.channel==target.channel above
- unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
- return null;
- }
-
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UnzipLocal());
}
}
+ private class UnzipRemote extends SecureFileCallable {
+ private final RemoteInputStream in;
+ UnzipRemote(RemoteInputStream in) {
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ unzip(dir, in);
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
+ private class UnzipLocal extends SecureFileCallable {
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ assert !FilePath.this.isRemote(); // this.channel==target.channel above
+ unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
/**
* When this {@link FilePath} represents a tar file, extracts that tar file.
@@ -526,23 +564,36 @@ public final class FilePath implements SerializableOnlyOverRemoting {
// TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream
if (this.channel!=target.channel) {// local -> remote or remote->local
final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY);
- target.act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- readFromTar(FilePath.this.getName(),dir,compression.extract(in));
- return null;
- }
-
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UntarRemote(compression, in));
} else {// local -> local or remote->remote
- target.act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- readFromTar(FilePath.this.getName(),dir,compression.extract(FilePath.this.read()));
- return null;
- }
- private static final long serialVersionUID = 1L;
- });
+ target.act(new UntarLocal(compression));
+ }
+ }
+ private class UntarRemote extends SecureFileCallable {
+ private final TarCompression compression;
+ private final RemoteInputStream in;
+ UntarRemote(TarCompression compression, RemoteInputStream in) {
+ this.compression = compression;
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ readFromTar(FilePath.this.getName(), dir, compression.extract(in));
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
+ private class UntarLocal extends SecureFileCallable {
+ private final TarCompression compression;
+ UntarLocal(TarCompression compression) {
+ this.compression = compression;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ readFromTar(FilePath.this.getName(), dir, compression.extract(FilePath.this.read()));
+ return null;
}
+ private static final long serialVersionUID = 1L;
}
/**
@@ -555,13 +606,19 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*/
public void unzipFrom(InputStream _in) throws IOException, InterruptedException {
final InputStream in = new RemoteInputStream(_in, Flag.GREEDY);
- act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException {
- unzip(dir, in);
- return null;
- }
- private static final long serialVersionUID = 1L;
- });
+ act(new UnzipFrom(in));
+ }
+ private class UnzipFrom extends SecureFileCallable {
+ private final InputStream in;
+ UnzipFrom(InputStream in) {
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException {
+ unzip(dir, in);
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
}
private void unzip(File dir, InputStream in) throws IOException {
@@ -586,6 +643,10 @@ public final class FilePath implements SerializableOnlyOverRemoting {
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
File f = new File(dir, e.getName());
+ if (!f.toPath().normalize().startsWith(dir.toPath())) {
+ throw new IOException(
+ "Zip " + zipFile.getPath() + " contains illegal file name that breaks out of the target directory: " + e.getName());
+ }
if (e.isDirectory()) {
mkdirs(f);
} else {
@@ -616,12 +677,13 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* Absolutizes this {@link FilePath} and returns the new one.
*/
public FilePath absolutize() throws IOException, InterruptedException {
- return new FilePath(channel,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File f, VirtualChannel channel) throws IOException {
- return f.getAbsolutePath();
- }
- }));
+ return new FilePath(channel, act(new Absolutize()));
+ }
+ private static class Absolutize extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ public String invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getAbsolutePath();
+ }
}
/**
@@ -634,14 +696,22 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.456
*/
public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- symlinking(f);
- Util.createSymlink(f.getParentFile(),target,f.getName(),listener);
- return null;
- }
- });
+ act(new SymlinkTo(target, listener));
+ }
+ private class SymlinkTo extends SecureFileCallable {
+ private final String target;
+ private final TaskListener listener;
+ SymlinkTo(String target, TaskListener listener) {
+ this.target = target;
+ this.listener = listener;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ symlinking(f);
+ Util.createSymlink(f.getParentFile(), target, f.getName(), listener);
+ return null;
+ }
}
/**
@@ -652,12 +722,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.456
*/
public String readLink() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- return Util.resolveSymlink(reading(f));
- }
- });
+ return act(new ReadLink());
+ }
+ private class ReadLink extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ return Util.resolveSymlink(reading(f));
+ }
}
@Override
@@ -721,17 +793,25 @@ public final class FilePath implements SerializableOnlyOverRemoting {
public void untarFrom(InputStream _in, final TarCompression compression) throws IOException, InterruptedException {
try {
final InputStream in = new RemoteInputStream(_in, Flag.GREEDY);
- act(new SecureFileCallable() {
- public Void invoke(File dir, VirtualChannel channel) throws IOException {
- readFromTar("input stream",dir, compression.extract(in));
- return null;
- }
- private static final long serialVersionUID = 1L;
- });
+ act(new UntarFrom(compression, in));
} finally {
_in.close();
}
}
+ private class UntarFrom extends SecureFileCallable {
+ private final TarCompression compression;
+ private final InputStream in;
+ UntarFrom(TarCompression compression, InputStream in) {
+ this.compression = compression;
+ this.in = in;
+ }
+ @Override
+ public Void invoke(File dir, VirtualChannel channel) throws IOException {
+ readFromTar("input stream",dir, compression.extract(in));
+ return null;
+ }
+ private static final long serialVersionUID = 1L;
+ }
/**
* Given a tgz/zip file, extracts it to the given target directory, if necessary.
@@ -881,9 +961,10 @@ public final class FilePath implements SerializableOnlyOverRemoting {
}
/**
- * Reads the URL on the current VM, and writes all the data to this {@link FilePath}
- * (this is different from resolving URL remotely.)
- *
+ * Reads the URL on the current VM, and streams the data to this file using the Remoting channel.
+ *
This is different from resolving URL remotely.
+ * If you instead wished to open an HTTP(S) URL on the remote side,
+ * prefer {@code RobustHTTPClient.copyFromRemotely}.
* @since 1.293
*/
public void copyFrom(URL url) throws IOException, InterruptedException {
@@ -987,11 +1068,6 @@ public final class FilePath implements SerializableOnlyOverRemoting {
return channel.call(wrapper);
} catch (TunneledInterruptedException e) {
throw (InterruptedException)new InterruptedException(e.getMessage()).initCause(e);
- } catch (AbortException e) {
- throw e; // pass through so that the caller can catch it as AbortException
- } catch (IOException e) {
- // wrap it into a new IOException so that we get the caller's stack trace as well.
- throw new IOException("remote file operation failed: " + remote + " at " + channel + ": " + e, e);
}
} else {
// the file is on the local machine.
@@ -1094,23 +1170,28 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.522
*/
public Callable asCallableWith(final FileCallable task) {
- return new Callable() {
- @Override
- public V call() throws IOException {
- try {
- return act(task);
- } catch (InterruptedException e) {
- throw (IOException)new InterruptedIOException().initCause(e);
- }
+ return new CallableWith<>(task);
+ }
+ private class CallableWith implements Callable {
+ private final FileCallable task;
+ CallableWith(FileCallable task) {
+ this.task = task;
+ }
+ @Override
+ public V call() throws IOException {
+ try {
+ return act(task);
+ } catch (InterruptedException e) {
+ throw (IOException)new InterruptedIOException().initCause(e);
}
+ }
- @Override
- public void checkRoles(RoleChecker checker) throws SecurityException {
- task.checkRoles(checker);
- }
+ @Override
+ public void checkRoles(RoleChecker checker) throws SecurityException {
+ task.checkRoles(checker);
+ }
- private static final long serialVersionUID = 1L;
- };
+ private static final long serialVersionUID = 1L;
}
/**
@@ -1118,12 +1199,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* on which this file is available.
*/
public URI toURI() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public URI invoke(File f, VirtualChannel channel) {
- return f.toURI();
- }
- });
+ return act(new ToURI());
+ }
+ private static class ToURI extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public URI invoke(File f, VirtualChannel channel) {
+ return f.toURI();
+ }
}
/**
@@ -1156,70 +1239,54 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* Creates this directory.
*/
public void mkdirs() throws IOException, InterruptedException {
- if(!act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- if(mkdirs(f) || f.exists())
- return true; // OK
+ if (!act(new Mkdirs())) {
+ throw new IOException("Failed to mkdirs: " + remote);
+ }
+ }
+ private class Mkdirs extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ if(mkdirs(f) || f.exists())
+ return true; // OK
- // following Ant task to avoid possible race condition.
- Thread.sleep(10);
+ // following Ant task to avoid possible race condition.
+ Thread.sleep(10);
- return f.mkdirs() || f.exists();
- }
- }))
- throw new IOException("Failed to mkdirs: "+remote);
+ return mkdirs(f) || f.exists();
+ }
}
/**
* Deletes this directory, including all its contents recursively.
*/
public void deleteRecursive() throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- deleteRecursive(deleting(f));
- return null;
- }
- });
+ act(new DeleteRecursive());
+ }
+ private class DeleteRecursive extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ Util.deleteRecursive(fileToPath(f), path -> deleting(path.toFile()));
+ return null;
+ }
}
/**
* Deletes all the contents of this directory, but not the directory itself
*/
public void deleteContents() throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- deleteContentsRecursive(f);
- return null;
- }
- });
+ act(new DeleteContents());
}
-
- 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 class DeleteContents extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ Util.deleteContentsRecursive(fileToPath(f), path -> deleting(path.toFile()));
+ return null;
}
}
- 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.
*
@@ -1305,17 +1372,25 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*/
public FilePath createTempFile(final String prefix, final String suffix) throws IOException, InterruptedException {
try {
- return new FilePath(this,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File dir, VirtualChannel channel) throws IOException {
- File f = writing(File.createTempFile(prefix, suffix, dir));
- return f.getName();
- }
- }));
+ return new FilePath(this, act(new CreateTempFile(prefix, suffix)));
} catch (IOException e) {
throw new IOException("Failed to create a temp file on "+remote,e);
}
}
+ private class CreateTempFile extends SecureFileCallable {
+ private final String prefix;
+ private final String suffix;
+ CreateTempFile(String prefix, String suffix) {
+ this.prefix = prefix;
+ this.suffix = suffix;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File dir, VirtualChannel channel) throws IOException {
+ File f = writing(File.createTempFile(prefix, suffix, dir));
+ return f.getName();
+ }
+ }
/**
* Creates a temporary file in this directory and set the contents to the
@@ -1361,30 +1436,42 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*/
public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException {
try {
- return new FilePath(channel,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File dir, VirtualChannel channel) throws IOException {
- if(!inThisDirectory)
- dir = new File(System.getProperty("java.io.tmpdir"));
- else
- mkdirs(dir);
+ return new FilePath(channel, act(new CreateTextTempFile(inThisDirectory, prefix, suffix, contents)));
+ } catch (IOException e) {
+ throw new IOException("Failed to create a temp file on "+remote,e);
+ }
+ }
+ private final class CreateTextTempFile extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final boolean inThisDirectory;
+ private final String prefix;
+ private final String suffix;
+ private final String contents;
+ CreateTextTempFile(boolean inThisDirectory, String prefix, String suffix, String contents) {
+ this.inThisDirectory = inThisDirectory;
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.contents = contents;
+ }
+ @Override
+ public String invoke(File dir, VirtualChannel channel) throws IOException {
+ if(!inThisDirectory)
+ dir = new File(System.getProperty("java.io.tmpdir"));
+ else
+ mkdirs(dir);
- File f;
- try {
- f = creating(File.createTempFile(prefix, suffix, dir));
- } catch (IOException e) {
- throw new IOException("Failed to create a temporary directory in "+dir,e);
- }
+ File f;
+ try {
+ f = creating(File.createTempFile(prefix, suffix, dir));
+ } catch (IOException e) {
+ throw new IOException("Failed to create a temporary directory in "+dir,e);
+ }
- try (Writer w = new FileWriter(writing(f))) {
- w.write(contents);
- }
+ try (Writer w = new FileWriter(writing(f))) {
+ w.write(contents);
+ }
- return f.getAbsolutePath();
- }
- }));
- } catch (IOException e) {
- throw new IOException("Failed to create a temp file on "+remote,e);
+ return f.getAbsolutePath();
}
}
@@ -1400,23 +1487,47 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @return
* The new FilePath pointing to the temporary directory
* @since 1.311
- * @see File#createTempFile(String, String)
+ * @see Files#createTempDirectory(Path, String, FileAttribute[])
*/
public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException {
try {
- return new FilePath(this,act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File dir, VirtualChannel channel) throws IOException {
- File f = File.createTempFile(prefix, suffix, dir);
- f.delete();
- f.mkdir();
- return f.getName();
- }
- }));
+ String[] s;
+ if (StringUtils.isBlank(suffix)) {
+ s = new String[]{prefix, "tmp"}; // see File.createTempFile - tmp is used if suffix is null
+ } else {
+ s = new String[]{prefix, suffix};
+ }
+ String name = StringUtils.join(s, ".");
+ return new FilePath(this, act(new CreateTempDir(name)));
} catch (IOException e) {
throw new IOException("Failed to create a temp directory on "+remote,e);
}
}
+ private class CreateTempDir extends SecureFileCallable {
+ private final String name;
+ CreateTempDir(String name) {
+ this.name = name;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File dir, VirtualChannel channel) throws IOException {
+
+ Path tempPath;
+ final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
+
+ if (isPosix) {
+ tempPath = Files.createTempDirectory(Util.fileToPath(dir), name,
+ PosixFilePermissions.asFileAttribute(EnumSet.allOf(PosixFilePermission.class)));
+ } else {
+ tempPath = Files.createTempDirectory(Util.fileToPath(dir), name, new FileAttribute>[] {});
+ }
+
+ if (tempPath.toFile() == null) {
+ throw new IOException("Failed to obtain file from path " + dir + " on " + remote);
+ }
+ return tempPath.toFile().getName();
+ }
+ }
/**
* Deletes this file.
@@ -1424,26 +1535,30 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @return true, for a modicum of compatibility
*/
public boolean delete() throws IOException, InterruptedException {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- Util.deleteFile(deleting(f));
- return null;
- }
- });
+ act(new Delete());
return true;
}
+ private class Delete extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ Util.deleteFile(deleting(f));
+ return null;
+ }
+ }
/**
* Checks if the file exists.
*/
public boolean exists() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Boolean invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).exists();
- }
- });
+ return act(new Exists());
+ }
+ private class Exists extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Boolean invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).exists();
+ }
}
/**
@@ -1454,12 +1569,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @see #touch(long)
*/
public long lastModified() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Long invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).lastModified();
- }
- });
+ return act(new LastModified());
+ }
+ private class LastModified extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).lastModified();
+ }
}
/**
@@ -1468,26 +1585,39 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.299
*/
public void touch(final long timestamp) throws IOException, InterruptedException {
- act(new SecureFileCallable() {
+ act(new Touch(timestamp));
+ }
+ private class Touch extends SecureFileCallable {
+ private final long timestamp;
+ Touch(long timestamp) {
+ this.timestamp = timestamp;
+ }
private static final long serialVersionUID = -5094638816500738429L;
+ @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);
return null;
}
- });
}
private void setLastModifiedIfPossible(final long timestamp) throws IOException, InterruptedException {
- String message = act(new SecureFileCallable() {
+ String message = act(new SetLastModified(timestamp));
+
+ if (message!=null) {
+ LOGGER.warning(message);
+ }
+ }
+ private class SetLastModified extends SecureFileCallable {
+ private final long timestamp;
+ SetLastModified(long timestamp) {
+ this.timestamp = timestamp;
+ }
private static final long serialVersionUID = -828220335793641630L;
+ @Override
public String invoke(File f, VirtualChannel channel) throws IOException {
if(!writing(f).setLastModified(timestamp)) {
if (Functions.isWindows()) {
@@ -1500,23 +1630,20 @@ public final class FilePath implements SerializableOnlyOverRemoting {
}
return null;
}
- });
-
- if (message!=null) {
- LOGGER.warning(message);
- }
}
/**
* Checks if the file is a directory.
*/
public boolean isDirectory() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Boolean invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).isDirectory();
- }
- });
+ return act(new IsDirectory());
+ }
+ private final class IsDirectory extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Boolean invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).isDirectory();
+ }
}
/**
@@ -1525,12 +1652,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.129
*/
public long length() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Long invoke(File f, VirtualChannel channel) throws IOException {
- return stating(f).length();
- }
- });
+ return act(new Length());
+ }
+ private class Length extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return stating(f).length();
+ }
}
/**
@@ -1538,12 +1667,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.542
*/
public long getFreeDiskSpace() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
- return f.getFreeSpace();
- }
- });
+ return act(new GetFreeDiskSpace());
+ }
+ private static class GetFreeDiskSpace extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getFreeSpace();
+ }
}
/**
@@ -1551,12 +1682,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.542
*/
public long getTotalDiskSpace() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
- return f.getTotalSpace();
- }
- });
+ return act(new GetTotalDiskSpace());
+ }
+ private static class GetTotalDiskSpace extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getTotalSpace();
+ }
}
/**
@@ -1564,12 +1697,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.542
*/
public long getUsableDiskSpace() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
- return f.getUsableSpace();
- }
- });
+ return act(new GetUsableDiskSpace());
+ }
+ private static class GetUsableDiskSpace extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Long invoke(File f, VirtualChannel channel) throws IOException {
+ return f.getUsableSpace();
+ }
}
/**
@@ -1583,32 +1718,46 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*
* please note mask is expected to be an octal if you use chmod command line values,
* so preceded by a '0' in java notation, ie chmod(0644)
+ *
+ * Only supports setting read, write, or execute permissions for the
+ * owner, group, or others, so the largest permissible value is 0777.
+ * Attempting to set larger values (i.e. the setgid, setuid, or sticky
+ * bits) will cause an IOException to be thrown.
*
* @since 1.303
* @see #mode()
*/
public void chmod(final int mask) throws IOException, InterruptedException {
if(!isUnix() || mask==-1) return;
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- // TODO first check for Java 7+ and use PosixFileAttributeView
- _chmod(writing(f), mask);
+ act(new Chmod(mask));
+ }
+ private class Chmod extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final int mask;
+ Chmod(int mask) {
+ this.mask = mask;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ _chmod(writing(f), mask);
- return null;
- }
- });
+ return null;
+ }
}
/**
- * Run chmod via jnr-posix
+ * Change permissions via NIO.
*/
private static void _chmod(File f, int mask) throws IOException {
// TODO WindowsPosix actually does something here (WindowsLibC._wchmod); should we let it?
// Anyway the existing calls already skip this method if on Windows.
if (File.pathSeparatorChar==';') return; // noop
- PosixAPI.jnr().chmod(f.getAbsolutePath(),mask);
+ if (Util.NATIVE_CHMOD_MODE) {
+ PosixAPI.jnr().chmod(f.getAbsolutePath(), mask);
+ } else {
+ Files.setPosixFilePermissions(fileToPath(f), Util.modeToPermissions(mask));
+ }
}
private static boolean CHMOD_WARNED = false;
@@ -1623,12 +1772,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*/
public int mode() throws IOException, InterruptedException, PosixException {
if(!isUnix()) return -1;
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Integer invoke(File f, VirtualChannel channel) throws IOException {
- return IOUtils.mode(stating(f));
- }
- });
+ return act(new Mode());
+ }
+ private class Mode extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Integer invoke(File f, VirtualChannel channel) throws IOException {
+ return IOUtils.mode(stating(f));
+ }
}
/**
@@ -1673,8 +1824,15 @@ public final class FilePath implements SerializableOnlyOverRemoting {
if (filter != null && !(filter instanceof Serializable)) {
throw new IllegalArgumentException("Non-serializable filter of " + filter.getClass());
}
- return act(new SecureFileCallable>() {
+ return act(new ListFilter(filter), (filter != null ? filter : this).getClass().getClassLoader());
+ }
+ private class ListFilter extends SecureFileCallable> {
+ private final FileFilter filter;
+ ListFilter(FileFilter filter) {
+ this.filter = filter;
+ }
private static final long serialVersionUID = 1L;
+ @Override
public List invoke(File f, VirtualChannel channel) throws IOException {
File[] children = reading(f).listFiles(filter);
if (children == null) {
@@ -1687,7 +1845,6 @@ public final class FilePath implements SerializableOnlyOverRemoting {
return r;
}
- }, (filter!=null?filter:this).getClass().getClassLoader());
}
/**
@@ -1731,8 +1888,19 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*/
@Nonnull
public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
+ return act(new ListGlob(includes, excludes, defaultExcludes));
+ }
+ private class ListGlob extends SecureFileCallable {
+ private final String includes;
+ private final String excludes;
+ private final boolean defaultExcludes;
+ ListGlob(String includes, String excludes, boolean defaultExcludes) {
+ this.includes = includes;
+ this.excludes = excludes;
+ this.defaultExcludes = defaultExcludes;
+ }
private static final long serialVersionUID = 1L;
+ @Override
public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
String[] files = glob(reading(f), includes, excludes, defaultExcludes);
@@ -1742,7 +1910,6 @@ public final class FilePath implements SerializableOnlyOverRemoting {
return r;
}
- });
}
/**
@@ -1757,7 +1924,12 @@ public final class FilePath implements SerializableOnlyOverRemoting {
throw new IOException("Expecting Ant GLOB pattern, but saw '"+includes+"'. See http://ant.apache.org/manual/Types/fileset.html for syntax");
FileSet fs = Util.createFileSet(dir,includes,excludes);
fs.setDefaultexcludes(defaultExcludes);
- DirectoryScanner ds = fs.getDirectoryScanner(new Project());
+ DirectoryScanner ds;
+ try {
+ ds = fs.getDirectoryScanner(new Project());
+ } catch (BuildException x) {
+ throw new IOException(x.getMessage());
+ }
String[] files = ds.getIncludedFiles();
return files;
}
@@ -1767,33 +1939,31 @@ public final class FilePath implements SerializableOnlyOverRemoting {
*/
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();
- actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- try (InputStream fis = Files.newInputStream(reading(f).toPath());
- 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);
- }
- return null;
- }
- });
+ actAsync(new Read(p));
return p.getIn();
}
+ private class Read extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final Pipe p;
+ Read(Pipe p) {
+ this.p = p;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ try (InputStream fis = Files.newInputStream(fileToPath(reading(f)));
+ OutputStream out = p.getOut()) {
+ org.apache.commons.io.IOUtils.copy(fis, out);
+ } catch (Exception x) {
+ p.error(x);
+ }
+ return null;
+ }
+ }
/**
* Reads this file from the specific offset.
@@ -1858,11 +2028,16 @@ public final class FilePath implements SerializableOnlyOverRemoting {
}
/**
- * 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))));
}
}
@@ -1885,49 +2060,48 @@ public final class FilePath implements SerializableOnlyOverRemoting {
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 SecureFileCallable() {
+ return act(new WritePipe());
+ }
+ private class WritePipe extends SecureFileCallable {
private static final long serialVersionUID = 1L;
+ @Override
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))));
}
- });
}
/**
* 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 {
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- mkdirs(f.getParentFile());
- try (OutputStream fos = Files.newOutputStream(writing(f).toPath());
- Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) {
- w.write(content);
- } catch (InvalidPathException e) {
- throw new IOException(e);
- }
- return null;
+ act(new Write(encoding, content));
+ }
+ private class Write extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final String encoding;
+ private final String content;
+ Write(String encoding, String content) {
+ this.encoding = encoding;
+ this.content = content;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ mkdirs(f.getParentFile());
+ try (OutputStream fos = Files.newOutputStream(fileToPath(writing(f)));
+ Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) {
+ w.write(content);
}
- });
+ return null;
+ }
}
/**
@@ -1935,12 +2109,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @see Util#getDigestOf(File)
*/
public String digest() throws IOException, InterruptedException {
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public String invoke(File f, VirtualChannel channel) throws IOException {
- return Util.getDigestOf(reading(f));
- }
- });
+ return act(new Digest());
+ }
+ private class Digest extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File f, VirtualChannel channel) throws IOException {
+ return Util.getDigestOf(reading(f));
+ }
}
/**
@@ -1951,13 +2127,19 @@ public final class FilePath implements SerializableOnlyOverRemoting {
if(this.channel != target.channel) {
throw new IOException("renameTo target must be on the same host");
}
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- reading(f).renameTo(creating(new File(target.remote)));
- return null;
- }
- });
+ act(new RenameTo(target));
+ }
+ private class RenameTo extends SecureFileCallable {
+ private final FilePath target;
+ RenameTo(FilePath target) {
+ this.target = target;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ Files.move(fileToPath(reading(f)), fileToPath(creating(new File(target.remote))), LinkOption.NOFOLLOW_LINKS);
+ return null;
+ }
}
/**
@@ -1969,8 +2151,15 @@ public final class FilePath implements SerializableOnlyOverRemoting {
if(this.channel != target.channel) {
throw new IOException("pullUpTo target must be on the same host");
}
- act(new SecureFileCallable() {
+ act(new MoveAllChildrenTo(target));
+ }
+ private class MoveAllChildrenTo extends SecureFileCallable {
+ private final FilePath target;
+ MoveAllChildrenTo(FilePath target) {
+ this.target = target;
+ }
private static final long serialVersionUID = 1L;
+ @Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
// JENKINS-16846: if f.getName() is the same as one of the files/directories in f,
// then the rename op will fail
@@ -1979,7 +2168,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
throw new IOException("Failed to rename "+f+" to "+tmp);
File t = new File(target.getRemote());
-
+
for(File child : reading(tmp).listFiles()) {
File target = new File(t, child.getName());
if(!stating(child).renameTo(creating(target)))
@@ -1988,7 +2177,6 @@ public final class FilePath implements SerializableOnlyOverRemoting {
deleting(tmp).delete();
return null;
}
- });
}
/**
@@ -2009,11 +2197,32 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @since 1.311
*/
public void copyToWithPermission(FilePath target) throws IOException, InterruptedException {
+ // Use NIO copy with StandardCopyOption.COPY_ATTRIBUTES when copying on the same machine.
+ if (this.channel == target.channel) {
+ act(new CopyToWithPermission(target));
+ return;
+ }
+
copyTo(target);
// copy file permission
target.chmod(mode());
target.setLastModifiedIfPossible(lastModified());
}
+ private class CopyToWithPermission extends SecureFileCallable {
+ private final FilePath target;
+ CopyToWithPermission(FilePath target) {
+ this.target = target;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ File targetFile = new File(target.remote);
+ File targetDir = targetFile.getParentFile();
+ filterNonNull().mkdirs(targetDir);
+ Files.createDirectories(fileToPath(targetDir));
+ Files.copy(fileToPath(reading(f)), fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+ return null;
+ }
+ }
/**
* Sends the contents of this file into the given {@link OutputStream}.
@@ -2021,24 +2230,28 @@ public final class FilePath implements SerializableOnlyOverRemoting {
public void copyTo(OutputStream os) throws IOException, InterruptedException {
final OutputStream out = new RemoteOutputStream(os);
- act(new SecureFileCallable() {
- private static final long serialVersionUID = 4088559042349254141L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- try (InputStream fis = Files.newInputStream(reading(f).toPath())) {
- org.apache.commons.io.IOUtils.copy(fis, out);
- return null;
- } catch (InvalidPathException e) {
- throw new IOException(e);
- } finally {
- out.close();
- }
- }
- });
+ act(new CopyTo(out));
// make sure the writes fully got delivered to 'os' before we return.
// this is needed because I/O operation is asynchronous
syncIO();
}
+ private class CopyTo extends SecureFileCallable {
+ private static final long serialVersionUID = 4088559042349254141L;
+ private final OutputStream out;
+ CopyTo(OutputStream out) {
+ this.out = out;
+ }
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ try (InputStream fis = Files.newInputStream(fileToPath(reading(f)))) {
+ org.apache.commons.io.IOUtils.copy(fis, out);
+ return null;
+ } finally {
+ out.close();
+ }
+ }
+ }
/**
* With fix to JENKINS-11251 (remoting 2.15), this is no longer necessary.
@@ -2053,7 +2266,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
// legacy agent.jar. Handle this gracefully
try {
LOGGER.log(Level.WARNING,"Looks like an old agent.jar. Please update "+ Which.jarFile(Channel.class)+" to the new version",e);
- } catch (IOException _) {
+ } catch (IOException ignored) {
// really ignore this time
}
}
@@ -2136,64 +2349,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
public int copyRecursiveTo(final DirScanner scanner, final FilePath target, final String description) throws IOException, InterruptedException {
if(this.channel==target.channel) {
// local to local copy.
- return act(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Integer invoke(File base, VirtualChannel channel) throws IOException {
- if(!base.exists()) return 0;
- assert target.channel==null;
- final File dest = new File(target.remote);
- final AtomicInteger count = new AtomicInteger();
- scanner.scan(base, reading(new FileVisitor() {
- @Override
- public void visit(File f, String relativePath) throws IOException {
- if (f.isFile()) {
- File target = new File(dest, relativePath);
- mkdirsE(target.getParentFile());
- Util.copyFile(f, writing(target));
- count.incrementAndGet();
- }
- }
-
- @Override
- public boolean understandsSymlink() {
- return true;
- }
-
- @Override
- public void visitSymlink(File link, String target, String relativePath) throws IOException {
- try {
- mkdirsE(new File(dest, relativePath).getParentFile());
- writing(new File(dest, target));
- Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
- } catch (InterruptedException x) {
- throw new IOException(x);
- }
- count.incrementAndGet();
- }
- }));
- return count.get();
- }
- });
+ return act(new CopyRecursiveLocal(target, scanner));
} else
if(this.channel==null) {
// local -> remote copy
final Pipe pipe = Pipe.createLocalToRemote();
- Future future = target.actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Void invoke(File f, VirtualChannel channel) throws IOException {
- try (InputStream in = pipe.getIn()) {
- readFromTar(remote + '/' + description, f,TarCompression.GZIP.extract(in));
- return null;
- }
- }
- });
- Future future2 = actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- @Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
- }
- });
+ Future future = target.actAsync(new ReadToTar(pipe, description));
+ Future future2 = actAsync(new WriteToTar(scanner, pipe));
try {
// JENKINS-9540 in case the reading side failed, report that error first
future.get();
@@ -2210,14 +2373,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
// remote -> local copy
final Pipe pipe = Pipe.createRemoteToLocal();
- Future future = actAsync(new SecureFileCallable() {
- private static final long serialVersionUID = 1L;
- public Integer invoke(File f, VirtualChannel channel) throws IOException {
- try (OutputStream out = pipe.getOut()) {
- return writeToTar(f, scanner, TarCompression.GZIP.compress(out));
- }
- }
- });
+ Future future = actAsync(new CopyRecursiveRemoteToLocal(pipe, scanner));
try {
readFromTar(remote + '/' + description,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn()));
} catch (IOException e) {// BuildException or IOException
@@ -2228,8 +2384,8 @@ public final class FilePath implements SerializableOnlyOverRemoting {
// report both errors
e.addSuppressed(x);
throw e;
- } catch (TimeoutException _) {
- // remote is hanging
+ } catch (TimeoutException ignored) {
+ // remote is hanging, just throw the original exception
throw e;
}
}
@@ -2245,7 +2401,117 @@ public final class FilePath implements SerializableOnlyOverRemoting {
}
}
}
-
+ private class CopyRecursiveLocal extends SecureFileCallable {
+ private final FilePath target;
+ private final DirScanner scanner;
+ CopyRecursiveLocal(FilePath target, DirScanner scanner) {
+ this.target = target;
+ this.scanner = scanner;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Integer invoke(File base, VirtualChannel channel) throws IOException {
+ if (!base.exists()) {
+ return 0;
+ }
+ assert target.channel == null;
+ final File dest = new File(target.remote);
+ final AtomicInteger count = new AtomicInteger();
+ scanner.scan(base, reading(new FileVisitor() {
+ private boolean exceptionEncountered;
+ private boolean logMessageShown;
+ @Override
+ public void visit(File f, String relativePath) throws IOException {
+ if (f.isFile()) {
+ File target = new File(dest, relativePath);
+ mkdirsE(target.getParentFile());
+ Path targetPath = fileToPath(writing(target));
+ exceptionEncountered = exceptionEncountered || !tryCopyWithAttributes(f, targetPath);
+ if (exceptionEncountered) {
+ Files.copy(fileToPath(f), targetPath, StandardCopyOption.REPLACE_EXISTING);
+ if (!logMessageShown) {
+ LOGGER.log(Level.INFO,
+ "JENKINS-52325: Jenkins failed to retain attributes when copying to {0}, so proceeding without attributes.",
+ dest.getAbsolutePath());
+ logMessageShown = true;
+ }
+ }
+ count.incrementAndGet();
+ }
+ }
+ private boolean tryCopyWithAttributes(File f, Path targetPath) {
+ try {
+ Files.copy(fileToPath(f), targetPath,
+ StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ LOGGER.log(Level.FINE, "Unable to copy: {0}", e.getMessage());
+ return false;
+ }
+ return true;
+ }
+ @Override
+ public boolean understandsSymlink() {
+ return true;
+ }
+ @Override
+ public void visitSymlink(File link, String target, String relativePath) throws IOException {
+ try {
+ mkdirsE(new File(dest, relativePath).getParentFile());
+ writing(new File(dest, target));
+ Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
+ } catch (InterruptedException x) {
+ throw new IOException(x);
+ }
+ count.incrementAndGet();
+ }
+ }));
+ return count.get();
+ }
+ }
+ private class ReadToTar extends SecureFileCallable {
+ private final Pipe pipe;
+ private final String description;
+ ReadToTar(Pipe pipe, String description) {
+ this.pipe = pipe;
+ this.description = description;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException {
+ try (InputStream in = pipe.getIn()) {
+ readFromTar(remote + '/' + description, f, TarCompression.GZIP.extract(in));
+ return null;
+ }
+ }
+ }
+ private class WriteToTar extends SecureFileCallable {
+ private final DirScanner scanner;
+ private final Pipe pipe;
+ WriteToTar(DirScanner scanner, Pipe pipe) {
+ this.scanner = scanner;
+ this.pipe = pipe;
+ }
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
+ }
+ }
+ private class CopyRecursiveRemoteToLocal extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private final Pipe pipe;
+ private final DirScanner scanner;
+ CopyRecursiveRemoteToLocal(Pipe pipe, DirScanner scanner) {
+ this.pipe = pipe;
+ this.scanner = scanner;
+ }
+ @Override
+ public Integer invoke(File f, VirtualChannel channel) throws IOException {
+ try (OutputStream out = pipe.getOut()) {
+ return writeToTar(f, scanner, TarCompression.GZIP.compress(out));
+ }
+ }
+ }
/**
* Writes files in 'this' directory to a tar stream.
@@ -2295,6 +2561,10 @@ public final class FilePath implements SerializableOnlyOverRemoting {
TarArchiveEntry te;
while ((te = t.getNextTarEntry()) != null) {
File f = new File(baseDir, te.getName());
+ if (!f.toPath().normalize().startsWith(baseDir.toPath())) {
+ throw new IOException(
+ "Tar " + name + " contains illegal file name that breaks out of the target directory: " + te.getName());
+ }
if (te.isDirectory()) {
mkdirs(f);
} else {
@@ -2386,9 +2656,20 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* @throws InterruptedException not only in case of a channel failure, but also if too many operations were performed without finding any matches
* @since 1.484
*/
- public String validateAntFileMask(final String fileMasks, final int bound, final boolean caseSensitive) throws IOException, InterruptedException {
- return act(new MasterToSlaveFileCallable() {
+ public @CheckForNull String validateAntFileMask(final String fileMasks, final int bound, final boolean caseSensitive) throws IOException, InterruptedException {
+ return act(new ValidateAntFileMask(fileMasks, caseSensitive, bound));
+ }
+ private class ValidateAntFileMask extends MasterToSlaveFileCallable {
+ private final String fileMasks;
+ private final boolean caseSensitive;
+ private final int bound;
+ ValidateAntFileMask(String fileMasks, boolean caseSensitive, int bound) {
+ this.fileMasks = fileMasks;
+ this.caseSensitive = caseSensitive;
+ this.bound = bound;
+ }
private static final long serialVersionUID = 1;
+ @Override
public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
if(fileMasks.startsWith("~"))
return Messages.FilePath_TildaDoesntWork();
@@ -2534,7 +2815,6 @@ public final class FilePath implements SerializableOnlyOverRemoting {
if(idx2==-1) return idx1;
return Math.min(idx1,idx2);
}
- });
}
private static final UrlFactory DEFAULT_URL_FACTORY = new UrlFactory();
@@ -2775,6 +3055,11 @@ public final class FilePath implements SerializableOnlyOverRemoting {
return classLoader;
}
+ @Override
+ public String toString() {
+ return callable.toString();
+ }
+
private static final long serialVersionUID = 1L;
}
@@ -2799,11 +3084,13 @@ public final class FilePath implements SerializableOnlyOverRemoting {
* (User's home directory in the Unix sense) of the given channel.
*/
public static FilePath getHomeDirectory(VirtualChannel ch) throws InterruptedException, IOException {
- return ch.call(new MasterToSlaveCallable() {
- public FilePath call() throws IOException {
- return new FilePath(new File(System.getProperty("user.home")));
- }
- });
+ return ch.call(new GetHomeDirectory());
+ }
+ private static class GetHomeDirectory extends MasterToSlaveCallable {
+ @Override
+ public FilePath call() throws IOException {
+ return new FilePath(new File(System.getProperty("user.home")));
+ }
}
/**
@@ -2933,11 +3220,12 @@ public final class FilePath implements SerializableOnlyOverRemoting {
return f;
}
- private boolean mkdirs(File dir) {
+ private boolean mkdirs(File dir) throws IOException {
if (dir.exists()) return false;
filterNonNull().mkdirs(dir);
- return dir.mkdirs();
+ Files.createDirectories(fileToPath(dir));
+ return true;
}
private File mkdirsE(File dir) throws IOException {
@@ -2948,5 +3236,91 @@ public final class FilePath implements SerializableOnlyOverRemoting {
return IOUtils.mkdirs(dir);
}
+ /**
+ * Check if the relative child is really a descendant after symlink resolution if any.
+ *
+ * TODO un-restrict it in a weekly after the patch
+ */
+ @Restricted(NoExternalUse.class)
+ public boolean isDescendant(@Nonnull String potentialChildRelativePath) throws IOException, InterruptedException {
+ return act(new IsDescendant(potentialChildRelativePath));
+ }
+
+ private class IsDescendant extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ private String potentialChildRelativePath;
+
+ private IsDescendant(@Nonnull String potentialChildRelativePath){
+ this.potentialChildRelativePath = potentialChildRelativePath;
+ }
+
+ @Override
+ public Boolean invoke(@Nonnull File parentFile, @Nonnull VirtualChannel channel) throws IOException, InterruptedException {
+ 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 {
+ parentRealPath = parentAbsolutePath.toRealPath();
+ }
+ catch(NoSuchFileException e) {
+ throw new IllegalArgumentException("The parent does not exist");
+ }
+
+ // example: "a/b/c" that will become "b/c" then just "c", and finally an empty string
+ String remainingPath = potentialChildRelativePath;
+
+ Path currentFilePath = parentFile.toPath();
+ while (!remainingPath.isEmpty()) {
+ 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) {
+ currentFilePath = directChild;
+ } else {
+ currentFilePath = childFileSymbolic.toPath();
+ }
+
+ Path currentFileAbsolutePath = currentFilePath.toAbsolutePath();
+ try{
+ Path child = currentFileAbsolutePath.toRealPath();
+ if (!child.startsWith(parentRealPath)) {
+ return false;
+ }
+ } catch (NoSuchFileException e) {
+ // nonexistent file
+ // 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);
+ }
+ }
+
+ return true;
+ }
+
+ 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 static final SoloFilePathFilter UNRESTRICTED = SoloFilePathFilter.wrap(FilePathFilter.UNRESTRICTED);
}
diff --git a/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java b/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
index 1e02e11f7f4dfdbedf2f00b853535725c1d43f86..9e162d666feb466d4487911fb4b2b2cdbd81a99a 100644
--- a/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
+++ b/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
@@ -50,12 +50,12 @@ public abstract class FileSystemProvisionerDescriptor extends Descriptortrue.
+ * perform the necessary deletion operation, and return {@code true}.
*
*
* If the workspace isn't the one created by this {@link FileSystemProvisioner}, or if the
* workspace can be simply deleted by {@link FilePath#deleteRecursive()}, then simply
- * return false to give other {@link FileSystemProvisionerDescriptor}s a chance to
+ * return {@code false} to give other {@link FileSystemProvisionerDescriptor}s a chance to
* discard them.
*
* @param ws
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index 99e76891f069d53870bebd572ac423d4c13e37e4..a2275d7d1fbbe8ad66808c8a92bd98d259ec5f16 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -26,6 +26,7 @@
package hudson;
import hudson.model.Slave;
+import hudson.security.*;
import jenkins.util.SystemProperties;
import hudson.cli.CLICommand;
import hudson.console.ConsoleAnnotationDescriptor;
@@ -46,6 +47,7 @@ import hudson.model.JobPropertyDescriptor;
import hudson.model.ModelObject;
import hudson.model.Node;
import hudson.model.PageDecorator;
+import jenkins.model.SimplePageDecorator;
import hudson.model.PaneStatusProperties;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterDefinition.ParameterDescriptor;
@@ -56,11 +58,6 @@ import hudson.model.View;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.search.SearchableModelObject;
-import hudson.security.AccessControlled;
-import hudson.security.AuthorizationStrategy;
-import hudson.security.GlobalSecurityConfiguration;
-import hudson.security.Permission;
-import hudson.security.SecurityRealm;
import hudson.security.captcha.CaptchaSupport;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.Cloud;
@@ -125,6 +122,7 @@ import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.regex.Pattern;
+import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@@ -136,7 +134,6 @@ import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
-import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
import org.apache.commons.jelly.Script;
@@ -399,11 +396,11 @@ public class Functions {
* is chosen, this part remains intact.
*
*
- * The 524 is the path from {@link Job} to {@link Run}.
+ * The {@code 524} is the path from {@link Job} to {@link Run}.
*
*
- * The bbb portion is the path after that till the last
- * {@link Run} subtype. The ccc portion is the part
+ * The {@code bbb} portion is the path after that till the last
+ * {@link Run} subtype. The {@code ccc} portion is the part
* after that.
*/
public static final class RunUrl {
@@ -471,6 +468,16 @@ public class Functions {
return new TreeMap