diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..71b25de5fede2f693026268b69839c15b95465dd
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,39 @@
+# Handle line endings automatically for files detected as text
+# and leave all files detected as binary untouched.
+* text=auto
+
+#
+# The above will handle all files NOT found below
+#
+# These files are text and should be normalized (Convert crlf => lf)
+*.css text
+*.groovy text
+*.htm text
+*.html text
+*.java text
+*.js text
+*.json text
+*.jelly text
+*.jellytag text
+*.less text
+*.properties text
+*.rb text
+*.sh text
+*.txt text
+*.xml text
+
+# These files are binary and should be left untouched
+# (binary is a macro for -text -diff)
+*.class binary
+*.gz binary
+*.tgz binary
+*.ear binary
+*.gif binary
+*.hpi binary
+*.ico binary
+*.jar binary
+*.jpg binary
+*.jpeg binary
+*.png binary
+*.war binary
+*.zip binary
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000000000000000000000000000000000..0e47cddae6542c7463aad91efb363a5402336b3d
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,35 @@
+See [JENKINS-XXXXX](https://issues.jenkins-ci.org/browse/JENKINS-XXXXX).
+
+
+
+### Proposed changelog entries
+
+* Entry 1: Issue, Human-readable Text
+* ...
+
+
+
+### Submitter checklist
+
+- [ ] JIRA issue is well described
+- [ ] Changelog entry appropriate for the audience affected by the change (users or developer, depending on the change). [Examples](https://github.com/jenkins-infra/jenkins.io/blob/master/content/_data/changelogs/weekly.yml)
+ * Use the `Internal: ` prefix if the change has no user-visible impact (API, test frameworks, etc.)
+- [ ] Appropriate autotests or explanation to why this change has no tests
+- [ ] For dependency updates: links to external changelogs and, if possible, full diffs
+
+
+
+### Desired reviewers
+
+@mention
+
+
diff --git a/BUILDING.TXT b/BUILDING.TXT
index 09766de355e22b3f2469dc88cc0a1ba35d8aa19c..bde5cadf353210259628baa34b1813f0acddc5b1 100644
--- a/BUILDING.TXT
+++ b/BUILDING.TXT
@@ -4,7 +4,6 @@ execution), run:
mvn clean install -pl war -am -DskipTests
The WAR file will be in war/target/jenkins.war (you can play with it)
-You can deactivate test-harness execution with -Dskip-test-harness
For more information on building Jenkins, visit
https://wiki.jenkins-ci.org/display/JENKINS/Building+Jenkins
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7a2d5e0c53ec6c9a1dcaaedf21fada835c68ea8c..01de1fea2cc3acf1ea8e561dcb74edb6cdd64aba 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,109 @@
# Contributing to Jenkins
-For information on contributing to Jenkins, check out the https://wiki.jenkins-ci.org/display/JENKINS/Beginners+Guide+to+Contributing and https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins wiki pages over at the official https://wiki.jenkins-ci.org . They will help you get started with contributing to Jenkins.
+
+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/.
+
+## Getting started
+
+1. Fork the repository on GitHub
+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/),
+ 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/)
+
+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)).
+
+## 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/).
+
+If you want simply to have the `jenkins.war` file as fast as possible without tests, run:
+
+ mvn clean package -pl war -am -DskipTests
+
+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)).
+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)
+and then attach IDE Debugger to it.
+
+## Testing changes
+
+Jenkins core includes unit and functional tests as a part of the repository.
+
+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.
+
+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.
+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.
+All proposed changes are submitted and code reviewed using the _GitHub Pull Request_ process.
+
+To submit a pull request:
+
+1. Commit changes and push them to your fork on GitHub.
+It is a good practice is to create branches instead of pushing to master.
+2. In GitHub Web UI click the _New Pull Request_ button
+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).
+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.
+ * Usually we merge pull requests after 2 votes from reviewers or after 1 vote and 1-week delay without extra reviews.
+
+Once your Pull Request is ready to be merged,
+the repository maintainers will integrate it, prepare changelogs and
+ensure it gets released in one of incoming Weekly releases.
+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.
+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)
+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).
+
+## 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/).
+
+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.
+If you want to update that build flow (e.g. "add more checks"),
+just submit a pull request.
+
+# Links
+
+* [Jenkins Contribution Landing Page](https://jenkins.io/participate/)
+* [Jenkins IRC Channel](https://jenkins.io/chat/)
+* [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))
+
diff --git a/Jenkinsfile b/Jenkinsfile
index cc03884ab9354f1dac934257d8fcf566ded8fee6..65dd3a66c7d6bd30eeb3b18b131d625a96aeeec1 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,5 +1,7 @@
+#!/usr/bin/env groovy
+
/*
- * This Jenkinsfile is intended to run on https://ci.jenkins-ci.org and may fail anywhere else.
+ * This Jenkinsfile is intended to run on https://ci.jenkins.io and may fail anywhere else.
* It makes assumptions about plugins being installed, labels mapping to nodes that can build what is needed, etc.
*
* The required labels are "java" and "docker" - "java" would be any node that can run Java builds. It doesn't need
@@ -9,152 +11,63 @@
// TEST FLAG - to make it easier to turn on/off unit tests for speeding up access to later stuff.
def runTests = true
-
-// Only keep the 10 most recent builds.
-properties([[$class: 'jenkins.model.BuildDiscarderProperty', strategy: [$class: 'LogRotator',
- numToKeepStr: '50',
- artifactNumToKeepStr: '20']]])
-
-String packagingBranch = (binding.hasVariable('packagingBranch')) ? packagingBranch : 'jenkins-2.0'
-
-timestampedNode('java') {
-
- // First stage is actually checking out the source. Since we're using Multibranch
- // currently, we can use "checkout scm".
- stage "Checkout source"
-
- checkout scm
-
- // Now run the actual build.
- stage "Build and test"
-
- // We're wrapping this in a timeout - if it takes more than 180 minutes, kill it.
- 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 -XX:MaxPermSize=1024m",
- "MAVEN_OPTS=-Xmx1536m -Xms512m -XX:MaxPermSize=1024m"]) {
- // Actually run Maven!
- // The -Dmaven.repo.local=${pwd()}/.repository means that Maven will create a
- // .repository directory at the root of the build (which it gets from the
- // pwd() Workflow call) and use that for the local Maven repository.
- sh "mvn -Pdebug -U clean install ${runTests ? '-Dmaven.test.failure.ignore=true -Dconcurrency=1' : '-DskipTests'} -V -B -Dmaven.repo.local=${pwd()}/.repository"
- }
- }
-
- // Once we've built, archive the artifacts and the test results.
- stage "Archive artifacts and test results"
-
- step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar, **/target/*.war, **/target/*.hpi', fingerprint: true])
- if (runTests) {
- step([$class: 'JUnitResultArchiver', healthScaleFactor: 20.0, testResults: '**/target/surefire-reports/*.xml'])
- }
-}
-
-def debFileName
-def rpmFileName
-def suseFileName
-
-// Run the packaging build on a node with the "docker" label.
-timestampedNode('docker') {
- // First stage here is getting prepped for packaging.
- stage "packaging - docker prep"
-
- // Docker environment to build packagings
- dir('packaging-docker') {
- git branch: packagingBranch, url: 'https://github.com/jenkinsci/packaging.git'
- sh 'docker build -t jenkins-packaging-builder:0.1 docker'
- }
-
- stage "packaging - actually packaging"
- // Working packaging code, separate branch with fixes
- dir('packaging') {
- deleteDir()
-
- docker.image("jenkins-packaging-builder:0.1").inside("-u root") {
- git branch: packagingBranch, url: 'https://github.com/jenkinsci/packaging.git'
-
- try {
- // Saw issues with unstashing inside a container, and not sure copy artifact plugin would work here.
- // So, simple wget.
- sh "wget -q ${currentBuild.absoluteUrl}/artifact/war/target/jenkins.war"
- sh "make clean deb rpm suse BRAND=./branding/jenkins.mk BUILDENV=./env/test.mk CREDENTIAL=./credentials/test.mk WAR=jenkins.war"
- } catch (Exception e) {
- error "Packaging failed: ${e}"
- } finally {
- // Needed to make sure the output of the build can be deleted by later runs.
- // Hackish, yes, but rpm builds as a numeric UID only user fail, so...
- sh "chmod -R a+w target || true"
- sh "chmod a+w jenkins.war || true"
- }
- dir("target/debian") {
- def debFilesFound = findFiles(glob: "*.deb")
- if (debFilesFound.size() > 0) {
- debFileName = debFilesFound[0]?.name
- }
- }
-
- dir("target/rpm") {
- def rpmFilesFound = findFiles(glob: "*.rpm")
- if (rpmFilesFound.size() > 0) {
- rpmFileName = rpmFilesFound[0]?.name
+def failFast = false
+
+properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '20'))])
+
+// see https://github.com/jenkins-infra/documentation/blob/master/ci.adoc for information on what node types are available
+def buildTypes = ['Linux', 'Windows']
+
+def builds = [:]
+for(i = 0; i < buildTypes.size(); i++) {
+ def buildType = buildTypes[i]
+ builds[buildType] = {
+ node(buildType.toLowerCase()) {
+ timestamps {
+ // First stage is actually checking out the source. Since we're using Multibranch
+ // currently, we can use "checkout scm".
+ stage('Checkout') {
+ checkout scm
}
- }
- dir("target/suse") {
- def suseFilesFound = findFiles(glob: "*.rpm")
- if (suseFilesFound.size() > 0) {
- suseFileName = suseFilesFound[0]?.name
+ // 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"]) {
+ // 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"
+ if(isUnix()) {
+ sh mvnCmd
+ sh 'test `git status --short | tee /dev/stderr | wc --bytes` -eq 0'
+ } else {
+ bat mvnCmd
+ }
+ }
+ }
}
- }
-
- step([$class: 'ArtifactArchiver', artifacts: 'target/**/*', fingerprint: true])
- // Fail the build if we didn't find at least one of the packages, meaning they weren't built but
- // somehow make didn't error out.
- if (debFileName == null || rpmFileName == null || suseFileName == null) {
- error "At least one of Debian, RPM or SuSE packages are missing, so failing the build."
- }
- }
+ // Once we've built, archive the artifacts and the test results.
+ stage("${buildType} Publishing") {
+ def files = findFiles(glob: '**/target/*.jar, **/target/*.war, **/target/*.hpi')
+ renameFiles(files, buildType.toLowerCase())
- }
-
-}
-
-stage "Package testing"
-
-if (runTests) {
- if (!env.BRANCH_NAME.startsWith("PR")) {
- // NOTE: As of now, a lot of package tests will fail. See https://issues.jenkins-ci.org/issues/?filter=15257 for
- // possible open JIRAs.
-
- // Basic parameters
- String artifactName = (binding.hasVariable('artifactName')) ? artifactName : 'jenkins'
- String jenkinsPort = (binding.hasVariable('jenkinsPort')) ? jenkinsPort : '8080'
-
- // Set up
- String debfile = "artifact://${env.JOB_NAME}/${env.BUILD_NUMBER}#target/debian/${debFileName}"
- String rpmfile = "artifact://${env.JOB_NAME}/${env.BUILD_NUMBER}#target/rpm/${rpmFileName}"
- String susefile = "artifact://${env.JOB_NAME}/${env.BUILD_NUMBER}#target/suse/${suseFileName}"
-
- timestampedNode("docker") {
- stage "Load Lib"
- dir('workflowlib') {
- deleteDir()
- git branch: packagingBranch, url: 'https://github.com/jenkinsci/packaging.git'
- flow = load 'workflow/installertest.groovy'
+ archiveArtifacts artifacts: '**/target/*.jar, **/target/*.war, **/target/*.hpi',
+ fingerprint: true
+ if (runTests) {
+ junit healthScaleFactor: 20.0, testResults: '**/target/surefire-reports/*.xml'
+ }
+ }
}
}
- // Run the real tests within docker node label
- flow.fetchAndRunJenkinsInstallerTest("docker", rpmfile, susefile, debfile,
- packagingBranch, artifactName, jenkinsPort)
- } else {
- echo "Not running package testing against pull requests"
}
-} else {
- echo "Skipping package tests"
}
+builds.failFast = failFast
+parallel builds
// 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
@@ -164,8 +77,8 @@ void withMavenEnv(List envVars = [], def body) {
// to be made more flexible.
// Using the "tool" Workflow call automatically installs those tools on the
// node.
- String mvntool = tool name: "mvn3.3.3", type: 'hudson.tasks.Maven$MavenInstallation'
- String jdktool = tool name: "jdk8_51", type: 'hudson.model.JDK'
+ String mvntool = tool name: "mvn", type: 'hudson.tasks.Maven$MavenInstallation'
+ String jdktool = tool name: "jdk8", type: 'hudson.model.JDK'
// Set JAVA_HOME, MAVEN_HOME and special PATH variables for the tools we're
// using.
@@ -180,11 +93,14 @@ void withMavenEnv(List envVars = [], def body) {
}
}
-// Runs the given body within a Timestamper wrapper on the given label.
-def timestampedNode(String label, Closure body) {
- node(label) {
- wrap([$class: 'TimestamperBuildWrapper']) {
- 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 501778372da7f1ab5613f35365efdb50a89d5279..6dc6c47ecef715c771fcfdeb9ccd83cc45cf4962 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,4 @@ Jenkins is **licensed** under the **[MIT License]**. The terms of the license ar
[GitHub]: https://github.com/jenkinsci/jenkins
[website]: https://jenkins.io/
[@jenkinsci]: https://twitter.com/jenkinsci
-[Contributing]: https://wiki.jenkins-ci.org/display/JENKINS/contributing
-[Extend Jenkins]: https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins
[wiki]: https://wiki.jenkins-ci.org
diff --git a/changelog.html b/changelog.html
index e94c5449d54a1ba055c2a2ce0334ff3ba273adba..20a2db735b6fd3d3a0c137d1d5484d76c354f8d4 100644
--- a/changelog.html
+++ b/changelog.html
@@ -1,2828 +1,15 @@
-
-
-
-
- Changelog
-
-
-
-
-
-
Legend:
-
- major enhancement enhancement
- major bug fix bug fix
- xxxxx
-
-
-
-
-Help other Jenkins users by letting the community know which releases you've used,
-and whether they had any significant issues.
-Legend:
- = I use it on my production site without major issues.
- = I don't recommend it.
- = I tried it but rolled back to a previous version.
-View ratings below, and click one of the icons next to your version to provide your input.
-
- Ask for confirmation before canceling/aborting runs.
- (issue 30565)
-
- Add newline after the text in userContent/readme.txt.
- (PR #2532)
-
- Fixed the missing icon in the System Script console.
- (issue 37814)
-
- Print warnings to system logs and administrative monitors
- when Jenkins initializarion does not reach the final milestone.
- (issue 37874,
- diagnostics for issue-37759)
-
- Developer API: UpdateSite#getJsonSignatureValidator() can be now
- overriden and used in plugins.
- (PR #2532)
-
- Make Cloud.PROVISION permission independent from Jenkins.ADMINISTER.
- (issue 37616)
-
- Allow the use of custom JSON signature validator for Update Site metadata signature checks.
- (issue 36537)
-
- Do not process null CRON specifications in build triggers.
- (issue 36748, enhances fix in 2.15)
-
- Setup wizard now checks if the restart is supported on the system before displaying the restart button.
- (issue 33374)
-
- Test Windows junctions before Java 7 symlink in symbolic link checks.
- (issue 29956)
-
- Fixed background color in the ComboBoxList element in order to make options visible.
- (issue 37549)
-
- Fixed editing default view description with automatic refresh.
- System message is not being displayed instead of the view description.
- (issue 37360)
-
- Fixed process tree management logic on Solaris with 64-bit JVMs.
- (issue 37559)
-
- Prevent File descriptor leaks when reading plugin manifests.
- It causes failures during the upgrade of detached plugins on Windows.
- (issue 37332, regression in 2.16)
-
- Prevent resource leaks in AntClassLoader being used in the core.
- (issue 37561)
-
- Fix the wrong message about empty field in the case duplicate item name in the New Item dialog.
- (issue 34532)
-
- Allow invoking Upgrade Wizard when Jenkins starts up.
- It can be done by placing an empty jenkins.install.InstallUtil.lastExecVersion file
- in JENKINS_HOME.
- (issue 37438)
-
- Replace repetitious "website" and "dependencies" text in the Setup Wizard by icons.
- (issue 37523)
-
- Expose Job name to system logs when Jenkins fails to create a new build with IllegalStateException.
- (issue 33549)
-
- Downgrade Queue#maintain() message for dead executors during task mapping
- from INFO to FINE.
- (PR #2510)
-
- Better diagnostics and robustness against old ChangeLogAnnotator API usage in plugins.
- Enhances JENKINS-23365 fix in 1.569.
- (issue 36757)
-
- Prevent open file leak when the agent channel onClose() listener writes to the already closed log.
- (issue 37098)
-
- Stop A/B testing of the remoting JNLP3 protocol due to the known issues.
- The protocol can be enabled manually via the jenkins.slaves.JnlpSlaveAgentProtocol3.enabled
- system property.
- (issue 37315)
-
- When checking Update Center, append ?uctest parameter to HTTP and HTTPS URLs only.
- (issue 37189)
-
- Incorrect formatting of messages in the Upate Center and Setup Wizard.
- (issue 36757)
-
- Massive cleanup of issues reported by FindBugs.
- User-visible issues - wrong log message formatting bugs in the Update Center and user creation logic.
- (issue 36717,
- PR #2459)
-
- Remoting 2.61: JNLP Slave connection issue with JNLP3-connect
- when the generated encrypted cookie contains a newline symbols.
- (issue 37140)
-
- Fix plugin dependency resolution. Jenkins will now refuse to load plugins with unsatisfied dependencies, which resulted in difficult to diagnose problems. This may result in errors on startup if your instance has an invalid plugin configuration., check the Jenkins log for details.
- (issue 21486)
-
- Decouple bouncycastle libraries from Jenkins into bouncycastle-api plugin.
- (issue 36923)
-
- Upgrade to instance-identity module 2.1.
- (issue 36922)
-
- Hide the Java Web Start launcher when the TCP agent port is disabled.
- (issue 36996)
-
- Allow admins to control the enabled agent protocols on their instance from the global security settings screen.
- (issue 37032)
-
- Make sure that the All view is created.
- (issue 36908)
-
- Remove trailing space from Hudson.DisplayName in Spanish, which resulted in problems with Blue Ocean.
- (issue 36940)
-
- Tell browsers not to cache or try to autocomplete forms in Jenkins to prevent problems due to invalid data in form submissions.
- From now on, only select form fields (e.g. job name) will offer autocompletion.
- (issue 18435)
-
- Add a cache for user information to fix performance regression due to SECURITY-243.
- (issue 35493)
-
- Prevent null pointer exceptions when not entering a cron spec for a trigger.
- (issue 36748)
-
- Defend against some fatal startup errors.
- (issue 36666)
-
- Use the icon specified by the computer implementation on its overview page.
- (issue 36775)
-
- Internal: Extract the CLI command offline-node from core.
- (issue 34468)
-
- Prevent stack overflow when using classes with complex generic type arguments
- (e.g. hudson.model.Run or hudson.model.Job).
- Regression in Groovy 2.4,
- see GROOVY-7826 for more info.
- (issue 34751)
-
- Do not invoke PingFailureAnalyzer for agent=>master ping failures.
- (issue 35190)
-
- Adapt the Setup Wizard GUI to provide a similar user experience when upgrading Jenkins.
- (issue 33663)
-
-
- Improve extensibility of the Setup Wizard GUI:
- InstallState and InstallStateFilter extension points.
- (PR 2281 as supplimentary change for
- issue 33663)
-
- Improve User Experience in the New Item form. Submit button is always visible.
- (issue 34244)
-
- Do not throw exceptions if Jenkins.getInstance() returns null instance.
- It was causing failures on Jenkins agents in the case of unexpected API usage by agents.
- (issue 34857, regression in 2.4)
-
- Replace jenkins.model.Jenkins.disableExceptionOnNullInstance by
- jenkins.model.Jenkins.enableExceptionOnNullInstance to address the
- behavior change.
- (issue 34857)
-
- Add a hudson.model.UpdateCenter.defaultUpdateSiteId system property,
- which allows specifying an alternate default Update Site ID.
- (issue 34674)
-
- Allow setting of properties from context.xml and web.xml
- in addition to setting system properties from the command line.
- (issue 34755)
-
- Remove the historical initialization of CVS changelog parser for jobs without explicit SCM definition.
- Warning! This change may potentially cause a regression if a Jenkins plugin depends on this default behavior and injects changelogs without SCM.
- (issue 4610)
-
- Add the JOB_BASE_NAME environment variable to builds (job name without path).
- (issue 25164)
-
- Allow overriding Jenkins UpdateCenter by a custom implementation.
- (issue 34733)
-
- Allow overriding Jenkins PluginManager by a custom implementation.
- (issue 34681)
-
- Installation Wizard: Allow altering the list of suggested plugins from update sites.
- (issue 34833)
-
- Prevent hanging of the Installation Wizard if the default Update Site ID cannot be resolved.
- In such case an error message will be displayed.
- (issue 34675)
-
- Prevent hanging of the Installation Wizard if the internet check is skipped for the default update site.
- (issue 34705)
-
- Do not fail with error when enabling a plugin, which has been already enabled.
- It prevents errors in the new Installation Wizard, which installs plugins in parallel.
- (issue 34710)
-
- Plugin Manager was building incorrect list of bundled plugins for nested dependencies.
- (issue 34748)
-
- Prevent fatal failure of the updates check PeriodicWork if update site certificate is missing.
- (issue 34745)
-
- Pick up missing Downloadable items on restart if all update centers are up to date.
- (issue 32886)
-
- Allow starting non-AbstractProject (e.g. Pipeline) jobs from CLI.
- (issue 28071)
-
- Disable JSESSIONID in URLs when running in the JBoss web container.
- It prevents Error 404 due to invalid links starting from Jenkins 1.556.
- More info: WFLY-4782
- (issue 34675)
-
- Prevent RSS ID collisions for items with same name in different folders.
- (issue 34767)
-
- Prevent NoSuchMethodException in loginLink.jelly
- when attempting to start a job using REST API.
- (issue 31618)
-
- Make ToolInstallers to follow HTTP 30x redirects.
- (issue 23507)
-
- Prevent NullPointerException in the parameter definition job property
- if it gets initialized incorrectly.
- (issue 34370)
-
- Bundle Font Awesome and Google Fonts: Roboto dependencies
- to prevent failures in the offline mode.
- (issue 34628)
-
- Internal: CLI commands disconnect-node and reload-configuration were extracted from the core to CLI.
- (issue 34328 and
- issue 31900)
-
- Internal: Support latest source version to avoid compile time warnings with JDK7.
- annotation-indexer and
- sezpoz have been updated to 1.11.
- (issue 32978)
-
- Developer API: Switch Jenkins.getInstance() to @Nonnull.
- (new system property)
- (pull 2297)
-
- Remoting, scalability: Ensure that the unexporter cleans up whatever it can each GC sweep.
- (issue 34213)
-
- Remoting: Force class load on UserRequest to prevent deadlocks on Windows nodes agents in the case of multiple classloaders.
- (Controlled by hudson.remoting.RemoteClassLoader.force)
- (issue 19445)
-
- More detailed information about the new features in Jenkins 2.0 on the overview page.
-
-
-
-
- New password-protected setup wizard shown on first run to guide users through installation of popular plugins and setting up an admin user.
- (issue 30749,
- issue 9598)
-
- Plugin bundling overhaul: Bundled plugins are only installed if necessary when upgrading, all plugins can be uninstalled.
- (issue 20617)
-
- Redesigned job configuration form makes it easier to understand the option hierarchy, and to navigate the form.
- (issue 32357)
-
- Richer 'Create Item' form with job icons and job categories (once a threshold of three categories has been reached).
- (issue 31162)
-
-
- Upgrade wizard encourages installation of Pipeline related plugins when upgrading from 1.x.
- (issue 33662)
-
- Jenkins now requires Servlet 3.1. Upgraded embedded Winstone-Jetty to Jetty 9 accordingly.
- This removes AJP support when using the embedded Winstone-Jetty container.
- (issue 23378)
-
- Bundled Groovy updated from 1.8.9 to 2.4.6.
- (issue 21249)
-
- Added option to prohibit anonymous access to security realm "Logged in users can do anything", enable by default.
- (issue 30749)
-
- Renamed 'slave' to 'agent' on the UI.
- (issue 27268)
-
- Improvements to inline documentation of numerous form fields in Jenkins global and job configuration.
- (issue 33364)
-
- Change default CSRF protection crumb name to Jenkins-Crumb for nginx compatibility.
- (issue 12875)
-
-
- Enforce correct icon size in list view.
- (issue 33799)
-
- CLI: Fixed NPE when non-existent run is requested.
- (issue 33942)
-
-
- Improve logging and error message when JNLP is already in use.
- (issue 33453)
-
- NullPointerException from BuildTrigger$DescriptorImpl.doCheck when using Build other projects in Promotion process of a CloudBees template, and perhaps other circumstances.
- (issue 32525)
-
- Improved the Build Other Projects help message.
- (issue 32134)
-
- Move periodic task log files from JENKINS_HOME/*.log to JENKINS_HOME/logs/tasks/*.log and rotate them periodically rather than overwrite every execution.
- (issue 33068)
-
- Fix documentation of proxy configuration.
- (pull 2060)
-
- Allow changing the directory used for the extraction of plugin archives via the --pluginroot CLI option (also controllable via the hudson.PluginManager.workDir system property / context parameter. Also document the --webroot CLI parameter in java -jar jenkins.war --help
- (issue 32765)
-
- Added support of default values in the enum.jelly form element.
- (PR 1926)
-
- Bytecode Compatibility Transformer computes the common super class without loading classes.
- Fixes the ClassCircularityError exception in Ruby Runtime Plugin.
- (issue 31019)
-
- Extended Choice parameter definitions could not be saved since 1.637.
- (issue 31458)
-
- Display expected CRON run times even if a warning occurs.
- (issue 29059)
-
- Rework the online-node command implementation, no functional changes.
- (issue 31776)
-
- Revert trigger optimizations made in 1.621 by PR 1617.
- (issue 30745)
-
- Delegate CLI's delete-node command to the overridable Computer.doDoDelete() method.
- Fixes the issue in OpenStack and JClouds plugins.
- (issue 31098, regression in 1.618)
-
- Prevent autocorrect of username on mobile devices in login forms.
- (PR 1531)
-
- Describe the built-in JDK as "(System)".
- (issue 755)
-
- Update JNA library to 4.2.1 in order to integrate fixes for linux-ppc64 and linux-arm platforms.
- (issue 15792)
-
- Since 1.598 overrides of Descriptor.getId were not correctly handled by form binding, breaking at least the CloudBees Templates plugin.
- (issue 26781)
-
- Reverted in 1.611, reimplemented in 1.627. Archiving of large artifacts. Tar implementation cannot handle files having a size >8GB.
- (issue 10629)
-
- The queue state was not updated between scheduling builds.
- (issue 27708,
- issue 27871)
-
- PeepholePermalink RunListenerImpl oncompleted should be triggered before downstream builds are triggered.
- (issue 20989)
-
- NPE when /script used on offline slave.
- (issue 26751)
-
- Make periodic workspace cleanup configurable through system properties.
- (issue 21322)
-
- Do not offer to restart on /restart and /safeRestart if the configuration does not support it.
- (issue 27414)
-
- Polling was skipped while quieting down, resulting in ignored commit notifications. This behavior was changed.
- (issue 26208)
-
- Starting this version, native packages are produced from the new repository.
- File issues related to installers and packages in the packaging component.
-
- JSONP served with the wrong MIME type and rejected by Chrome.
- (issue 27607)
-
- Security file pattern whitelist was broken for some plugins since 1.597.
- (issue 27055)
-
- Lock an Executor without creating a Thread
- (issue 25938)
-
- Hide flyweight master executor when ≥1 heavyweight executors running as subtasks
- (issue 26900)
-
- Way to mark an Executable that should not block isReadyToRestart
- (issue 22941)
-
- Refactor the Queue and Nodes to use a consistent locking strategy
- (issue 27565)
- Note that this change involved moving slave definitions outside the main config.xml file.
- If you downgrade after this, your slave settings will be lost.
-
- Makes the Jenkins is loading screen not block on the extensions loading lock
- (issue 27563)
-
- AdjunctManager: exception upon startup
- (issue 15355)
-
- Removes race condition rendering the list of executors
- (issue 27564)
-
- Tidy up the locks that were causing deadlocks with the once retention strategy in durable tasks
- (issue 27476)
-
- Remove any requirement from Jenkins Core to lock on the Queue when rendering the Jenkins UI
- (issue 27566)
-
- Added a switch (-Dhudson.model.User.allowNonExistentUserToLogin=true) to let users login even when the record is not found in the backend security realm.
- (issue 22346)
-
- Avoid deadlock when using build-monitor-plugin.
- (issue 27183)
-
- As security hardening, mark "remember me" cookie as HTTP only
- (issue 27277)
-
- Show displayName in build remote API.
- (issue 26723)
-
- JENKINS_HOME layout change: builds are now keyed by build numbers and not timestamps.
- See Wiki for details
- and downgrade.
- (issue 24380)
-
- Do not throw exception on /signup when not possible.
- (issue 11172)
-
- Tool installer which downloads and unpacks archives should not fail the build if the tool already exists and the server returns an error code.
- (issue 26196)
-
- Use Windows line endings for batch file build steps.
- (issue 7478)
-
- Reduced the logging clutter about the lack of @ExportedBean.
- (issue 24458)
-
- Character encoding problem in form submission when file parameters are present.
- (issue 11543)
-
- Improved error handling and "in-progress" UI feedback in JNLP slave to service installation.
-
- Winstone 2.4: reverse proxy support in the logging, request header size limit control, and different private key password from keystore password.
- (issue 23665)
-
- umask setting on Debian did not work.
- (pull 1397)
-
- handle job move when buildDir is configured to a custom location.
- (issue 24825)
-
- Failure to migrate legacy user records in 1.576 properly broke Jenkins, resulted in NullPointerExceptions.
- (issue 24317)
-
- Jenkins did not correctly display icons contributed by plugins in 1.576.
- (issue 24316)
-
- Moved JUnit reporting functionality to a plugin.
- (issue 23263)
-
- Fixed ClassCastException on org.dom4j.DocumentFactory
- (issue 13709)
-
- Jenkins now logs warnings when it fails to export objects to XML/JSON.
- This can result in a lot of log output in case of heavy API use.
- We recommend that API users use the ?tree parameter instead of ?depth.
- (commit)
-
- Allow BuildStep to work with non-AbstractProject
- (issue 23713)
-
- Improved class loading performance when using Groovy.
- (issue 24309)
-
- Prevent NullPointerException from Executor.run.
- (issue 24110)
-
- Make the lifetime of queue items cache configurable.
- (issue 19691)
-
- Support --username/--password authentication for CLIMethod based CLI commands.
- (issue 23988)
-
- Don't link to /safeRestart after update if Jenkins cannot restart itself.
- (issue 24032)
-
- Properly consider busy executors when reducing a node's executor count.
- (issue 24095)
-
- Jenkins can now kill Win32 processes from Win64 JVMs.
- (issue 23410)
-
- Allow custom security realm plugins to fire events to SecurityListeners.
- (issue 23417)
-
- Recover gracefully if a build permalink has a non-numeric value.
- (issue 21631)
-
- Fix form submission via the Enter key for Internet Explorer version 9.
- (issue 22373)
-
- When Jenkins had a lot of jobs, submitting a view configuration change could overload the web server, even if few of the jobs were selected.
- (issue 20327)
-
- Fixed a reference counting bug in the remoting layer.
- (commit)
-
- Avoid repeatedly reading symlinks from disk to resolve build permalinks.
- (issue 22822)
-
- Show custom build display name in executors widget.
- (issue 10477)
-
- CodeMirror support for shell steps broke initial configuration.
- (issue 23151)
-
- Jenkins on Linux can not restart after plugin update when started without full path to java executable
- (issue 22818)
-
- Fixed NullPointerException when a build triggering returns null cause
- (issue 20499)
-
- Fixed NullPointerException on plugin installations when invalid update center is set
- (issue 20031)
-
- Use DISABLED_ANIME icon while building a disabled project
- (issue 8358)
-
- Process the items hierarchy when displaying the Show Poll Thread Count option
- (issue 22934)
-
- Compressed output was turned on even before Access Denied errors were shown for disallowed Remote API requests, yielding a confusing error.
- (issue 17374)
- (issue 18116)
-
- Properly close input streams in FileParameterValue
- (issue 22693)
-
- Incorrect failure age in the JUnit test results
- (issue 18626)
-
- Enforcing build trigger authentication at runtime by checking authentication associated with a build, rather than at configuration time.
- For compatibility, enforcement is skipped by default; you must install the Authorize Project plugin or similar for this to take effect.
- The “upstream pseudo trigger” was also removed in favor of a true trigger configured on the downstream project;
- you may use either the post-build action or the trigger according to where you want this configuration stored, and who is authorized to change it.
- (issue 16956)
-
- Fixed NPE from view new job name autocompletion since 1.553.
- (issue 22142)
-
- Deadlocks in concurrent builds under some conditions since 1.556.
- (issue 22560)
-
- JNLP slaves are now handled through NIO-based remoting channels for better scalability.
-
- Fixed ArrayIndexOutOfBoundsException in XStream with Oracle JDK8 release version
- (issue 18537)
-
- Corrected permission checks for copy-job and create-job CLI commands.
- (issue 22262)
-
- identity.key, used to secure some communications with Jenkins, now stored encrypted with the master key.
-
- When dynamically loading a plugin which another loaded plugin already had an optional dependency on, class loading errors could result before restart.
- (issue 19976)
-
- Memory leaks in the old data monitor.
- (issue 19544)
-
- Ability for custom view types to disable automatic refresh.
- (issue 21190)
- (issue 21191)
-
- Option to download metadata directly from Jenkins rather than going through the browser.
- (issue 19081)
-
- Allow JDK8 (and other versions) to be downloaded by JDKInstaller correctly.
- (issue 22347)
-
- Build history widget only showed the last day of builds.
- (Due to JENKINS-20892, even with this fix at most 20 builds are shown.)
- (issue 21159)
-
- Random class loading error mostly known to affect static analysis plugins.
- (issue 12124)
-
- After restarting Jenkins, users known only from changelogs could be shown as First Last _first.last@some.org_, breaking mail delivery.
- (issue 16332)
-
- CLI build -s -v command caused 100% CPU usage on the master.
- (issue 20965)
-
- Slave started from Java Web Start can now install itself as a systemd service.
-
- Split the “raw HTML” markup formatter out of core into a bundled plugin.
-
- Do not show Maven modules and matrix configurations in the Copy Job dialog.
- (issue 19559)
-
- Builds disappear after renaming a job.
- (issue 18678)
-
- When clicking Apply to rename a job, tell the user that Save must be used instead.
- (issue 17401)
-
- Exception from XStream running Maven builds on strange Java versions.
- (issue 21183)
-
- When clicking Apply results in an exception (error page), show it, rather than creating an empty dialog.
- (issue 20772)
-
-
-
-Older changelogs can be found in a separate file
-
-
-
+-->
\ No newline at end of file
diff --git a/cli/pom.xml b/cli/pom.xml
index f1c05303da3ad10e43a239948d51cff7331dbf1f..6b00f47b5f898bc021129364b9f50efed77abf7e 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
org.jenkins-ci.mainpom
- 2.23-SNAPSHOT
+ 2.92-SNAPSHOTcli
@@ -24,10 +24,17 @@
powermock-api-mockitotest
+
+ org.kohsuke
+ access-modifier-annotation
+ commons-codeccommons-codec
- 1.4
+
+
+ commons-io
+ commons-io${project.groupId}
@@ -42,9 +49,20 @@
org.jvnet.localizerlocalizer
- 1.23
+ 1.24
+ org.apache.sshd
+ sshd-core
+ 1.6.0
+ true
+
+
+ org.slf4j
+ slf4j-jdk14
+ true
+
+ org.jenkins-citrilead-ssh2build214-jenkins-1
@@ -59,11 +77,13 @@
- attached
+ singlepackage
- jar-with-dependencies
+
+ jar-with-dependencies
+ hudson.cli.CLI
@@ -88,6 +108,7 @@
Messages.propertiestarget/generated-sources/localizer
+ true
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index 9eca2ec8efb8097ec8c42ab113d5f758570f3dc1..cbd3acd04458b6fe1416089e1660784f54bf6686 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -25,13 +25,13 @@ package hudson.cli;
import hudson.cli.client.Messages;
import hudson.remoting.Channel;
+import hudson.remoting.NamingThreadFactory;
import hudson.remoting.PingThread;
import hudson.remoting.Pipe;
import hudson.remoting.RemoteInputStream;
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.SocketChannelStream;
import hudson.remoting.SocketOutputStream;
-
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HostnameVerifier;
@@ -58,6 +58,7 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PublicKey;
@@ -71,16 +72,18 @@ import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
+import org.apache.commons.io.FileUtils;
/**
* CLI entry point to Jenkins.
*
* @author Kohsuke Kawaguchi
*/
-public class CLI {
+public class CLI implements AutoCloseable {
private final ExecutorService pool;
private final Channel channel;
private final CliEntryPoint entryPoint;
@@ -89,6 +92,11 @@ public class CLI {
private final String httpsProxyTunnel;
private final String authorization;
+ /**
+ * For tests only.
+ * @deprecated Specific to {@link Mode#REMOTING}.
+ */
+ @Deprecated
public CLI(URL jenkins) throws IOException, InterruptedException {
this(jenkins,null);
}
@@ -110,26 +118,27 @@ public class CLI {
public CLI(URL jenkins, ExecutorService exec, String httpsProxyTunnel) throws IOException, InterruptedException {
this(new CLIConnectionFactory().url(jenkins).executorService(exec).httpsProxyTunnel(httpsProxyTunnel));
}
-
+
+ /**
+ * @deprecated Specific to {@link Mode#REMOTING}.
+ */
+ @Deprecated
/*package*/ CLI(CLIConnectionFactory factory) throws IOException, InterruptedException {
URL jenkins = factory.jenkins;
this.httpsProxyTunnel = factory.httpsProxyTunnel;
this.authorization = factory.authorization;
ExecutorService exec = factory.exec;
- String url = jenkins.toExternalForm();
- if(!url.endsWith("/")) url+='/';
-
ownsPool = exec==null;
- pool = exec!=null ? exec : Executors.newCachedThreadPool();
+ pool = exec!=null ? exec : Executors.newCachedThreadPool(new NamingThreadFactory(Executors.defaultThreadFactory(), "CLI.pool"));
Channel _channel;
try {
- _channel = connectViaCliPort(jenkins, getCliTcpPort(url));
+ _channel = connectViaCliPort(jenkins, getCliTcpPort(jenkins));
} catch (IOException e) {
- LOGGER.log(Level.FINE,"Failed to connect via CLI port. Falling back to HTTP",e);
+ LOGGER.log(Level.FINE, "Failed to connect via CLI port. Falling back to HTTP", e);
try {
- _channel = connectViaHttp(url);
+ _channel = connectViaHttp(jenkins);
} catch (IOException e2) {
e.addSuppressed(e2);
throw e;
@@ -144,13 +153,15 @@ public class CLI {
throw new IOException(Messages.CLI_VersionMismatch());
}
- private Channel connectViaHttp(String url) throws IOException {
- LOGGER.log(FINE, "Trying to connect to {0} via HTTP", url);
- url+="cli";
- URL jenkins = new URL(url);
+ /**
+ * @deprecated Specific to {@link Mode#REMOTING}.
+ */
+ @Deprecated
+ private Channel connectViaHttp(URL url) throws IOException {
+ LOGGER.log(FINE, "Trying to connect to {0} via Remoting over HTTP", url);
- FullDuplexHttpStream con = new FullDuplexHttpStream(jenkins,authorization);
- Channel ch = new Channel("Chunked connection to "+jenkins,
+ FullDuplexHttpStream con = new FullDuplexHttpStream(url, "cli?remoting=true", authorization);
+ Channel ch = new Channel("Chunked connection to " + url,
pool,con.getInputStream(),con.getOutputStream());
final long interval = 15*1000;
final long timeout = (interval * 3) / 4;
@@ -163,8 +174,17 @@ public class CLI {
return ch;
}
+ /**
+ * @deprecated Specific to {@link Mode#REMOTING}.
+ */
+ @Deprecated
private Channel connectViaCliPort(URL jenkins, CliPort clip) throws IOException {
- LOGGER.log(FINE, "Trying to connect directly via TCP/IP to {0}", clip.endpoint);
+ LOGGER.log(FINE, "Trying to connect directly via Remoting over TCP/IP to {0}", clip.endpoint);
+
+ if (authorization != null) {
+ LOGGER.warning("-auth ignored when using JNLP agent port");
+ }
+
final Socket s = new Socket();
// this prevents a connection from silently terminated by the router in between or the other peer
// and that goes without unnoticed. However, the time out is often very long (for example 2 hours
@@ -176,9 +196,10 @@ public class CLI {
if (httpsProxyTunnel!=null) {
String[] tokens = httpsProxyTunnel.split(":");
+ LOGGER.log(Level.FINE, "Using HTTP proxy {0}:{1} to connect to CLI port", new Object[]{tokens[0], tokens[1]});
s.connect(new InetSocketAddress(tokens[0], Integer.parseInt(tokens[1])));
PrintStream o = new PrintStream(s.getOutputStream());
- o.print("CONNECT " + clip.endpoint.getHostName() + ":" + clip.endpoint.getPort() + " HTTP/1.0\r\n\r\n");
+ o.print("CONNECT " + clip.endpoint.getHostString() + ":" + clip.endpoint.getPort() + " HTTP/1.0\r\n\r\n");
// read the response from the proxy
ByteArrayOutputStream rsp = new ByteArrayOutputStream();
@@ -188,8 +209,15 @@ public class CLI {
rsp.write(ch);
}
String head = new BufferedReader(new StringReader(rsp.toString("ISO-8859-1"))).readLine();
- if (!head.startsWith("HTTP/1.0 200 "))
- throw new IOException("Failed to establish a connection through HTTP proxy: "+rsp);
+
+ if (head == null) {
+ throw new IOException("Unexpected empty response");
+ }
+ if (!(head.startsWith("HTTP/1.0 200 ") || head.startsWith("HTTP/1.1 200 "))) {
+ s.close();
+ LOGGER.log(Level.SEVERE, "Failed to tunnel the CLI port through the HTTP proxy. Falling back to HTTP.");
+ throw new IOException("Failed to establish a connection through HTTP proxy: " + rsp);
+ }
// HTTP proxies (at least the one I tried --- squid) doesn't seem to do half-close very well.
// So instead of relying on it, we'll just send the close command and then let the server
@@ -254,13 +282,14 @@ public class CLI {
/**
* If the server advertises CLI endpoint, returns its location.
+ * @deprecated Specific to {@link Mode#REMOTING}.
*/
- protected CliPort getCliTcpPort(String url) throws IOException {
- URL _url = new URL(url);
- if (_url.getHost()==null || _url.getHost().length()==0) {
+ @Deprecated
+ protected CliPort getCliTcpPort(URL url) throws IOException {
+ if (url.getHost()==null || url.getHost().length()==0) {
throw new IOException("Invalid URL: "+url);
}
- URLConnection head = _url.openConnection();
+ URLConnection head = url.openConnection();
try {
head.connect();
} catch (IOException e) {
@@ -277,9 +306,7 @@ public class CLI {
flushURLConnection(head);
if (p1==null && p2==null) {
- // we aren't finding headers we are expecting. Is this even running Jenkins?
- if (head.getHeaderField("X-Hudson")==null && head.getHeaderField("X-Jenkins")==null)
- throw new IOException("There's no Jenkins running at "+url);
+ verifyJenkinsConnection(head);
throw new IOException("No X-Jenkins-CLI2-Port among " + head.getHeaderFields().keySet());
}
@@ -288,6 +315,27 @@ public class CLI {
else return new CliPort(new InetSocketAddress(h,Integer.parseInt(p1)),identity,1);
}
+ /**
+ * Make sure the connection is open against Jenkins server.
+ *
+ * @param c The open connection.
+ * @throws IOException in case of communication problem.
+ * @throws NotTalkingToJenkinsException when connection is not made to Jenkins service.
+ */
+ /*package*/ static void verifyJenkinsConnection(URLConnection c) throws IOException {
+ if (c.getHeaderField("X-Hudson")==null && c.getHeaderField("X-Jenkins")==null)
+ throw new NotTalkingToJenkinsException(c);
+ }
+ /*package*/ static final class NotTalkingToJenkinsException extends IOException {
+ public NotTalkingToJenkinsException(String s) {
+ super(s);
+ }
+
+ public NotTalkingToJenkinsException(URLConnection c) {
+ super("There's no Jenkins running at " + c.getURL().toString());
+ }
+ }
+
/**
* Flush the supplied {@link URLConnection} input and close the
* connection nicely.
@@ -374,14 +422,11 @@ public class CLI {
}
public static void main(final String[] _args) throws Exception {
-// Logger l = Logger.getLogger(Channel.class.getName());
-// l.setLevel(ALL);
-// ConsoleHandler h = new ConsoleHandler();
-// h.setLevel(ALL);
-// l.addHandler(h);
-//
try {
System.exit(_main(_args));
+ } catch (NotTalkingToJenkinsException ex) {
+ System.err.println(ex.getMessage());
+ System.exit(3);
} catch (Throwable t) {
// if the CLI main thread die, make sure to kill the JVM.
t.printStackTrace();
@@ -389,6 +434,7 @@ public class CLI {
}
}
+ private enum Mode {HTTP, SSH, REMOTING}
public static int _main(String[] _args) throws Exception {
List args = Arrays.asList(_args);
PrivateKeyProvider provider = new PrivateKeyProvider();
@@ -402,19 +448,52 @@ public class CLI {
boolean tryLoadPKey = true;
+ Mode mode = null;
+
+ String user = null;
+ String auth = null;
+ boolean strictHostKey = false;
+
while(!args.isEmpty()) {
String head = args.get(0);
if (head.equals("-version")) {
System.out.println("Version: "+computeVersion());
return 0;
}
+ if (head.equals("-http")) {
+ if (mode != null) {
+ printUsage("-http clashes with previously defined mode " + mode);
+ return -1;
+ }
+ mode = Mode.HTTP;
+ args = args.subList(1, args.size());
+ continue;
+ }
+ if (head.equals("-ssh")) {
+ if (mode != null) {
+ printUsage("-ssh clashes with previously defined mode " + mode);
+ return -1;
+ }
+ mode = Mode.SSH;
+ args = args.subList(1, args.size());
+ continue;
+ }
+ if (head.equals("-remoting")) {
+ if (mode != null) {
+ printUsage("-remoting clashes with previously defined mode " + mode);
+ return -1;
+ }
+ mode = Mode.REMOTING;
+ args = args.subList(1, args.size());
+ continue;
+ }
if(head.equals("-s") && args.size()>=2) {
url = args.get(1);
args = args.subList(2,args.size());
continue;
}
if (head.equals("-noCertificateCheck")) {
- System.out.println("Skipping HTTPS certificate checks altogether. Note that this is not secure at all.");
+ LOGGER.info("Skipping HTTPS certificate checks altogether. Note that this is not secure at all.");
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{new NoCheckTrustManager()}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
@@ -445,11 +524,37 @@ public class CLI {
sshAuthRequestedExplicitly = true;
continue;
}
+ if (head.equals("-strictHostKey")) {
+ strictHostKey = true;
+ args = args.subList(1, args.size());
+ continue;
+ }
+ if (head.equals("-user") && args.size() >= 2) {
+ user = args.get(1);
+ args = args.subList(2, args.size());
+ continue;
+ }
+ if (head.equals("-auth") && args.size() >= 2) {
+ auth = args.get(1);
+ args = args.subList(2, args.size());
+ continue;
+ }
if(head.equals("-p") && args.size()>=2) {
httpProxy = args.get(1);
args = args.subList(2,args.size());
continue;
}
+ if (head.equals("-logger") && args.size() >= 2) {
+ Level level = parse(args.get(1));
+ for (Handler h : Logger.getLogger("").getHandlers()) {
+ h.setLevel(level);
+ }
+ for (Logger logger : new Logger[] {LOGGER, FullDuplexHttpStream.LOGGER, PlainCLIProtocol.LOGGER, Logger.getLogger("org.apache.sshd")}) { // perhaps also Channel
+ logger.setLevel(level);
+ }
+ args = args.subList(2, args.size());
+ continue;
+ }
break;
}
@@ -458,16 +563,53 @@ public class CLI {
return -1;
}
+ if (!url.endsWith("/")) {
+ url += '/';
+ }
+
if(args.isEmpty())
args = Arrays.asList("help"); // default to help
if (tryLoadPKey && !provider.hasKeys())
provider.readFromDefaultLocations();
+ if (mode == null) {
+ mode = Mode.HTTP;
+ }
+
+ LOGGER.log(FINE, "using connection mode {0}", mode);
+
+ if (user != null && auth != null) {
+ LOGGER.warning("-user and -auth are mutually exclusive");
+ }
+
+ if (mode == Mode.SSH) {
+ if (user == null) {
+ // TODO SshCliAuthenticator already autodetects the user based on public key; why cannot AsynchronousCommand.getCurrentUser do the same?
+ LOGGER.warning("-user required when using -ssh");
+ return -1;
+ }
+ return SSHCLI.sshConnection(url, user, args, provider, strictHostKey);
+ }
+
+ if (strictHostKey) {
+ LOGGER.warning("-strictHostKey meaningful only with -ssh");
+ }
+
+ if (user != null) {
+ LOGGER.warning("Warning: -user ignored unless using -ssh");
+ }
+
CLIConnectionFactory factory = new CLIConnectionFactory().url(url).httpsProxyTunnel(httpProxy);
String userInfo = new URL(url).getUserInfo();
if (userInfo != null) {
factory = factory.basicAuth(userInfo);
+ } else if (auth != null) {
+ factory = factory.basicAuth(auth.startsWith("@") ? FileUtils.readFileToString(new File(auth.substring(1))).trim() : auth);
+ }
+
+ if (mode == Mode.HTTP) {
+ return plainHttpConnection(url, args, factory);
}
CLI cli = factory.connect();
@@ -478,22 +620,21 @@ public class CLI {
cli.authenticate(provider.getKeys());
} catch (IllegalStateException e) {
if (sshAuthRequestedExplicitly) {
- System.err.println("The server doesn't support public key authentication");
+ LOGGER.warning("The server doesn't support public key authentication");
return -1;
}
} catch (UnsupportedOperationException e) {
if (sshAuthRequestedExplicitly) {
- System.err.println("The server doesn't support public key authentication");
+ LOGGER.warning("The server doesn't support public key authentication");
return -1;
}
} catch (GeneralSecurityException e) {
if (sshAuthRequestedExplicitly) {
- System.err.println(e.getMessage());
- LOGGER.log(FINE,e.getMessage(),e);
+ LOGGER.log(WARNING, null, e);
return -1;
}
- System.err.println("[WARN] Failed to authenticate with your SSH keys. Proceeding as anonymous");
- LOGGER.log(FINE,"Failed to authenticate with your SSH keys.",e);
+ LOGGER.warning("Failed to authenticate with your SSH keys. Proceeding as anonymous");
+ LOGGER.log(FINE, null, e);
}
}
@@ -506,6 +647,88 @@ public class CLI {
}
}
+ private static int plainHttpConnection(String url, List args, CLIConnectionFactory factory) throws IOException, InterruptedException {
+ LOGGER.log(FINE, "Trying to connect to {0} via plain protocol over HTTP", url);
+ FullDuplexHttpStream streams = new FullDuplexHttpStream(new URL(url), "cli?remoting=false", factory.authorization);
+ class ClientSideImpl extends PlainCLIProtocol.ClientSide {
+ boolean complete;
+ int exit = -1;
+ ClientSideImpl(InputStream is, OutputStream os) throws IOException {
+ super(is, os);
+ if (is.read() != 0) { // cf. FullDuplexHttpService
+ throw new IOException("expected to see initial zero byte; perhaps you are connecting to an old server which does not support -http?");
+ }
+ }
+ @Override
+ protected void onExit(int code) {
+ this.exit = code;
+ finished();
+ }
+ @Override
+ protected void onStdout(byte[] chunk) throws IOException {
+ System.out.write(chunk);
+ }
+ @Override
+ protected void onStderr(byte[] chunk) throws IOException {
+ System.err.write(chunk);
+ }
+ @Override
+ protected void handleClose() {
+ finished();
+ }
+ private synchronized void finished() {
+ complete = true;
+ notifyAll();
+ }
+ }
+ try (final ClientSideImpl connection = new ClientSideImpl(streams.getInputStream(), streams.getOutputStream())) {
+ for (String arg : args) {
+ connection.sendArg(arg);
+ }
+ connection.sendEncoding(Charset.defaultCharset().name());
+ connection.sendLocale(Locale.getDefault().toString());
+ connection.sendStart();
+ connection.begin();
+ final OutputStream stdin = connection.streamStdin();
+ new Thread("input reader") {
+ @Override
+ public void run() {
+ try {
+ int c;
+ while ((c = System.in.read()) != -1) { // TODO use InputStream.available
+ stdin.write(c);
+ }
+ connection.sendEndStdin();
+ } catch (IOException x) {
+ LOGGER.log(Level.WARNING, null, x);
+ }
+ }
+ }.start();
+ new Thread("ping") { // JENKINS-46659
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(10_000);
+ while (!connection.complete) {
+ LOGGER.fine("sending ping");
+ connection.sendEncoding(Charset.defaultCharset().name()); // no-op at this point
+ Thread.sleep(10_000);
+ }
+ } catch (IOException | InterruptedException x) {
+ LOGGER.log(Level.WARNING, null, x);
+ }
+ }
+
+ }.start();
+ synchronized (connection) {
+ while (!connection.complete) {
+ connection.wait();
+ }
+ }
+ return connection.exit;
+ }
+ }
+
private static String computeVersion() {
Properties props = new Properties();
try {
@@ -550,7 +773,9 @@ public class CLI {
*
* @return
* identity of the server represented as a public key.
+ * @deprecated Specific to {@link Mode#REMOTING}.
*/
+ @Deprecated
public PublicKey authenticate(Iterable privateKeys) throws IOException, GeneralSecurityException {
Pipe c2s = Pipe.createLocalToRemote();
Pipe s2c = Pipe.createRemoteToLocal();
@@ -576,6 +801,10 @@ public class CLI {
}
}
+ /**
+ * @deprecated Specific to {@link Mode#REMOTING}.
+ */
+ @Deprecated
public PublicKey authenticate(KeyPair key) throws IOException, GeneralSecurityException {
return authenticate(Collections.singleton(key));
}
@@ -585,5 +814,5 @@ public class CLI {
System.err.println(Messages.CLI_Usage());
}
- private static final Logger LOGGER = Logger.getLogger(CLI.class.getName());
+ static final Logger LOGGER = Logger.getLogger(CLI.class.getName());
}
diff --git a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java
index a2e5681039effafbb22765a249a508fe8fdb22b2..894e4c0fdac60ff446e3a67ecf68e44192d93f97 100644
--- a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java
+++ b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java
@@ -32,6 +32,7 @@ public class CLIConnectionFactory {
/**
* This {@link ExecutorService} is used to execute closures received from the server.
+ * Used only in Remoting mode.
*/
public CLIConnectionFactory executorService(ExecutorService es) {
this.exec = es;
@@ -59,15 +60,24 @@ public class CLIConnectionFactory {
/**
* Convenience method to call {@link #authorization} with the HTTP basic authentication.
+ * Currently unused.
*/
public CLIConnectionFactory basicAuth(String username, String password) {
return basicAuth(username+':'+password);
}
+ /**
+ * Convenience method to call {@link #authorization} with the HTTP basic authentication.
+ * Cf. {@code BasicHeaderApiTokenAuthenticator}.
+ */
public CLIConnectionFactory basicAuth(String userInfo) {
return authorization("Basic " + new String(Base64.encodeBase64((userInfo).getBytes())));
}
-
+
+ /**
+ * @deprecated Specific to Remoting-based protocol.
+ */
+ @Deprecated
public CLI connect() throws IOException, InterruptedException {
return new CLI(this);
}
diff --git a/cli/src/main/java/hudson/cli/CliEntryPoint.java b/cli/src/main/java/hudson/cli/CliEntryPoint.java
index 57bd42e553b77336c2a0db0085ae4ce0474c76b2..fe1f6c835097888be71632babbe31f1b6530fab5 100644
--- a/cli/src/main/java/hudson/cli/CliEntryPoint.java
+++ b/cli/src/main/java/hudson/cli/CliEntryPoint.java
@@ -34,7 +34,9 @@ import java.util.Locale;
* Remotable interface for CLI entry point on the server side.
*
* @author Kohsuke Kawaguchi
+ * @deprecated Specific to Remoting-based protocol.
*/
+@Deprecated
public interface CliEntryPoint {
/**
* Just like the static main method.
diff --git a/cli/src/main/java/hudson/cli/CliPort.java b/cli/src/main/java/hudson/cli/CliPort.java
index 8faab7de41bee0c88fa90cdaa0cbb8c9599f6a97..56165e766ba6186489d4e3017b0a7a35510358ab 100644
--- a/cli/src/main/java/hudson/cli/CliPort.java
+++ b/cli/src/main/java/hudson/cli/CliPort.java
@@ -8,9 +8,9 @@ import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
-/**
- * @author Kohsuke Kawaguchi
- */
+ /**
+ * @deprecated Specific to Remoting mode.
+ */
public final class CliPort {
/**
* The TCP endpoint to talk to.
diff --git a/cli/src/main/java/hudson/cli/Connection.java b/cli/src/main/java/hudson/cli/Connection.java
index 1c1ada471fdbaaded6f2203ec130a7e69a671a6f..017051a64a646a76b4ecfe81966381e3874adbca 100644
--- a/cli/src/main/java/hudson/cli/Connection.java
+++ b/cli/src/main/java/hudson/cli/Connection.java
@@ -56,6 +56,9 @@ import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
+/**
+ * Used by Jenkins core only in deprecated Remoting-based CLI.
+ */
public class Connection {
public final InputStream in;
public final OutputStream out;
diff --git a/cli/src/main/java/hudson/cli/DiagnosedStreamCorruptionException.java b/cli/src/main/java/hudson/cli/DiagnosedStreamCorruptionException.java
new file mode 100644
index 0000000000000000000000000000000000000000..4708b425dbb77a715910ac28c67ec84aca79db0f
--- /dev/null
+++ b/cli/src/main/java/hudson/cli/DiagnosedStreamCorruptionException.java
@@ -0,0 +1,55 @@
+package hudson.cli;
+
+import java.io.PrintWriter;
+import java.io.StreamCorruptedException;
+import java.io.StringWriter;
+
+// TODO COPIED FROM hudson.remoting
+
+/**
+ * Signals a {@link StreamCorruptedException} with some additional diagnostic information.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+class DiagnosedStreamCorruptionException extends StreamCorruptedException {
+ private final Exception diagnoseFailure;
+ private final byte[] readBack;
+ private final byte[] readAhead;
+
+ DiagnosedStreamCorruptionException(Exception cause, Exception diagnoseFailure, byte[] readBack, byte[] readAhead) {
+ initCause(cause);
+ this.diagnoseFailure = diagnoseFailure;
+ this.readBack = readBack;
+ this.readAhead = readAhead;
+ }
+
+ public Exception getDiagnoseFailure() {
+ return diagnoseFailure;
+ }
+
+ public byte[] getReadBack() {
+ return readBack;
+ }
+
+ public byte[] getReadAhead() {
+ return readAhead;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(super.toString()).append("\n");
+ buf.append("Read back: ").append(HexDump.toHex(readBack)).append('\n');
+ buf.append("Read ahead: ").append(HexDump.toHex(readAhead));
+ if (diagnoseFailure!=null) {
+ StringWriter w = new StringWriter();
+ PrintWriter p = new PrintWriter(w);
+ diagnoseFailure.printStackTrace(p);
+ p.flush();
+
+ buf.append("\nDiagnosis problem:\n ");
+ buf.append(w.toString().trim().replace("\n","\n "));
+ }
+ return buf.toString();
+ }
+}
diff --git a/cli/src/main/java/hudson/cli/FlightRecorderInputStream.java b/cli/src/main/java/hudson/cli/FlightRecorderInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..ebdd18a192fec4d2e29e25c85dbeb0c2679e40d6
--- /dev/null
+++ b/cli/src/main/java/hudson/cli/FlightRecorderInputStream.java
@@ -0,0 +1,191 @@
+package hudson.cli;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+// TODO COPIED FROM hudson.remoting
+
+/**
+ * Filter input stream that records the content as it's read, so that it can be reported
+ * in case of a catastrophic stream corruption problem.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+class FlightRecorderInputStream extends InputStream {
+
+ /**
+ * Size (in bytes) of the flight recorder ring buffer used for debugging remoting issues.
+ * @since 2.41
+ */
+ static final int BUFFER_SIZE = Integer.getInteger("hudson.remoting.FlightRecorderInputStream.BUFFER_SIZE", 1024 * 1024);
+
+ private final InputStream source;
+ private ByteArrayRingBuffer recorder = new ByteArrayRingBuffer(BUFFER_SIZE);
+
+ FlightRecorderInputStream(InputStream source) {
+ this.source = source;
+ }
+
+ /**
+ * Rewinds the record buffer and forget everything that was recorded.
+ */
+ public void clear() {
+ recorder = new ByteArrayRingBuffer(BUFFER_SIZE);
+ }
+
+ /**
+ * Gets the recorded content.
+ */
+ public byte[] getRecord() {
+ return recorder.toByteArray();
+ }
+
+ /**
+ * Creates a {@link DiagnosedStreamCorruptionException} based on the recorded content plus read ahead.
+ * The caller is responsible for throwing the exception.
+ */
+ public DiagnosedStreamCorruptionException analyzeCrash(Exception problem, String diagnosisName) {
+ final ByteArrayOutputStream readAhead = new ByteArrayOutputStream();
+ final IOException[] error = new IOException[1];
+
+ Thread diagnosisThread = new Thread(diagnosisName+" stream corruption diagnosis thread") {
+ public void run() {
+ int b;
+ try {
+ // not all InputStream will look for the thread interrupt flag, so check that explicitly to be defensive
+ while (!Thread.interrupted() && (b=source.read())!=-1) {
+ readAhead.write(b);
+ }
+ } catch (IOException e) {
+ error[0] = e;
+ }
+ }
+ };
+
+ // wait up to 1 sec to grab as much data as possible
+ diagnosisThread.start();
+ try {
+ diagnosisThread.join(1000);
+ } catch (InterruptedException ignored) {
+ // we are only waiting for a fixed amount of time, so we'll pretend like we were in a busy loop
+ Thread.currentThread().interrupt();
+ // fall through
+ }
+
+ IOException diagnosisProblem = error[0]; // capture the error, if any, before we kill the thread
+ if (diagnosisThread.isAlive())
+ diagnosisThread.interrupt(); // if it's not dead, kill
+
+ return new DiagnosedStreamCorruptionException(problem,diagnosisProblem,getRecord(),readAhead.toByteArray());
+
+ }
+
+ @Override
+ public int read() throws IOException {
+ int i = source.read();
+ if (i>=0)
+ recorder.write(i);
+ return i;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ len = source.read(b, off, len);
+ if (len>0)
+ recorder.write(b,off,len);
+ return len;
+ }
+
+ /**
+ * To record the bytes we've skipped, convert the call to read.
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ byte[] buf = new byte[(int)Math.min(n,64*1024)];
+ return read(buf,0,buf.length);
+ }
+
+ @Override
+ public int available() throws IOException {
+ return source.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ source.close();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ // http://stackoverflow.com/a/3651696/12916
+ private static class ByteArrayRingBuffer extends OutputStream {
+
+ byte[] data;
+
+ int capacity, pos = 0;
+
+ boolean filled = false;
+
+ public ByteArrayRingBuffer(int capacity) {
+ data = new byte[capacity];
+ this.capacity = capacity;
+ }
+
+ @Override
+ public synchronized void write(int b) {
+ if (pos == capacity) {
+ filled = true;
+ pos = 0;
+ }
+ data[pos++] = (byte) b;
+ }
+
+ public synchronized byte[] toByteArray() {
+ if (!filled) {
+ return Arrays.copyOf(data, pos);
+ }
+ byte[] ret = new byte[capacity];
+ System.arraycopy(data, pos, ret, 0, capacity - pos);
+ System.arraycopy(data, 0, ret, capacity - pos, pos);
+ return ret;
+ }
+
+ /** @author @roadrunner2 */
+ @Override public synchronized void write(byte[] buf, int off, int len) {
+ // no point in trying to copy more than capacity; this also simplifies logic below
+ if (len > capacity) {
+ off += (len - capacity);
+ len = capacity;
+ }
+
+ // copy to buffer, but no farther than the end
+ int num = Math.min(len, capacity - pos);
+ if (num > 0) {
+ System.arraycopy(buf, off, data, pos, num);
+ off += num;
+ len -= num;
+ pos += num;
+ }
+
+ // wrap around if necessary
+ if (pos == capacity) {
+ filled = true;
+ pos = 0;
+ }
+
+ // copy anything still left
+ if (len > 0) {
+ System.arraycopy(buf, off, data, pos, len);
+ pos += len;
+ }
+ }
+
+ }
+
+}
diff --git a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
index abde9bb2022664eda42f5b28d91e9d58fbcfe04f..9b18142b7824257af32e510e4eb99d5cc677cf69 100644
--- a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
+++ b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
@@ -1,9 +1,7 @@
package hudson.cli;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -16,11 +14,11 @@ import org.apache.commons.codec.binary.Base64;
/**
* Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over
* HTTP, which is a request/response protocol.
- *
+ * {@code FullDuplexHttpService} is the counterpart on the server side.
* @author Kohsuke Kawaguchi
*/
public class FullDuplexHttpStream {
- private final URL target;
+ private final URL base;
/**
* Authorization header value needed to get through the HTTP layer.
*/
@@ -29,10 +27,18 @@ public class FullDuplexHttpStream {
private final OutputStream output;
private final InputStream input;
+ /**
+ * A way to get data from the server.
+ * There will be an initial zero byte used as a handshake which you should expect and ignore.
+ */
public InputStream getInputStream() {
return input;
}
+ /**
+ * A way to upload data to the server.
+ * You will need to write to this and {@link OutputStream#flush} it to finish establishing a connection.
+ */
public OutputStream getOutputStream() {
return output;
}
@@ -49,20 +55,40 @@ public class FullDuplexHttpStream {
}
/**
- * @param target
+ * @param target something like {@code http://jenkins/cli?remoting=true}
+ * which we then need to split into {@code http://jenkins/} + {@code cli?remoting=true}
+ * in order to construct a crumb issuer request
+ * @deprecated use {@link #FullDuplexHttpStream(URL, String, String)} instead
+ */
+ @Deprecated
+ public FullDuplexHttpStream(URL target, String authorization) throws IOException {
+ this(new URL(target.toString().replaceFirst("/cli.*$", "/")), target.toString().replaceFirst("^.+/(cli.*)$", "$1"), authorization);
+ }
+
+ /**
+ * @param base the base URL of Jenkins
+ * @param relativeTarget
* The endpoint that we are making requests to.
* @param authorization
* The value of the authorization header, if non-null.
*/
- public FullDuplexHttpStream(URL target, String authorization) throws IOException {
- this.target = target;
+ public FullDuplexHttpStream(URL base, String relativeTarget, String authorization) throws IOException {
+ if (!base.toString().endsWith("/")) {
+ throw new IllegalArgumentException(base.toString());
+ }
+ if (relativeTarget.startsWith("/")) {
+ throw new IllegalArgumentException(relativeTarget);
+ }
+
+ this.base = tryToResolveRedirects(base, authorization);
this.authorization = authorization;
- CrumbData crumbData = new CrumbData();
+ URL target = new URL(this.base, relativeTarget);
UUID uuid = UUID.randomUUID(); // so that the server can correlate those two connections
// server->client
+ LOGGER.fine("establishing download side");
HttpURLConnection con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST to avoid caching
con.setRequestMethod("POST");
@@ -71,16 +97,16 @@ public class FullDuplexHttpStream {
if (authorization != null) {
con.addRequestProperty("Authorization", authorization);
}
- if(crumbData.isValid) {
- con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
- }
con.getOutputStream().close();
input = con.getInputStream();
- // make sure we hit the right URL
- if(con.getHeaderField("Hudson-Duplex")==null)
- throw new IOException(target+" doesn't look like Jenkins");
+ // make sure we hit the right URL; no need for CLI.verifyJenkinsConnection here
+ if (con.getHeaderField("Hudson-Duplex") == null) {
+ throw new CLI.NotTalkingToJenkinsException("There's no Jenkins running at " + target + ", or is not serving the HTTP Duplex transport");
+ }
+ LOGGER.fine("established download side"); // calling getResponseCode or getHeaderFields breaks everything
- // client->server uses chunked encoded POST for unlimited capacity.
+ // client->server uses chunked encoded POST for unlimited capacity.
+ LOGGER.fine("establishing upload side");
con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST
con.setRequestMethod("POST");
@@ -91,69 +117,29 @@ public class FullDuplexHttpStream {
if (authorization != null) {
con.addRequestProperty ("Authorization", authorization);
}
-
- if(crumbData.isValid) {
- con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
- }
output = con.getOutputStream();
+ LOGGER.fine("established upload side");
}
- static final int BLOCK_SIZE = 1024;
- static final Logger LOGGER = Logger.getLogger(FullDuplexHttpStream.class.getName());
-
- private final class CrumbData {
- String crumbName;
- String crumb;
- boolean isValid;
-
- private CrumbData() {
- this.crumbName = "";
- this.crumb = "";
- this.isValid = false;
- getData();
- }
-
- private void getData() {
- try {
- String base = createCrumbUrlBase();
- String[] pair = readData(base + "?xpath=concat(//crumbRequestField,\":\",//crumb)").split(":", 2);
- crumbName = pair[0];
- crumb = pair[1];
- isValid = true;
- LOGGER.fine("Crumb data: "+crumbName+"="+crumb);
- } catch (IOException e) {
- // presumably this Hudson doesn't use crumb
- LOGGER.log(Level.FINE,"Failed to get crumb data",e);
- }
- }
-
- private String createCrumbUrlBase() {
- String url = target.toExternalForm();
- return new StringBuilder(url.substring(0, url.lastIndexOf("/cli"))).append("/crumbIssuer/api/xml/").toString();
- }
-
- private String readData(String dest) throws IOException {
- HttpURLConnection con = (HttpURLConnection) new URL(dest).openConnection();
+ // As this transport mode is using POST, it is necessary to resolve possible redirections using GET first.
+ private URL tryToResolveRedirects(URL base, String authorization) {
+ try {
+ HttpURLConnection con = (HttpURLConnection) base.openConnection();
if (authorization != null) {
con.addRequestProperty("Authorization", authorization);
}
- try {
- BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
- String line = reader.readLine();
- String nextLine = reader.readLine();
- if (nextLine != null) {
- System.err.println("Warning: received junk from " + dest);
- System.err.println(line);
- System.err.println(nextLine);
- while ((nextLine = reader.readLine()) != null) {
- System.err.println(nextLine);
- }
- }
- return line;
- }
- finally {
- con.disconnect();
- }
+ con.getInputStream().close();
+ base = con.getURL();
+ } catch (Exception ex) {
+ // Do not obscure the problem propagating the exception. If the problem is real it will manifest during the
+ // actual exchange so will be reported properly there. If it is not real (no permission in UI but sufficient
+ // for CLI connection using one of its mechanisms), there is no reason to bother user about it.
+ LOGGER.log(Level.FINE, "Failed to resolve potential redirects", ex);
}
+ return base;
}
+
+ static final int BLOCK_SIZE = 1024;
+ static final Logger LOGGER = Logger.getLogger(FullDuplexHttpStream.class.getName());
+
}
diff --git a/cli/src/main/java/hudson/cli/HexDump.java b/cli/src/main/java/hudson/cli/HexDump.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad37158bc16d0e7939a34831bd7628120eb61207
--- /dev/null
+++ b/cli/src/main/java/hudson/cli/HexDump.java
@@ -0,0 +1,47 @@
+package hudson.cli;
+
+// TODO COPIED FROM hudson.remoting
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+class HexDump {
+ private static final String CODE = "0123456789abcdef";
+
+ public static String toHex(byte[] buf) {
+ return toHex(buf,0,buf.length);
+ }
+ public static String toHex(byte[] buf, int start, int len) {
+ StringBuilder r = new StringBuilder(len*2);
+ boolean inText = false;
+ for (int i=0; i= 0x20 && b <= 0x7e) {
+ if (!inText) {
+ inText = true;
+ r.append('\'');
+ }
+ r.append((char) b);
+ } else {
+ if (inText) {
+ r.append("' ");
+ inText = false;
+ }
+ r.append("0x");
+ r.append(CODE.charAt((b>>4)&15));
+ r.append(CODE.charAt(b&15));
+ if (i < len - 1) {
+ if (b == 10) {
+ r.append('\n');
+ } else {
+ r.append(' ');
+ }
+ }
+ }
+ }
+ if (inText) {
+ r.append('\'');
+ }
+ return r.toString();
+ }
+}
diff --git a/cli/src/main/java/hudson/cli/PlainCLIProtocol.java b/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed5c453360dd2475ca7605adeee13476004b694e
--- /dev/null
+++ b/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
@@ -0,0 +1,346 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2017 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.cli;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ReadPendingException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.CountingInputStream;
+
+/**
+ * CLI protocol working over a plain socket-like connection, without SSH or Remoting.
+ * Each side consists of frames starting with an {@code int} length,
+ * then a {@code byte} opcode, then any opcode-specific data.
+ * The length does not count the length field itself nor the opcode, so it is nonnegative.
+ */
+class PlainCLIProtocol {
+
+ static final Logger LOGGER = Logger.getLogger(PlainCLIProtocol.class.getName());
+
+ /** One-byte operation to send to the other side. */
+ private enum Op {
+ /** UTF-8 command name or argument. */
+ ARG(true),
+ /** UTF-8 locale identifier. */
+ LOCALE(true),
+ /** UTF-8 client encoding. */
+ ENCODING(true),
+ /** Start running command. */
+ START(true),
+ /** Exit code, as int. */
+ EXIT(false),
+ /** Chunk of stdin, as int length followed by bytes. */
+ STDIN(true),
+ /** EOF on stdin. */
+ END_STDIN(true),
+ /** Chunk of stdout. */
+ STDOUT(false),
+ /** Chunk of stderr. */
+ STDERR(false);
+ /** True if sent from the client to the server; false if sent from the server to the client. */
+ final boolean clientSide;
+ Op(boolean clientSide) {
+ this.clientSide = clientSide;
+ }
+ }
+
+ static abstract class EitherSide implements Closeable {
+
+ private final CountingInputStream cis;
+ private final FlightRecorderInputStream flightRecorder;
+ final DataInputStream dis;
+ final DataOutputStream dos;
+
+ protected EitherSide(InputStream is, OutputStream os) {
+ cis = new CountingInputStream(is);
+ flightRecorder = new FlightRecorderInputStream(cis);
+ dis = new DataInputStream(flightRecorder);
+ dos = new DataOutputStream(os);
+ }
+
+ final void begin() {
+ new Reader().start();
+ }
+
+ private class Reader extends Thread {
+
+ Reader() {
+ super("PlainCLIProtocol"); // TODO set distinctive Thread.name
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ LOGGER.finest("reading frame");
+ int framelen;
+ try {
+ framelen = dis.readInt();
+ } catch (EOFException x) {
+ handleClose();
+ break; // TODO verify that we hit EOF immediately, not partway into framelen
+ }
+ if (framelen < 0) {
+ throw new IOException("corrupt stream: negative frame length");
+ }
+ byte b = dis.readByte();
+ if (b < 0) { // i.e., >127
+ throw new IOException("corrupt stream: negative operation code");
+ }
+ if (b >= Op.values().length) {
+ LOGGER.log(Level.WARNING, "unknown operation #{0}: {1}", new Object[] {b, HexDump.toHex(flightRecorder.getRecord())});
+ IOUtils.skipFully(dis, framelen);
+ continue;
+ }
+ Op op = Op.values()[b];
+ long start = cis.getByteCount();
+ LOGGER.log(Level.FINEST, "handling frame with {0} of length {1}", new Object[] {op, framelen});
+ boolean handled = handle(op, framelen);
+ if (handled) {
+ long actuallyRead = cis.getByteCount() - start;
+ if (actuallyRead != framelen) {
+ throw new IOException("corrupt stream: expected to read " + framelen + " bytes from " + op + " but read " + actuallyRead);
+ }
+ } else {
+ LOGGER.log(Level.WARNING, "unexpected {0}: {1}", new Object[] {op, HexDump.toHex(flightRecorder.getRecord())});
+ IOUtils.skipFully(dis, framelen);
+ }
+ }
+ } catch (ClosedChannelException x) {
+ LOGGER.log(Level.FINE, null, x);
+ handleClose();
+ } catch (IOException x) {
+ LOGGER.log(Level.WARNING, null, flightRecorder.analyzeCrash(x, "broken stream"));
+ } catch (ReadPendingException x) {
+ // in case trick in CLIAction does not work
+ LOGGER.log(Level.FINE, null, x);
+ handleClose();
+ } catch (RuntimeException x) {
+ LOGGER.log(Level.WARNING, null, x);
+ handleClose();
+ }
+ }
+
+ }
+
+ protected abstract void handleClose();
+
+ protected abstract boolean handle(Op op, int framelen) throws IOException;
+
+ private void writeOp(Op op) throws IOException {
+ dos.writeByte((byte) op.ordinal());
+ }
+
+ protected final synchronized void send(Op op) throws IOException {
+ dos.writeInt(0);
+ writeOp(op);
+ dos.flush();
+ }
+
+ protected final synchronized void send(Op op, int number) throws IOException {
+ dos.writeInt(4);
+ writeOp(op);
+ dos.writeInt(number);
+ dos.flush();
+ }
+
+ protected final synchronized void send(Op op, byte[] chunk, int off, int len) throws IOException {
+ dos.writeInt(len);
+ writeOp(op);
+ dos.write(chunk, off, len);
+ dos.flush();
+ }
+
+ protected final void send(Op op, byte[] chunk) throws IOException {
+ send(op, chunk, 0, chunk.length);
+ }
+
+ protected final void send(Op op, String text) throws IOException {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ new DataOutputStream(buf).writeUTF(text);
+ send(op, buf.toByteArray());
+ }
+
+ protected final byte[] readChunk(int framelen) throws IOException {
+ assert Thread.currentThread() instanceof EitherSide.Reader;
+ byte[] buf = new byte[framelen];
+ dis.readFully(buf);
+ return buf;
+ }
+
+ protected final OutputStream stream(final Op op) {
+ return new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ send(op, new byte[] {(byte) b});
+ }
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ send(op, b, off, len);
+ }
+ @Override
+ public void write(byte[] b) throws IOException {
+ send(op, b);
+ }
+ };
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ dos.close();
+ }
+
+ }
+
+ static abstract class ServerSide extends EitherSide {
+
+ ServerSide(InputStream is, OutputStream os) {
+ super(is, os);
+ }
+
+ @Override
+ protected final boolean handle(Op op, int framelen) throws IOException {
+ assert Thread.currentThread() instanceof EitherSide.Reader;
+ assert op.clientSide;
+ switch (op) {
+ case ARG:
+ onArg(dis.readUTF());
+ return true;
+ case LOCALE:
+ onLocale(dis.readUTF());
+ return true;
+ case ENCODING:
+ onEncoding(dis.readUTF());
+ return true;
+ case START:
+ onStart();
+ return true;
+ case STDIN:
+ onStdin(readChunk(framelen));
+ return true;
+ case END_STDIN:
+ onEndStdin();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected abstract void onArg(String text);
+
+ protected abstract void onLocale(String text);
+
+ protected abstract void onEncoding(String text);
+
+ protected abstract void onStart();
+
+ protected abstract void onStdin(byte[] chunk) throws IOException;
+
+ protected abstract void onEndStdin() throws IOException;
+
+ public final void sendExit(int code) throws IOException {
+ send(Op.EXIT, code);
+ }
+
+ public final OutputStream streamStdout() {
+ return stream(Op.STDOUT);
+ }
+
+ public final OutputStream streamStderr() {
+ return stream(Op.STDERR);
+ }
+
+ }
+
+ static abstract class ClientSide extends EitherSide {
+
+ ClientSide(InputStream is, OutputStream os) {
+ super(is, os);
+ }
+
+ @Override
+ protected boolean handle(Op op, int framelen) throws IOException {
+ assert Thread.currentThread() instanceof EitherSide.Reader;
+ assert !op.clientSide;
+ switch (op) {
+ case EXIT:
+ onExit(dis.readInt());
+ return true;
+ case STDOUT:
+ onStdout(readChunk(framelen));
+ return true;
+ case STDERR:
+ onStderr(readChunk(framelen));
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected abstract void onExit(int code);
+
+ protected abstract void onStdout(byte[] chunk) throws IOException;
+
+ protected abstract void onStderr(byte[] chunk) throws IOException;
+
+ public final void sendArg(String text) throws IOException {
+ send(Op.ARG, text);
+ }
+
+ public final void sendLocale(String text) throws IOException {
+ send(Op.LOCALE, text);
+ }
+
+ public final void sendEncoding(String text) throws IOException {
+ send(Op.ENCODING, text);
+ }
+
+ public final void sendStart() throws IOException {
+ send(Op.START);
+ }
+
+ public final OutputStream streamStdin() {
+ return stream(Op.STDIN);
+ }
+
+ public final void sendEndStdin() throws IOException {
+ send(Op.END_STDIN);
+ }
+
+ }
+
+ private PlainCLIProtocol() {}
+
+}
diff --git a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
index 834bb7234250d7d7bac2bd424aba5aa670be0fda..d7753a750498aff78325fc04326eb63b7e45209d 100644
--- a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
+++ b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
@@ -30,6 +30,9 @@ import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
@@ -127,15 +130,13 @@ public class PrivateKeyProvider {
}
private static String readPemFile(File f) throws IOException{
- FileInputStream is = new FileInputStream(f);
- try {
- DataInputStream dis = new DataInputStream(is);
+ try (InputStream is = Files.newInputStream(f.toPath());
+ DataInputStream dis = new DataInputStream(is)) {
byte[] bytes = new byte[(int) f.length()];
dis.readFully(bytes);
- dis.close();
return new String(bytes);
- } finally {
- is.close();
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
}
}
diff --git a/cli/src/main/java/hudson/cli/SSHCLI.java b/cli/src/main/java/hudson/cli/SSHCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..bdd6c2beb50e5e696af3467e5bae7598f41eb5ef
--- /dev/null
+++ b/cli/src/main/java/hudson/cli/SSHCLI.java
@@ -0,0 +1,133 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2017 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.cli;
+
+import hudson.util.QuotedStringTokenizer;
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import static java.util.logging.Level.FINE;
+import java.util.logging.Logger;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.keyverifier.DefaultKnownHostsServerKeyVerifier;
+import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.future.WaitableFuture;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * 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.
+ * That allows the {@code test} module to pick up a version of {@code sshd-core} from the {@code sshd} module via {@code jenkins-war}
+ * that may not match the version being used from the {@code cli} module and may not be compatible.
+ */
+class SSHCLI {
+
+ 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");
+ URLConnection conn = url.openConnection();
+ CLI.verifyJenkinsConnection(conn);
+ String endpointDescription = conn.getHeaderField("X-SSH-Endpoint");
+
+ if (endpointDescription == null) {
+ CLI.LOGGER.warning("No header 'X-SSH-Endpoint' returned by Jenkins");
+ return -1;
+ }
+
+ CLI.LOGGER.log(FINE, "Connecting via SSH to: {0}", endpointDescription);
+
+ int sshPort = Integer.parseInt(endpointDescription.split(":")[1]);
+ String sshHost = endpointDescription.split(":")[0];
+
+ StringBuilder command = new StringBuilder();
+
+ for (String arg : args) {
+ command.append(QuotedStringTokenizer.quote(arg));
+ command.append(' ');
+ }
+
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+
+ KnownHostsServerKeyVerifier verifier = new DefaultKnownHostsServerKeyVerifier(new ServerKeyVerifier() {
+ @Override
+ public boolean verifyServerKey(ClientSession clientSession, SocketAddress remoteAddress, PublicKey serverKey) {
+ CLI.LOGGER.log(Level.WARNING, "Unknown host key for {0}", remoteAddress.toString());
+ return !strictHostKey;
+ }
+ }, true);
+
+ client.setServerKeyVerifier(verifier);
+ client.start();
+
+ ConnectFuture cf = client.connect(user, sshHost, sshPort);
+ cf.await();
+ try (ClientSession session = cf.getSession()) {
+ for (KeyPair pair : provider.getKeys()) {
+ CLI.LOGGER.log(FINE, "Offering {0} private key", pair.getPrivate().getAlgorithm());
+ session.addPublicKeyIdentity(pair);
+ }
+ session.auth().verify(10000L);
+
+ try (ClientChannel channel = session.createExecChannel(command.toString())) {
+ channel.setIn(new NoCloseInputStream(System.in));
+ channel.setOut(new NoCloseOutputStream(System.out));
+ channel.setErr(new NoCloseOutputStream(System.err));
+ WaitableFuture wf = channel.open();
+ wf.await();
+
+ Set waitMask = channel.waitFor(Collections.singletonList(ClientChannelEvent.CLOSED), 0L);
+
+ if(waitMask.contains(ClientChannelEvent.TIMEOUT)) {
+ throw new SocketTimeoutException("Failed to retrieve command result in time: " + command);
+ }
+
+ Integer exitStatus = channel.getExitStatus();
+ return exitStatus;
+
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ private SSHCLI() {}
+
+}
diff --git a/cli/src/main/java/hudson/cli/SequenceOutputStream.java b/cli/src/main/java/hudson/cli/SequenceOutputStream.java
deleted file mode 100644
index fb6c26523b47317af89a975c7bd67930095ccabc..0000000000000000000000000000000000000000
--- a/cli/src/main/java/hudson/cli/SequenceOutputStream.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package hudson.cli;
-
-import java.io.OutputStream;
-import java.io.IOException;
-import java.io.SequenceInputStream;
-
-/**
- * {@link OutputStream} version of {@link SequenceInputStream}.
- *
- * Provides a single {@link OutputStream} view over multiple {@link OutputStream}s (each of the fixed length.)
- *
- * @author Kohsuke Kawaguchi
- */
-abstract class SequenceOutputStream extends OutputStream {
- protected static class Block {
- final OutputStream out;
- long capacity;
-
- public Block(OutputStream out, long capacity) {
- this.out = out;
- this.capacity = capacity;
- }
- }
-
- /**
- * Current block being written.
- */
- private Block block;
-
- protected SequenceOutputStream(Block block) {
- this.block = block;
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- while(len>0) {
- int sz = (int)Math.min(len, block.capacity);
- block.out.write(b,off,sz);
- block.capacity -=sz;
- len-=sz;
- off+=sz;
- swapIfNeeded();
- }
- }
-
- public void write(int b) throws IOException {
- block.out.write(b);
- block.capacity--;
- swapIfNeeded();
- }
-
- private void swapIfNeeded() throws IOException {
- if(block.capacity >0) return;
- block.out.close();
- block=next(block);
- }
-
- @Override
- public void flush() throws IOException {
- block.out.flush();
- }
-
- @Override
- public void close() throws IOException {
- block.out.close();
- block=null;
- }
-
- /**
- * Fetches the next {@link OutputStream} to write to,
- * along with their capacity.
- */
- protected abstract Block next(Block current) throws IOException;
-}
diff --git a/core/src/main/java/hudson/util/QuotedStringTokenizer.java b/cli/src/main/java/hudson/util/QuotedStringTokenizer.java
similarity index 100%
rename from core/src/main/java/hudson/util/QuotedStringTokenizer.java
rename to cli/src/main/java/hudson/util/QuotedStringTokenizer.java
diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties
index 699b4c47381227a92f7316331cbdbc3477759213..921fe67a211da560ac75fc355179a117b3c00dc2 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -2,10 +2,18 @@ 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\
- -i KEY : SSH private key file used for authentication\n\
- -p HOST:PORT : HTTP proxy host and port for HTTPS proxy tunneling. See http://jenkins-ci.org/https-proxy-tunnel\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\
\n\
The available commands depend on the server. Run the 'help' command to\n\
see the list.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_bg.properties b/cli/src/main/resources/hudson/cli/client/Messages_bg.properties
index 187d7208d6bea45ac5ed62fbac8dac35cc469937..acd0191d5c8bfbf854d5344b2d29f8463f3b55e9 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_bg.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_bg.properties
@@ -28,7 +28,7 @@ CLI.Usage=\
\u0441\u0440\u0435\u0434\u0430\u0442\u0430 \u201eJENKINS_URL\u201c)\n\
-i \u041a\u041b\u042e\u0427 : \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447 \u0437\u0430 SSH \u0437\u0430 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\n\
-p \u0425\u041e\u0421\u0422:\u041f\u041e\u0420\u0422 : \u0445\u043e\u0441\u0442 \u0438 \u043f\u043e\u0440\u0442 \u0437\u0430 \u0441\u044a\u0440\u0432\u044a\u0440-\u043f\u043e\u0441\u0440\u0435\u0434\u043d\u0438\u043a \u043f\u043e HTTP \u0437\u0430 \u0442\u0443\u043d\u0435\u043b \u043f\u043e HTTPS.\n\
- \u0417\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f: http://jenkins-ci.org/https-proxy-tunnel\n\
+ \u0417\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f: https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
-noCertificateCheck : \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430 \u0437\u0430 HTTPS (\u0412\u041d\u0418\u041c\u0410\u041d\u0418\u0415!)\n\
-noKeyAuth : \u0431\u0435\u0437 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0441 \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447 \u0437\u0430 SSH, \u043e\u043f\u0446\u0438\u044f\u0442\u0430 \u0435\n\
\u043d\u0435\u0441\u044a\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u0430 \u0441 \u043e\u043f\u0446\u0438\u044f\u0442\u0430 \u201e-i\u201c\n\n\
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_de.properties b/cli/src/main/resources/hudson/cli/client/Messages_de.properties
index 55769d64ded51906132d5ff94ef67e36aa9c66d1..70b91e222f0240ecbd2390eb7867765e0a6c3e69 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_de.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_de.properties
@@ -2,13 +2,13 @@
CLI.Usage=Jenkins Kommandozeilenschnittstelle (Jenkins CLI)\n\
Verwendung: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
Optionen:\n\
- -s URL : URL des Hudson-Servers (Wert der Umgebungsvariable JENKINS_URL ist der Vorgabewert)\n\
- -i KEY : Datei mit privatem SSH-Schl\u00fcssel zur Authentisierung\n\
- -p HOST\:PORT : HTTP-Proxy-Host und -Port f\u00fcr HTTPS-Proxy-Tunnel. Siehe http://jenkins-ci.org/https-proxy-tunnel\n\
- -noCertificateCheck : \u00dcberspringt die Zertifikatspr\u00fcfung bei HTTPS. Bitte mit Vorsicht einsetzen.\n\
- -noKeyAuth : \u00dcberspringt die Authentifizierung mit einem privaten SSH-Schl\u00fcssel. Nicht kombinierbar mit -i\n\
+ -s URL : URL des Jenkins-Servers (Wert der Umgebungsvariable JENKINS_URL ist der Vorgabewert)\n\
+ -i KEY : Datei mit privatem SSH-Schl\u00FCssel zur Authentisierung\n\
+ -p HOST\:PORT : HTTP-Proxy-Host und -Port f\u00FCr HTTPS-Proxy-Tunnel. Siehe https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ -noCertificateCheck : \u00DCberspringt die Zertifikatspr\u00FCfung bei HTTPS. Bitte mit Vorsicht einsetzen.\n\
+ -noKeyAuth : \u00DCberspringt die Authentifizierung mit einem privaten SSH-Schl\u00FCssel. Nicht kombinierbar mit -i\n\
\n\
- Die verf\u00fcgbaren Kommandos h\u00e4ngen vom kontaktierten Server ab. Verwenden Sie das Kommando help, um eine Liste aller verf\u00fcgbaren Kommandos anzuzeigen.
+ Die verf\u00FCgbaren Kommandos h\u00E4ngen vom kontaktierten Server ab. Verwenden Sie das Kommando help, um eine Liste aller verf\u00FCgbaren Kommandos anzuzeigen.
CLI.NoURL=Weder die Option -s noch eine Umgebungsvariable JENKINS_URL wurde spezifiziert.
CLI.NoSuchFileExists=Diese Datei existiert nicht {0}
CLI.VersionMismatch=Versionskonflikt: Diese Version von Jenkins CLI ist nicht mit dem kontaktierten Jenkins-Server kompatibel.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_fr.properties b/cli/src/main/resources/hudson/cli/client/Messages_fr.properties
new file mode 100644
index 0000000000000000000000000000000000000000..99fe0930f0d8df074c43e5a44ee25cf212381cff
--- /dev/null
+++ b/cli/src/main/resources/hudson/cli/client/Messages_fr.properties
@@ -0,0 +1,14 @@
+CLI.Usage=Jenkins CLI\n\
+ Utilisation: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
+ Options:\n\
+ -s URL : l''URL du serveur (par d\u00e9faut : variable d environnement JENKINS_URL)\n\
+ -i KEY : fichier de la cl\u00e9 priv\u00e9e SSH \u00e0 utiliser pour l''authentification\n\
+ -p HOST:PORT : h\u00f4te et port des proxys HTTP et HTTPS. Voir https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ -noCertificateCheck : contourne enti\u00e9rement la v\u00e9rification des certificats HTTPS. A utiliser avec pr\u00e9caution\n\
+ -noKeyAuth : ne charge pas la cl\u00e9 priv\u00e9e d''authentification SSH. En conflit avec -i\n\
+ \n\
+ Les commandes disponibles d\u00e9pendent du serveur. Lancer la commande 'help' pour\n\
+ obtenir la liste.
+CLI.NoURL=Erreur, ni -s ou la variable d''environnement JENKINS_URL ne sont renseign\u00e9es.
+CLI.VersionMismatch=Conflit de versions. Cet outil ne peut fonctionner avec le serveur Jenkins renseign\u00e9.
+CLI.NoSuchFileExists=Ce fichier n''existe pas: {0}
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_it.properties b/cli/src/main/resources/hudson/cli/client/Messages_it.properties
new file mode 100644
index 0000000000000000000000000000000000000000..6236107d9c7cff3786ea619aba6097291835d6f7
--- /dev/null
+++ b/cli/src/main/resources/hudson/cli/client/Messages_it.properties
@@ -0,0 +1,22 @@
+CLI.NoSuchFileExists=File non esistente: {0}
+CLI.NoURL=Non sono stati specificati n -s n la variabile d''ambiente JENKINS_URL.
+CLI.VersionMismatch=Le versioni non corrispondono. Quest''interfaccia della riga di comando non pu funzionare con questo server Jenkins.
+CLI.Usage=Interfaccia della riga di comando di Jenkins\n\
+Uso: java -jar jenkins-cli.jar [-s URL] comando [opzioni...] argomenti...\n\
+Opzioni:\n\
+-s URL : l''URL del server (impostazione predefinita: la variabile d''ambiente JENKINS_URL)\n\
+-http : utilizza un protocollo interfaccia della riga di comando in testo semplice su HTTP(S) (impostazione predefinita; mutualmente esclusiva con -ssh e -remoting)\n\
+-ssh : utilizza il protocollo SSH (richiede -user; la porta SSH deve essere aperta sul server e l''utente deve aver registrato una chiave pubblica)\n\
+-remoting : utilizza il protocollo deprecato Remoting channel (se abilitato sul server; solo per compatibilit con comandi o modalit comandi legacy)\n\
+-i KEY : file chiave privata SSH utilizzato per l''autenticazione (per l''utilizzo con -ssh o -remoting)\n\
+-p HOST:PORTA : host proxy HTTP e porta per il tunneling proxy HTTPS. Vedi https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+-noCertificateCheck : ometti completamente il controllo dei certificati HTTPS. Utilizzare con cautela\n\
+-noKeyAuth : non tentare di caricare la chiave privata per l''autenticazione SSH. In conflitto con -i\n\
+-user : specifica l''utente (per l''utilizzo con -ssh)\n\
+-strictHostKey : richiedi la modalit strict per il controllo delle chiavi host (per l''utilizzo con -ssh)\n\
+-logger FINE : abilita la registrazione dettagliata da parte del client\n\
+-auth [ UTENTE:SEGRETO | @FILE ] : specifica il nome utente e la password o il token API (o caricali entrambi da un file);\n\
+per l''utilizzo con -http, o -remoting ma solo quando la porta agente JNLP disabilitata\n\
+\n\
+I comandi disponibili dipendono dal server. Eseguire il comando ''help'' per\n\
+visualizzarne l''elenco.
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_pt_BR.properties b/cli/src/main/resources/hudson/cli/client/Messages_pt_BR.properties
index 61e1895c4dfb7d5dd96cf0dd4723671fb60c673a..779e88f1a6ecfe5230b2e29d84e4191a8c1de443 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_pt_BR.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_pt_BR.properties
@@ -26,7 +26,7 @@ CLI.Usage=Jenkins CLI\n\
Op\u00e7\u00f5es:\n\
-s URL : a URL do servidor (por padr\u00e3o a vari\u00e1vel de ambiente JENKINS_URL \u00e9 usada)\n\
-i KEY : arquivo contendo a chave SSH privada usada para autentica\u00e7\u00e3o\n\
- -p HOST:PORT : host e porta do proxy HTTP para tunelamento de proxy HTTPS. Veja http://jenkins-ci.org/https-proxy-tunnel\n\
+ -p HOST:PORT : host e porta do proxy HTTP para tunelamento de proxy HTTPS. Veja https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
-noCertificateCheck : ignora completamente a valida\u00e7\u00e3o dos certificados HTTPS. Use com cautela\n\
-noKeyAuth : n\u00e3o tenta carregar a chave privada para autentica\u00e7\u00e3o SSH. Conflita com -i\n\
\n\
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_zh_TW.properties b/cli/src/main/resources/hudson/cli/client/Messages_zh_TW.properties
index af303db03487aba6d903487ef308bae13d859464..4a9068624434d8c2bd6ef77c3fefe778c5fea59e 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_zh_TW.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_zh_TW.properties
@@ -28,7 +28,7 @@ CLI.Usage=Jenkins CLI\n\
\u9078\u9805:\n\
-s URL : \u4f3a\u670d\u5668 URL (\u9810\u8a2d\u503c\u70ba JENKINS_URL \u74b0\u5883\u8b8a\u6578)\n\
-i KEY : \u9a57\u8b49\u7528\u7684 SSH \u79c1\u9470\u6a94\n\
- -p HOST:PORT : \u5efa HTTPS Proxy Tunnel \u7684 HTTP \u4ee3\u7406\u4f3a\u670d\u5668\u4e3b\u6a5f\u53ca\u9023\u63a5\u57e0\u3002\u8acb\u53c3\u8003 http://jenkins-ci.org/https-proxy-tunnel\n\
+ -p HOST:PORT : \u5efa HTTPS Proxy Tunnel \u7684 HTTP \u4ee3\u7406\u4f3a\u670d\u5668\u4e3b\u6a5f\u53ca\u9023\u63a5\u57e0\u3002\u8acb\u53c3\u8003 https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
-noCertificateCheck : \u5b8c\u5168\u7565\u904e HTTPS \u6191\u8b49\u6aa2\u67e5\u3002\u8acb\u5c0f\u5fc3\u4f7f\u7528\n\
\n\
\u53ef\u7528\u7684\u6307\u4ee4\u53d6\u6c7a\u65bc\u4f3a\u670d\u5668\u3002\u57f7\u884c 'help' \u6307\u4ee4\u53ef\u4ee5\u67e5\u770b\u5b8c\u6574\u6e05\u55ae\u3002
diff --git a/cli/src/test/java/hudson/cli/PlainCLIProtocolTest.java b/cli/src/test/java/hudson/cli/PlainCLIProtocolTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4663fbafd5e0aa1a05852562ef8c8560f3d0cac3
--- /dev/null
+++ b/cli/src/test/java/hudson/cli/PlainCLIProtocolTest.java
@@ -0,0 +1,132 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2017 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.cli;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class PlainCLIProtocolTest {
+
+ @Test
+ public void ignoreUnknownOperations() throws Exception {
+ final PipedOutputStream upload = new PipedOutputStream();
+ final PipedOutputStream download = new PipedOutputStream();
+ class Client extends PlainCLIProtocol.ClientSide {
+ int code = -1;
+ final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+ Client() throws IOException {
+ super(new PipedInputStream(download), upload);
+ }
+ @Override
+ protected synchronized void onExit(int code) {
+ this.code = code;
+ notifyAll();
+ }
+ @Override
+ protected void onStdout(byte[] chunk) throws IOException {
+ stdout.write(chunk);
+ }
+ @Override
+ protected void onStderr(byte[] chunk) throws IOException {}
+ @Override
+ protected void handleClose() {}
+ void send() throws IOException {
+ sendArg("command");
+ sendStart();
+ streamStdin().write("hello".getBytes());
+ }
+ void newop() throws IOException {
+ dos.writeInt(0);
+ dos.writeByte(99);
+ dos.flush();
+ }
+ }
+ class Server extends PlainCLIProtocol.ServerSide {
+ String arg;
+ boolean started;
+ final ByteArrayOutputStream stdin = new ByteArrayOutputStream();
+ Server() throws IOException {
+ super(new PipedInputStream(upload), download);
+ }
+ @Override
+ protected void onArg(String text) {
+ arg = text;
+ }
+ @Override
+ protected void onLocale(String text) {}
+ @Override
+ protected void onEncoding(String text) {}
+ @Override
+ protected synchronized void onStart() {
+ started = true;
+ notifyAll();
+ }
+ @Override
+ protected void onStdin(byte[] chunk) throws IOException {
+ stdin.write(chunk);
+ }
+ @Override
+ protected void onEndStdin() throws IOException {}
+ @Override
+ protected void handleClose() {}
+ void send() throws IOException {
+ streamStdout().write("goodbye".getBytes());
+ sendExit(2);
+ }
+ void newop() throws IOException {
+ dos.writeInt(0);
+ dos.writeByte(99);
+ dos.flush();
+ }
+ }
+ Client client = new Client();
+ Server server = new Server();
+ client.begin();
+ server.begin();
+ client.send();
+ client.newop();
+ synchronized (server) {
+ while (!server.started) {
+ server.wait();
+ }
+ }
+ server.newop();
+ server.send();
+ synchronized (client) {
+ while (client.code == -1) {
+ client.wait();
+ }
+ }
+ assertEquals("hello", server.stdin.toString());
+ assertEquals("command", server.arg);
+ assertEquals("goodbye", client.stdout.toString());
+ assertEquals(2, client.code);
+ }
+
+}
diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
index 376b1fa815f3f62988f741c93a63f2f2d090563a..86970265089b4bf41cb7bc4aae7d6e320447d21a 100644
--- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
+++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
@@ -57,7 +57,7 @@ public class PrivateKeyProviderTest {
final File dsaKey = keyFile(".ssh/id_dsa");
final File rsaKey = keyFile(".ssh/id_rsa");
- run("-i", dsaKey.getAbsolutePath(), "-i", rsaKey.getAbsolutePath(), "-s", "http://example.com");
+ run("-remoting", "-i", dsaKey.getAbsolutePath(), "-i", rsaKey.getAbsolutePath(), "-s", "http://example.com");
verify(cli).authenticate(withKeyPairs(
keyPair(dsaKey),
@@ -73,7 +73,7 @@ public class PrivateKeyProviderTest {
final File dsaKey = keyFile(".ssh/id_dsa");
fakeHome();
- run("-s", "http://example.com");
+ run("-remoting", "-s", "http://example.com");
verify(cli).authenticate(withKeyPairs(
keyPair(rsaKey),
diff --git a/core/move-l10n.groovy b/core/move-l10n.groovy
index d774ec50d4817573f8d4ba37a33632745a1f63b9..c52e66ae53aecf6318efc70b079e6777df580bd6 100644
--- a/core/move-l10n.groovy
+++ b/core/move-l10n.groovy
@@ -13,6 +13,7 @@ for (p in new File(resDir, oldview).parentFile.listFiles()) {
def n = p.name;
if (n == "${basename}.properties" || n.startsWith("${basename}_") && n.endsWith(".properties")) {
def lines = p.readLines('ISO-8859-1');
+ // TODO does not handle multiline values correctly
def matches = lines.findAll({it.startsWith("${key}=")});
if (!matches.isEmpty()) {
lines.removeAll(matches);
@@ -24,6 +25,7 @@ for (p in new File(resDir, oldview).parentFile.listFiles()) {
} else {
def nue = new File(resDir, newview + n.substring(basename.length()));
println("moving ${matches.size()} matches from ${n} to ${nue.name}");
+ // TODO if the original lacked a trailing newline, this will corrupt previously final key
nue.withWriterAppend('ISO-8859-1') {out ->
matches.each {line -> out.writeLine(line)}
}
diff --git a/core/pom.xml b/core/pom.xml
index c0628ab4cd81be813c9987c39f6047dd9577e82e..86fe7180d7aa364190d919c4c855937ad08992b6 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.mainpom
- 2.23-SNAPSHOT
+ 2.92-SNAPSHOTjenkins-core
@@ -39,9 +39,9 @@ THE SOFTWARE.
true
- 1.243
+ 1.2532.5.6.SEC03
- 2.4.7
+ 2.4.11true
@@ -65,7 +65,7 @@ THE SOFTWARE.
org.jenkins-civersion-number
- 1.1
+ 1.4org.jenkins-ci
@@ -95,6 +95,12 @@ THE SOFTWARE.
com.google.injectguice
+
+
+ com.google.guava
+ guava
+
+
@@ -105,7 +111,7 @@ THE SOFTWARE.
com.github.jnrjnr-posix
- 3.0.1
+ 3.0.41org.kohsuke
@@ -115,7 +121,7 @@ THE SOFTWARE.
org.jenkins-citrilead-ssh2
- build217-jenkins-8
+ build-217-jenkins-11org.kohsuke.stapler
@@ -159,7 +165,7 @@ THE SOFTWARE.
org.kohsuke.staplerstapler-adjunct-timeline
- 1.4
+ 1.5org.kohsuke.stapler
@@ -197,7 +203,7 @@ THE SOFTWARE.
org.jenkins-ciannotation-indexer
- 1.11
+ 1.12org.jenkins-ci
@@ -212,7 +218,7 @@ THE SOFTWARE.
org.jvnet.localizerlocalizer
- 1.23
+ 1.24antlr
@@ -466,9 +472,9 @@ THE SOFTWARE.
test
- javax.servlet
- jstl
- 1.1.0
+ javax.servlet.jsp.jstl
+ javax.servlet.jsp.jstl-api
+ 1.2.1org.slf4j
@@ -491,12 +497,12 @@ THE SOFTWARE.
commons-collectionscommons-collections
- 3.2.1
+ 3.2.2org.jvnet.winpwinp
- 1.22
+ 1.25org.jenkins-ci
@@ -529,9 +535,9 @@ THE SOFTWARE.
1.8
- org.jvnet.libzfs
+ org.kohsukelibzfs
- 0.5
+ 0.8com.sun.solaris
@@ -566,16 +572,14 @@ THE SOFTWARE.
1.1
-
+ commons-codeccommons-codec
- 1.8org.kohsukeaccess-modifier-annotation
- 1.4
@@ -588,13 +592,7 @@ THE SOFTWARE.
commons-fileuploadcommons-fileupload
- 1.3.1-jenkins-1
-
-
-
- org.mindrot
- jbcrypt
- 0.3m
+ 1.3.1-jenkins-2
@@ -606,10 +604,21 @@ THE SOFTWARE.
/usr/local/yjp/lib/yjp.jar
-
- com.google.guava
- guava
-
+
+ com.google.guava
+ guava
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
+
+ com.google.guava
+ guava-testlib
+ test
+ com.jcraft
@@ -647,6 +656,7 @@ THE SOFTWARE.
generate-taglib-interface
+ record-core-location
@@ -696,6 +706,7 @@ THE SOFTWARE.
Messages.propertiestarget/generated-sources/localizer
+ true
@@ -703,7 +714,6 @@ THE SOFTWARE.
org.kohsukeaccess-modifier-checker
-
@@ -754,7 +764,7 @@ THE SOFTWARE.
com.sun.winswwinsw
- 1.16
+ 2.1.2binexe${project.build.outputDirectory}/windows-service
@@ -770,7 +780,7 @@ THE SOFTWARE.
0.5Ctrue
- -XX:MaxPermSize=128m -noverify
+ -noverify
@@ -845,21 +855,9 @@ THE SOFTWARE.
release
-
- org.codehaus.mojo
- apt-maven-plugin
-
-
-
-
- process
-
-
-
-
diff --git a/core/src/build-script/Cobertura.groovy b/core/src/build-script/Cobertura.groovy
index 073f6291b77c10c07ad948a47a264997e1c7069f..49b773b49ba17e79fa74975cf8a0b20d42153143 100644
--- a/core/src/build-script/Cobertura.groovy
+++ b/core/src/build-script/Cobertura.groovy
@@ -56,7 +56,6 @@ public class Cobertura {
}
sysproperty(key:"net.sourceforge.cobertura.datafile",value:ser)
sysproperty(key:"hudson.ClassicPluginStrategy.useAntClassLoader",value:"true")
- jvmarg(value:"-XX:MaxPermSize=128m")
}
}
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_id.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_id.properties
deleted file mode 100644
index c28903b2ddf3ae2e9e532bb7006518699b72bf77..0000000000000000000000000000000000000000
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_id.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-# This file is under the MIT License by authors
-
-NewVersionAvailable=Versi baru Jenkins ({0}) tersedia untuk diunduh (catatan perubahan).
-Or\ Upgrade\ Automatically=Atau Perbarui Secara Otomatis
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
index 7378e405c76f9e21a930a2e698b8baf8f024a484..510a7f5f861f964e339da3ac0755dab0a1779d10 100644
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
+++ b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_it.properties
@@ -1,27 +1,6 @@
-# The MIT License
-#
-# Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Giulio D'Ambrosi
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-NewVersionAvailable=C''\u00E8 una nuova versione di Jenkins ({0}) disponibile per il download (changelog).
-Or\ Upgrade\ Automatically=Oppure aggiorna automaticamente
-UpgradeComplete=Aggiornamento a Jenkins {0} completato, in attesa di riavvio.
-UpgradeCompleteRestartNotSupported=Aggiornamento a Jenkins {0} completato, in attesa di riavvio.
-UpgradeProgress=Aggiornamento a Jenkins {0} in corso oppure fallito.
+NewVersionAvailable=Una nuova versione di Jenkins ({0}) disponibile per il download \
+ (log delle modifiche).
+UpgradeComplete=Aggiornamento a Jenkins {0} completato, in attesa del riavvio.
+UpgradeCompleteRestartNotSupported=Aggiornamento a Jenkins {0} completato, in attesa del riavvio.
+UpgradeProgress=Aggiornamento a Jenkins {0} in corso.
+UpgradeFailed=Aggiornamento a Jenkins {0} non riuscito: {1}.
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties
index 6f5ff479b3092c96f38a93a2f6fb46a126fead29..18a2cbe3c311f03744e1c92f180bddd06a09461a 100644
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties
+++ b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties
@@ -1,6 +1,25 @@
-# This file is under the MIT License by authors
-
+# The MIT License
+#
+# Copyright (c) 2013-2016, Kohsuke Kawaguchi, Damian Szczepanik
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
NewVersionAvailable=Nowa wersja Jenkinsa ({0}) jest dost\u0119pna do pobrania (historia zmian).
-Or\ Upgrade\ Automatically=Albo uaktualnij automatycznie
+Or\ Upgrade\ Automatically=Uaktualnij automatycznie
UpgradeCompleteRestartNotSupported=Aktualizacja Jenkinsa do wersji {0} zako\u0144czy\u0142a si\u0119 pomy\u015Blnie, oczekiwanie na ponowne uruchomienie.
UpgradeProgress=Aktualizacja Jenkinsa do wersji {0} trwa lub nie powiod\u0142a si\u0119.
diff --git a/core/src/main/java/hudson/AbstractMarkupText.java b/core/src/main/java/hudson/AbstractMarkupText.java
index ed7e319711040b7bbb8ba657a7e1227dca85d626..1e78b3e10cfadca15f5745415141a6d4d8edf4dc 100644
--- a/core/src/main/java/hudson/AbstractMarkupText.java
+++ b/core/src/main/java/hudson/AbstractMarkupText.java
@@ -72,8 +72,8 @@ public abstract class AbstractMarkupText {
* Adds a start tag and end tag at the specified position.
*
*
- * For example, if the text was "abc", then addMarkup(1,2,"<b>","</b>")
- * would generate "a<b>b</b>c"
+ * For example, if the text was "abc", then {@code addMarkup(1,2,"","")}
+ * would generate {@code"abc"}
*/
public abstract void addMarkup( int startPos, int endPos, String startTag, String endTag );
diff --git a/core/src/main/java/hudson/BulkChange.java b/core/src/main/java/hudson/BulkChange.java
index 3c7da451f6384a6f516e7e2aa45851b5ea77f846..28b9fde4261074ca2e4828534139d16e18c8f464 100644
--- a/core/src/main/java/hudson/BulkChange.java
+++ b/core/src/main/java/hudson/BulkChange.java
@@ -54,7 +54,7 @@ import java.io.IOException;
*
*
*
- * Mutater methods should invoke {@code this.save()} so that if the method is called outside
+ * Mutator methods should invoke {@code this.save()} so that if the method is called outside
* a {@link BulkChange}, the result will be saved immediately.
*
*
@@ -78,7 +78,7 @@ public class BulkChange implements Closeable {
public BulkChange(Saveable saveable) {
this.parent = current();
this.saveable = saveable;
- // rememeber who allocated this object in case
+ // remember who allocated this object in case
// someone forgot to call save() at the end.
allocator = new Exception();
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index ff933347c3beda81f7dc0c9323bf41b2ac0771c0..bc00b2a0c9a68ac9412d4d7934a4afc445a84b3d 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -23,6 +23,13 @@
*/
package hudson;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+
+import jenkins.util.AntWithFindResourceClassLoader;
import jenkins.util.SystemProperties;
import com.google.common.collect.Lists;
import hudson.Plugin.DummyImpl;
@@ -53,25 +60,24 @@ import org.apache.tools.zip.ZipOutputStream;
import java.io.Closeable;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
-import java.lang.reflect.Field;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.jenkinsci.bytecode.Transformer;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -107,11 +113,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
if (isLinked(archive)) {
manifest = loadLinkedManifest(archive);
} else {
- JarFile jf = new JarFile(archive, false);
- try {
+ try (JarFile jf = new JarFile(archive, false)) {
manifest = jf.getManifest();
- } finally {
- jf.close();
}
}
return PluginWrapper.computeShortName(manifest, archive.getName());
@@ -126,11 +129,10 @@ public class ClassicPluginStrategy implements PluginStrategy {
try {
// Locate the manifest
String firstLine;
- FileInputStream manifestHeaderInput = new FileInputStream(archive);
- try {
+ try (InputStream manifestHeaderInput = Files.newInputStream(archive.toPath())) {
firstLine = IOUtils.readFirstLine(manifestHeaderInput, "UTF-8");
- } finally {
- manifestHeaderInput.close();
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
}
if (firstLine.startsWith("Manifest-Version:")) {
// this is the manifest already
@@ -140,11 +142,10 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
// Read the manifest
- FileInputStream manifestInput = new FileInputStream(archive);
- try {
+ try (InputStream manifestInput = Files.newInputStream(archive.toPath())) {
return new Manifest(manifestInput);
- } finally {
- manifestInput.close();
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
}
} catch (IOException e) {
throw new IOException("Failed to load " + archive, e);
@@ -176,11 +177,10 @@ public class ClassicPluginStrategy implements PluginStrategy {
"Plugin installation failed. No manifest at "
+ manifestFile);
}
- FileInputStream fin = new FileInputStream(manifestFile);
- try {
+ try (InputStream fin = Files.newInputStream(manifestFile.toPath())) {
manifest = new Manifest(fin);
- } finally {
- fin.close();
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
}
}
@@ -197,8 +197,10 @@ public class ClassicPluginStrategy implements PluginStrategy {
baseResourceURL = resolve(archive,atts.getValue("Resource-Path")).toURI().toURL();
} else {
File classes = new File(expandDir, "WEB-INF/classes");
- if (classes.exists())
+ if (classes.exists()) { // should not normally happen, due to createClassJarFromWebInfClasses
+ LOGGER.log(Level.WARNING, "Deprecated unpacked classes directory found in {0}", classes);
paths.add(classes);
+ }
File lib = new File(expandDir, "WEB-INF/lib");
File[] libs = lib.listFiles(JAR_FILTER);
if (libs != null)
@@ -267,7 +269,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
if (detached.shortName.equals(pluginName)) {
continue;
}
- if (BREAK_CYCLES.contains(pluginName + '/' + detached.shortName)) {
+ if (BREAK_CYCLES.contains(pluginName + ' ' + detached.shortName)) {
LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {pluginName, detached.shortName});
continue;
}
@@ -402,40 +404,42 @@ public class ClassicPluginStrategy implements PluginStrategy {
* Gets the minimum required version for the current version of Jenkins.
*
* @return the minimum required version for the current version of Jenkins.
- * @sice 2.16
+ * @since 2.16
*/
public VersionNumber getRequiredVersion() {
return new VersionNumber(requiredVersion);
}
+
+ @Override
+ public String toString() {
+ return shortName + " " + splitWhen.toString().replace(".*", "") + " " + requiredVersion;
+ }
}
- private static final List DETACHED_LIST = Collections.unmodifiableList(Arrays.asList(
- new DetachedPlugin("maven-plugin", "1.296", "1.296"),
- new DetachedPlugin("subversion", "1.310", "1.0"),
- new DetachedPlugin("cvs", "1.340", "0.1"),
- new DetachedPlugin("ant", "1.430.*", "1.0"),
- new DetachedPlugin("javadoc", "1.430.*", "1.0"),
- new DetachedPlugin("external-monitor-job", "1.467.*", "1.0"),
- new DetachedPlugin("ldap", "1.467.*", "1.0"),
- new DetachedPlugin("pam-auth", "1.467.*", "1.0"),
- new DetachedPlugin("mailer", "1.493.*", "1.2"),
- new DetachedPlugin("matrix-auth", "1.535.*", "1.0.2"),
- new DetachedPlugin("windows-slaves", "1.547.*", "1.0"),
- new DetachedPlugin("antisamy-markup-formatter", "1.553.*", "1.0"),
- new DetachedPlugin("matrix-project", "1.561.*", "1.0"),
- new DetachedPlugin("junit", "1.577.*", "1.0"),
- new DetachedPlugin("bouncycastle-api", "2.16.*", "2.16.0")
- ));
+ /** Record of which plugins which removed from core and when. */
+ private static final List DETACHED_LIST;
/** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
- private static final Set BREAK_CYCLES = new HashSet(Arrays.asList(
- "script-security/matrix-auth",
- "script-security/windows-slaves",
- "script-security/antisamy-markup-formatter",
- "script-security/matrix-project",
- "credentials/matrix-auth",
- "credentials/windows-slaves"
- ));
+ private static final Set BREAK_CYCLES;
+
+ static {
+ try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugins.txt")) {
+ DETACHED_LIST = ImmutableList.copyOf(configLines(is).map(line -> {
+ String[] pieces = line.split(" ");
+ return new DetachedPlugin(pieces[0], pieces[1] + ".*", pieces[2]);
+ }).collect(Collectors.toList()));
+ } catch (IOException x) {
+ throw new ExceptionInInitializerError(x);
+ }
+ try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugin-cycles.txt")) {
+ BREAK_CYCLES = ImmutableSet.copyOf(configLines(is).collect(Collectors.toSet()));
+ } catch (IOException x) {
+ throw new ExceptionInInitializerError(x);
+ }
+ }
+ private static Stream configLines(InputStream is) throws IOException {
+ return org.apache.commons.io.IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*"));
+ }
/**
* Computes the classloader that takes the class masking into account.
@@ -646,14 +650,13 @@ public class ClassicPluginStrategy implements PluginStrategy {
final long dirTime = archive.lastModified();
// this ZipOutputStream is reused and not created for each directory
- final ZipOutputStream wrappedZOut = new ZipOutputStream(new NullOutputStream()) {
+ try (ZipOutputStream wrappedZOut = new ZipOutputStream(new NullOutputStream()) {
@Override
public void putNextEntry(ZipEntry ze) throws IOException {
ze.setTime(dirTime+1999); // roundup
super.putNextEntry(ze);
}
- };
- try {
+ }) {
Zip z = new Zip() {
/**
* Forces the fixed timestamp for directories to make sure
@@ -672,8 +675,9 @@ public class ClassicPluginStrategy implements PluginStrategy {
z.setDestFile(classesJar);
z.add(mapper);
z.execute();
- } finally {
- wrappedZOut.close();
+ }
+ if (classesJar.isFile()) {
+ LOGGER.log(Level.WARNING, "Created {0}; update plugin to a version created with a newer harness", classesJar);
}
}
@@ -831,53 +835,11 @@ public class ClassicPluginStrategy implements PluginStrategy {
/**
* {@link AntClassLoader} with a few methods exposed, {@link Closeable} support, and {@link Transformer} support.
*/
- private final class AntClassLoader2 extends AntClassLoader implements Closeable {
- private final Vector pathComponents;
-
+ private final class AntClassLoader2 extends AntWithFindResourceClassLoader implements Closeable {
private AntClassLoader2(ClassLoader parent) {
- super(parent,true);
-
- try {
- Field $pathComponents = AntClassLoader.class.getDeclaredField("pathComponents");
- $pathComponents.setAccessible(true);
- pathComponents = (Vector)$pathComponents.get(this);
- } catch (NoSuchFieldException | IllegalAccessException e) {
- throw new Error(e);
- }
+ super(parent, true);
}
-
-
- public void addPathFiles(Collection paths) throws IOException {
- for (File f : paths)
- addPathFile(f);
- }
-
- public void close() throws IOException {
- cleanup();
- }
-
- /**
- * As of 1.8.0, {@link AntClassLoader} doesn't implement {@link #findResource(String)}
- * in any meaningful way, which breaks fast lookup. Implement it properly.
- */
- @Override
- protected URL findResource(String name) {
- URL url = null;
-
- // try and load from this loader if the parent either didn't find
- // it or wasn't consulted.
- Enumeration e = pathComponents.elements();
- while (e.hasMoreElements() && url == null) {
- File pathComponent = (File) e.nextElement();
- url = getResourceURL(pathComponent, name);
- if (url != null) {
- log("Resource " + name + " loaded from ant loader", Project.MSG_DEBUG);
- }
- }
-
- return url;
- }
-
+
@Override
protected Class defineClassFromData(File container, byte[] classData, String classname) throws IOException {
if (!DISABLE_TRANSFORMER)
diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java
index acf035676910ad3ac77f6e4b3164ed7358fc0f39..03efea55e4036a69b1b3297e083c8a3a86a351af 100644
--- a/core/src/main/java/hudson/DependencyRunner.java
+++ b/core/src/main/java/hudson/DependencyRunner.java
@@ -59,7 +59,7 @@ public class DependencyRunner implements Runnable {
Set topLevelProjects = new HashSet();
// Get all top-level projects
LOGGER.fine("assembling top level projects");
- for (AbstractProject p : Jenkins.getInstance().getAllItems(AbstractProject.class))
+ for (AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class))
if (p.getUpstreamProjects().size() == 0) {
LOGGER.fine("adding top level project " + p.getName());
topLevelProjects.add(p);
diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java
index 16d96ae441b0ff5a7cc4a3e59e8398db0cc595a4..dd334db858be47f551cf25a897d37b11d095fae5 100644
--- a/core/src/main/java/hudson/DescriptorExtensionList.java
+++ b/core/src/main/java/hudson/DescriptorExtensionList.java
@@ -31,7 +31,6 @@ import jenkins.model.Jenkins;
import hudson.model.ViewDescriptor;
import hudson.model.Descriptor.FormException;
import hudson.util.AdaptedIterator;
-import hudson.util.Memoizer;
import hudson.util.Iterators.FlattenIterator;
import hudson.slaves.NodeDescriptor;
import hudson.tasks.Publisher;
@@ -41,6 +40,8 @@ import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -213,14 +214,12 @@ public class DescriptorExtensionList, D extends Descrip
/**
* Stores manually registered Descriptor instances. Keyed by the {@link Describable} type.
*/
- private static final Memoizer>> legacyDescriptors = new Memoizer>>() {
- public CopyOnWriteArrayList compute(Class key) {
- return new CopyOnWriteArrayList();
- }
- };
+ @SuppressWarnings("rawtypes")
+ private static final Map>> legacyDescriptors = new ConcurrentHashMap<>();
+ @SuppressWarnings({"unchecked", "rawtypes"})
private static > CopyOnWriteArrayList>> getLegacyDescriptors(Class type) {
- return (CopyOnWriteArrayList)legacyDescriptors.get(type);
+ return legacyDescriptors.computeIfAbsent(type, key -> new CopyOnWriteArrayList());
}
/**
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index 5cdc672bf4cd8caf70f40d045c8e10a7bb4bb49c..1849ecbf66aebaf6ca6a4df5ce197b5b825c8b36 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -43,6 +43,7 @@ import java.util.Arrays;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Logger;
+import javax.annotation.Nonnull;
/**
* Environment variables.
@@ -88,7 +89,7 @@ public class EnvVars extends TreeMap {
super(CaseInsensitiveComparator.INSTANCE);
}
- public EnvVars(Map m) {
+ public EnvVars(@Nonnull Map m) {
this();
putAll(m);
@@ -100,7 +101,7 @@ public class EnvVars extends TreeMap {
}
}
- public EnvVars(EnvVars m) {
+ public EnvVars(@Nonnull EnvVars m) {
// this constructor is so that in future we can get rid of the downcasting.
this((Map)m);
}
@@ -210,13 +211,15 @@ public class EnvVars extends TreeMap {
private final Comparator super String> comparator;
+ @Nonnull
private final EnvVars target;
+ @Nonnull
private final Map overrides;
private Map> refereeSetMap;
private List orderedVariableNames;
- public OverrideOrderCalculator(EnvVars target, Map overrides) {
+ public OverrideOrderCalculator(@Nonnull EnvVars target, @Nonnull Map overrides) {
comparator = target.comparator();
this.target = target;
this.overrides = overrides;
@@ -323,9 +326,9 @@ public class EnvVars extends TreeMap {
/**
* Overrides all values in the map by the given map. Expressions in values will be expanded.
* See {@link #override(String, String)}.
- * @return this
+ * @return {@code this}
*/
- public EnvVars overrideExpandingAll(Map all) {
+ public EnvVars overrideExpandingAll(@Nonnull Map all) {
for (String key : new OverrideOrderCalculator(this, all).getOrderedVariableNames()) {
override(key, expand(all.get(key)));
}
diff --git a/core/src/main/java/hudson/Extension.java b/core/src/main/java/hudson/Extension.java
index 5ad913ef6ad39a1e8f2c35168c0863e48ec31221..1ca863d2f4f5cbdf46c66ede57c9e59920e5781b 100644
--- a/core/src/main/java/hudson/Extension.java
+++ b/core/src/main/java/hudson/Extension.java
@@ -74,7 +74,8 @@ public @interface Extension {
/**
* Used for sorting extensions.
*
- * Extensions will be sorted in the descending order of the ordinal.
+ * Extensions will be sorted in the descending order of the ordinal. In other words,
+ * the extensions with the highest numbers will be chosen first.
* This is a rather poor approach to the problem, so its use is generally discouraged.
*
* @since 1.306
diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java
index 397f57030dbb538a2a3f1ecb0d7712e572c6a7fa..2420f05057463381ca99ba188209ef78847756d6 100644
--- a/core/src/main/java/hudson/ExtensionList.java
+++ b/core/src/main/java/hudson/ExtensionList.java
@@ -30,7 +30,6 @@ import jenkins.ExtensionComponentSet;
import jenkins.model.Jenkins;
import hudson.util.AdaptedIterator;
import hudson.util.DescriptorList;
-import hudson.util.Memoizer;
import hudson.util.Iterators;
import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson;
@@ -40,12 +39,15 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
+import jenkins.util.io.OnMaster;
/**
* Retains the known extension instances for the given type 'T'.
@@ -68,7 +70,7 @@ import javax.annotation.Nonnull;
* @see jenkins.model.Jenkins#getExtensionList(Class)
* @see jenkins.model.Jenkins#getDescriptorList(Class)
*/
-public class ExtensionList extends AbstractList {
+public class ExtensionList extends AbstractList implements OnMaster {
/**
* @deprecated as of 1.417
* Use {@link #jenkins}
@@ -203,6 +205,21 @@ public class ExtensionList extends AbstractList {
}
}
+ @Override
+ public boolean removeAll(Collection> c) {
+ boolean removed = false;
+ try {
+ for (Object o : c) {
+ removed |= removeSync(o);
+ }
+ return removed;
+ } finally {
+ if (extensions != null && removed) {
+ fireOnChangeListeners();
+ }
+ }
+ }
+
private synchronized boolean removeSync(Object o) {
boolean removed = removeComponent(legacyInstances, o);
if(extensions!=null) {
@@ -379,11 +396,12 @@ public class ExtensionList extends AbstractList {
return create((Jenkins)hudson,type);
}
+ @SuppressWarnings({"unchecked", "rawtypes"})
public static ExtensionList create(Jenkins jenkins, Class type) {
if(type.getAnnotation(LegacyInstancesAreScopedToHudson.class)!=null)
return new ExtensionList(jenkins,type);
else {
- return new ExtensionList(jenkins,type,staticLegacyInstances.get(type));
+ return new ExtensionList(jenkins, type, staticLegacyInstances.computeIfAbsent(type, key -> new CopyOnWriteArrayList()));
}
}
@@ -402,13 +420,29 @@ public class ExtensionList extends AbstractList {
}
/**
- * Places to store static-scope legacy instances.
+ * Convenience method allowing lookup of the only instance of a given type.
+ * Equivalent to {@code ExtensionList.lookup(Class).get(Class)} if there is one instance,
+ * and throws an {@code IllegalStateException} otherwise.
+ *
+ * @param type The type to look up.
+ * @return the singleton instance of the given type in its list.
+ * @throws IllegalStateException if there are no instances, or more than one
+ *
+ * @since TODO
*/
- private static final Memoizer staticLegacyInstances = new Memoizer() {
- public CopyOnWriteArrayList compute(Class key) {
- return new CopyOnWriteArrayList();
+ public static @Nonnull U lookupSingleton(Class type) {
+ ExtensionList all = lookup(type);
+ if (all.size() != 1) {
+ throw new IllegalStateException("Expected 1 instance of " + type.getName() + " but got " + all.size());
}
- };
+ return all.get(0);
+ }
+
+ /**
+ * Places to store static-scope legacy instances.
+ */
+ @SuppressWarnings("rawtypes")
+ private static final Map staticLegacyInstances = new ConcurrentHashMap<>();
/**
* Exposed for the test harness to clear all legacy extension instances.
diff --git a/core/src/main/java/hudson/ExtensionListView.java b/core/src/main/java/hudson/ExtensionListView.java
index 59b81ffd95869297d20ea67694b923652235812d..100dad6fb758520864230cd18157b52bc062986e 100644
--- a/core/src/main/java/hudson/ExtensionListView.java
+++ b/core/src/main/java/hudson/ExtensionListView.java
@@ -23,6 +23,7 @@
*/
package hudson;
+import hudson.tasks.UserNameResolver;
import jenkins.model.Jenkins;
import hudson.util.CopyOnWriteList;
diff --git a/core/src/main/java/hudson/ExtensionPoint.java b/core/src/main/java/hudson/ExtensionPoint.java
index f49a5a096cc7ba2b6671b3ec8a0dd5b23746f3a2..e544ed7576ad411694e868a149dbf2ee1a6971b2 100644
--- a/core/src/main/java/hudson/ExtensionPoint.java
+++ b/core/src/main/java/hudson/ExtensionPoint.java
@@ -29,6 +29,7 @@ import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
+import jenkins.util.io.OnMaster;
/**
* Marker interface that designates extensible components
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 6b2529eabe6a7084cc45dce9c5e07ed0a6376731..ad13edf5f338a1c5a1c0a81b9f3c0bae6d7cdbc1 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -25,7 +25,6 @@
*/
package hudson;
-import jenkins.util.SystemProperties;
import com.google.common.annotations.VisibleForTesting;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
@@ -59,30 +58,9 @@ import hudson.util.IOUtils;
import hudson.util.NamingThreadFactory;
import hudson.util.io.Archiver;
import hudson.util.io.ArchiverFactory;
-import jenkins.FilePathFilter;
-import jenkins.MasterToSlaveFileCallable;
-import jenkins.SlaveToMasterFileCallable;
-import jenkins.SoloFilePathFilter;
-import jenkins.model.Jenkins;
-import jenkins.util.ContextResettingExecutorService;
-import jenkins.util.VirtualFile;
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.io.input.CountingInputStream;
-import org.apache.tools.ant.DirectoryScanner;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.types.FileSet;
-import org.apache.tools.zip.ZipEntry;
-import org.apache.tools.zip.ZipFile;
-import org.kohsuke.accmod.Restricted;
-import org.kohsuke.accmod.restrictions.NoExternalUse;
-import org.kohsuke.stapler.Stapler;
-
-import javax.annotation.CheckForNull;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
@@ -99,6 +77,8 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -116,16 +96,37 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
-import static hudson.FilePath.TarCompression.*;
-import static hudson.Util.*;
+import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import jenkins.FilePathFilter;
+import jenkins.MasterToSlaveFileCallable;
+import jenkins.SlaveToMasterFileCallable;
+import jenkins.SoloFilePathFilter;
+import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
+import jenkins.util.ContextResettingExecutorService;
+import jenkins.util.VirtualFile;
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.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipFile;
import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.Stapler;
+
+import static hudson.FilePath.TarCompression.GZIP;
+import static hudson.Util.deleteFile;
+import static hudson.Util.fixEmpty;
+import static hudson.Util.isSymlink;
+import java.util.Collections;
/**
* {@link File} like object with remoting support.
@@ -167,7 +168,7 @@ import org.jenkinsci.remoting.RoleSensitive;
* }
* // if 'file' is on a different node, this FileCallable will
* // be transferred to that node and executed there.
- * private static final class Freshen implements FileCallable<Void> {
+ * private static final class Freshen implements FileCallable<Void> {
* private static final long serialVersionUID = 1;
* @Override public Void invoke(File f, VirtualChannel channel) {
* // f and file represent the same thing
@@ -233,9 +234,9 @@ public final class FilePath implements Serializable {
*
* @param channel
* To create a path that represents a remote path, pass in a {@link Channel}
- * that's connected to that machine. If null, that means the local file path.
+ * that's connected to that machine. If {@code null}, that means the local file path.
*/
- public FilePath(VirtualChannel channel, String remote) {
+ public FilePath(@CheckForNull VirtualChannel channel, @Nonnull String remote) {
this.channel = channel instanceof LocalChannel ? null : channel;
this.remote = normalize(remote);
}
@@ -247,7 +248,7 @@ public final class FilePath implements Serializable {
* A "local" path means a file path on the computer where the
* constructor invocation happened.
*/
- public FilePath(File localPath) {
+ public FilePath(@Nonnull File localPath) {
this.channel = null;
this.remote = normalize(localPath.getPath());
}
@@ -257,12 +258,12 @@ public final class FilePath implements Serializable {
* @param base starting point for resolution, and defines channel
* @param rel a path which if relative will be resolved against base
*/
- public FilePath(FilePath base, String rel) {
+ public FilePath(@Nonnull FilePath base, @Nonnull String rel) {
this.channel = base.channel;
this.remote = normalize(resolvePathIfRelative(base, rel));
}
- private String resolvePathIfRelative(FilePath base, String rel) {
+ private String resolvePathIfRelative(@Nonnull FilePath base, @Nonnull String rel) {
if(isAbsolute(rel)) return rel;
if(base.isUnix()) {
// shouldn't need this replace, but better safe than sorry
@@ -277,7 +278,7 @@ public final class FilePath implements Serializable {
/**
* Is the given path name an absolute path?
*/
- private static boolean isAbsolute(String rel) {
+ private static boolean isAbsolute(@Nonnull String rel) {
return rel.startsWith("/") || DRIVE_PATTERN.matcher(rel).matches() || UNC_PATTERN.matcher(rel).matches();
}
@@ -289,7 +290,7 @@ public final class FilePath implements Serializable {
* {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
* so remove them.
*/
- private static String normalize(String path) {
+ private static String normalize(@Nonnull String path) {
StringBuilder buf = new StringBuilder();
// Check for prefix designating absolute path
Matcher m = ABSOLUTE_PREFIX_PATTERN.matcher(path);
@@ -389,11 +390,8 @@ public final class FilePath implements Serializable {
}
public void zip(FilePath dst) throws IOException, InterruptedException {
- OutputStream os = dst.write();
- try {
+ try (OutputStream os = dst.write()) {
zip(os);
- } finally {
- os.close();
}
}
@@ -555,7 +553,7 @@ public final class FilePath implements Serializable {
* @see #unzip(FilePath)
*/
public void unzipFrom(InputStream _in) throws IOException, InterruptedException {
- final InputStream in = new RemoteInputStream(_in);
+ final InputStream in = new RemoteInputStream(_in, Flag.GREEDY);
act(new SecureFileCallable() {
public Void invoke(File dir, VirtualChannel channel) throws IOException {
unzip(dir, in);
@@ -594,11 +592,8 @@ public final class FilePath implements Serializable {
if (p != null) {
mkdirs(p);
}
- InputStream input = zip.getInputStream(e);
- try {
+ try (InputStream input = zip.getInputStream(e)) {
IOUtils.copy(input, writing(f));
- } finally {
- input.close();
}
try {
FilePath target = new FilePath(f);
@@ -724,7 +719,7 @@ public final class FilePath implements Serializable {
*/
public void untarFrom(InputStream _in, final TarCompression compression) throws IOException, InterruptedException {
try {
- final InputStream in = new RemoteInputStream(_in);
+ 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));
@@ -733,7 +728,7 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
});
} finally {
- org.apache.commons.io.IOUtils.closeQuietly(_in);
+ _in.close();
}
}
@@ -837,7 +832,7 @@ public final class FilePath implements Serializable {
return true;
} catch (IOException x) {
if (listener != null) {
- x.printStackTrace(listener.error("Failed to download " + archive + " from agent; will retry from master"));
+ Functions.printStackTrace(x, listener.error("Failed to download " + archive + " from agent; will retry from master"));
}
}
}
@@ -868,8 +863,7 @@ public final class FilePath implements Serializable {
this.archive = archive;
}
@Override public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
- InputStream in = archive.openStream();
- try {
+ try (InputStream in = archive.openStream()) {
CountingInputStream cis = new CountingInputStream(in);
try {
if (archive.toExternalForm().endsWith(".zip")) {
@@ -880,8 +874,6 @@ public final class FilePath implements Serializable {
} catch (IOException x) {
throw new IOException(String.format("Failed to unpack %s (%d bytes read)", archive, cis.getByteCount()), x);
}
- } finally {
- in.close();
}
return null;
}
@@ -894,11 +886,8 @@ public final class FilePath implements Serializable {
* @since 1.293
*/
public void copyFrom(URL url) throws IOException, InterruptedException {
- InputStream in = url.openStream();
- try {
+ try (InputStream in = url.openStream()) {
copyFrom(in);
- } finally {
- in.close();
}
}
@@ -908,11 +897,8 @@ public final class FilePath implements Serializable {
* @since 1.293
*/
public void copyFrom(InputStream in) throws IOException, InterruptedException {
- OutputStream os = write();
- try {
+ try (OutputStream os = write()) {
org.apache.commons.io.IOUtils.copy(in, os);
- } finally {
- os.close();
}
}
@@ -938,16 +924,9 @@ public final class FilePath implements Serializable {
throw new IOException(e);
}
} else {
- InputStream i = file.getInputStream();
- OutputStream o = write();
- try {
+ try (InputStream i = file.getInputStream();
+ OutputStream o = write()) {
org.apache.commons.io.IOUtils.copy(i,o);
- } finally {
- try {
- o.close();
- } finally {
- i.close();
- }
}
}
}
@@ -955,7 +934,7 @@ public final class FilePath implements Serializable {
/**
* Code that gets executed on the machine where the {@link FilePath} is local.
* Used to act on {@link FilePath}.
- * Warning: implementations must be serializable, so prefer a static nested class to an inner class.
+ * Warning: implementations must be serializable, so prefer a static nested class to an inner class.
*
*
* Subtypes would likely want to extend from either {@link MasterToSlaveCallable}
@@ -1224,7 +1203,7 @@ public final class FilePath implements Serializable {
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 problemsl ike JENKINS-10113
+ // 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);
@@ -1396,11 +1375,8 @@ public final class FilePath implements Serializable {
throw new IOException("Failed to create a temporary directory in "+dir,e);
}
- Writer w = new FileWriter(writing(f));
- try {
+ try (Writer w = new FileWriter(writing(f))) {
w.write(contents);
- } finally {
- w.close();
}
return f.getAbsolutePath();
@@ -1494,8 +1470,13 @@ public final class FilePath implements Serializable {
act(new SecureFileCallable() {
private static final long serialVersionUID = -5094638816500738429L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
- if(!f.exists())
- new FileOutputStream(creating(f)).close();
+ if(!f.exists()) {
+ try {
+ Files.newOutputStream(creating(f).toPath()).close();
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
+ }
+ }
if(!stating(f).setLastModified(timestamp))
throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
return null;
@@ -1655,6 +1636,7 @@ public final class FilePath implements Serializable {
*
* This method returns direct children of the directory denoted by the 'this' object.
*/
+ @Nonnull
public List list() throws IOException, InterruptedException {
return list((FileFilter)null);
}
@@ -1664,6 +1646,7 @@ public final class FilePath implements Serializable {
*
* @return can be empty but never null. Doesn't contain "." and ".."
*/
+ @Nonnull
public List listDirectories() throws IOException, InterruptedException {
return list(new DirectoryFilter());
}
@@ -1684,6 +1667,7 @@ public final class FilePath implements Serializable {
* If this {@link FilePath} represents a remote path,
* the filter object will be executed on the remote machine.
*/
+ @Nonnull
public List list(final FileFilter filter) throws IOException, InterruptedException {
if (filter != null && !(filter instanceof Serializable)) {
throw new IllegalArgumentException("Non-serializable filter of " + filter.getClass());
@@ -1692,7 +1676,9 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
public List invoke(File f, VirtualChannel channel) throws IOException {
File[] children = reading(f).listFiles(filter);
- if(children ==null) return null;
+ if (children == null) {
+ return Collections.emptyList();
+ }
ArrayList r = new ArrayList(children.length);
for (File child : children)
@@ -1711,6 +1697,7 @@ public final class FilePath implements Serializable {
* @return
* can be empty but always non-null.
*/
+ @Nonnull
public FilePath[] list(final String includes) throws IOException, InterruptedException {
return list(includes, null);
}
@@ -1725,6 +1712,7 @@ public final class FilePath implements Serializable {
* can be empty but always non-null.
* @since 1.407
*/
+ @Nonnull
public FilePath[] list(final String includes, final String excludes) throws IOException, InterruptedException {
return list(includes, excludes, true);
}
@@ -1740,6 +1728,7 @@ public final class FilePath implements Serializable {
* can be empty but always non-null.
* @since 1.465
*/
+ @Nonnull
public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException {
return act(new SecureFileCallable() {
private static final long serialVersionUID = 1L;
@@ -1761,6 +1750,7 @@ public final class FilePath implements Serializable {
* @return
* A set of relative file names from the base directory.
*/
+ @Nonnull
private static String[] glob(File dir, String includes, String excludes, boolean defaultExcludes) throws IOException {
if(isAbsolute(includes))
throw new IOException("Expecting Ant GLOB pattern, but saw '"+includes+"'. See http://ant.apache.org/manual/Types/fileset.html for syntax");
@@ -1775,8 +1765,13 @@ public final class FilePath implements Serializable {
* Reads this file.
*/
public InputStream read() throws IOException, InterruptedException {
- if(channel==null)
- return new FileInputStream(reading(new File(remote)));
+ if(channel==null) {
+ try {
+ return Files.newInputStream(reading(new File(remote)).toPath());
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
+ }
+ }
final Pipe p = Pipe.createRemoteToLocal();
actAsync(new SecureFileCallable() {
@@ -1784,15 +1779,13 @@ public final class FilePath implements Serializable {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(reading(f));
- Util.copyStream(fis, p.getOut());
+ 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);
- } finally {
- org.apache.commons.io.IOUtils.closeQuietly(fis);
- org.apache.commons.io.IOUtils.closeQuietly(p.getOut());
}
return null;
}
@@ -1846,10 +1839,9 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
- final OutputStream out = new java.util.zip.GZIPOutputStream(p.getOut(), 8192);
- RandomAccessFile raf = null;
- try {
- raf = new RandomAccessFile(reading(f), "r");
+ try (OutputStream os = p.getOut();
+ OutputStream out = new java.util.zip.GZIPOutputStream(os, 8192);
+ RandomAccessFile raf = new RandomAccessFile(reading(f), "r")) {
raf.seek(offset);
byte[] buf = new byte[8192];
int len;
@@ -1857,15 +1849,6 @@ public final class FilePath implements Serializable {
out.write(buf, 0, len);
}
return null;
- } finally {
- IOUtils.closeQuietly(out);
- if (raf != null) {
- try {
- raf.close();
- } catch (IOException e) {
- // ignore
- }
- }
}
}
});
@@ -1877,11 +1860,8 @@ public final class FilePath implements Serializable {
* Reads this file into a string, by using the current system encoding.
*/
public String readToString() throws IOException, InterruptedException {
- InputStream in = read();
- try {
+ try (InputStream in = read()) {
return org.apache.commons.io.IOUtils.toString(in);
- } finally {
- in.close();
}
}
@@ -1904,7 +1884,11 @@ public final class FilePath implements Serializable {
if(channel==null) {
File f = new File(remote).getAbsoluteFile();
mkdirs(f.getParentFile());
- return new FileOutputStream(writing(f));
+ try {
+ return Files.newOutputStream(writing(f).toPath());
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
+ }
}
return act(new SecureFileCallable() {
@@ -1912,8 +1896,12 @@ public final class FilePath implements Serializable {
public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
f = f.getAbsoluteFile();
mkdirs(f.getParentFile());
- FileOutputStream fos = new FileOutputStream(writing(f));
- return new RemoteOutputStream(fos);
+ try {
+ OutputStream fos = Files.newOutputStream(writing(f).toPath());
+ return new RemoteOutputStream(fos);
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
+ }
}
});
}
@@ -1930,12 +1918,11 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
mkdirs(f.getParentFile());
- FileOutputStream fos = new FileOutputStream(writing(f));
- Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos);
- try {
+ try (OutputStream fos = Files.newOutputStream(writing(f).toPath());
+ Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) {
w.write(content);
- } finally {
- w.close();
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
}
return null;
}
@@ -2008,11 +1995,8 @@ public final class FilePath implements Serializable {
*/
public void copyTo(FilePath target) throws IOException, InterruptedException {
try {
- OutputStream out = target.write();
- try {
+ try (OutputStream out = target.write()) {
copyTo(out);
- } finally {
- out.close();
}
} catch (IOException e) {
throw new IOException("Failed to copy "+this+" to "+target,e);
@@ -2039,14 +2023,13 @@ public final class FilePath implements Serializable {
act(new SecureFileCallable() {
private static final long serialVersionUID = 4088559042349254141L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(reading(f));
- Util.copyStream(fis,out);
+ 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 {
- org.apache.commons.io.IOUtils.closeQuietly(fis);
- org.apache.commons.io.IOUtils.closeQuietly(out);
+ out.close();
}
}
});
@@ -2058,7 +2041,7 @@ public final class FilePath implements Serializable {
/**
* With fix to JENKINS-11251 (remoting 2.15), this is no longer necessary.
- * But I'm keeping it for a while so that users who manually deploy slave.jar has time to deploy new version
+ * But I'm keeping it for a while so that users who manually deploy agent.jar has time to deploy new version
* before this goes away.
*/
private void syncIO() throws InterruptedException {
@@ -2066,9 +2049,9 @@ public final class FilePath implements Serializable {
if (channel!=null)
channel.syncLocalIO();
} catch (AbstractMethodError e) {
- // legacy slave.jar. Handle this gracefully
+ // legacy agent.jar. Handle this gracefully
try {
- LOGGER.log(Level.WARNING,"Looks like an old slave.jar. Please update "+ Which.jarFile(Channel.class)+" to the new version",e);
+ LOGGER.log(Level.WARNING,"Looks like an old agent.jar. Please update "+ Which.jarFile(Channel.class)+" to the new version",e);
} catch (IOException _) {
// really ignore this time
}
@@ -2182,7 +2165,7 @@ public final class FilePath implements Serializable {
writing(new File(dest, target));
Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
} catch (InterruptedException x) {
- throw (IOException) new IOException(x.toString()).initCause(x);
+ throw new IOException(x);
}
count.incrementAndGet();
}
@@ -2198,11 +2181,9 @@ public final class FilePath implements Serializable {
Future future = target.actAsync(new SecureFileCallable() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
- try {
- readFromTar(remote + '/' + description, f,TarCompression.GZIP.extract(pipe.getIn()));
+ try (InputStream in = pipe.getIn()) {
+ readFromTar(remote + '/' + description, f,TarCompression.GZIP.extract(in));
return null;
- } finally {
- pipe.getIn().close();
}
}
});
@@ -2217,7 +2198,12 @@ public final class FilePath implements Serializable {
future.get();
return future2.get();
} catch (ExecutionException e) {
- throw new IOException(e);
+ Throwable cause = e.getCause();
+ if (cause == null) cause = e;
+ throw cause instanceof IOException
+ ? (IOException) cause
+ : new IOException(cause)
+ ;
}
} else {
// remote -> local copy
@@ -2226,10 +2212,8 @@ public final class FilePath implements Serializable {
Future future = actAsync(new SecureFileCallable() {
private static final long serialVersionUID = 1L;
public Integer invoke(File f, VirtualChannel channel) throws IOException {
- try {
- return writeToTar(f, scanner, TarCompression.GZIP.compress(pipe.getOut()));
- } finally {
- pipe.getOut().close();
+ try (OutputStream out = pipe.getOut()) {
+ return writeToTar(f, scanner, TarCompression.GZIP.compress(out));
}
}
});
@@ -2241,7 +2225,8 @@ public final class FilePath implements Serializable {
throw e; // the remote side completed successfully, so the error must be local
} catch (ExecutionException x) {
// report both errors
- throw new IOException(Functions.printThrowable(e),x);
+ e.addSuppressed(x);
+ throw e;
} catch (TimeoutException _) {
// remote is hanging
throw e;
@@ -2250,7 +2235,12 @@ public final class FilePath implements Serializable {
try {
return future.get();
} catch (ExecutionException e) {
- throw new IOException(e);
+ Throwable cause = e.getCause();
+ if (cause == null) cause = e;
+ throw cause instanceof IOException
+ ? (IOException) cause
+ : new IOException(cause)
+ ;
}
}
}
@@ -2295,17 +2285,16 @@ public final class FilePath implements Serializable {
/**
* Reads from a tar stream and stores obtained files to the base dir.
- * @since TODO supports large files > 10 GB, migration to commons-compress
+ * Supports large files > 10 GB since 1.627 when this was migrated to use commons-compress.
*/
private void readFromTar(String name, File baseDir, InputStream in) throws IOException {
- TarArchiveInputStream t = new TarArchiveInputStream(in);
-
+
// TarInputStream t = new TarInputStream(in);
- try {
+ try (TarArchiveInputStream t = new TarArchiveInputStream(in)) {
TarArchiveEntry te;
while ((te = t.getNextTarEntry()) != null) {
- File f = new File(baseDir,te.getName());
- if(te.isDirectory()) {
+ File f = new File(baseDir, te.getName());
+ if (te.isDirectory()) {
mkdirs(f);
} else {
File parent = f.getParentFile();
@@ -2315,22 +2304,20 @@ public final class FilePath implements Serializable {
if (te.isSymbolicLink()) {
new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL);
} else {
- IOUtils.copy(t,f);
+ IOUtils.copy(t, f);
f.setLastModified(te.getModTime().getTime());
- int mode = te.getMode()&0777;
- if(mode!=0 && !Functions.isWindows()) // be defensive
- _chmod(f,mode);
+ int mode = te.getMode() & 0777;
+ if (mode != 0 && !Functions.isWindows()) // be defensive
+ _chmod(f, mode);
}
}
}
- } catch(IOException e) {
- throw new IOException("Failed to extract "+name,e);
+ } catch (IOException e) {
+ throw new IOException("Failed to extract " + name, e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // process this later
- throw new IOException("Failed to extract "+name,e);
- } finally {
- t.close();
+ throw new IOException("Failed to extract " + name, e);
}
}
@@ -2347,6 +2334,7 @@ public final class FilePath implements Serializable {
}
private static final class IsUnix extends MasterToSlaveCallable {
+ @Nonnull
public Boolean call() throws IOException {
return File.pathSeparatorChar==':';
}
@@ -2372,7 +2360,7 @@ public final class FilePath implements Serializable {
}
/**
- * Same as {@link #validateFileMask(String, int, boolean)} with caseSensitive set to true
+ * Same as {@link #validateAntFileMask(String, int, boolean)} with caseSensitive set to true
*/
public String validateAntFileMask(final String fileMasks, final int bound) throws IOException, InterruptedException {
return validateAntFileMask(fileMasks, bound, true);
@@ -2382,7 +2370,7 @@ public final class FilePath implements Serializable {
* Default bound for {@link #validateAntFileMask(String, int, boolean)}.
* @since 1.592
*/
- public static int VALIDATE_ANT_FILE_MASK_BOUND = SystemProperties.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000);
+ public static int VALIDATE_ANT_FILE_MASK_BOUND = Integer.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000);
/**
* Like {@link #validateAntFileMask(String)} but performing only a bounded number of operations.
@@ -2424,7 +2412,7 @@ public final class FilePath implements Serializable {
for (String token : Util.tokenize(fileMask))
matched &= hasMatch(dir,token,caseSensitive);
if(matched)
- return Messages.FilePath_validateAntFileMask_whitespaceSeprator();
+ return Messages.FilePath_validateAntFileMask_whitespaceSeparator();
}
// a common mistake is to assume the wrong base dir, and there are two variations
@@ -2581,7 +2569,7 @@ public final class FilePath implements Serializable {
}
/**
- * Shortcut for {@link #validateFileMask(String,true,boolean)} as the left-hand side can be null.
+ * Shortcut for {@link #validateFileMask(String,boolean,boolean)} with {@code errorIfNotExist} true, as the left-hand side can be null.
*/
public static FormValidation validateFileMask(@CheckForNull FilePath path, String value, boolean caseSensitive) throws IOException {
if(path==null) return FormValidation.ok();
@@ -2853,6 +2841,11 @@ public final class FilePath implements Serializable {
new NamingThreadFactory(new DaemonThreadFactory(), "FilePath.localPool"))
));
+
+ /**
+ * Channel to the current instance.
+ */
+ @Nonnull
public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
private @Nonnull SoloFilePathFilter filterNonNull() {
diff --git a/core/src/main/java/hudson/FileSystemProvisioner.java b/core/src/main/java/hudson/FileSystemProvisioner.java
index 60978a00482112d7a09d118e709051285d920751..13f55b7370028832eba66ea7e16af84cb19547aa 100644
--- a/core/src/main/java/hudson/FileSystemProvisioner.java
+++ b/core/src/main/java/hudson/FileSystemProvisioner.java
@@ -31,6 +31,8 @@ import hudson.model.Describable;
import hudson.model.Job;
import hudson.model.TaskListener;
import hudson.util.io.ArchiverFactory;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
import jenkins.model.Jenkins;
import hudson.model.listeners.RunListener;
import hudson.scm.SCM;
@@ -52,7 +54,7 @@ import java.io.OutputStream;
* STILL A WORK IN PROGRESS. SUBJECT TO CHANGE! DO NOT EXTEND.
*
* TODO: is this per {@link Computer}? Per {@link Job}?
- * -> probably per agent.
+ * → probably per agent.
*
*
Design Problems
*
@@ -215,11 +217,10 @@ public abstract class FileSystemProvisioner implements ExtensionPoint, Describab
*/
public WorkspaceSnapshot snapshot(AbstractBuild, ?> build, FilePath ws, String glob, TaskListener listener) throws IOException, InterruptedException {
File wss = new File(build.getRootDir(),"workspace.tgz");
- OutputStream os = new BufferedOutputStream(new FileOutputStream(wss));
- try {
- ws.archive(ArchiverFactory.TARGZ,os,glob);
- } finally {
- os.close();
+ try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(wss.toPath()))) {
+ ws.archive(ArchiverFactory.TARGZ, os, glob);
+ } catch (InvalidPathException e) {
+ throw new IOException(e);
}
return new WorkspaceSnapshotImpl();
}
diff --git a/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java b/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
index ce0371ceec0ecf79d7c4d06061e5a558358dfa6f..1e02e11f7f4dfdbedf2f00b853535725c1d43f86 100644
--- a/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
+++ b/core/src/main/java/hudson/FileSystemProvisionerDescriptor.java
@@ -63,7 +63,7 @@ public abstract class FileSystemProvisionerDescriptor extends Descriptor());
+ return s.toString();
+ }
+ private static void doPrintStackTrace(@Nonnull StringBuilder s, @Nonnull Throwable t, @CheckForNull Throwable higher, @Nonnull String prefix, @Nonnull Set encountered) {
+ if (!encountered.add(t)) {
+ s.append("\n");
+ return;
+ }
+ if (Util.isOverridden(Throwable.class, t.getClass(), "printStackTrace", PrintWriter.class)) {
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ s.append(sw.toString());
+ return;
+ }
+ Throwable lower = t.getCause();
+ if (lower != null) {
+ doPrintStackTrace(s, lower, t, prefix, encountered);
+ }
+ for (Throwable suppressed : t.getSuppressed()) {
+ s.append(prefix).append("Also: ");
+ doPrintStackTrace(s, suppressed, t, prefix + "\t", encountered);
+ }
+ if (lower != null) {
+ s.append(prefix).append("Caused: ");
+ }
+ String summary = t.toString();
+ if (lower != null) {
+ String suffix = ": " + lower;
+ if (summary.endsWith(suffix)) {
+ summary = summary.substring(0, summary.length() - suffix.length());
+ }
+ }
+ s.append(summary).append(IOUtils.LINE_SEPARATOR);
+ StackTraceElement[] trace = t.getStackTrace();
+ int end = trace.length;
+ if (higher != null) {
+ StackTraceElement[] higherTrace = higher.getStackTrace();
+ while (end > 0) {
+ int higherEnd = end + higherTrace.length - trace.length;
+ if (higherEnd <= 0 || !higherTrace[higherEnd - 1].equals(trace[end - 1])) {
+ break;
+ }
+ end--;
+ }
+ }
+ for (int i = 0; i < end; i++) {
+ s.append(prefix).append("\tat ").append(trace[i]).append(IOUtils.LINE_SEPARATOR);
+ }
+ }
+
+ /**
+ * Like {@link Throwable#printStackTrace(PrintWriter)} but using {@link #printThrowable} format.
+ * @param t an exception to print
+ * @param pw the log
+ * @since 2.43
+ */
+ public static void printStackTrace(@CheckForNull Throwable t, @Nonnull PrintWriter pw) {
+ pw.println(printThrowable(t).trim());
+ }
+
+ /**
+ * Like {@link Throwable#printStackTrace(PrintStream)} but using {@link #printThrowable} format.
+ * @param t an exception to print
+ * @param ps the log
+ * @since 2.43
+ */
+ public static void printStackTrace(@CheckForNull Throwable t, @Nonnull PrintStream ps) {
+ ps.println(printThrowable(t).trim());
}
/**
@@ -1725,7 +1798,7 @@ public class Functions {
}
/**
- * Generate a series of <script> tags to include script.js
+ * Generate a series of {@code
diff --git a/core/src/main/resources/hudson/PluginManager/installed.properties b/core/src/main/resources/hudson/PluginManager/installed.properties
index 2fa0cafb8083f2b150ecd074aeb95ea0321dc2b7..0c1b07c42737b8ee2958fe7c7f1691c9eb8c56b8 100644
--- a/core/src/main/resources/hudson/PluginManager/installed.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed.properties
@@ -19,6 +19,5 @@
# 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.
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
downgradeTo=Downgrade to {0}
-requires.restart=This Jenkins instance requires a restart. Changing the state of plugins at this time is strongly discouraged. Restart Jenkins before proceeding.
\ No newline at end of file
+requires.restart=This Jenkins instance requires a restart. Changing the state of plugins at this time is strongly discouraged. Restart Jenkins before proceeding.
diff --git a/core/src/main/resources/hudson/PluginManager/installed_bg.properties b/core/src/main/resources/hudson/PluginManager/installed_bg.properties
index c950862a63b41df6b8dedcfffb431ff6a6e94386..60dc8c4e29ac354bafffe5de51bec57b6b793d95 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_bg.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_bg.properties
@@ -26,8 +26,6 @@ Enabled=\
\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d
Name=\
\u0418\u043c\u0435
-Pinned=\
- \u0424\u0438\u043a\u0441\u0438\u0440\u0430\u043d\u0430 \u0432\u0435\u0440\u0441\u0438\u044f
Previously\ installed\ version=\
\u041f\u0440\u0435\u0434\u0438\u0448\u043d\u043e \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430 \u0432\u0435\u0440\u0441\u0438\u044f
Restart\ Once\ No\ Jobs\ Are\ Running=\
@@ -36,8 +34,6 @@ Uncheck\ to\ disable\ the\ plugin=\
\u041c\u0430\u0445\u043d\u0435\u0442\u0435 \u043e\u0442\u043c\u0435\u0442\u043a\u0430\u0442\u0430 \u0437\u0430 \u0437\u0430\u0431\u0440\u0430\u043d\u0430 \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430
Uninstall=\
\u0414\u0435\u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0435
-Unpin=\
- \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0430 \u0432\u0435\u0440\u0441\u0438\u044f
Version=\
\u0412\u0435\u0440\u0441\u0438\u044f
downgradeTo=\
@@ -62,8 +58,6 @@ This\ plugin\ cannot\ be\ enabled=\
\u0422\u0430\u0437\u0438 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u0432\u043a\u043b\u044e\u0447\u0438
Filter=\
\u0424\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435
-wiki.url=\
- \u0422\u0430\u0437\u0438 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u0438\u0437\u043a\u043b\u044e\u0447\u0438
requires.restart=\
\u0422\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442\u0435 Jenkins, \u043f\u0440\u0435\u0434\u0438 \u0434\u0430 \u043f\u0440\u0430\u0432\u0438\u0442\u0435 \u043f\u043e\u0432\u0435\u0447\u0435 \u043f\u0440\u043e\u043c\u0435\u043d\u0438 \u043f\u043e\
\u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0438\u0442\u0435. \u041e\u043f\u0430\u0441\u043d\u043e \u0435 \u0434\u0430 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0438\u0442\u0435 \u0431\u0435\u0437 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435.
diff --git a/core/src/main/resources/hudson/PluginManager/installed_cs.properties b/core/src/main/resources/hudson/PluginManager/installed_cs.properties
index 21847ed21d95ec27e0b955dc11f6614d8e745f13..9fb56abb6a8981744a87550484f14cb02aa68927 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_cs.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_cs.properties
@@ -3,11 +3,9 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Zm\u011Bny se projev\u00ED a\u017E po restartu Jenkinse
Enabled=Povolen\u00E9
Name=Jm\u00E9no
-Pinned=Preferovat
Previously\ installed\ version=P\u0159edchoz\u00ED nainstalovan\u00E1 verze
Restart\ Once\ No\ Jobs\ Are\ Running=Restartovat a\u017E po dokon\u010Den\u00ED v\u0161ech \u00FAloh.
Uncheck\ to\ disable\ the\ plugin=Od\u0161krtnout pro deaktivaci modulu
Uninstall=Odinstalovat
Version=Verze
downgradeTo=Vr\u00E1tit se k {0}
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_da.properties b/core/src/main/resources/hudson/PluginManager/installed_da.properties
index 4086d2a08ac457e4508899f2278ebd1c94b988ad..9e17d018924b1a85654c414ad1927ddc02722b72 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_da.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_da.properties
@@ -21,15 +21,12 @@
# THE SOFTWARE.
Version=Version
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
Restart\ Once\ No\ Jobs\ Are\ Running=Genstart n\u00e5r ingen jobs k\u00f8rer
Previously\ installed\ version=Forudg\u00e5ende installerede version
New\ plugins\ will\ take\ effect\ once\ you\ restart\ Jenkins=Nye plugins tr\u00e6der i kraft efter du har genstartet Jenkins
Uncheck\ to\ disable\ the\ plugin=Fjern flueben for at sl\u00e5 plugin''et fra
No\ plugins\ installed.=Ingen installerede plugins.
downgradeTo=Nedgrader til {0}
-Pinned=L\u00e5st
Name=Navn
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=\u00c6ndringer tr\u00e6der i kraft efter Jenkins er genstartet
Enabled=Sl\u00e5et til
-Unpin=L\u00e5s op
diff --git a/core/src/main/resources/hudson/PluginManager/installed_de.properties b/core/src/main/resources/hudson/PluginManager/installed_de.properties
index 3653a5c9e839f877ae82b4bfb8ce5761c8ec8656..442d08f12707b16915c7d46dfe698185392783ba 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_de.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_de.properties
@@ -21,17 +21,24 @@
# THE SOFTWARE.
No\ plugins\ installed.=Keine Plugins installiert.
-Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=nderungen treten nach einem Neustart von Jenkins in Kraft.
-Uncheck\ to\ disable\ the\ plugin=Zum Deaktivieren des Plugins Markierung lschen
+Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=\u00C4nderungen treten nach einem Neustart von Jenkins in Kraft.
+Uncheck\ to\ disable\ the\ plugin=Zum Deaktivieren des Plugins Markierung entfernen
Enabled=Aktiviert
Name=Name
Version=Version
Restart\ Once\ No\ Jobs\ Are\ Running=Neu starten, sobald keine Jobs mehr laufen.
Previously\ installed\ version=Vorher installierte Version
downgradeTo={0} wiederherstellen
-Pinned=Gesperrt
-Unpin=Entsperren
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
Uninstall=Deinstallieren
Uninstallation\ pending=Zur Deinstallation vorgemerkt
Update\ Center=Update-Center
+This\ plugin\ cannot\ be\ disabled=Dieses Plugin kann nicht deaktiviert werden.
+requires.restart=Jenkins erfordert einen Neustart. Die Plugin-Konfiguration sollte nicht weiter ge\u00E4ndert werden. Starten Sie Jenkins neu, bevor Sie fortfahren.
+It\ has\ one\ or\ more\ installed\ dependants=Mindestens ein Plugin ist von diesem abh\u00E4ngig
+Warning=Warnung
+Filter=Filter
+It\ has\ one\ or\ more\ enabled\ dependants=Mindestens ein aktives Plugin ist von diesem abh\u00E4ngig
+It\ has\ one\ or\ more\ disabled\ dependencies=Mindestens eine Abh\u00E4ngigkeit ist deaktiviert
+This\ plugin\ cannot\ be\ uninstalled=Dieses Plugin kann nicht deinstalliert werden.
+No\ description\ available.=Keine Beschreibung verf\u00FCgbar.
+This\ plugin\ cannot\ be\ enabled=Dieses Plugin kann nicht aktiviert werden.
diff --git a/core/src/main/resources/hudson/PluginManager/installed_es.properties b/core/src/main/resources/hudson/PluginManager/installed_es.properties
index 70956f31d1b389dd4f44eef441fb8e53e1bcaa8f..34f849e78dccc253e5099bc7b453a12f3e0596ed 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_es.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_es.properties
@@ -27,10 +27,7 @@ Name=Nombre
Version=Versin
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Los cambios no estarn disponibles hasta que Jenkins se reinicie
Restart\ Once\ No\ Jobs\ Are\ Running=Reiniciar cuando no haya tareas en ejecucin
-wiki.url="http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins"
downgradeTo=Bajar a la version {0}.
Previously\ installed\ version=Versin previamente instalada.
-Pinned=marcado
Uninstall=Desinstalar
-Unpin=desmarcar
Update\ Center=Centro de actualizaciones
diff --git a/core/src/main/resources/hudson/PluginManager/installed_fi.properties b/core/src/main/resources/hudson/PluginManager/installed_fi.properties
index c04443e10d2fe07012c6a0233fd44bf0581752dc..209c8d6e2d3903add7a171d1838baf01770f505f 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_fi.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_fi.properties
@@ -23,10 +23,8 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Muutokset astuvat voimaan kun Jenkins k\u00E4ynnistet\u00E4\u00E4n
Enabled=Aktivoitu
Name=Nimi
-Pinned=Lukittu
Previously\ installed\ version=Aiemmin asennettu versio
Restart\ Once\ No\ Jobs\ Are\ Running=K\u00E4ynnist\u00E4 heti kun k\u00E4\u00E4nn\u00F6ksi\u00E4 ei ole ajossa
Uncheck\ to\ disable\ the\ plugin=Poist ruudun rasti poistaaksesi liit\u00E4nn\u00E4inen k\u00E4yt\u00F6st\u00E4
-Unpin=Vapauta lukitus
Version=Versio
downgradeTo=Palaa versioon {0}
diff --git a/core/src/main/resources/hudson/PluginManager/installed_fr.properties b/core/src/main/resources/hudson/PluginManager/installed_fr.properties
index fde1e85e1c11cb7e8ef32524056d3193409e6fa4..b3767a6e65ecde4201c44f5c2db50a59c22e5dd2 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_fr.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_fr.properties
@@ -28,9 +28,6 @@ Uncheck\ to\ disable\ the\ plugin=D\u00e9cochez pour d\u00e9sactiver le plugin
Enabled=Activ\u00e9
Name=Nom
Uninstall=D\u00E9sinstaller
-Unpin=Annuler \u00E9pingler
Version=Version
-Pinned=\u00C9pingl\u00E9
Previously\ installed\ version=Version pr\u00E9c\u00E9dente
downgradeTo=R\u00E9trograder \u00E0 {0}
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_hu.properties b/core/src/main/resources/hudson/PluginManager/installed_hu.properties
index 01d83570d004de8d58e6774dc17923350d7e5bd6..a71aafe3567e5b4b7f530dc97750319017773d7e 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_hu.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_hu.properties
@@ -28,4 +28,3 @@ Restart\ Once\ No\ Jobs\ Are\ Running=\u00DAjraind\u00EDt\u00E1s ha m\u00E1r nin
Uncheck\ to\ disable\ the\ plugin=T\u00F6r\u00F6lje a jel\u00F6l\u00E9st a be\u00E9p\u00FCl\u0151 kikapcsol\u00E1s\u00E1hoz
Version=Verzi\u00F3
downgradeTo=Visszafriss\u00EDt\u00E9s {0} verzi\u00F3ra
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_id.properties b/core/src/main/resources/hudson/PluginManager/installed_id.properties
deleted file mode 100644
index 880aaa392fad34f80ba91b0b22d21709a4c848e1..0000000000000000000000000000000000000000
--- a/core/src/main/resources/hudson/PluginManager/installed_id.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-# This file is under the MIT License by authors
-
-Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Perubahan akan berefek ketika anda mengulang Jenkins
-Enabled=Diaktifkan
-Name=Nama
-Previously\ installed\ version=Versi terpasang sebelumnya
-Restart\ Once\ No\ Jobs\ Are\ Running=Restart Begitu Tidak Ada Pekerjaan Berjalan
-Version=Versi
diff --git a/core/src/main/resources/hudson/PluginManager/installed_it.properties b/core/src/main/resources/hudson/PluginManager/installed_it.properties
index 0c30e7269d122832fc67c9585b1bffb81121b59c..a1361443e9b08becd079f771c3ad652f1aceefed 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_it.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_it.properties
@@ -20,15 +20,25 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Le modifiche avranno effetto quando riavvierai Jenkins
+Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Le modifiche avranno effetto al riavvio di Jenkins
Enabled=Attivo
Name=Nome
-Pinned=Bloccato
Previously\ installed\ version=Versione precedente
-Restart\ Once\ No\ Jobs\ Are\ Running=Riavvia quando non ci sono lavori in esecuzione
-Uncheck\ to\ disable\ the\ plugin=Deseleziona per disattivare il plugin
-Uninstall=Disintalla
-Unpin=Sblocca
+Restart\ Once\ No\ Jobs\ Are\ Running=Riavvia quando non ci sono processi in esecuzione
+Uncheck\ to\ disable\ the\ plugin=Deselezionare per disattivare il plugin
+Uninstall=Disinstalla
Version=Versione
downgradeTo=Retrocedi a
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
+It\ has\ one\ or\ more\ disabled\ dependencies=Ha una o pi dipendenze disabilitate
+Update\ Center=Centro aggiornamenti
+This\ plugin\ cannot\ be\ uninstalled=Impossibile disinstallare questo plugin
+requires.restart=Quest''istanza di Jenkins deve essere riavviata. La modifica dello stato dei plugin in questa condizione caldamente sconsigliata. Riavviare Jenkins prima di procedere.
+No\ plugins\ installed.=Nessun plugin installato.
+Filter=Filtro
+It\ has\ one\ or\ more\ installed\ dependants=Ha uno o pi plugin dipendenti installati
+It\ has\ one\ or\ more\ enabled\ dependants=Ha uno o pi plugin dipendenti abilitati
+This\ plugin\ cannot\ be\ enabled=Questo plugin non pu essere abilitato
+This\ plugin\ cannot\ be\ disabled=Questo plugin non pu essere disabilitato
+No\ description\ available.=Nessuna descrizione disponibile.
+Uninstallation\ pending=Disinstallazione in sospeso
+Warning=Avviso
diff --git a/core/src/main/resources/hudson/PluginManager/installed_ja.properties b/core/src/main/resources/hudson/PluginManager/installed_ja.properties
index cca9a2797f7350e2a3c8134cd565fddc7811f0d7..8e9aaf1ef60c3fe9b450e5938855407ea3b3810d 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_ja.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_ja.properties
@@ -27,9 +27,6 @@ Enabled=\u6709\u52b9\u5316
Name=\u540d\u524d
Version=\u30d0\u30fc\u30b8\u30e7\u30f3
Restart\ Once\ No\ Jobs\ Are\ Running=\u30b8\u30e7\u30d6\u304c\u5b9f\u884c\u4e2d\u3067\u306a\u3051\u308c\u3070\u518d\u8d77\u52d5
-Pinned=\u30d4\u30f3
-Unpin=\u89e3\u9664
-wiki.url=http://wiki.jenkins-ci.org/display/JA/Pinned+Plugins
Previously\ installed\ version=\u524d\u56de\u30d0\u30fc\u30b8\u30e7\u30f3
downgradeTo={0} \u306b\u30c0\u30a6\u30f3\u30b0\u30ec\u30fc\u30c9
Update\ Center=\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30bb\u30f3\u30bf\u30fc
diff --git a/core/src/main/resources/hudson/PluginManager/installed_ko.properties b/core/src/main/resources/hudson/PluginManager/installed_ko.properties
index 7a6397fbf43c26b8aff2d1febbb00bca4a35f369..1d745838f474d6d7a274acca4520adbe482b0bc3 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_ko.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_ko.properties
@@ -23,13 +23,10 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Jenkins\uC744 \uC7AC\uC2DC\uC791\uD558\uBA74 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC801\uC6A9\uB429\uB2C8\uB2E4.
Enabled=\uC0AC\uC6A9\uAC00\uB2A5
Name=\uC774\uB984
-Pinned=\uACE0\uC815\uB428
Previously\ installed\ version=\uC774\uC804 \uC124\uCE58 \uBC84\uC804
Restart\ Once\ No\ Jobs\ Are\ Running=\uB3D9\uC791\uC911\uC778 \uC791\uC5C5\uC774 \uC5C6\uC73C\uBA74 \uD55C\uBC88 \uC7AC\uAE30\uB3D9\uD569\uB2C8\uB2E4.
Uncheck\ to\ disable\ the\ plugin=\uC0AC\uC6A9\uBD88\uAC00 \uD50C\uB7EC\uADF8\uC778 \uCCB4\uD06C\uD574\uC81C
Uninstall=\uC124\uCE58 \uC81C\uAC70
Uninstallation\ pending=\uC0AD\uC81C \uB300\uAE30
-Unpin=\uACE0\uC815 \uD574\uC81C
Version=\uBC84\uC804
downgradeTo={0}\uC73C\uB85C \uB2E4\uC6B4\uADF8\uB808\uC774\uB4DC
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_lv.properties b/core/src/main/resources/hudson/PluginManager/installed_lv.properties
index 27b7271b2c7ca80d3bf812a3945af09a2c23521a..6539b07429a8033f1f0bd1e9a25b1f4919dcec87 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_lv.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_lv.properties
@@ -3,11 +3,8 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Izmai\u0146as st\u0101sies sp\u0113k\u0101 p\u0113c Jenkins p\u0101rstart\u0113\u0161anas
Enabled=Iespejots
Name=Nosaukums
-Pinned=Piesaist\u012Bts
Previously\ installed\ version=Iepriek\u0161 instal\u0113t\u0101 versija
Restart\ Once\ No\ Jobs\ Are\ Running=P\u0101rstart\u0113 tikl\u012Bdz neviens uzdevums nestr\u0101d\u0101
Uncheck\ to\ disable\ the\ plugin=At\u0137eks\u0113 lai atsp\u0113jotu spraudni
Uninstall=Atinstal\u0113t
-Unpin=Atsiet
Version=Versija
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_nl.properties b/core/src/main/resources/hudson/PluginManager/installed_nl.properties
index 78c5ea28610ab25a901a9f3959b97abf820a91b1..aa7cc818944895680c9fafd0b01dbf47cf58fb2d 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_nl.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_nl.properties
@@ -27,10 +27,7 @@ Restart\ Once\ No\ Jobs\ Are\ Running=Opnieuw starten
Uncheck\ to\ disable\ the\ plugin=Vink aan om de plugin te de-activeren.
Enabled=Actief
Name=Naam
-Unpin=Losmaken
Version=Versie
-Pinned=Vastgezet
Previously\ installed\ version=Vorige ge\u00EFnstalleerde versie
Restart\ Now=Nu herstarten
downgradeTo=Versie {0} terugzetten
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_pl.properties b/core/src/main/resources/hudson/PluginManager/installed_pl.properties
index df5e36320154937824fc6736d73c062a9ea776cd..4fb0ab51f38e684b709ed63b14ead78d5bce3bec 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_pl.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_pl.properties
@@ -1,6 +1,6 @@
# The MIT License
#
-# Copyright (c) 2004-2010, Sun Microsystems, Inc.
+# Copyright (c) 2004-2017, Sun Microsystems, Damian Szczepanik
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -23,13 +23,19 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Zmiany zostan\u0105 wprowadzone po ponownym uruchomieniu Jenkinsa
Enabled=W\u0142\u0105czone wtyczki
Name=Nazwa
-Pinned=Przypi\u0119ta
Previously\ installed\ version=Poprzednia zainstalowana wersja
Restart\ Once\ No\ Jobs\ Are\ Running=Uruchom ponownie gdy \u017Cadne zadania nie s\u0105 wykonywane
Uncheck\ to\ disable\ the\ plugin=Odznacz aby wy\u0142\u0105czy\u0107 wtyczk\u0119
Uninstall=Odinstaluj
-Unpin=Odepnij
Version=Wersja
-downgradeTo=Powr\u00F3\u0107 do starszej wersji {0}
+downgradeTo=Powr\u00F3\u0107 do wersji {0}
requires.restart=Wymagane jest ponowne uruchomienie Jenkinsa. Zmiany wtyczek w tym momencie s\u0105 bardzo niewskazane. Uruchom ponownie Jenkinsa, zanim wprowadzisz zmiany.
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
\ No newline at end of file
+Uninstallation\ pending=Trwa odinstalowywanie
+This\ plugin\ cannot\ be\ disabled=Ta wtyczka nie mo\u017Ce by\u0107 wy\u0142\u0105czona
+No\ plugins\ installed.=Brak zainstalowanych wtyczek
+This\ plugin\ cannot\ be\ enabled=Ta wtyczka nie mo\u017Ce by\u0107 w\u0142\u0105czona
+Update\ Center=Centrum aktualizacji
+Filter=Filtruj
+No\ description\ available.=Opis nie jest dost\u0119pny
+Warning=Ostrze\u017Cenie
+New\ plugins\ will\ take\ effect\ once\ you\ restart\ Jenkins=Nowe wtyczki zostan\u0105 w\u0142\u0105czone po ponownym uruchomieniu Jenkinsa.
diff --git a/core/src/main/resources/hudson/PluginManager/installed_pt_BR.properties b/core/src/main/resources/hudson/PluginManager/installed_pt_BR.properties
index 492b30e39c76f80fbb68d008212cd664ef2d08fa..543c634fa81a45edad7691004e9cae6b15accbd3 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_pt_BR.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_pt_BR.properties
@@ -24,15 +24,12 @@ No\ plugins\ installed.=Nenhum plugin instalado.
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=A mudan\u00e7as ter\u00e3o efeito quando o Jenkins for reiniciado
Uncheck\ to\ disable\ the\ plugin=Desmarque para desativar o plugin
Enabled=Habilitar
-Pinned=Fixado
Previously\ installed\ version=Vers\u00E3o anterior instalada
Restart\ Once\ No\ Jobs\ Are\ Running=Reiniciar assim que nenhum job estiver rodando
Uninstall=Desinstalar
-Unpin=Desprender
Version=Vers\u00e3o
Name=Nome
downgradeTo=Regredir para {0}
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
Filter=Filtro
Update\ Center=Central de atualiza\u00e7\u00e3o
No\ description\ available.=Nenhuma descri\u00e7\u00e3o dispon\u00edvel
diff --git a/core/src/main/resources/hudson/PluginManager/installed_pt_PT.properties b/core/src/main/resources/hudson/PluginManager/installed_pt_PT.properties
index 32259b92eca3c3e50a56ec81e1a3107c2827e1ae..e4464f4900255725f2afe88835223683096d8d60 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_pt_PT.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_pt_PT.properties
@@ -3,11 +3,9 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=As altera\u00E7\u00F5es ser\u00E3o aplicadas quando reiniciares o Jenkins
Enabled=Ativado
Name=Nome
-Pinned=Fixo
Previously\ installed\ version=\u00DAltima vers\u00E3o instalada
Restart\ Once\ No\ Jobs\ Are\ Running=Reiniciar quando n\u00E3o estiverem Jobs em execu\u00E7\u00E3o
Uncheck\ to\ disable\ the\ plugin=Seleccione para desactivar o plugin
Uninstall=Desinstalar
Version=Vers\u00E3o
downgradeTo=Downgrade para {0}
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_ro.properties b/core/src/main/resources/hudson/PluginManager/installed_ro.properties
index 9d960272ae146aaa3bba204479e7ecd274637254..a0fc137d09387e6dff7b675cb0a52bca9d2de112 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_ro.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_ro.properties
@@ -3,11 +3,8 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=Schimb\u0103rile vor avea efect c\u00E2nd ve\u021Bi reporni Jenkins
Enabled=Activat
Name=Denumire
-Pinned=Fixat
Previously\ installed\ version=Versiunea instalat\u0103 anterior
Restart\ Once\ No\ Jobs\ Are\ Running=Reporne\u0219te odat\u0103 ce nu mai sunt joburi ce ruleaz\u0103
Uncheck\ to\ disable\ the\ plugin=Debifa\u021Bi pentru a dezactiva pluginul
-Unpin=Defixeaz\u0103
Version=Versiune
downgradeTo=Retrogradeaz\u0103 la
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_ru.properties b/core/src/main/resources/hudson/PluginManager/installed_ru.properties
index 8fd3ed203354f97eeb08bc116a5c431388a1762a..02ece9626713737dc2422ba15194ea032ea8e010 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_ru.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_ru.properties
@@ -23,13 +23,10 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u0441\u0442\u0443\u043f\u044f\u0442 \u0432 \u0441\u0438\u043b\u0443 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 Jenkins
Enabled=\u0410\u043a\u0442\u0438\u0432\u043d\u044b\u0439
Name=\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435
-Pinned=\u041f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0451\u043d\u043d\u044b\u0435
Previously\ installed\ version=\u0420\u0430\u043d\u0435\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438
Restart\ Once\ No\ Jobs\ Are\ Running=\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0432\u0441\u0435\u0445 \u0437\u0430\u0434\u0430\u0447
Uncheck\ to\ disable\ the\ plugin=\u0421\u043d\u0438\u043c\u0438\u0442\u0435 \u0444\u043b\u0430\u0436\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d
Uninstall=\u0423\u0434\u0430\u043b\u0438\u0442\u044c
Uninstallation\ pending=\u041e\u0436\u0438\u0434\u0430\u043d\u0438\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f...
-Unpin=\u041e\u0442\u043a\u0440\u0435\u043f\u0438\u0442\u044c
Version=\u0412\u0435\u0440\u0441\u0438\u044f
downgradeTo=\u0412\u0435\u0440\u043d\u0443\u0442\u044c \u043a \u0432\u0435\u0440\u0441\u0438\u0438 {0}
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_sk.properties b/core/src/main/resources/hudson/PluginManager/installed_sk.properties
index c8e910951476144131acf6f7d57f124a68bf1a0b..a8be79712009f819087e3ea9a128f30c00b12407 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_sk.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_sk.properties
@@ -9,4 +9,3 @@ Uncheck\ to\ disable\ the\ plugin=Odzna\u010Den\u00EDm zak\u00E1\u017Eete plugin
Uninstall=Odin\u0161taluj
Uninstallation\ pending=Odin\u0161tal\u00E1cia \u010Dak\u00E1
Version=Verzia
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_sr.properties b/core/src/main/resources/hudson/PluginManager/installed_sr.properties
new file mode 100644
index 0000000000000000000000000000000000000000..b555a4dbe95c4f1292178ce39b49dcc2e388a2c7
--- /dev/null
+++ b/core/src/main/resources/hudson/PluginManager/installed_sr.properties
@@ -0,0 +1,26 @@
+# This file is under the MIT License by authors
+
+Update\ Center=\u0426\u0435\u043D\u0442\u0430\u0440 \u0437\u0430 \u0410\u0436\u0443\u0440\u0438\u0440\u0430\u045A\u0435
+Filter=\u041F\u0440\u043E\u0444\u0438\u043B\u0442\u0440\u0438\u0440\u0430\u0458
+Warning=\u0423\u043F\u043E\u0437\u043E\u0440\u0435\u045A\u0435
+requires.restart=\u041F\u043E\u0442\u0440\u0435\u0431\u043D\u043E \u0458\u0435 \u043F\u043E\u043D\u043E\u0432\u043E \u043F\u043E\u043A\u0440\u0435\u043D\u0443\u0442\u0438 Jenkins. \u041E\u043F\u0430\u0441\u043D\u043E \u0458\u0435 \u043C\u0435\u045A\u0430\u0442\u0438 \u043C\u043E\u0434\u0443\u043B\u0435 \u0443 \u043E\u0432\u043E \u0432\u0440\u0435\u043C\u0435 - \u043F\u043E\u043D\u043E\u0432\u043E Jenkins \u043F\u0440\u0435 \u043D\u0435\u0433\u043E \u0448\u0442\u043E \u0434\u0435\u043B\u0443\u0458\u0435\u0442\u0435 \u0434\u0430\u0459\u0435.
+This\ plugin\ cannot\ be\ enabled=\u041E\u0432\u0430 \u043C\u043E\u0434\u0443\u043B\u0430 \u043D\u0435\u043C\u043E\u0436\u0435 \u0431\u0438\u0442\u0438 \u043E\u043C\u043E\u0433\u0443\u045B\u0435\u043D\u0430
+This\ plugin\ cannot\ be\ disabled=\u041E\u0432\u0430 \u043C\u043E\u0434\u0443\u043B\u0430 \u043D\u0435\u043C\u043E\u0436\u0435 \u0431\u0438\u0442\u0438 \u043E\u043D\u0435\u043C\u043E\u0433\u0443\u045B\u0435\u043D\u0430
+This\ plugin\ cannot\ be\ uninstalled=\u041E\u0432\u0430 \u043C\u043E\u0434\u0443\u043B\u0430 \u043D\u0435\u043C\u043E\u0436\u0435 \u0432\u0438\u0442\u0438 \u0434\u0435\u0438\u043D\u0441\u0442\u0430\u043B\u0438\u0440\u0430\u043D\u0430
+It\ has\ one\ or\ more\ disabled\ dependencies=\u041F\u043E\u0441\u0442\u043E\u0458\u0438 \u043D\u0430\u0458\u043C\u0430\u045A\u0435 \u0458\u0435\u0434\u043D\u0430 \u0438\u0441\u043A\u0459\u0443\u0447\u0435\u043D\u0430 \u043C\u043E\u0434\u0443\u043B\u0430 \u043E\u0434 \u043A\u043E\u0458\u0435 \u043E\u0432\u0430 \u0437\u0430\u0432\u0438\u0441\u0438
+It\ has\ one\ or\ more\ enabled\ dependants=\u041F\u043E\u0441\u0442\u043E\u0458\u0438 \u043D\u0430\u0458\u043C\u0430\u045A\u0435 \u0458\u0435\u043D\u0434\u0430 \u0434\u0440\u0443\u0433\u0430 \u043C\u043E\u0434\u0443\u043B\u0430 \u043A\u043E\u0458\u0430 \u0437\u0430\u0432\u0438\u0441\u0438 \u043E\u0434 \u045A\u0435
+It\ has\ one\ or\ more\ installed\ dependants=\u041F\u043E\u0441\u0442\u043E\u0458\u0438 \u043D\u0430\u0458\u043C\u0430\u045A\u0435 \u0458\u0435\u043D\u0434\u0430 \u0434\u0440\u0443\u0433\u0430 \u043C\u043E\u0434\u0443\u043B\u0430 \u043A\u043E\u0458\u0430 \u0437\u0430\u0432\u0438\u0441\u0438 \u043E\u0434 \u045A\u0435
+No\ plugins\ installed.=\u041D\u0435\u043C\u0430 \u0438\u043D\u0441\u0442\u0430\u043B\u0438\u0440\u0430\u043D\u0438\u0445 \u043C\u043E\u0434\u0443\u043B\u0430
+Uncheck\ to\ disable\ the\ plugin=\u0423\u043A\u043B\u043E\u043D\u0438\u0442\u0435 \u043A\u0432\u0430\u0447\u0438\u0446\u0443 \u0434\u0430 \u043E\u043D\u0435\u043C\u043E\u0433\u0443\u045B\u0438\u0442\u0435 \u043C\u043E\u0434\u0443\u043B\u0443
+Enabled=\u0410\u043A\u0442\u0438\u043D\u0432\u043E
+Name=\u0418\u043C\u0435
+Version=\u0412\u0435\u0440\u0437\u0438\u0458\u0430
+Previously\ installed\ version=\u041F\u0440\u0435\u0442\u0445\u043E\u0434\u043D\u043E \u0438\u043D\u0441\u0442\u0430\u043B\u0438\u0440\u0430\u043D\u0430 \u0432\u0435\u0440\u0437\u0438\u0458\u0430
+Uninstall=\u0414\u0435\u0438\u043D\u0441\u0442\u0430\u043B\u0430\u0446\u0438\u0458\u0430
+No\ description\ available.=\u041D\u0435\u043C\u0430 \u043E\u043F\u0438\u0441\u0430
+downgradeTo=\u0412\u0440\u0430\u0442\u0438 \u0432\u0435\u0440\u0437\u0438\u0458\u0443 \u043D\u0430\u0437\u0430\u0434 \u043D\u0430 {0}
+Uninstallation\ pending=\u0414\u0435\u0438\u043D\u0441\u0442\u0430\u043B\u0430\u0446\u0438\u0458\u0430 \u0458\u0435 \u0443 \u0442\u043E\u043A\u0443
+Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=\u041F\u0440\u043E\u043C\u0435\u043D\u0435 \u045B\u0435 \u0441\u0442\u0443\u043F\u0438\u0442\u0438 \u043D\u0430\u043A\u043E\u043D \u043F\u043E\u043D\u043E\u0432\u043D\u043E\u0433 \u043F\u043E\u043A\u0440\u0435\u0442\u0430\u045A\u0430 Jenkins
+Restart\ Once\ No\ Jobs\ Are\ Running=\u041F\u043D\u043E\u0432\u043E \u043F\u043E\u043A\u0440\u0435\u043D\u0442\u0438 \u043A\u0430\u0434 \u043D\u0435 \u0431\u0443\u0434\u0435 \u0431\u0438\u043B\u043E \u0442\u0435\u043A\u0443\u045B\u0438\u0445 \u0437\u0430\u0434\u0430\u0442\u0430\u043A\u0430.
+New\ plugins\ will\ take\ effect\ once\ you\ restart\ Jenkins=\u041D\u043E\u0432\u0435 \u043C\u043E\u0434\u0443\u043B\u0435 \u045B\u0435 \u0441\u0442\u0443\u043F\u0438\u0442\u0438 \u043D\u0430\u043A\u043E\u043D \u043F\u043E\u043D\u043E\u0432\u043E\u0433 \u043F\u043E\u043A\u0440\u0435\u0442\u0430\u045A\u0430
+Restart\ Now=\u041F\u043E\u043D\u043E\u0432\u043E \u043F\u043E\u043A\u0440\u0435\u043D\u0438 \u0441\u0430\u0434\u0430
diff --git a/core/src/main/resources/hudson/PluginManager/installed_uk.properties b/core/src/main/resources/hudson/PluginManager/installed_uk.properties
deleted file mode 100644
index cdfbd4ea6110127b07f899d1dbdf51677200eddf..0000000000000000000000000000000000000000
--- a/core/src/main/resources/hudson/PluginManager/installed_uk.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# This file is under the MIT License by authors
-
-Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC820\uD0A8\uC2A4\uB97C \uC7AC\uC2DC\uC791\uD560\uB54C \uC801\uC6A9\uB429\uB2C8\uB2E4
-Enabled=\uD65C\uC131\uD654\uB428
-Name=\uC774\uB984
-Pinned=\uD540 \uACE0\uC815
-Previously\ installed\ version=\uC774\uC804\uC5D0 \uC124\uCE58\uD55C \uBC84\uC804
-Restart\ Once\ No\ Jobs\ Are\ Running=\uC544\uBB34\uB7F0 \uC791\uC5C5\uC774 \uC5C6\uC744 \uB54C \uC7AC\uC2DC\uC791\uD558\uAE30
-Uncheck\ to\ disable\ the\ plugin=\uD50C\uB7EC\uADF8\uC778\uC744 \uBE44\uD65C\uC131\uD654\uD558\uB824\uBA74 \uCCB4\uD06C\uB97C \uD574\uC9C0\uD558\uC2ED\uC2DC\uC624
-Uninstall=\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438
-Unpin=\uD540 \uD574\uCCB4
-Version=\uBC84\uC804
-downgradeTo={0}\uB85C \uB2E4\uC6B4\uADF8\uB808\uC774\uB4DC
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
diff --git a/core/src/main/resources/hudson/PluginManager/installed_zh_CN.properties b/core/src/main/resources/hudson/PluginManager/installed_zh_CN.properties
index 20edd4eb6a73edac358789c094d1ca71ff907074..3ef1b371c792341317b2a226f596a927283bd428 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_zh_CN.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_zh_CN.properties
@@ -23,12 +23,14 @@
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=\u6240\u6709\u6539\u53D8\u4F1A\u5728\u91CD\u65B0\u542F\u52A8Jenkins\u4EE5\u540E\u751F\u6548\u3002
Enabled=\u542F\u7528
Name=\u540D\u79F0
-Pinned=\u7ED1\u5B9A
Previously\ installed\ version=\u4E0A\u4E00\u4E2A\u5B89\u88C5\u7684\u7248\u672C
Restart\ Once\ No\ Jobs\ Are\ Running=\u5F53\u6CA1\u6709\u4EFB\u52A1\u65F6\u91CD\u542F
Uncheck\ to\ disable\ the\ plugin=\u53D6\u6D88\u9009\u62E9\u4EE5\u7981\u7528\u63D2\u4EF6
Uninstall=\u5378\u8F7D
-Unpin=\u89E3\u9664\u7ED1\u5B9A
Version=\u7248\u672C
downgradeTo=\u964D\u5230
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
+No\ plugins\ installed.=\u6CA1\u6709\u5B89\u88C5\u4EFB\u52A1\u63D2\u4EF6\u3002
+Update\ Center=\u66F4\u65B0\u4E2D\u5FC3
+Warning=\u8B66\u544A
+requires.restart=Jenkins\u9700\u8981\u91CD\u542F\u3002\u8FD9\u65F6\u5019\u4E0D\u5EFA\u8BAE\u4FEE\u6539\u63D2\u4EF6\u7684\u72B6\u6001\u3002\u5728\u64CD\u4F5C\u4E4B\u524D\u5148\u91CD\u542FJenkins\u3002
+Filter=\u8FC7\u6EE4
diff --git a/core/src/main/resources/hudson/PluginManager/installed_zh_TW.properties b/core/src/main/resources/hudson/PluginManager/installed_zh_TW.properties
index 205f23e21971174a1f3015c5ca9368066df6d4f8..9721180a51cfad58cb54873faaed0fa82bba6b11 100644
--- a/core/src/main/resources/hudson/PluginManager/installed_zh_TW.properties
+++ b/core/src/main/resources/hudson/PluginManager/installed_zh_TW.properties
@@ -30,11 +30,8 @@ Enabled=\u5DF2\u555F\u7528
Name=\u540d\u7a31
Version=\u7248\u672c
Previously\ installed\ version=\u524D\u4E00\u5B89\u88DD\u7248\u672C
-Pinned=\u5df2\u639b\u8f09
downgradeTo=\u964d\u7248\u6210 {0}
-Unpin=\u5378\u9664
-wiki.url=http://wiki.jenkins-ci.org/display/JENKINS/Pinned+Plugins
Uninstallation\ pending=\u89e3\u9664\u5b89\u88dd\u4f5c\u696d\u64f1\u7f6e\u4e2d
Uninstall=\u89E3\u9664\u5B89\u88DD
diff --git a/core/src/main/resources/hudson/PluginManager/sidepanel_it.properties b/core/src/main/resources/hudson/PluginManager/sidepanel_it.properties
index e958a8920a6662354d75c26989c7ce2fdb343bb2..c96e127302423eb5a100c094ca6157131abb07c1 100644
--- a/core/src/main/resources/hudson/PluginManager/sidepanel_it.properties
+++ b/core/src/main/resources/hudson/PluginManager/sidepanel_it.properties
@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-Back\ to\ Dashboard=Torna alla Dashboard
+Back\ to\ Dashboard=Torna al cruscotto
Manage\ Jenkins=Configura Jenkins
diff --git a/core/src/main/resources/hudson/PluginManager/sidepanel_sr.properties b/core/src/main/resources/hudson/PluginManager/sidepanel_sr.properties
new file mode 100644
index 0000000000000000000000000000000000000000..baba7c569c30a788802dce6a9cf9ba11f431ebc2
--- /dev/null
+++ b/core/src/main/resources/hudson/PluginManager/sidepanel_sr.properties
@@ -0,0 +1,5 @@
+# This file is under the MIT License by authors
+
+Back\ to\ Dashboard=\u041D\u0430\u0437\u0430\u0434 \u043A\u0430 \u043A\u043E\u043D\u0442\u043E\u0440\u043B\u043D\u043E\u0458 \u043F\u0430\u043D\u0435\u043B\u0438
+Manage\ Jenkins=\u0423\u043F\u0440\u0430\u0432\u0459\u0430\u045A\u0435 Jenkins-\u043E\u043C
+Update\ Center=\u0426\u0435\u043D\u0442\u0430\u0440 \u0437\u0430 \u0410\u0436\u0443\u0440\u0438\u0440\u0430\u045A\u0435
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/PluginManager/sidepanel_zh_CN.properties b/core/src/main/resources/hudson/PluginManager/sidepanel_zh_CN.properties
index c63351b31f8573e980fb54d6d1e4d3f5e3c467f3..3a25d8fbda4b454f5f1a63ef9a0fa92ad56d0758 100644
--- a/core/src/main/resources/hudson/PluginManager/sidepanel_zh_CN.properties
+++ b/core/src/main/resources/hudson/PluginManager/sidepanel_zh_CN.properties
@@ -19,6 +19,6 @@
# 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.
-
-Back\ to\ Dashboard=\u8FD4\u56DE
-Manage\ Jenkins=\u7cfb\u7edf\u7ba1\u7406
+Back\ to\ Dashboard=\u8FD4\u56DE\u5230\u5DE5\u4F5C\u53F0
+Manage\ Jenkins=\u7CFB\u7EDF\u7BA1\u7406
+Update\ Center=\u66F4\u65B0\u4E2D\u5FC3
diff --git a/core/src/main/resources/hudson/PluginManager/sites_it.properties b/core/src/main/resources/hudson/PluginManager/sites_it.properties
new file mode 100644
index 0000000000000000000000000000000000000000..9dc12245d206aa0643f62647168207ac3a1c8601
--- /dev/null
+++ b/core/src/main/resources/hudson/PluginManager/sites_it.properties
@@ -0,0 +1,25 @@
+# The MIT License
+#
+# Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# 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.
+
+Update\ Center=Centro aggiornamenti
+Remove=Rimuovi
+Add...=Aggiungi...
diff --git a/core/src/main/resources/hudson/PluginManager/sites_sr.properties b/core/src/main/resources/hudson/PluginManager/sites_sr.properties
new file mode 100644
index 0000000000000000000000000000000000000000..008225b821ad3154447f184aeb3fba9c275af713
--- /dev/null
+++ b/core/src/main/resources/hudson/PluginManager/sites_sr.properties
@@ -0,0 +1,5 @@
+# This file is under the MIT License by authors
+
+Update\ Center=\u0426\u0435\u043D\u0442\u0430\u0440 \u0437\u0430 \u0430\u0436\u0443\u0440\u0438\u0440\u0430\u045A\u0435
+Add...=\u0414\u043E\u0434\u0430\u0458...
+Remove=\u0423\u043A\u043B\u043E\u043D\u0438
diff --git a/core/src/main/resources/hudson/model/AbstractProject/BecauseOfUpstreamBuildInProgress/summary_de.properties b/core/src/main/resources/hudson/PluginManager/sites_zh_CN.properties
similarity index 89%
rename from core/src/main/resources/hudson/model/AbstractProject/BecauseOfUpstreamBuildInProgress/summary_de.properties
rename to core/src/main/resources/hudson/PluginManager/sites_zh_CN.properties
index 97f2d5a94b8d73a3d20bd96842a2422c07119a6d..bca86d11735e41a428bc714cdbacb0d1d86d350e 100644
--- a/core/src/main/resources/hudson/model/AbstractProject/BecauseOfUpstreamBuildInProgress/summary_de.properties
+++ b/core/src/main/resources/hudson/PluginManager/sites_zh_CN.properties
@@ -1,6 +1,6 @@
# The MIT License
#
-# Copyright (c) 2004-2013, Sun Microsystems, Inc., Harald Albers
+# Copyright (c) 2017, suren
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -20,4 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-description=Vorgelagertes Projekt {0} ist bereits in Arbeit.
+Update\ Center=\u66F4\u65B0\u4E2D\u5FC3
+Add...=\u65B0\u589E...
+Remove=\u79FB\u9664
diff --git a/core/src/main/resources/hudson/PluginManager/tabBar_ca.properties b/core/src/main/resources/hudson/PluginManager/tabBar_ca.properties
index 018ec6ea8aa597537866ccde6ece6b7c04cf2672..7465dffa99a8abad29aeb6525002444fe77b6f69 100644
--- a/core/src/main/resources/hudson/PluginManager/tabBar_ca.properties
+++ b/core/src/main/resources/hudson/PluginManager/tabBar_ca.properties
@@ -3,4 +3,4 @@
Advanced=Avan\u00E7at
Available=Disponible
Installed=Instal\u00B7lats
-Updates=Actulitzacions
+Updates=Actualitzacions
diff --git a/core/src/main/resources/hudson/PluginManager/tabBar_it.properties b/core/src/main/resources/hudson/PluginManager/tabBar_it.properties
index 96dce41f20e5c74795974e65a1e0f7834c40cddf..9cff73b66f0b91e5d7c271654e22f92c90b6b9cd 100644
--- a/core/src/main/resources/hudson/PluginManager/tabBar_it.properties
+++ b/core/src/main/resources/hudson/PluginManager/tabBar_it.properties
@@ -24,3 +24,4 @@ Advanced=Avanzate
Available=Disponibili
Installed=Installati
Updates=Aggiornamenti
+Sites=Siti
diff --git a/core/src/main/resources/hudson/PluginManager/tabBar_sr.properties b/core/src/main/resources/hudson/PluginManager/tabBar_sr.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c7e4f8cb4bab6a7ffadb29e6a162a1da3633fb29
--- /dev/null
+++ b/core/src/main/resources/hudson/PluginManager/tabBar_sr.properties
@@ -0,0 +1,7 @@
+# This file is under the MIT License by authors
+
+Updates=\u041D\u0430\u0434\u0433\u0440\u0430\u0434\u045A\u0435
+Available=\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E
+Installed=\u0418\u043D\u0441\u0442\u0430\u043B\u0438\u0440\u0430\u043D\u043E
+Advanced=\u041D\u0430\u043F\u0440\u0435\u0434\u043D\u043E
+Sites=\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0435
diff --git a/core/src/main/resources/hudson/PluginManager/tabBar_th.properties b/core/src/main/resources/hudson/PluginManager/tabBar_th.properties
deleted file mode 100644
index 73f99e3b966497deb42e710586e87df830e6d6f6..0000000000000000000000000000000000000000
--- a/core/src/main/resources/hudson/PluginManager/tabBar_th.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file is under the MIT License by authors
-
-Advanced=\u0E02\u0E31\u0E49\u0E19\u0E2A\u0E39\u0E07
-Available=\u0E17\u0E35\u0E48\u0E21\u0E35\u0E2D\u0E22\u0E39\u0E48
-Installed=\u0E15\u0E34\u0E14\u0E15\u0E31\u0E49\u0E07\u0E41\u0E25\u0E49\u0E27
-Updates=\u0E1B\u0E23\u0E31\u0E1A\u0E1B\u0E23\u0E38\u0E07
diff --git a/core/src/main/resources/hudson/PluginManager/tabBar_zh_CN.properties b/core/src/main/resources/hudson/PluginManager/tabBar_zh_CN.properties
index 45b2aef346df11e9c9ba89a5e78a9a1befdba46f..6465bd4b886b7c238edd2fa66ee7ebba56820253 100644
--- a/core/src/main/resources/hudson/PluginManager/tabBar_zh_CN.properties
+++ b/core/src/main/resources/hudson/PluginManager/tabBar_zh_CN.properties
@@ -24,3 +24,4 @@ Advanced=\u9AD8\u7EA7
Available=\u53EF\u9009\u63D2\u4EF6
Installed=\u5DF2\u5B89\u88C5
Updates=\u53EF\u66F4\u65B0
+Sites=\u7F51\u7AD9
diff --git a/core/src/main/resources/hudson/PluginManager/table.jelly b/core/src/main/resources/hudson/PluginManager/table.jelly
index e8c75170b412f01cdc4ac3fa8b082801d1057be6..52fed7654972f959acec2c9943fcd857db702e40 100644
--- a/core/src/main/resources/hudson/PluginManager/table.jelly
+++ b/core/src/main/resources/hudson/PluginManager/table.jelly
@@ -49,12 +49,13 @@ THE SOFTWARE.
-