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..44c7e2a1f11464b4f5550f6d74f1d6f4cee957fb
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,41 @@
+# Description
+
+See [JENKINS-XXXXX](https://issues.jenkins-ci.org/browse/JENKINS-XXXXX).
+
+Details: TODO
+
+
+
+### Changelog entries
+
+Proposed changelog entries:
+
+* Entry 1: Issue, Human-readable Text
+* ...
+
+
+
+### Submitter checklist
+
+- [ ] JIRA issue is well described
+- [ ] Link to JIRA ticket in description, if appropriate
+- [ ] Appropriate autotests or explanation to why this change has no tests
+- [ ] For new API and extension points: Link to the reference implementation in open-source (or example in Javadoc)
+
+
+
+### 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..f99fe77067ef6c25597bf54df9e0debdaff27fef 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,3 @@
# 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.
+For information on contributing to Jenkins, check out https://jenkins.io/redirect/contribute/. That page will help you get started with contributing to Jenkins.
diff --git a/Jenkinsfile b/Jenkinsfile
index cc03884ab9354f1dac934257d8fcf566ded8fee6..5f960ab4b57c7654f9f875ecb3b854dd37971ba2 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,67 @@
// TEST FLAG - to make it easier to turn on/off unit tests for speeding up access to later stuff.
def runTests = true
+def failFast = false
// 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
+// 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/rpm") {
- def rpmFilesFound = findFiles(glob: "*.rpm")
- if (rpmFilesFound.size() > 0) {
- rpmFileName = rpmFilesFound[0]?.name
- }
- }
- 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 -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.
+ def mvnCmd = "mvn -Pdebug -U clean install ${runTests ? '-Dmaven.test.failure.ignore=true' : '-DskipTests'} -V -B -Dmaven.repo.local=${pwd()}/.repository"
+ if(isUnix()) {
+ sh mvnCmd
+ } else {
+ bat "$mvnCmd -Duser.name=yay" // INFRA-1032 workaround
+ }
+ }
+ }
}
- }
-
- 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."
- }
- }
-
- }
-
-}
-stage "Package testing"
+ // Once we've built, archive the artifacts and the test results.
+ stage("${buildType} Archive Artifacts / Test Results") {
+ def files = findFiles(glob: '**/target/*.jar, **/target/*.war, **/target/*.hpi')
+ renameFiles(files, buildType.toLowerCase())
-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 +81,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 +97,16 @@ 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()
+// This hacky method is used because File is not whitelisted,
+// so we can't use renameTo or friends
+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 cce063d3c8a78ea9b8b0cdc14b536c15f11644b8..20a2db735b6fd3d3a0c137d1d5484d76c354f8d4 100644
--- a/changelog.html
+++ b/changelog.html
@@ -1,2729 +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.
-
- 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 65ad4f7f204c6aeb5ea0381ae32150c922a7d648..cf52a39ab24f05a9a591f0cf5bb48c52ddbe7518 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
org.jenkins-ci.mainpom
- 2.19-SNAPSHOT
+ 2.51-SNAPSHOTcli
@@ -24,6 +24,11 @@
powermock-api-mockitotest
+
+ org.kohsuke
+ access-modifier-annotation
+ 1.7
+ commons-codeccommons-codec
@@ -42,7 +47,7 @@
org.jvnet.localizerlocalizer
- 1.23
+ 1.24org.jenkins-ci
@@ -88,6 +93,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 525f66d65b1ab0c1d339c27936d3751910748329..4df7c421b6c9d81f8751a3cb716f09ed76d01bbe 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -25,6 +25,7 @@ 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;
@@ -71,6 +72,8 @@ import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.*;
@@ -80,7 +83,7 @@ import static java.util.logging.Level.*;
*
* @author Kohsuke Kawaguchi
*/
-public class CLI {
+public class CLI implements AutoCloseable {
private final ExecutorService pool;
private final Channel channel;
private final CliEntryPoint entryPoint;
@@ -121,7 +124,7 @@ public class CLI {
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 {
@@ -176,9 +179,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,11 +192,15 @@ public class CLI {
rsp.write(ch);
}
String head = new BufferedReader(new StringReader(rsp.toString("ISO-8859-1"))).readLine();
+
if (head == null) {
throw new IOException("Unexpected empty response");
}
- if (!head.startsWith("HTTP/1.0 200 "))
- throw new IOException("Failed to establish a connection through HTTP proxy: "+rsp);
+ 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
@@ -377,12 +385,12 @@ 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);
-//
+ Logger l = Logger.getLogger(ROOT_LOGGER_NAME);
+ l.setLevel(SEVERE);
+ ConsoleHandler h = new ConsoleHandler();
+ h.setLevel(SEVERE);
+ l.addHandler(h);
+
try {
System.exit(_main(_args));
} catch (Throwable t) {
@@ -417,7 +425,7 @@ public class CLI {
continue;
}
if (head.equals("-noCertificateCheck")) {
- System.out.println("Skipping HTTPS certificate checks altogether. Note that this is not secure at all.");
+ System.err.println("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());
@@ -453,6 +461,15 @@ public class CLI {
args = args.subList(2,args.size());
continue;
}
+ if(head.equals("-v")) {
+ args = args.subList(1,args.size());
+ Logger l = Logger.getLogger(ROOT_LOGGER_NAME);
+ l.setLevel(FINEST);
+ for (Handler h : l.getHandlers()) {
+ h.setLevel(FINEST);
+ }
+ continue;
+ }
break;
}
@@ -589,4 +606,5 @@ public class CLI {
}
private static final Logger LOGGER = Logger.getLogger(CLI.class.getName());
+ private static final String ROOT_LOGGER_NAME = CLI.class.getPackage().getName();
}
diff --git a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
index 834bb7234250d7d7bac2bd424aba5aa670be0fda..5fe402afc69d3cc6b0b1ea21377b7ffe896baa63 100644
--- a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
+++ b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
@@ -127,15 +127,11 @@ public class PrivateKeyProvider {
}
private static String readPemFile(File f) throws IOException{
- FileInputStream is = new FileInputStream(f);
- try {
- DataInputStream dis = new DataInputStream(is);
+ try (FileInputStream is = new FileInputStream(f);
+ DataInputStream dis = new DataInputStream(is)) {
byte[] bytes = new byte[(int) f.length()];
dis.readFully(bytes);
- dis.close();
return new String(bytes);
- } finally {
- is.close();
}
}
diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties
index 699b4c47381227a92f7316331cbdbc3477759213..1c091fbfb0d20ce33432ee40dd0f356c40f236c1 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -3,9 +3,10 @@ CLI.Usage=Jenkins CLI\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\
+ -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\
+ -v : verbose output. Display logs on the console by setting j.u.l log level to FINEST\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..1fb09d2cb4089e8d08699729b2b05823f1767fee 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_de.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_de.properties
@@ -4,7 +4,7 @@ CLI.Usage=Jenkins Kommandozeilenschnittstelle (Jenkins CLI)\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\
+ -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\
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/core/pom.xml b/core/pom.xml
index 28de7b936b0895fbd2da9aafdf565c18c943e6d3..dbfe9b7315240d582249bbfa7c004883c3fca10c 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.mainpom
- 2.19-SNAPSHOT
+ 2.51-SNAPSHOTjenkins-core
@@ -39,9 +39,9 @@ THE SOFTWARE.
true
- 1.243
+ 1.2502.5.6.SEC03
- 2.4.7
+ 2.4.8true
@@ -65,7 +65,7 @@ THE SOFTWARE.
org.jenkins-civersion-number
- 1.1
+ 1.3org.jenkins-ci
@@ -159,7 +159,7 @@ THE SOFTWARE.
org.kohsuke.staplerstapler-adjunct-timeline
- 1.4
+ 1.5org.kohsuke.stapler
@@ -212,7 +212,7 @@ THE SOFTWARE.
org.jvnet.localizerlocalizer
- 1.23
+ 1.24antlr
@@ -491,12 +491,12 @@ THE SOFTWARE.
commons-collectionscommons-collections
- 3.2.1
+ 3.2.2org.jvnet.winpwinp
- 1.22
+ 1.24org.jenkins-ci
@@ -541,7 +541,7 @@ THE SOFTWARE.
net.java.sezpozsezpoz
- 1.11
+ 1.12org.kohsuke.jinterop
@@ -591,12 +591,6 @@ THE SOFTWARE.
1.3.1-jenkins-1
-
- org.mindrot
- jbcrypt
- 0.3m
-
-
-
- com.google.guava
- guava
-
+
+ com.google.guava
+ guava
+
+
+ com.google.guava
+ guava-testlib
+ test
+ com.jcraft
@@ -647,6 +646,7 @@ THE SOFTWARE.
generate-taglib-interface
+ record-core-location
@@ -696,6 +696,7 @@ THE SOFTWARE.
Messages.propertiestarget/generated-sources/localizer
+ true
@@ -754,7 +755,7 @@ THE SOFTWARE.
com.sun.winswwinsw
- 1.16
+ 2.0.2binexe${project.build.outputDirectory}/windows-service
@@ -771,6 +772,7 @@ THE SOFTWARE.
0.5Ctrue-XX:MaxPermSize=128m -noverify
+ false
@@ -845,21 +847,9 @@ THE SOFTWARE.
release
-
- org.codehaus.mojo
- apt-maven-plugin
-
-
-
-
- process
-
-
-
-
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/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..792e02a0b73abb959f141bfd1f227d87a25996c0 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -107,11 +107,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());
@@ -176,11 +173,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
"Plugin installation failed. No manifest at "
+ manifestFile);
}
- FileInputStream fin = new FileInputStream(manifestFile);
- try {
+ try (FileInputStream fin = new FileInputStream(manifestFile)) {
manifest = new Manifest(fin);
- } finally {
- fin.close();
}
}
@@ -646,14 +640,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 +665,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
z.setDestFile(classesJar);
z.add(mapper);
z.execute();
- } finally {
- wrappedZOut.close();
}
}
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/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..f2fcab81d403e63588322b73ceacae33dce66617 100644
--- a/core/src/main/java/hudson/ExtensionList.java
+++ b/core/src/main/java/hudson/ExtensionList.java
@@ -46,6 +46,7 @@ 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 +69,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 +204,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) {
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..bb68b4e067d31284f9cf61ff94c35b5866421f2a 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -389,11 +389,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 +552,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 +591,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 +718,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));
@@ -837,7 +831,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 +862,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 +873,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 +885,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 +896,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 +923,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();
- }
}
}
}
@@ -1224,7 +1202,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 +1374,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();
@@ -1877,11 +1852,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();
}
}
@@ -1931,11 +1903,8 @@ public final class FilePath implements Serializable {
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 (Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) {
w.write(content);
- } finally {
- w.close();
}
return null;
}
@@ -2008,11 +1977,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);
@@ -2198,11 +2164,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();
}
}
});
@@ -2226,10 +2190,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));
}
}
});
@@ -2295,17 +2257,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 +2276,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);
}
}
@@ -2424,7 +2383,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
diff --git a/core/src/main/java/hudson/FileSystemProvisioner.java b/core/src/main/java/hudson/FileSystemProvisioner.java
index 60978a00482112d7a09d118e709051285d920751..8b9967e2bb63694dec3e5361c63a5cd5e08a204f 100644
--- a/core/src/main/java/hudson/FileSystemProvisioner.java
+++ b/core/src/main/java/hudson/FileSystemProvisioner.java
@@ -215,11 +215,8 @@ 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(new FileOutputStream(wss))) {
+ ws.archive(ArchiverFactory.TARGZ, os, glob);
}
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());
}
/**
diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java
index 4a6ed74f850d6983901d402fcf0b0c32a1aa92e8..ade4ce93c8c4f1698e1352c9259a4beb181ec57d 100644
--- a/core/src/main/java/hudson/Launcher.java
+++ b/core/src/main/java/hudson/Launcher.java
@@ -42,6 +42,8 @@ import org.apache.commons.io.input.NullInputStream;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.GuardedBy;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
@@ -57,6 +59,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;
+import hudson.Proc.ProcWithJenkins23271Patch;
/**
* Starts a process.
@@ -296,7 +299,7 @@ public abstract class Launcher {
* Sets the environment variable overrides.
*
*
- * In adition to what the current process
+ * In addition to what the current process
* is inherited (if this is going to be launched from a agent agent, that
* becomes the "current" process), these variables will be also set.
*/
@@ -383,9 +386,37 @@ public abstract class Launcher {
/**
* Starts the process and waits for its completion.
+ * @return Return code of the invoked process
+ * @throws IOException Operation error (e.g. remote call failure)
+ * @throws InterruptedException The process has been interrupted
*/
public int join() throws IOException, InterruptedException {
- return start().join();
+ // The logging around procHolderForJoin prevents the preliminary object deallocation we saw in JENKINS-23271
+ final Proc procHolderForJoin = start();
+ LOGGER.log(Level.FINER, "Started the process {0}", procHolderForJoin);
+
+ if (procHolderForJoin instanceof ProcWithJenkins23271Patch) {
+ return procHolderForJoin.join();
+ } else {
+ // Fallback to the internal handling logic
+ if (!(procHolderForJoin instanceof LocalProc)) {
+ // We consider that the process may be at risk of JENKINS-23271
+ LOGGER.log(Level.FINE, "Process {0} of type {1} is neither {2} nor instance of {3}. "
+ + "If this process operates with Jenkins agents via remote invocation, you may get into JENKINS-23271",
+ new Object[] {procHolderForJoin, procHolderForJoin.getClass(), LocalProc.class, ProcWithJenkins23271Patch.class});
+ }
+ try {
+ final int returnCode = procHolderForJoin.join();
+ if (LOGGER.isLoggable(Level.FINER)) {
+ LOGGER.log(Level.FINER, "Process {0} has finished with the return code {1}", new Object[]{procHolderForJoin, returnCode});
+ }
+ return returnCode;
+ } finally {
+ if (procHolderForJoin.isAlive()) { // Should never happen but this forces Proc to not be removed and early GC by escape analysis
+ LOGGER.log(Level.WARNING, "Process {0} has not finished after the join() method completion", procHolderForJoin);
+ }
+ }
+ }
}
/**
@@ -972,7 +1003,7 @@ public abstract class Launcher {
private static final long serialVersionUID = 1L;
}
- public static final class ProcImpl extends Proc {
+ public static final class ProcImpl extends Proc implements ProcWithJenkins23271Patch {
private final RemoteProcess process;
private final IOTriplet io;
@@ -983,12 +1014,28 @@ public abstract class Launcher {
@Override
public void kill() throws IOException, InterruptedException {
- process.kill();
+ try {
+ process.kill();
+ } finally {
+ if (this.isAlive()) { // Should never happen but this forces Proc to not be removed and early GC by escape analysis
+ LOGGER.log(Level.WARNING, "Process {0} has not really finished after the kill() method execution", this);
+ }
+ }
}
@Override
public int join() throws IOException, InterruptedException {
- return process.join();
+ try {
+ final int returnCode = process.join();
+ if (LOGGER.isLoggable(Level.FINER)) {
+ LOGGER.log(Level.FINER, "Process {0} has finished with the return code {1}", new Object[]{this, returnCode});
+ }
+ return returnCode;
+ } finally {
+ if (this.isAlive()) { // Should never happen but this forces Proc to not be removed and early GC by escape analysis
+ LOGGER.log(Level.WARNING, "Process {0} has not really finished after the join() method completion", this);
+ }
+ }
}
@Override
@@ -1017,7 +1064,7 @@ public abstract class Launcher {
* A launcher which delegates to a provided inner launcher.
* Allows subclasses to only implement methods they want to override.
* Originally, this launcher has been implemented in
- *
+ *
* Custom Tools Plugin.
*
* @author rcampbell
diff --git a/core/src/main/java/hudson/LocalPluginManager.java b/core/src/main/java/hudson/LocalPluginManager.java
index 25e48c053d0bf2dc7ef897dd6db8f0261e979ce3..bde452ca7aa58713435fa70c8d09fd84d676bcc1 100644
--- a/core/src/main/java/hudson/LocalPluginManager.java
+++ b/core/src/main/java/hudson/LocalPluginManager.java
@@ -70,7 +70,7 @@ public class LocalPluginManager extends PluginManager {
* If the war file has any "/WEB-INF/plugins/*.jpi", extract them into the plugin directory.
*
* @return
- * File names of the bundled plugins. Like {"ssh-slaves.jpi","subvesrion.jpi"}
+ * File names of the bundled plugins. Like {"ssh-slaves.jpi","subversion.jpi"}
*/
@Override
protected Collection loadBundledPlugins() {
diff --git a/core/src/main/java/hudson/Main.java b/core/src/main/java/hudson/Main.java
index 8e8ec46dd40adffefc2a88e11256d8b36b46a8db..66173630ac08d2afa47f64d3d22d01244830fac8 100644
--- a/core/src/main/java/hudson/Main.java
+++ b/core/src/main/java/hudson/Main.java
@@ -133,7 +133,7 @@ public class Main {
}
// write the output to a temporary file first.
- File tmpFile = File.createTempFile("hudson","log");
+ File tmpFile = File.createTempFile("jenkins","log");
try {
FileOutputStream os = new FileOutputStream(tmpFile);
diff --git a/core/src/main/java/hudson/Plugin.java b/core/src/main/java/hudson/Plugin.java
index 75af4fda013ef329000e81e1bed297d63dc6ab6a..5406fb21cbf396eb9616f0885c06b21c3f514f5b 100644
--- a/core/src/main/java/hudson/Plugin.java
+++ b/core/src/main/java/hudson/Plugin.java
@@ -53,8 +53,8 @@ import org.kohsuke.stapler.HttpResponses;
*
* One instance of a plugin is created by Hudson, and used as the entry point
@@ -280,7 +280,7 @@ public abstract class Plugin implements Saveable {
* Controls the file where {@link #load()} and {@link #save()}
* persists data.
*
- * This method can be also overriden if the plugin wants to
+ * This method can be also overridden if the plugin wants to
* use a custom {@link XStream} instance to persist data.
*
* @since 1.245
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index f1a463a5ff43bd87d225d223b1f7cbecae7ab87f..25c1b78d983a965bdd98bfe077f34381aa4b606b 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -67,6 +67,7 @@ import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
@@ -116,7 +117,6 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -141,6 +141,10 @@ import org.xml.sax.helpers.DefaultHandler;
import static hudson.init.InitMilestone.*;
import hudson.model.DownloadService;
import hudson.util.FormValidation;
+import java.io.ByteArrayInputStream;
+import java.net.JarURLConnection;
+import java.net.URLConnection;
+import java.util.jar.JarEntry;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
@@ -814,100 +818,102 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
*/
@Restricted(NoExternalUse.class)
public void dynamicLoad(File arc, boolean removeExisting) throws IOException, InterruptedException, RestartRequiredException {
- LOGGER.info("Attempting to dynamic load "+arc);
- PluginWrapper p = null;
- String sn;
- try {
- sn = strategy.getShortName(arc);
- } catch (AbstractMethodError x) {
- LOGGER.log(WARNING, "JENKINS-12753 fix not active: {0}", x.getMessage());
- p = strategy.createPluginWrapper(arc);
- sn = p.getShortName();
- }
- PluginWrapper pw = getPlugin(sn);
- if (pw!=null) {
- if (removeExisting) { // try to load disabled plugins
- for (Iterator i = plugins.iterator(); i.hasNext();) {
- pw = i.next();
- if(sn.equals(pw.getShortName())) {
- i.remove();
- pw = null;
- break;
+ try (ACLContext context = ACL.as(ACL.SYSTEM)) {
+ LOGGER.info("Attempting to dynamic load "+arc);
+ PluginWrapper p = null;
+ String sn;
+ try {
+ sn = strategy.getShortName(arc);
+ } catch (AbstractMethodError x) {
+ LOGGER.log(WARNING, "JENKINS-12753 fix not active: {0}", x.getMessage());
+ p = strategy.createPluginWrapper(arc);
+ sn = p.getShortName();
+ }
+ PluginWrapper pw = getPlugin(sn);
+ if (pw!=null) {
+ if (removeExisting) { // try to load disabled plugins
+ for (Iterator i = plugins.iterator(); i.hasNext();) {
+ pw = i.next();
+ if(sn.equals(pw.getShortName())) {
+ i.remove();
+ pw = null;
+ break;
+ }
}
+ } else {
+ throw new RestartRequiredException(Messages._PluginManager_PluginIsAlreadyInstalled_RestartRequired(sn));
}
- } else {
- throw new RestartRequiredException(Messages._PluginManager_PluginIsAlreadyInstalled_RestartRequired(sn));
}
- }
- if (p == null) {
- p = strategy.createPluginWrapper(arc);
- }
- if (p.supportsDynamicLoad()== YesNoMaybe.NO)
- throw new RestartRequiredException(Messages._PluginManager_PluginDoesntSupportDynamicLoad_RestartRequired(sn));
+ if (p == null) {
+ p = strategy.createPluginWrapper(arc);
+ }
+ if (p.supportsDynamicLoad()== YesNoMaybe.NO)
+ throw new RestartRequiredException(Messages._PluginManager_PluginDoesntSupportDynamicLoad_RestartRequired(sn));
- // there's no need to do cyclic dependency check, because we are deploying one at a time,
- // so existing plugins can't be depending on this newly deployed one.
+ // there's no need to do cyclic dependency check, because we are deploying one at a time,
+ // so existing plugins can't be depending on this newly deployed one.
- plugins.add(p);
- if (p.isActive())
- activePlugins.add(p);
- synchronized (((UberClassLoader) uberClassLoader).loaded) {
- ((UberClassLoader) uberClassLoader).loaded.clear();
- }
-
- try {
- p.resolvePluginDependencies();
- strategy.load(p);
+ plugins.add(p);
+ if (p.isActive())
+ activePlugins.add(p);
+ synchronized (((UberClassLoader) uberClassLoader).loaded) {
+ ((UberClassLoader) uberClassLoader).loaded.clear();
+ }
- Jenkins.getInstance().refreshExtensions();
+ try {
+ p.resolvePluginDependencies();
+ strategy.load(p);
- p.getPlugin().postInitialize();
- } catch (Exception e) {
- failedPlugins.add(new FailedPlugin(sn, e));
- activePlugins.remove(p);
- plugins.remove(p);
- throw new IOException("Failed to install "+ sn +" plugin",e);
- }
+ Jenkins.getInstance().refreshExtensions();
- // run initializers in the added plugin
- Reactor r = new Reactor(InitMilestone.ordering());
- final ClassLoader loader = p.classLoader;
- r.addAll(new InitializerFinder(loader) {
- @Override
- protected boolean filter(Method e) {
- return e.getDeclaringClass().getClassLoader() != loader || super.filter(e);
+ p.getPlugin().postInitialize();
+ } catch (Exception e) {
+ failedPlugins.add(new FailedPlugin(sn, e));
+ activePlugins.remove(p);
+ plugins.remove(p);
+ throw new IOException("Failed to install "+ sn +" plugin",e);
}
- }.discoverTasks(r));
- try {
- new InitReactorRunner().run(r);
- } catch (ReactorException e) {
- throw new IOException("Failed to initialize "+ sn +" plugin",e);
- }
- // recalculate dependencies of plugins optionally depending the newly deployed one.
- for (PluginWrapper depender: plugins) {
- if (depender.equals(p)) {
- // skip itself.
- continue;
+ // run initializers in the added plugin
+ Reactor r = new Reactor(InitMilestone.ordering());
+ final ClassLoader loader = p.classLoader;
+ r.addAll(new InitializerFinder(loader) {
+ @Override
+ protected boolean filter(Method e) {
+ return e.getDeclaringClass().getClassLoader() != loader || super.filter(e);
+ }
+ }.discoverTasks(r));
+ try {
+ new InitReactorRunner().run(r);
+ } catch (ReactorException e) {
+ throw new IOException("Failed to initialize "+ sn +" plugin",e);
}
- for (Dependency d: depender.getOptionalDependencies()) {
- if (d.shortName.equals(p.getShortName())) {
- // this plugin depends on the newly loaded one!
- // recalculate dependencies!
- try {
- getPluginStrategy().updateDependency(depender, p);
- } catch (AbstractMethodError x) {
- LOGGER.log(WARNING, "{0} does not yet implement updateDependency", getPluginStrategy().getClass());
+
+ // recalculate dependencies of plugins optionally depending the newly deployed one.
+ for (PluginWrapper depender: plugins) {
+ if (depender.equals(p)) {
+ // skip itself.
+ continue;
+ }
+ for (Dependency d: depender.getOptionalDependencies()) {
+ if (d.shortName.equals(p.getShortName())) {
+ // this plugin depends on the newly loaded one!
+ // recalculate dependencies!
+ try {
+ getPluginStrategy().updateDependency(depender, p);
+ } catch (AbstractMethodError x) {
+ LOGGER.log(WARNING, "{0} does not yet implement updateDependency", getPluginStrategy().getClass());
+ }
+ break;
}
- break;
}
}
- }
- // Redo who depends on who.
- resolveDependantPlugins();
+ // Redo who depends on who.
+ resolveDependantPlugins();
- LOGGER.info("Plugin " + p.getShortName()+":"+p.getVersion() + " dynamically installed");
+ LOGGER.info("Plugin " + p.getShortName()+":"+p.getVersion() + " dynamically installed");
+ }
}
@Restricted(NoExternalUse.class)
@@ -935,7 +941,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
* If the war file has any "/WEB-INF/plugins/[*.jpi | *.hpi]", extract them into the plugin directory.
*
* @return
- * File names of the bundled plugins. Like {"ssh-slaves.hpi","subvesrion.jpi"}
+ * File names of the bundled plugins. Like {"ssh-slaves.hpi","subversion.jpi"}
* @throws Exception
* Any exception will be reported and halt the startup.
*/
@@ -948,7 +954,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
protected void copyBundledPlugin(URL src, String fileName) throws IOException {
fileName = fileName.replace(".hpi",".jpi"); // normalize fileNames to have the correct suffix
String legacyName = fileName.replace(".jpi",".hpi");
- long lastModified = src.openConnection().getLastModified();
+ long lastModified = getModificationDate(src);
File file = new File(rootDir, fileName);
// normalization first, if the old file exists.
@@ -959,7 +965,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// - bundled version and current version differs (by timestamp).
if (!file.exists() || file.lastModified() != lastModified) {
FileUtils.copyURLToFile(src, file);
- file.setLastModified(src.openConnection().getLastModified());
+ file.setLastModified(getModificationDate(src));
// lastModified is set for two reasons:
// - to avoid unpacking as much as possible, but still do it on both upgrade and downgrade
// - to make sure the value is not changed after each restart, so we can avoid
@@ -970,19 +976,19 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// See https://groups.google.com/d/msg/jenkinsci-dev/kRobm-cxFw8/6V66uhibAwAJ
}
- private static @CheckForNull Manifest parsePluginManifest(URL bundledJpi) {
+ /*package*/ static @CheckForNull Manifest parsePluginManifest(URL bundledJpi) {
try {
URLClassLoader cl = new URLClassLoader(new URL[]{bundledJpi});
InputStream in=null;
try {
URL res = cl.findResource(PluginWrapper.MANIFEST_FILENAME);
if (res!=null) {
- in = res.openStream();
+ in = getBundledJpiManifestStream(res);
Manifest manifest = new Manifest(in);
return manifest;
}
} finally {
- IOUtils.closeQuietly(in);
+ Util.closeAndLogFailures(in, LOGGER, PluginWrapper.MANIFEST_FILENAME, bundledJpi.toString());
if (cl instanceof Closeable)
((Closeable)cl).close();
}
@@ -991,6 +997,81 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
return null;
}
+
+ /**
+ * Retrieves input stream for the Manifest url.
+ * The method intelligently handles the case of {@link JarURLConnection} pointing to files within JAR.
+ * @param url Url of the manifest file
+ * @return Input stream, which allows to retrieve manifest. This stream must be closed outside
+ * @throws IOException Operation error
+ */
+ @Nonnull
+ /*package*/ static InputStream getBundledJpiManifestStream(@Nonnull URL url) throws IOException {
+ URLConnection uc = url.openConnection();
+ InputStream in = null;
+ // Magic, which allows to avoid using stream generated for JarURLConnection.
+ // It prevents getting into JENKINS-37332 due to the file descriptor leak
+ if (uc instanceof JarURLConnection) {
+ final JarURLConnection jarURLConnection = (JarURLConnection) uc;
+ final String entryName = jarURLConnection.getEntryName();
+
+ try(final JarFile jarFile = jarURLConnection.getJarFile()) {
+ final JarEntry entry = (entryName != null && jarFile != null) ? jarFile.getJarEntry(entryName) : null;
+ if (entry != null && jarFile != null) {
+ try(InputStream i = jarFile.getInputStream(entry)) {
+ byte[] manifestBytes = IOUtils.toByteArray(i);
+ in = new ByteArrayInputStream(manifestBytes);
+ }
+ } else {
+ LOGGER.log(Level.WARNING, "Failed to locate the JAR file for {0}"
+ + "The default URLConnection stream access will be used, file descriptor may be leaked.",
+ url);
+ }
+ }
+ }
+
+ // If input stream is undefined, use the default implementation
+ if (in == null) {
+ in = url.openStream();
+ }
+
+ return in;
+ }
+
+ /**
+ * Retrieves modification date of the specified file.
+ * The method intelligently handles the case of {@link JarURLConnection} pointing to files within JAR.
+ * @param url Url of the file
+ * @return Modification date
+ * @throws IOException Operation error
+ */
+ @Nonnull
+ /*package*/ static long getModificationDate(@Nonnull URL url) throws IOException {
+ URLConnection uc = url.openConnection();
+
+ // It prevents file descriptor leak if the URL references a file within JAR
+ // See JENKINS-37332 for more info
+ // The code idea is taken from https://github.com/jknack/handlebars.java/pull/394
+ if (uc instanceof JarURLConnection) {
+ final JarURLConnection connection = (JarURLConnection) uc;
+ final URL jarURL = connection.getJarFileURL();
+ if (jarURL.getProtocol().equals("file")) {
+ uc = null;
+ String file = jarURL.getFile();
+ return new File(file).lastModified();
+ } else {
+ // We access the data without file protocol
+ if (connection.getEntryName() != null) {
+ LOGGER.log(WARNING, "Accessing modification date of {0} file, which is an entry in JAR file. "
+ + "The access protocol is not file:, falling back to the default logic (risk of file descriptor leak).",
+ url);
+ }
+ }
+ }
+
+ // Fallbak to the default implementation
+ return uc.getLastModified();
+ }
/**
* Rename a legacy file to a new name, with care to Windows where {@link File#renameTo(File)}
@@ -1065,8 +1146,11 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
/**
* Get the plugin instance with the given short name.
* @param shortName the short name of the plugin
- * @return The plugin singleton or null if a plugin with the given short name does not exist.
+ * @return The plugin singleton or {@code null} if a plugin with the given short name does not exist.
+ * The fact the plugin is loaded does not mean it is enabled and fully initialized for the current Jenkins session.
+ * Use {@link PluginWrapper#isActive()} to check it.
*/
+ @CheckForNull
public PluginWrapper getPlugin(String shortName) {
for (PluginWrapper p : getPlugins()) {
if(p.getShortName().equals(shortName))
@@ -1079,8 +1163,11 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
* Get the plugin instance that implements a specific class, use to find your plugin singleton.
* Note: beware the classloader fun.
* @param pluginClazz The class that your plugin implements.
- * @return The plugin singleton or null if for some reason the plugin is not loaded.
+ * @return The plugin singleton or {@code null} if for some reason the plugin is not loaded.
+ * The fact the plugin is loaded does not mean it is enabled and fully initialized for the current Jenkins session.
+ * Use {@link Plugin#getWrapper()} and then {@link PluginWrapper#isActive()} to check it.
*/
+ @CheckForNull
public PluginWrapper getPlugin(Class extends Plugin> pluginClazz) {
for (PluginWrapper p : getPlugins()) {
if(pluginClazz.isInstance(p.getPlugin()))
@@ -1137,7 +1224,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
for (PluginWrapper p : activePlugins) {
if (p.classLoader==cl) {
if (oneAndOnly!=null)
- return null; // ambigious
+ return null; // ambiguous
oneAndOnly = p;
}
}
@@ -1331,7 +1418,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
UpdateSite.Plugin plugin = getPlugin(pluginName, siteName);
// There could be cases like:
// 'plugin.ambiguous.updatesite' where both
- // 'plugin' @ 'ambigiuous.updatesite' and 'plugin.ambiguous' @ 'updatesite' resolve to valid plugins
+ // 'plugin' @ 'ambiguous.updatesite' and 'plugin.ambiguous' @ 'updatesite' resolve to valid plugins
if (plugin != null) {
if (p != null) {
throw new Failure("Ambiguous plugin: " + n);
@@ -1445,10 +1532,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
sites.add(new UpdateSite(UpdateCenter.ID_DEFAULT, site));
- return HttpResponses.redirectToContextRoot();
+ return new HttpRedirect("advanced");
}
-
@RequirePOST
public HttpResponse doProxyConfigure(StaplerRequest req) throws IOException, ServletException {
Jenkins jenkins = Jenkins.getInstance();
@@ -1476,7 +1562,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
// Parse the request
- FileItem fileItem = (FileItem) upload.parseRequest(req).get(0);
+ FileItem fileItem = upload.parseRequest(req).get(0);
String fileName = Util.getFileName(fileItem.getName());
if("".equals(fileName)){
return new HttpRedirect("advanced");
@@ -1489,7 +1575,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// first copy into a temporary file name
File t = File.createTempFile("uploaded", ".jpi");
t.deleteOnExit();
- fileItem.write(t);
+ try {
+ fileItem.write(t);
+ } catch (Exception e) {
+ // Exception thrown is too generic so at least limit the scope where it can occur
+ throw new ServletException(e);
+ }
fileItem.delete();
final String baseName = identifyPluginShortName(t);
@@ -1525,9 +1616,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
element("dependencies", dependencies);
new UpdateSite(UpdateCenter.ID_UPLOAD, null).new Plugin(UpdateCenter.ID_UPLOAD, cfg).deploy(true);
return new HttpRedirect("../updateCenter");
- } catch (IOException e) {
- throw e;
- } catch (Exception e) {// grrr. fileItem.write throws this
+ } catch (FileUploadException e) {
throw new ServletException(e);
}
}
@@ -1862,7 +1951,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
* Stores {@link Plugin} instances.
*/
/*package*/ static final class PluginInstanceStore {
- final Map store = new Hashtable();
+ final Map store = new ConcurrentHashMap();
}
/**
@@ -1871,6 +1960,11 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
@Extension @Symbol("pluginCycleDependencies")
public static final class PluginCycleDependenciesMonitor extends AdministrativeMonitor {
+ @Override
+ public String getDisplayName() {
+ return Messages.PluginManager_PluginCycleDependenciesMonitor_DisplayName();
+ }
+
private transient volatile boolean isActive = false;
private transient volatile List pluginsWithCycle;
@@ -1930,6 +2024,11 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
return !pluginsToBeUpdated.isEmpty();
}
+ @Override
+ public String getDisplayName() {
+ return Messages.PluginManager_PluginUpdateMonitor_DisplayName();
+ }
+
/**
* adds a message about a plugin to the manage screen
* @param pluginName the plugins name
diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java
index b97e977ede356f90d2955a6db7e10ec1d77be7e3..5932f0777e84ffb801743192145a6c26afe77168 100644
--- a/core/src/main/java/hudson/PluginWrapper.java
+++ b/core/src/main/java/hudson/PluginWrapper.java
@@ -417,7 +417,7 @@ public class PluginWrapper implements Comparable, ModelObject {
/**
* Returns the required Jenkins core version of this plugin.
* @return the required Jenkins core version of this plugin.
- * @since XXX
+ * @since 2.16
*/
@Exported
public @CheckForNull String getRequiredCoreVersion() {
@@ -576,7 +576,7 @@ public class PluginWrapper implements Comparable, ModelObject {
if (dependency == null) {
PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName);
if (failedDependency != null) {
- dependencyErrors.add(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), d.version));
+ dependencyErrors.add(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), failedDependency.getVersion()));
break;
} else {
dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version));
@@ -708,11 +708,8 @@ public class PluginWrapper implements Comparable, ModelObject {
File backup = getBackupFile();
if (backup.exists()) {
try {
- JarFile backupPlugin = new JarFile(backup);
- try {
+ try (JarFile backupPlugin = new JarFile(backup)) {
return backupPlugin.getManifest().getMainAttributes().getValue("Plugin-Version");
- } finally {
- backupPlugin.close();
}
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to get backup version from " + backup, e);
@@ -748,6 +745,11 @@ public class PluginWrapper implements Comparable, ModelObject {
return !plugins.isEmpty();
}
+ @Override
+ public String getDisplayName() {
+ return Messages.PluginWrapper_PluginWrapperAdministrativeMonitor_DisplayName();
+ }
+
public Collection getPlugins() {
return plugins.values();
}
diff --git a/core/src/main/java/hudson/Proc.java b/core/src/main/java/hudson/Proc.java
index a2d17298272025d7416086606cd8d9fb8578bb3a..d5f82a6511c9b6df18d395e693585e3f1d174c90 100644
--- a/core/src/main/java/hudson/Proc.java
+++ b/core/src/main/java/hudson/Proc.java
@@ -49,6 +49,8 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* External process wrapper.
@@ -157,7 +159,7 @@ public abstract class Proc {
kill();
}
} catch (InterruptedException | IOException | RuntimeException x) {
- x.printStackTrace(listener.error("Failed to join a process"));
+ Functions.printStackTrace(x, listener.error("Failed to join a process"));
}
}
});
@@ -316,7 +318,7 @@ public abstract class Proc {
try {
int r = proc.waitFor();
- // see http://wiki.jenkins-ci.org/display/JENKINS/Spawning+processes+from+build
+ // see https://jenkins.io/redirect/troubleshooting/process-leaked-file-descriptors
// problems like that shows up as infinite wait in join(), which confuses great many users.
// So let's do a timed wait here and try to diagnose the problem
if (copier!=null) copier.join(10*1000);
@@ -324,7 +326,7 @@ public abstract class Proc {
if((copier!=null && copier.isAlive()) || (copier2!=null && copier2.isAlive())) {
// looks like handles are leaking.
// closing these handles should terminate the threads.
- String msg = "Process leaked file descriptors. See http://wiki.jenkins-ci.org/display/JENKINS/Spawning+processes+from+build for more information";
+ String msg = "Process leaked file descriptors. See https://jenkins.io/redirect/troubleshooting/process-leaked-file-descriptors for more information";
Throwable e = new Exception().fillInStackTrace();
LOGGER.log(Level.WARNING,msg,e);
@@ -431,7 +433,7 @@ public abstract class Proc {
* @deprecated as of 1.399. Replaced by {@link Launcher.RemoteLauncher.ProcImpl}
*/
@Deprecated
- public static final class RemoteProc extends Proc {
+ public static final class RemoteProc extends Proc implements ProcWithJenkins23271Patch {
private final Future process;
public RemoteProc(Future process) {
@@ -440,7 +442,14 @@ public abstract class Proc {
@Override
public void kill() throws IOException, InterruptedException {
- process.cancel(true);
+ try {
+ process.cancel(true);
+ } finally {
+ if (this.isAlive()) { // Should never happen but this forces Proc to not be removed and early GC by escape analysis
+ // TODO: Report exceptions if they happen?
+ LOGGER.log(Level.WARNING, "Process {0} has not really finished after the kill() method execution", this);
+ }
+ }
}
@Override
@@ -448,8 +457,8 @@ public abstract class Proc {
try {
return process.get();
} catch (InterruptedException e) {
- // aborting. kill the process
- process.cancel(true);
+ LOGGER.log(Level.FINE, String.format("Join operation has been interrupted for the process %s. Killing the process", this), e);
+ kill();
throw e;
} catch (ExecutionException e) {
if(e.getCause() instanceof IOException)
@@ -457,6 +466,10 @@ public abstract class Proc {
throw new IOException("Failed to join the process",e);
} catch (CancellationException x) {
return -1;
+ } finally {
+ if (this.isAlive()) { // Should never happen but this forces Proc to not be removed and early GC by escape analysis
+ LOGGER.log(Level.WARNING, "Process {0} has not really finished after the join() method completion", this);
+ }
}
}
@@ -486,4 +499,15 @@ public abstract class Proc {
* Debug switch to have the thread display the process it's waiting for.
*/
public static boolean SHOW_PID = false;
+
+ /**
+ * An instance of {@link Proc}, which has an internal workaround for JENKINS-23271.
+ * It presumes that the instance of the object is guaranteed to be used after the {@link Proc#join()} call.
+ * See JENKINS-23271>
+ * @author Oleg Nenashev
+ */
+ @Restricted(NoExternalUse.class)
+ public interface ProcWithJenkins23271Patch {
+ // Empty marker interface
+ }
}
diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java
index f7ce804f5e8e0c0ca3fbdbc39baf1da83035cc35..f1b7011f424bba53fb8d6712b3e21f54ff146680 100644
--- a/core/src/main/java/hudson/ProxyConfiguration.java
+++ b/core/src/main/java/hudson/ProxyConfiguration.java
@@ -205,7 +205,7 @@ public final class ProxyConfiguration extends AbstractDescribableImpl0)
- str.append(buf,0,len);
- } finally {
- r.close();
+ while ((len = r.read(buf, 0, buf.length)) > 0)
+ str.append(buf, 0, len);
}
return str.toString();
@@ -492,10 +489,19 @@ public class Util {
*/
//Taken from http://svn.apache.org/viewvc/maven/shared/trunk/file-management/src/main/java/org/apache/maven/shared/model/fileset/util/FileSetManager.java?view=markup
public static boolean isSymlink(@Nonnull File file) throws IOException {
- Boolean r = isSymlinkJava7(file);
- if (r != null) {
- return r;
- }
+ /*
+ * Windows Directory Junctions are effectively the same as Linux symlinks to directories.
+ * Unfortunately, the Java 7 NIO2 API function isSymbolicLink does not treat them as such.
+ * It thinks of them as normal directories. To use the NIO2 API & treat it like a symlink,
+ * you have to go through BasicFileAttributes and do the following check:
+ * isSymbolicLink() || isOther()
+ * The isOther() call will include Windows reparse points, of which a directory junction is.
+ *
+ * Since we already have a function that detects Windows junctions or symlinks and treats them
+ * both as symlinks, let's use that function and always call it before calling down to the
+ * NIO2 API.
+ *
+ */
if (Functions.isWindows()) {
try {
return Kernel32Utils.isJunctionOrSymlink(file);
@@ -503,6 +509,10 @@ public class Util {
// fall through
}
}
+ Boolean r = isSymlinkJava7(file);
+ if (r != null) {
+ return r;
+ }
String name = file.getName();
if (name.equals(".") || name.equals(".."))
return false;
@@ -517,7 +527,7 @@ public class Util {
return !fileInCanonicalParent.getCanonicalFile().equals( fileInCanonicalParent.getAbsoluteFile() );
}
- @SuppressWarnings("NP_BOOLEAN_RETURN_NULL")
+ @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
private static Boolean isSymlinkJava7(@Nonnull File file) throws IOException {
try {
Path path = file.toPath();
@@ -555,7 +565,7 @@ public class Util {
* Creates a new temporary directory.
*/
public static File createTempDir() throws IOException {
- File tmp = File.createTempFile("hudson", "tmp");
+ File tmp = File.createTempFile("jenkins", "tmp");
if(!tmp.delete())
throw new IOException("Failed to delete "+tmp);
if(!tmp.mkdirs())
@@ -755,12 +765,9 @@ public class Util {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[1024];
- DigestInputStream in =new DigestInputStream(source,md5);
- try {
- while(in.read(buffer)>=0)
+ try (DigestInputStream in = new DigestInputStream(source, md5)) {
+ while (in.read(buffer) >= 0)
; // simply discard the input
- } finally {
- in.close();
}
return toHexString(md5.digest());
} catch (NoSuchAlgorithmException e) {
@@ -793,11 +800,8 @@ public class Util {
*/
@Nonnull
public static String getDigestOf(@Nonnull File file) throws IOException {
- InputStream is = new FileInputStream(file);
- try {
+ try (InputStream is = new FileInputStream(file)) {
return getDigestOf(new BufferedInputStream(is));
- } finally {
- is.close();
}
}
@@ -1363,7 +1367,7 @@ public class Util {
PrintStream log = listener.getLogger();
log.printf("ln %s %s failed%n",targetPath, new File(baseDir, symlinkPath));
Util.displayIOException(e,listener);
- e.printStackTrace( log );
+ Functions.printStackTrace(e, log);
}
}
@@ -1594,6 +1598,7 @@ public class Util {
/**
* Return true iff the parameter does not denote an absolute URI and not a scheme-relative URI.
+ * @since 2.3 / 1.651.2
*/
public static boolean isSafeToRedirectTo(@Nonnull String uri) {
return !isAbsoluteUri(uri) && !uri.startsWith("//");
@@ -1619,6 +1624,28 @@ public class Util {
p.load(new StringReader(properties));
return p;
}
+
+ /**
+ * Closes the item and logs error to the log in the case of error.
+ * Logging will be performed on the {@code WARNING} level.
+ * @param toClose Item to close. Nothing will happen if it is {@code null}
+ * @param logger Logger, which receives the error
+ * @param closeableName Name of the closeable item
+ * @param closeableOwner String representation of the closeable holder
+ * @since 2.19, but TODO update once un-restricted
+ */
+ @Restricted(NoExternalUse.class)
+ public static void closeAndLogFailures(@CheckForNull Closeable toClose, @Nonnull Logger logger,
+ @Nonnull String closeableName, @Nonnull String closeableOwner) {
+ if (toClose == null) {
+ return;
+ }
+ try {
+ toClose.close();
+ } catch(IOException ex) {
+ logger.log(Level.WARNING, String.format("Failed to close %s of %s", closeableName, closeableOwner), ex);
+ }
+ }
public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT"));
diff --git a/core/src/main/java/hudson/WebAppMain.java b/core/src/main/java/hudson/WebAppMain.java
index de518fcb720ff0562e1d3076de7e5ac5a61d084b..2e087e0348844a34dad29148478aaa7daa6c3513 100644
--- a/core/src/main/java/hudson/WebAppMain.java
+++ b/core/src/main/java/hudson/WebAppMain.java
@@ -77,7 +77,11 @@ import static java.util.logging.Level.*;
* @author Kohsuke Kawaguchi
*/
public class WebAppMain implements ServletContextListener {
- private final RingBufferLogHandler handler = new RingBufferLogHandler() {
+
+ // use RingBufferLogHandler class name to configure for backward compatibility
+ private static final int DEFAULT_RING_BUFFER_SIZE = SystemProperties.getInteger(RingBufferLogHandler.class.getName() + ".defaultSize", 256);
+
+ private final RingBufferLogHandler handler = new RingBufferLogHandler(DEFAULT_RING_BUFFER_SIZE) {
@Override public synchronized void publish(LogRecord record) {
if (record.getLevel().intValue() >= Level.INFO.intValue()) {
super.publish(record);
@@ -287,7 +291,7 @@ public class WebAppMain implements ServletContextListener {
/**
* Installs log handler to monitor all Hudson logs.
*/
- @edu.umd.cs.findbugs.annotations.SuppressWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE")
+ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE")
private void installLogger() {
Jenkins.logRecords = handler.getView();
Logger.getLogger("").addHandler(handler);
@@ -371,10 +375,16 @@ public class WebAppMain implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
try (ACLContext old = ACL.as(ACL.SYSTEM)) {
- terminated = true;
Jenkins instance = Jenkins.getInstanceOrNull();
- if (instance != null)
- instance.cleanUp();
+ try {
+ if (instance != null) {
+ instance.cleanUp();
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e);
+ }
+
+ terminated = true;
Thread t = initThread;
if (t != null && t.isAlive()) {
LOGGER.log(Level.INFO, "Shutting down a Jenkins instance that was still starting up", new Throwable("reason"));
@@ -382,7 +392,7 @@ public class WebAppMain implements ServletContextListener {
}
// Logger is in the system classloader, so if we don't do this
- // the whole web app will never be undepoyed.
+ // the whole web app will never be undeployed.
Logger.getLogger("").removeHandler(handler);
} finally {
JenkinsJVMAccess._setJenkinsJVM(false);
diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java
index 00eae060e9f378a4631ab505bc3e9daedb39fad2..9f438eededb82797f92ae944386c8486be187b8b 100644
--- a/core/src/main/java/hudson/XmlFile.java
+++ b/core/src/main/java/hudson/XmlFile.java
@@ -28,7 +28,7 @@ import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.StreamException;
-import com.thoughtworks.xstream.io.xml.XppDriver;
+import com.thoughtworks.xstream.io.xml.Xpp3Driver;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Descriptor;
import hudson.util.AtomicFileWriter;
@@ -51,6 +51,7 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -137,15 +138,10 @@ public final class XmlFile {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Reading "+file);
}
- InputStream in = new BufferedInputStream(new FileInputStream(file));
- try {
+ try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
return xs.fromXML(in);
- } catch (XStreamException e) {
- throw new IOException("Unable to read "+file,e);
- } catch(Error e) {// mostly reflection errors
+ } catch (XStreamException | Error e) {
throw new IOException("Unable to read "+file,e);
- } finally {
- in.close();
}
}
@@ -157,16 +153,12 @@ public final class XmlFile {
* if the XML representation is completely new.
*/
public Object unmarshal( Object o ) throws IOException {
- InputStream in = new BufferedInputStream(new FileInputStream(file));
- try {
+
+ try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
// TODO: expose XStream the driver from XStream
return xs.unmarshal(DEFAULT_DRIVER.createReader(in), o);
- } catch (XStreamException e) {
- throw new IOException("Unable to read "+file,e);
- } catch(Error e) {// mostly reflection errors
+ } catch (XStreamException | Error e) {
throw new IOException("Unable to read "+file,e);
- } finally {
- in.close();
}
}
@@ -205,9 +197,19 @@ public final class XmlFile {
* Opens a {@link Reader} that loads XML.
* This method uses {@link #sniffEncoding() the right encoding},
* not just the system default encoding.
+ * @throws IOException Encoding issues
+ * @return Reader for the file. should be close externally once read.
*/
public Reader readRaw() throws IOException {
- return new InputStreamReader(new FileInputStream(file),sniffEncoding());
+ FileInputStream fileInputStream = new FileInputStream(file);
+ try {
+ return new InputStreamReader(fileInputStream, sniffEncoding());
+ } catch(IOException ex) {
+ // Exception may happen if we fail to find encoding or if this encoding is unsupported.
+ // In such case we close the underlying stream and rethrow.
+ Util.closeAndLogFailures(fileInputStream, LOGGER, "FileInputStream", file.toString());
+ throw ex;
+ }
}
/**
@@ -224,11 +226,8 @@ public final class XmlFile {
* Writer will not be closed by the implementation.
*/
public void writeRawTo(Writer w) throws IOException {
- Reader r = readRaw();
- try {
- Util.copyStream(r,w);
- } finally {
- r.close();
+ try (Reader r = readRaw()) {
+ Util.copyStream(r, w);
}
}
@@ -247,10 +246,10 @@ public final class XmlFile {
this.encoding = encoding;
}
}
- InputSource input = new InputSource(file.toURI().toASCIIString());
- input.setByteStream(new FileInputStream(file));
- try {
+ try (InputStream in = new FileInputStream(file)) {
+ InputSource input = new InputSource(file.toURI().toASCIIString());
+ input.setByteStream(in);
JAXP.newSAXParser().parse(input,new DefaultHandler() {
private Locator loc;
@Override
@@ -292,9 +291,6 @@ public final class XmlFile {
throw new IOException("Failed to detect encoding of "+file,e);
} catch (ParserConfigurationException e) {
throw new AssertionError(e); // impossible
- } finally {
- // some JAXP implementations appear to leak the file handle if we just call parse(File,DefaultHandler)
- input.getByteStream().close();
}
}
@@ -307,7 +303,7 @@ public final class XmlFile {
private static final SAXParserFactory JAXP = SAXParserFactory.newInstance();
- private static final XppDriver DEFAULT_DRIVER = new XppDriver();
+ private static final Xpp3Driver DEFAULT_DRIVER = new Xpp3Driver();
static {
JAXP.setNamespaceAware(true);
diff --git a/core/src/main/java/hudson/cli/BuildCommand.java b/core/src/main/java/hudson/cli/BuildCommand.java
index dbe330ec23270b0f3ea26975a0063fe00cf0a1bc..1b96e157330e99243fec70bc361de534fb0521ed 100644
--- a/core/src/main/java/hudson/cli/BuildCommand.java
+++ b/core/src/main/java/hudson/cli/BuildCommand.java
@@ -147,7 +147,7 @@ public class BuildCommand extends CLICommand {
SCMTriggerItem item = SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(job);
if (item == null)
throw new AbortException(job.getFullDisplayName()+" has no SCM trigger, but checkSCM was specified");
- // pre-emtively check for a polling veto
+ // preemptively check for a polling veto
if (SCMDecisionHandler.firstShouldPollVeto(job) != null) {
return 0;
}
diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java
index 66eef745b86b2ffe6ad0247a6de5a78b82f4fc4f..0053c278bc3ab5333f81f55470c3f9592244e774 100644
--- a/core/src/main/java/hudson/cli/CLIAction.java
+++ b/core/src/main/java/hudson/cli/CLIAction.java
@@ -63,12 +63,11 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy {
}
public String getDisplayName() {
-
return "Jenkins CLI";
}
public String getUrlName() {
- return "cli";
+ return jenkins.CLI.DISABLED ? null : "cli";
}
public void doCommand(StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java
index 2f846c2e8c393d1fbee3a5f2c67bedd78fe05813..bc5d2ea4c5d84de24b88239f4e4e9759569980e4 100644
--- a/core/src/main/java/hudson/cli/CLICommand.java
+++ b/core/src/main/java/hudson/cli/CLICommand.java
@@ -29,6 +29,7 @@ import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.cli.declarative.CLIMethod;
import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson;
+import hudson.Functions;
import jenkins.util.SystemProperties;
import hudson.cli.declarative.OptionHandlerExtension;
import jenkins.model.Jenkins;
@@ -124,6 +125,13 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
*/
public transient PrintStream stdout,stderr;
+ /**
+ * Shared text, which is reported back to CLI if an error happens in commands
+ * taking lists of parameters.
+ * @since 2.26
+ */
+ static final String CLI_LISTPARAM_SUMMARY_ERROR_TEXT = "Error occurred while performing this command, see previous stderr output.";
+
/**
* Connected to stdin of the CLI agent.
*
@@ -291,7 +299,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
stderr.println("");
stderr.println("ERROR: " + errorMsg);
LOGGER.log(Level.WARNING, errorMsg, e);
- e.printStackTrace(stderr);
+ Functions.printStackTrace(e, stderr);
return 1;
} finally {
if(sc != null)
@@ -311,7 +319,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
public Channel checkChannel() throws AbortException {
if (channel==null)
- throw new AbortException("This command can only run with Jenkins CLI. See https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+CLI");
+ throw new AbortException("This command can only run with Jenkins CLI. See https://jenkins.io/redirect/cli-command-requires-channel");
return channel;
}
@@ -325,7 +333,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
return new ClientAuthenticationCache(channel).get();
} catch (IOException e) {
stderr.println("Failed to access the stored credential");
- e.printStackTrace(stderr); // recover
+ Functions.printStackTrace(e, stderr); // recover
}
return Jenkins.ANONYMOUS;
}
@@ -393,11 +401,11 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
* @throws IllegalArgumentException
* If the execution can't continue due to wrong input parameter (job doesn't exist etc.)
* @throws IllegalStateException
- * If the execution can't continue due to an incorect state of Jenkins, job, build etc.
+ * If the execution can't continue due to an incorrect state of Jenkins, job, build etc.
* @throws AbortException
* If the execution can't continue due to an other (rare, but foreseeable) issue
* @throws AccessDeniedException
- * If the caller doesn't have sufficent rights for requested action
+ * If the caller doesn't have sufficient rights for requested action
* @throws BadCredentialsException
* If bad credentials were provided to CLI
*/
diff --git a/core/src/main/java/hudson/cli/CancelQuietDownCommand.java b/core/src/main/java/hudson/cli/CancelQuietDownCommand.java
index d38a99edd1396a75fb4368cec281a2178c16396f..24b2ec301851fea81867b277c953d45ca1785097 100644
--- a/core/src/main/java/hudson/cli/CancelQuietDownCommand.java
+++ b/core/src/main/java/hudson/cli/CancelQuietDownCommand.java
@@ -33,7 +33,7 @@ import java.util.logging.Logger;
* Cancel previous quiet down Jenkins - preparation for a restart
*
* @author pjanouse
- * @since TODO
+ * @since 2.14
*/
@Extension
public class CancelQuietDownCommand extends CLICommand {
diff --git a/core/src/main/java/hudson/cli/ClearQueueCommand.java b/core/src/main/java/hudson/cli/ClearQueueCommand.java
index 909e49bff3a1430614921166c176a9b3764a57f2..8e76a477aa12af83cf315b4c35a0e7253401a223 100644
--- a/core/src/main/java/hudson/cli/ClearQueueCommand.java
+++ b/core/src/main/java/hudson/cli/ClearQueueCommand.java
@@ -34,7 +34,7 @@ import java.util.logging.Logger;
* Clears the build queue
*
* @author pjanouse
- * @since TODO
+ * @since 1.654
*/
@Extension
public class ClearQueueCommand extends CLICommand {
diff --git a/core/src/main/java/hudson/cli/CliCrumbExclusion.java b/core/src/main/java/hudson/cli/CliCrumbExclusion.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac49349ccfcc91207325bd3933b75c2c8593ed77
--- /dev/null
+++ b/core/src/main/java/hudson/cli/CliCrumbExclusion.java
@@ -0,0 +1,52 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016 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.Extension;
+import hudson.security.csrf.CrumbExclusion;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Makes CLI HTTP fallback work with CSRF protection enabled (JENKINS-18114).
+ */
+@Extension
+@Restricted(DoNotUse.class)
+public class CliCrumbExclusion extends CrumbExclusion {
+ @Override
+ public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+ String pathInfo = request.getPathInfo();
+ if (pathInfo != null && "/cli".equals(pathInfo)) {
+ chain.doFilter(request, response);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java
index f2160fe31f6121ad399007d35330863def725781..d663957fe9debe8998bb698ece5067fa40b410bd 100644
--- a/core/src/main/java/hudson/cli/CliProtocol.java
+++ b/core/src/main/java/hudson/cli/CliProtocol.java
@@ -42,7 +42,7 @@ public class CliProtocol extends AgentProtocol {
@Override
public String getName() {
- return "CLI-connect";
+ return jenkins.CLI.DISABLED ? null : "CLI-connect";
}
/**
diff --git a/core/src/main/java/hudson/cli/CliProtocol2.java b/core/src/main/java/hudson/cli/CliProtocol2.java
index cd7d8b04f64a9e51d7ce4f19bbc052c5a494d444..bb14599dbfea037d7d07517d828ffb9aa7f968b9 100644
--- a/core/src/main/java/hudson/cli/CliProtocol2.java
+++ b/core/src/main/java/hudson/cli/CliProtocol2.java
@@ -25,7 +25,7 @@ import java.security.Signature;
public class CliProtocol2 extends CliProtocol {
@Override
public String getName() {
- return "CLI2-connect";
+ return jenkins.CLI.DISABLED ? null : "CLI2-connect";
}
/**
diff --git a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
index 7c2127bc6d2eec668027c934414d90a4633af44b..fb754417861c61c3e17b3c45ae66572d4f25b1c9 100644
--- a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
+++ b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
@@ -51,11 +51,8 @@ public class ClientAuthenticationCache implements Serializable {
}
});
if (store.exists()) {
- InputStream istream = store.read();
- try {
+ try (InputStream istream = store.read()) {
props.load(istream);
- } finally {
- istream.close();
}
}
}
@@ -109,11 +106,8 @@ public class ClientAuthenticationCache implements Serializable {
}
private void save() throws IOException, InterruptedException {
- OutputStream os = store.write();
- try {
- props.store(os,"Credential store");
- } finally {
- os.close();
+ try (OutputStream os = store.write()) {
+ props.store(os, "Credential store");
}
// try to protect this file from other users, if we can.
store.chmod(0600);
diff --git a/core/src/main/java/hudson/cli/ConnectNodeCommand.java b/core/src/main/java/hudson/cli/ConnectNodeCommand.java
index ff9017a76e2a74c4ebb8677966e201385534b897..ae20f5e60848507503cca9f3ff0d3a21cb9c53a5 100644
--- a/core/src/main/java/hudson/cli/ConnectNodeCommand.java
+++ b/core/src/main/java/hudson/cli/ConnectNodeCommand.java
@@ -37,13 +37,14 @@ import java.util.List;
import java.util.logging.Logger;
/**
+ * Reconnect to a node or nodes.
* @author pjanouse
- * @since TODO
+ * @since 2.6
*/
@Extension
public class ConnectNodeCommand extends CLICommand {
- @Argument(metaVar="NAME", usage="Slave name, or empty string for master; comama-separated list is supported", required=true, multiValued=true)
+ @Argument(metaVar="NAME", usage="Slave name, or empty string for master; comma-separated list is supported", required=true, multiValued=true)
private List nodes;
@Option(name="-f", usage="Cancel any currently pending connect operation and retry from scratch")
@@ -96,7 +97,7 @@ public class ConnectNodeCommand extends CLICommand {
}
if (errorOccurred) {
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/ConsoleCommand.java b/core/src/main/java/hudson/cli/ConsoleCommand.java
index 92fd0e74c6b80160c49136e3f9efc2bd4d91abc7..f4c3f719b5fdb98d3548a7c572136a4458d6e849 100644
--- a/core/src/main/java/hudson/cli/ConsoleCommand.java
+++ b/core/src/main/java/hudson/cli/ConsoleCommand.java
@@ -78,12 +78,9 @@ public class ConsoleCommand extends CLICommand {
pos = logText.writeLogTo(pos, w);
} while (!logText.isComplete());
} else {
- InputStream logInputStream = run.getLogInputStream();
- try {
- IOUtils.skip(logInputStream,pos);
- org.apache.commons.io.IOUtils.copy(new InputStreamReader(logInputStream,run.getCharset()),w);
- } finally {
- logInputStream.close();
+ try (InputStream logInputStream = run.getLogInputStream()) {
+ IOUtils.skip(logInputStream, pos);
+ IOUtils.copy(new InputStreamReader(logInputStream, run.getCharset()), w);
}
}
} finally {
diff --git a/core/src/main/java/hudson/cli/DeleteJobCommand.java b/core/src/main/java/hudson/cli/DeleteJobCommand.java
index 127ec43f6c59672859aa45f589290b9b3c15a28b..199296f87fc48d09a14d289ef4c36cea8f05901f 100644
--- a/core/src/main/java/hudson/cli/DeleteJobCommand.java
+++ b/core/src/main/java/hudson/cli/DeleteJobCommand.java
@@ -34,8 +34,9 @@ import java.util.HashSet;
import java.util.logging.Logger;
/**
+ * CLI command, which deletes a job or multiple jobs.
* @author pjanouse
- * @since TODO
+ * @since 1.618
*/
@Extension
public class DeleteJobCommand extends CLICommand {
@@ -83,7 +84,7 @@ public class DeleteJobCommand extends CLICommand {
}
if (errorOccurred) {
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/DeleteNodeCommand.java b/core/src/main/java/hudson/cli/DeleteNodeCommand.java
index 5fc70fc764aba7388f3a265e9f410bdcd2818ad0..03001fccc3a10886fb164632952e426d31862ba7 100644
--- a/core/src/main/java/hudson/cli/DeleteNodeCommand.java
+++ b/core/src/main/java/hudson/cli/DeleteNodeCommand.java
@@ -34,13 +34,14 @@ import java.util.List;
import java.util.logging.Logger;
/**
+ * CLI command, which deletes Jenkins nodes.
* @author pjanouse
- * @since TODO
+ * @since 1.618
*/
@Extension
public class DeleteNodeCommand extends CLICommand {
- @Argument(usage="Nodes name to delete", required=true, multiValued=true)
+ @Argument(usage="Names of nodes to delete", required=true, multiValued=true)
private List nodes;
@Override
@@ -82,7 +83,7 @@ public class DeleteNodeCommand extends CLICommand {
}
if (errorOccurred) {
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/DeleteViewCommand.java b/core/src/main/java/hudson/cli/DeleteViewCommand.java
index 900e9cdc915b04028574cbbdd5300f79bee2bb69..894978e683fcac696a634fcad383c199e5dec840 100644
--- a/core/src/main/java/hudson/cli/DeleteViewCommand.java
+++ b/core/src/main/java/hudson/cli/DeleteViewCommand.java
@@ -95,7 +95,7 @@ public class DeleteViewCommand extends CLICommand {
}
if (errorOccurred) {
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
index fdf9058050f2fe09445afdfb37d69fa586d5bcbb..65c95106eb5f8653007b8d62bf8aa5c8c5e987e0 100644
--- a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
+++ b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
@@ -37,12 +37,13 @@ import java.util.List;
import java.util.logging.Logger;
/**
+ * CLI Command, which disconnects nodes.
* @author pjanouse
- * @since TODO
+ * @since 2.4
*/
@Extension
public class DisconnectNodeCommand extends CLICommand {
- @Argument(metaVar = "NAME", usage = "Slave name, or empty string for master; comama-separated list is supported", required = true, multiValued = true)
+ @Argument(metaVar = "NAME", usage = "Slave name, or empty string for master; comma-separated list is supported", required = true, multiValued = true)
private List nodes;
@Option(name = "-m", usage = "Record the reason about why you are disconnecting this node")
@@ -94,7 +95,7 @@ public class DisconnectNodeCommand extends CLICommand {
}
if (errorOccurred) {
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/GroovyCommand.java b/core/src/main/java/hudson/cli/GroovyCommand.java
index 144c2dfe0cfe9d846e73c54cc77b4ab350af95b2..e7b247951650711c23cffb22d1bc0273dc78e0f1 100644
--- a/core/src/main/java/hudson/cli/GroovyCommand.java
+++ b/core/src/main/java/hudson/cli/GroovyCommand.java
@@ -71,14 +71,17 @@ public class GroovyCommand extends CLICommand {
binding.setProperty("stdout",stdout);
binding.setProperty("stderr",stderr);
binding.setProperty("channel",channel);
- String j = getClientEnvironmentVariable("JOB_NAME");
- if (j!=null) {
- Item job = Jenkins.getActiveInstance().getItemByFullName(j);
- binding.setProperty("currentJob", job);
- String b = getClientEnvironmentVariable("BUILD_NUMBER");
- if (b!=null && job instanceof AbstractProject) {
- Run r = ((AbstractProject) job).getBuildByNumber(Integer.parseInt(b));
- binding.setProperty("currentBuild", r);
+
+ if (channel != null) {
+ String j = getClientEnvironmentVariable("JOB_NAME");
+ if (j != null) {
+ Item job = Jenkins.getActiveInstance().getItemByFullName(j);
+ binding.setProperty("currentJob", job);
+ String b = getClientEnvironmentVariable("BUILD_NUMBER");
+ if (b != null && job instanceof AbstractProject) {
+ Run r = ((AbstractProject) job).getBuildByNumber(Integer.parseInt(b));
+ binding.setProperty("currentBuild", r);
+ }
}
}
diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java
index 2807d872d07ec721c8976a1427b12bf04a47f162..31f998d0aa5be777ef55f2d4c1bfbf3aab4fe459 100644
--- a/core/src/main/java/hudson/cli/GroovyshCommand.java
+++ b/core/src/main/java/hudson/cli/GroovyshCommand.java
@@ -96,7 +96,7 @@ public class GroovyshCommand extends CLICommand {
private static final long serialVersionUID = 1L;
@SuppressWarnings("unused")
- @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS",justification="Closure invokes this via reflection")
+ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS",justification="Closure invokes this via reflection")
public Object doCall(Object[] args) {
assert(args.length == 1);
assert(args[0] instanceof Shell);
@@ -119,7 +119,7 @@ public class GroovyshCommand extends CLICommand {
private static final long serialVersionUID = 1L;
@SuppressWarnings("unused")
- @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS",justification="Closure invokes this via reflection")
+ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS",justification="Closure invokes this via reflection")
public Object doCall(Object[] args) throws ChannelClosedException {
if (args.length == 1 && args[0] instanceof ChannelClosedException) {
throw (ChannelClosedException)args[0];
diff --git a/core/src/main/java/hudson/cli/InstallPluginCommand.java b/core/src/main/java/hudson/cli/InstallPluginCommand.java
index 21484e05bfe4333e77483f907903506ae7a05217..c21a96628d5f55fd7ac38145c2cde2938a12d30e 100644
--- a/core/src/main/java/hudson/cli/InstallPluginCommand.java
+++ b/core/src/main/java/hudson/cli/InstallPluginCommand.java
@@ -75,17 +75,20 @@ public class InstallPluginCommand extends CLICommand {
h.checkPermission(PluginManager.UPLOAD_PLUGINS);
PluginManager pm = h.getPluginManager();
+ if (sources.size() > 1 && name != null) {
+ throw new IllegalArgumentException("-name is incompatible with multiple sources");
+ }
+
for (String source : sources) {
// is this a file?
if (channel!=null) {
FilePath f = new FilePath(channel, source);
if (f.exists()) {
stdout.println(Messages.InstallPluginCommand_InstallingPluginFromLocalFile(f));
- if (name==null)
- name = f.getBaseName();
- f.copyTo(getTargetFilePath());
+ String n = name != null ? name : f.getBaseName();
+ f.copyTo(getTargetFilePath(n));
if (dynamicLoad)
- pm.dynamicLoad(getTargetFile());
+ pm.dynamicLoad(getTargetFile(n));
continue;
}
}
@@ -94,16 +97,21 @@ public class InstallPluginCommand extends CLICommand {
try {
URL u = new URL(source);
stdout.println(Messages.InstallPluginCommand_InstallingPluginFromUrl(u));
- if (name==null) {
- name = u.getPath();
- name = name.substring(name.lastIndexOf('/')+1);
- name = name.substring(name.lastIndexOf('\\')+1);
- int idx = name.lastIndexOf('.');
- if (idx>0) name = name.substring(0,idx);
+ String n;
+ if (name != null) {
+ n = name;
+ } else {
+ n = u.getPath();
+ n = n.substring(n.lastIndexOf('/') + 1);
+ n = n.substring(n.lastIndexOf('\\') + 1);
+ int idx = n.lastIndexOf('.');
+ if (idx > 0) {
+ n = n.substring(0, idx);
+ }
}
- getTargetFilePath().copyFrom(u);
+ getTargetFilePath(n).copyFrom(u);
if (dynamicLoad)
- pm.dynamicLoad(getTargetFile());
+ pm.dynamicLoad(getTargetFile(n));
continue;
} catch (MalformedURLException e) {
// not an URL
@@ -149,11 +157,11 @@ public class InstallPluginCommand extends CLICommand {
return 0; // all success
}
- private FilePath getTargetFilePath() {
- return new FilePath(getTargetFile());
+ private static FilePath getTargetFilePath(String name) {
+ return new FilePath(getTargetFile(name));
}
- private File getTargetFile() {
+ private static File getTargetFile(String name) {
return new File(Jenkins.getActiveInstance().getPluginManager().rootDir,name+".jpi");
}
}
diff --git a/core/src/main/java/hudson/cli/OfflineNodeCommand.java b/core/src/main/java/hudson/cli/OfflineNodeCommand.java
index b92cee59cae8324283eca3cd7848cb4f709e6ff6..e003a633b28ce80895a5257e74973918c7ee9a2b 100644
--- a/core/src/main/java/hudson/cli/OfflineNodeCommand.java
+++ b/core/src/main/java/hudson/cli/OfflineNodeCommand.java
@@ -39,8 +39,9 @@ import java.util.HashSet;
import java.util.List;
/**
+ * CLI Command, which puts the Jenkins node offline.
* @author pjanouse
- * @since TODO
+ * @since 2.15
*/
@Extension
public class OfflineNodeCommand extends CLICommand {
@@ -88,7 +89,7 @@ public class OfflineNodeCommand extends CLICommand {
}
if (errorOccurred) {
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/OnlineNodeCommand.java b/core/src/main/java/hudson/cli/OnlineNodeCommand.java
index 5890ef72e07fd5eb7a2334a9b74850444860c925..5cb190fcd8ccf6bdb2c7f4e3e4c7e55888ad6505 100644
--- a/core/src/main/java/hudson/cli/OnlineNodeCommand.java
+++ b/core/src/main/java/hudson/cli/OnlineNodeCommand.java
@@ -37,8 +37,9 @@ import java.util.HashSet;
import java.util.List;
/**
+ * CLI Command, which moves the node to the online state.
* @author pjanouse
- * @since TODO
+ * @since 1.642
*/
@Extension
public class OnlineNodeCommand extends CLICommand {
@@ -86,7 +87,7 @@ public class OnlineNodeCommand extends CLICommand {
}
if (errorOccurred){
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/QuietDownCommand.java b/core/src/main/java/hudson/cli/QuietDownCommand.java
index 76d4b78dd2618f819499fe6ff4b4a080151596d2..4d84055c8de21667dac56c35ef6397cb253e7574 100644
--- a/core/src/main/java/hudson/cli/QuietDownCommand.java
+++ b/core/src/main/java/hudson/cli/QuietDownCommand.java
@@ -34,7 +34,7 @@ import java.util.logging.Logger;
* Quiet down Jenkins - preparation for a restart
*
* @author pjanouse
- * @since TODO
+ * @since 2.14
*/
@Extension
public class QuietDownCommand extends CLICommand {
diff --git a/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java b/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java
index 35cdad086c92015c756a2537bc113c35c420a869..25199ec5bed80421701fdbb9bb94537563b754a8 100644
--- a/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java
+++ b/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java
@@ -28,10 +28,10 @@ import hudson.Extension;
import jenkins.model.Jenkins;
/**
- * Reload everything from file system
+ * Reload everything from the file system.
*
* @author pjanouse
- * @since TODO
+ * @since 2.4
*/
@Extension
public class ReloadConfigurationCommand extends CLICommand {
diff --git a/core/src/main/java/hudson/cli/ReloadJobCommand.java b/core/src/main/java/hudson/cli/ReloadJobCommand.java
index 800161b29be47ce3b4a7aa129eb8b64302a17efd..17b0379f731bee1c311c435789cfb9a2bb08e94d 100644
--- a/core/src/main/java/hudson/cli/ReloadJobCommand.java
+++ b/core/src/main/java/hudson/cli/ReloadJobCommand.java
@@ -38,8 +38,9 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
+ * Reloads job from the disk.
* @author pjanouse
- * @since TODO
+ * @since 1.633
*/
@Extension
public class ReloadJobCommand extends CLICommand {
@@ -100,7 +101,7 @@ public class ReloadJobCommand extends CLICommand {
}
if (errorOccurred) {
- throw new AbortException("Error occured while performing this command, see previous stderr output.");
+ throw new AbortException(CLI_LISTPARAM_SUMMARY_ERROR_TEXT);
}
return 0;
}
diff --git a/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java
index 161fe1879dd68776e06d25c97ed78547e97f5738..319e4424a9e44b8b95d1b00b19403f0a805dc994 100644
--- a/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java
+++ b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java
@@ -28,8 +28,9 @@ import hudson.model.Node;
import org.kohsuke.args4j.Argument;
/**
+ * CLI command, which waits till the node switches to the offline state.
* @author pjanouse
- * @since TODO
+ * @since 2.16
*/
@Extension
public class WaitNodeOfflineCommand extends CLICommand {
diff --git a/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java
index fa33bdd17dcb242de0b05339f8862e5e972fc9df..2e9713ad6f4cd4e6034ca11f8e1ae9748d1315cf 100644
--- a/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java
+++ b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java
@@ -28,8 +28,9 @@ import hudson.model.Node;
import org.kohsuke.args4j.Argument;
/**
+ * CLI command, which waits till the node switches to the online state.
* @author pjanouse
- * @since TODO
+ * @since 2.16
*/
@Extension
public class WaitNodeOnlineCommand extends CLICommand {
diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java
index e063bb08cdd2996b08e0fa24e519ca051c093ca2..2d17d1b29b159b6b7971d0baf2ef8ace0246001b 100644
--- a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java
+++ b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java
@@ -27,6 +27,7 @@ import hudson.AbortException;
import hudson.Extension;
import hudson.ExtensionComponent;
import hudson.ExtensionFinder;
+import hudson.Functions;
import hudson.Util;
import hudson.cli.CLICommand;
import hudson.cli.CloneableCLICommand;
@@ -269,7 +270,7 @@ public class CLIRegisterer extends ExtensionFinder {
stderr.println("");
stderr.println("ERROR: " + errorMsg);
LOGGER.log(Level.WARNING, errorMsg, e);
- e.printStackTrace(stderr);
+ Functions.printStackTrace(e, stderr);
return 1;
}
}
diff --git a/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
index 784aef7a70a5a20b0805a17d6259b068b8ce1468..729d6f571b2ed1cca2fd7707c14993577458764e 100644
--- a/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
+++ b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
@@ -88,8 +88,8 @@ public class ViewOptionHandler extends OptionHandler {
* @throws IllegalStateException
* If cannot get active Jenkins instance or view can't contain a views
* @throws AccessDeniedException
- * If user doens't have a READ permission for the view
- * @since TODO
+ * If user doesn't have a READ permission for the view
+ * @since 1.618
*/
@CheckForNull
public View getView(final String name) {
diff --git a/core/src/main/java/hudson/cli/util/ScriptLoader.java b/core/src/main/java/hudson/cli/util/ScriptLoader.java
index e2252efbd5f78f9670bd272e6fa130180e78b3ce..8484c1e95926e09ecb20876dda6d5a3bdf0962c9 100644
--- a/core/src/main/java/hudson/cli/util/ScriptLoader.java
+++ b/core/src/main/java/hudson/cli/util/ScriptLoader.java
@@ -35,11 +35,8 @@ public class ScriptLoader extends MasterToSlaveCallable {
} catch (MalformedURLException e) {
throw new AbortException("Unable to find a script "+script);
}
- InputStream s = url.openStream();
- try {
+ try (InputStream s = url.openStream()) {
return IOUtils.toString(s);
- } finally {
- s.close();
}
}
}
diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java
index c27fec63643527d8b9f775f6e1c6b6a739a47f62..c0b281c6ca9bce6cfda181c35b98aca9ae8dbe45 100644
--- a/core/src/main/java/hudson/console/ConsoleNote.java
+++ b/core/src/main/java/hudson/console/ConsoleNote.java
@@ -51,6 +51,9 @@ import java.util.Collection;
import java.util.List;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
+import hudson.remoting.ClassFilter;
+import jenkins.security.HMACConfidentialKey;
+import jenkins.util.SystemProperties;
/**
* Data that hangs off from a console output.
@@ -94,8 +97,6 @@ import com.jcraft.jzlib.GZIPOutputStream;
* {@link ConsoleNote} always sticks to a particular point in the console output.
*
*
- * This design allows descendant processes of Hudson to emit {@link ConsoleNote}s. For example, Ant forked
- * by a shell forked by Hudson can put an encoded note in its stdout, and Hudson will correctly understands that.
* The preamble and postamble includes a certain ANSI escape sequence designed in such a way to minimize garbage
* if this output is observed by a human being directly.
*
@@ -121,6 +122,15 @@ import com.jcraft.jzlib.GZIPOutputStream;
* @since 1.349
*/
public abstract class ConsoleNote implements Serializable, Describable>, ExtensionPoint {
+
+ private static final HMACConfidentialKey MAC = new HMACConfidentialKey(ConsoleNote.class, "MAC");
+ /**
+ * Allows historical build records with unsigned console notes to be displayed, at the expense of any security.
+ * Disables checking of {@link #MAC} so do not set this flag unless you completely trust all users capable of affecting build output,
+ * which in practice means that all SCM committers as well as all Jenkins users with any non-read-only access are consider administrators.
+ */
+ static /* nonfinal for tests & script console */ boolean INSECURE = SystemProperties.getBoolean(ConsoleNote.class.getName() + ".INSECURE");
+
/**
* When the line of a console output that this annotation is attached is read by someone,
* a new {@link ConsoleNote} is de-serialized and this method is invoked to annotate that line.
@@ -170,11 +180,8 @@ public abstract class ConsoleNote implements Serializable, Describable implements Serializable, Describable implements Serializable, Describable 0) { // new format
+ mac = new byte[macSz];
+ decoded.readFully(mac);
+ sz = decoded.readInt();
+ } else {
+ mac = null;
+ sz = - macSz;
+ }
byte[] buf = new byte[sz];
decoded.readFully(buf);
@@ -223,12 +245,19 @@ public abstract class ConsoleNote implements Serializable, Describable implements Serializable, Describable 0) { // new format
+ IOUtils.skip(decoded, macSz);
+ int sz = decoded.readInt();
+ IOUtils.skip(decoded, sz);
+ } else { // old format
+ int sz = -macSz;
+ IOUtils.skip(decoded, sz);
+ }
byte[] postamble = new byte[POSTAMBLE.length];
in.readFully(postamble);
diff --git a/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java b/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java
index 4e16de8c99777110775a14af93c1af97a65d3f63..3d842e1d79c5b9bf9535339a4952abd5876c3ee2 100644
--- a/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java
+++ b/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageChecker.java
@@ -64,10 +64,10 @@ public class HudsonHomeDiskUsageChecker extends PeriodicWork {
private static final Logger LOGGER = Logger.getLogger(HudsonHomeDiskUsageChecker.class.getName());
/**
- * Gets the minimum amount of space to check for, with a default of 1GB
+ * Gets the minimum amount of space to check for, with a default of 10GB
*/
public static long FREE_SPACE_THRESHOLD = Long.getLong(
HudsonHomeDiskUsageChecker.class.getName() + ".freeSpaceThreshold",
- 1024L*1024*1024);
+ 1024L*1024*1024*10);
}
diff --git a/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageMonitor.java b/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageMonitor.java
index 20cd973989e3d386bef902beace1f97334e9069c..337f739dcb827d0d718d83208ff47cc909bf6b00 100644
--- a/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageMonitor.java
+++ b/core/src/main/java/hudson/diagnosis/HudsonHomeDiskUsageMonitor.java
@@ -32,6 +32,7 @@ import org.jenkinsci.Symbol;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.interceptor.RequirePOST;
import java.io.IOException;
import java.util.List;
@@ -64,6 +65,7 @@ public final class HudsonHomeDiskUsageMonitor extends AdministrativeMonitor {
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
+ @RequirePOST
public HttpResponse doAct(@QueryParameter String no) throws IOException {
if(no!=null) {
disable(true);
diff --git a/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java b/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java
index 7465b200e327c5deb34d539dc3d8c31346441440..8b3778262ef5650e2aa00f03e63f2fcb07c589c5 100644
--- a/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java
+++ b/core/src/main/java/hudson/diagnosis/NullIdDescriptorMonitor.java
@@ -50,6 +50,11 @@ import static hudson.init.InitMilestone.EXTENSIONS_AUGMENTED;
@Extension @Symbol("nullId")
public class NullIdDescriptorMonitor extends AdministrativeMonitor {
+ @Override
+ public String getDisplayName() {
+ return Messages.NullIdDescriptorMonitor_DisplayName();
+ }
+
private final List problems = new ArrayList();
@Override
diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
index 90324b67ca9196e6e7b259ab34f90e7b8d649f24..0feeb62c8e8665130ec581c3e748e4b02f941ce6 100644
--- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
+++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
@@ -52,6 +52,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
+
import jenkins.model.Jenkins;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
diff --git a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java
index 61f6b0fe6ec17975b2796f1f3ad5244e4abac6ff..8f7dffa349d7a8fdc8c39f864ebfb75fdc35b40f 100644
--- a/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java
+++ b/core/src/main/java/hudson/diagnosis/ReverseProxySetupMonitor.java
@@ -37,6 +37,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.Stapler;
+import org.kohsuke.stapler.interceptor.RequirePOST;
/**
* Looks out for a broken reverse proxy setup that doesn't rewrite the location header correctly.
@@ -84,6 +85,7 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor {
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
+ @RequirePOST
public HttpResponse doAct(@QueryParameter String no) throws IOException {
if(no!=null) { // dismiss
disable(true);
@@ -93,5 +95,10 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor {
return new HttpRedirect("https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+says+my+reverse+proxy+setup+is+broken");
}
}
+
+ @Override
+ public String getDisplayName() {
+ return Messages.ReverseProxySetupMonitor_DisplayName();
+ }
}
diff --git a/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java b/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java
index f4507ca8d13f272bc0ec37802ed055bf32f465f0..7f5c02c0e35d131e3fb92f854b16c16e1bd2a0e9 100644
--- a/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java
+++ b/core/src/main/java/hudson/diagnosis/TooManyJobsButNoView.java
@@ -29,6 +29,7 @@ import hudson.Extension;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.interceptor.RequirePOST;
import java.io.IOException;
@@ -42,6 +43,12 @@ import java.io.IOException;
*/
@Extension @Symbol("tooManyJobsButNoView")
public class TooManyJobsButNoView extends AdministrativeMonitor {
+
+ @Override
+ public String getDisplayName() {
+ return Messages.TooManyJobsButNoView_DisplayName();
+ }
+
public boolean isActivated() {
Jenkins h = Jenkins.getInstance();
return h.getViews().size()==1 && h.getItemMap().size()> THRESHOLD;
@@ -50,6 +57,7 @@ public class TooManyJobsButNoView extends AdministrativeMonitor {
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
+ @RequirePOST
public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException {
if(req.hasParameter("no")) {
disable(true);
diff --git a/core/src/main/java/hudson/init/impl/InitialUserContent.java b/core/src/main/java/hudson/init/impl/InitialUserContent.java
index 85a7d7bb7e3c2cc33fa687bde259b97613a4add5..0407dd8bb64eaa7ca7ba087ce4022c45ed5904e1 100644
--- a/core/src/main/java/hudson/init/impl/InitialUserContent.java
+++ b/core/src/main/java/hudson/init/impl/InitialUserContent.java
@@ -42,7 +42,7 @@ public class InitialUserContent {
File userContentDir = new File(h.getRootDir(), "userContent");
if(!userContentDir.exists()) {
userContentDir.mkdirs();
- FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"), Messages.Hudson_USER_CONTENT_README());
+ FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"), Messages.Hudson_USER_CONTENT_README() + "\n");
}
}
}
diff --git a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
index 7a37561571fc2a8c419d748c72886c647453650b..be55c08ff4eb094724468d15ac4e0f69d4ed45d5 100644
--- a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
+++ b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
@@ -45,7 +45,7 @@ public class InstallUncaughtExceptionHandler {
"Failed to set the default UncaughtExceptionHandler. " +
"If any threads die due to unhandled coding errors then there will be no logging of this information. " +
"The lack of this diagnostic information will make it harder to track down issues which will reduce the supportability of Jenkins. " +
- "It is highly recomended that you consult the documentation that comes with you servlet container on how to allow the " +
+ "It is highly recommended that you consult the documentation that comes with you servlet container on how to allow the " +
"`setDefaultUncaughtExceptionHandler` permission and enable it.", ex);
}
}
diff --git a/core/src/main/java/hudson/init/package-info.java b/core/src/main/java/hudson/init/package-info.java
index 065b64a85cb1c1ad201c936d29e564c9cacf941a..9833d2090ec5adc3f1444d1a8828a2a9f1f519c9 100644
--- a/core/src/main/java/hudson/init/package-info.java
+++ b/core/src/main/java/hudson/init/package-info.java
@@ -33,7 +33,7 @@
*
*
* Such micro-scopic dependencies are organized into a bigger directed acyclic graph, which is then executed
- * via Session. During execution of the reactor, additional tasks can be discovred and added to
+ * via Session. During execution of the reactor, additional tasks can be discovered and added to
* the DAG. We use this additional indirection to:
*
*
diff --git a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java
index ca90fb83d42798b3a5e2da4b5b0db9f38669db13..54e3d6c7434e0838212f8f08fa64ef9cc6a470a9 100644
--- a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java
@@ -26,6 +26,8 @@ package hudson.lifecycle;
import jenkins.model.Jenkins;
import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* {@link Lifecycle} for Hudson installed as SMF service.
@@ -38,9 +40,16 @@ public class SolarisSMFLifecycle extends Lifecycle {
*/
@Override
public void restart() throws IOException, InterruptedException {
- Jenkins h = Jenkins.getInstanceOrNull(); // guard against repeated concurrent calls to restart
- if (h != null)
- h.cleanUp();
+ Jenkins jenkins = Jenkins.getInstanceOrNull(); // guard against repeated concurrent calls to restart
+ try {
+ if (jenkins != null) {
+ jenkins.cleanUp();
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e);
+ }
System.exit(0);
}
+
+ private static final Logger LOGGER = Logger.getLogger(SolarisSMFLifecycle.class.getName());
}
diff --git a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java
index 5d545a4fe4f528ca7934d90394fd65873c3e58c9..1af9c131558926666e3f2693d9ff14ae2311fa0f 100644
--- a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java
@@ -28,6 +28,8 @@ import com.sun.jna.Native;
import com.sun.jna.StringArray;
import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import static hudson.util.jna.GNUCLibrary.*;
@@ -65,9 +67,14 @@ public class UnixLifecycle extends Lifecycle {
@Override
public void restart() throws IOException, InterruptedException {
- Jenkins h = Jenkins.getInstanceOrNull(); // guard against repeated concurrent calls to restart
- if (h != null)
- h.cleanUp();
+ Jenkins jenkins = Jenkins.getInstanceOrNull(); // guard against repeated concurrent calls to restart
+ try {
+ if (jenkins != null) {
+ jenkins.cleanUp();
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e);
+ }
// close all files upon exec, except stdin, stdout, and stderr
int sz = LIBC.getdtablesize();
@@ -96,4 +103,6 @@ public class UnixLifecycle extends Lifecycle {
if (args==null)
throw new RestartNotSupportedException("Failed to obtain the command line arguments of the process",failedToObtainArgs);
}
+
+ private static final Logger LOGGER = Logger.getLogger(UnixLifecycle.class.getName());
}
diff --git a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java
index 978b3c79659546fcefff70c05a21aeb366a0b53c..a9142adbc67fdd4ad2d3fdc39f34bda3a0ea92f6 100644
--- a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java
+++ b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java
@@ -306,9 +306,9 @@ public class WindowsInstallerLink extends ManagementLink {
try {
return Kernel32Utils.waitForExitProcess(sei.hProcess);
} finally {
- FileInputStream fin = new FileInputStream(new File(pwd,"redirect.log"));
- IOUtils.copy(fin, out.getLogger());
- fin.close();
+ try (FileInputStream fin = new FileInputStream(new File(pwd,"redirect.log"))) {
+ IOUtils.copy(fin, out.getLogger());
+ }
}
}
diff --git a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java
index 11f364e953ba25fce29da0a4499aa567731cb219..94066417b0baf978edc37ae877a231869ffcd791 100644
--- a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java
@@ -110,16 +110,22 @@ public class WindowsServiceLifecycle extends Lifecycle {
File rootDir = Jenkins.getInstance().getRootDir();
File copyFiles = new File(rootDir,baseName+".copies");
- FileWriter w = new FileWriter(copyFiles, true);
- try {
- w.write(by.getAbsolutePath()+'>'+getHudsonWar().getAbsolutePath()+'\n');
- } finally {
- w.close();
+ try (FileWriter w = new FileWriter(copyFiles, true)) {
+ w.write(by.getAbsolutePath() + '>' + getHudsonWar().getAbsolutePath() + '\n');
}
}
@Override
public void restart() throws IOException, InterruptedException {
+ Jenkins jenkins = Jenkins.getInstanceOrNull();
+ try {
+ if (jenkins != null) {
+ jenkins.cleanUp();
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e);
+ }
+
File me = getHudsonWar();
File home = me.getParentFile();
diff --git a/core/src/main/java/hudson/logging/LogRecorderManager.java b/core/src/main/java/hudson/logging/LogRecorderManager.java
index e35a098d4bac0af2bc594e1d40f06c777d194ad4..698718634b2c12bbdcffc6f26339ec86e15b425b 100644
--- a/core/src/main/java/hudson/logging/LogRecorderManager.java
+++ b/core/src/main/java/hudson/logging/LogRecorderManager.java
@@ -129,7 +129,7 @@ public class LogRecorderManager extends AbstractModelObject implements ModelObje
/**
* Configure the logging level.
*/
- @edu.umd.cs.findbugs.annotations.SuppressWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE")
+ @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE")
public HttpResponse doConfigLogger(@QueryParameter String name, @QueryParameter String level) {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
Level lv;
diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java
index b723b9b5196176351913d2ad244d3f64bc02cf12..857934d6d8dbaf773b4397d064373a9263e37e08 100644
--- a/core/src/main/java/hudson/model/AbstractBuild.java
+++ b/core/src/main/java/hudson/model/AbstractBuild.java
@@ -633,7 +633,7 @@ public abstract class AbstractBuild
,R extends Abs
throw (InterruptedException)new InterruptedException().initCause(e);
} catch (IOException e) {
// checkout error not yet reported
- e.printStackTrace(listener.getLogger());
+ Functions.printStackTrace(e, listener.getLogger());
}
if (retryCount == 0) // all attempts failed
@@ -749,7 +749,7 @@ public abstract class AbstractBuild
,R extends Abs
// Channel is closed, do not continue
reportBrokenChannel(listener);
} catch (RuntimeException ex) {
-
- ex.printStackTrace(listener.error("Build step failed with exception"));
+ Functions.printStackTrace(ex, listener.error("Build step failed with exception"));
}
for (BuildStepListener bsl : BuildStepListener.all()) {
@@ -854,7 +853,7 @@ public abstract class AbstractBuild
,R extends Abs
}
/*
- * No need to to lock the entire AbstractBuild on change set calculcation
+ * No need to lock the entire AbstractBuild on change set calculation
*/
private transient Object changeSetLock = new Object();
diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index 3dd2b326035f5afd10b1a8c63c575db3010dce61..5f9f6c84dbfb783dcb461664ad29887144a3db71 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -44,7 +44,6 @@ import hudson.util.Secret;
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import jenkins.model.Jenkins;
import jenkins.security.NotReallyRoleSensitiveCallable;
-import org.acegisecurity.Authentication;
import jenkins.util.xml.XMLUtils;
import org.apache.tools.ant.taskdefs.Copy;
@@ -75,7 +74,6 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
import org.xml.sax.SAXException;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
@@ -135,6 +133,15 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
return AlternativeUiTextProvider.get(PRONOUN, this, Messages.AbstractItem_Pronoun());
}
+ /**
+ * Gets the term used in the UI to represent the kind of {@link Queue.Task} associated with this kind of
+ * {@link Item}. Must start with a capital letter. Defaults to "Build".
+ * @since2.50
+ */
+ public String getTaskNoun() {
+ return AlternativeUiTextProvider.get(TASK_NOUN, this, Messages.AbstractItem_TaskNoun());
+ }
+
@Exported
/**
* @return The display name of this object, or if it is not set, the name
@@ -235,27 +242,10 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
if (this.name.equals(newName))
return;
- // the test to see if the project already exists or not needs to be done in escalated privilege
- // to avoid overwriting
- ACL.impersonate(ACL.SYSTEM,new NotReallyRoleSensitiveCallable() {
- final Authentication user = Jenkins.getAuthentication();
- @Override
- public Void call() throws IOException {
- Item existing = parent.getItem(newName);
- if (existing != null && existing!=AbstractItem.this) {
- if (existing.getACL().hasPermission(user,Item.DISCOVER))
- // the look up is case insensitive, so we need "existing!=this"
- // to allow people to rename "Foo" to "foo", for example.
- // see http://www.nabble.com/error-on-renaming-project-tt18061629.html
- throw new IllegalArgumentException("Job " + newName + " already exists");
- else {
- // can't think of any real way to hide this, but at least the error message could be vague.
- throw new IOException("Unable to rename to " + newName);
- }
- }
- return null;
- }
- });
+ // the lookup is case insensitive, so we should not fail if this item was the “existing” one
+ // to allow people to rename "Foo" to "foo", for example.
+ // see http://www.nabble.com/error-on-renaming-project-tt18061629.html
+ Items.verifyItemDoesNotAlreadyExist(parent, newName, this);
File oldRoot = this.getRootDir();
@@ -354,12 +344,14 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
*/
public abstract Collection extends Job> getAllJobs();
+ @Exported
public final String getFullName() {
String n = getParent().getFullName();
if(n.length()==0) return getName();
else return n+'/'+getName();
}
+ @Exported
public final String getFullDisplayName() {
String n = getParent().getFullDisplayName();
if(n.length()==0) return getDisplayName();
@@ -673,9 +665,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
try {
XMLUtils.safeTransform(source, new StreamResult(out));
out.close();
- } catch (TransformerException e) {
- throw new IOException("Failed to persist config.xml", e);
- } catch (SAXException e) {
+ } catch (TransformerException | SAXException e) {
throw new IOException("Failed to persist config.xml", e);
}
@@ -768,4 +758,9 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
*/
public static final Message PRONOUN = new Message();
+ /**
+ * Replaceable noun for describing the kind of task that this item represents. Defaults to "Build".
+ */
+ public static final Message TASK_NOUN = new Message<>();
+
}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 2c2fe31c765bb0ccc6b6965106a7fc53f87bc494..cdf98e5083f828e0263da8428c514090615f7e3c 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -1151,7 +1151,7 @@ public abstract class AbstractProject
,R extends A
return new BlockedBecauseOfBuildInProgress(lastBuild);
} else {
// The build has been likely deleted after the isLogUpdated() call.
- // Another cause may be an API implemetation glitсh in the implementation for AbstractProject.
+ // Another cause may be an API implementation glitсh in the implementation for AbstractProject.
// Anyway, we should let the code go then.
LOGGER.log(Level.FINE, "The last build has been deleted during the non-concurrent cause creation. The build is not blocked anymore");
}
@@ -1370,11 +1370,11 @@ public abstract class AbstractProject
,R extends A
}
/**
- * Gets the specific trigger, or null if the propert is not configured for this job.
+ * Gets the specific trigger, or null if the property is not configured for this job.
*/
public T getTrigger(Class clazz) {
for (Trigger p : triggers()) {
diff --git a/core/src/main/java/hudson/model/Action.java b/core/src/main/java/hudson/model/Action.java
index 2e8d2e641ffba3a30094ce2c1875e548b418eff9..9e48742986de696c08afd2167701da1723d8a701 100644
--- a/core/src/main/java/hudson/model/Action.java
+++ b/core/src/main/java/hudson/model/Action.java
@@ -68,6 +68,11 @@ import javax.annotation.CheckForNull;
* (for example with {@link Build}) via XStream. In some other cases,
* {@link Action}s are transient and not persisted (such as
* when it's used with {@link Job}).
+ *
+ * The {@link Actionable#replaceAction(Action)}, {@link Actionable#addOrReplaceAction(Action)}, and
+ * {@link Actionable#removeAction(Action)} methods use {@link Action#equals(Object)} to determine whether to update
+ * or replace or remove an {@link Action}. As such, {@link Action} subclasses that provide a deep
+ * {@link #equals(Object)} will assist in reducing the need for unnecessary persistence.
*
* @author Kohsuke Kawaguchi
*/
diff --git a/core/src/main/java/hudson/model/Actionable.java b/core/src/main/java/hudson/model/Actionable.java
index 8285544821df9d98c258ea6ed0311128a2516587..10e894331e95764525c7265f6185b17e03b1315c 100644
--- a/core/src/main/java/hudson/model/Actionable.java
+++ b/core/src/main/java/hudson/model/Actionable.java
@@ -23,7 +23,7 @@
*/
package hudson.model;
-import hudson.ExtensionList;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Util;
import java.util.ArrayList;
import java.util.Collection;
@@ -32,7 +32,8 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
-
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import jenkins.model.ModelObjectWithContextMenu;
import jenkins.model.TransientActionFactory;
import org.kohsuke.stapler.StaplerRequest;
@@ -65,23 +66,21 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj
* This method by default returns only persistent actions
* (though some subclasses override it to return an extended unmodifiable list).
*
- * @return
- * may be empty but never null.
+ * @return a possibly empty list
* @deprecated Normally outside code should not call this method any more.
* Use {@link #getAllActions}, or {@link #addAction}, or {@link #replaceAction}.
* May still be called for compatibility reasons from subclasses predating {@link TransientActionFactory}.
*/
@Deprecated
- public List getActions() {
- if(actions == null) {
- synchronized (this) {
- if(actions == null) {
- actions = new CopyOnWriteArrayList();
- }
- }
- }
- return actions;
- }
+ @Nonnull
+ public List getActions() {
+ synchronized (this) {
+ if(actions == null) {
+ actions = new CopyOnWriteArrayList();
+ }
+ return actions;
+ }
+ }
/**
* Gets all actions, transient or persistent.
@@ -90,61 +89,223 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj
* @since 1.548
*/
@Exported(name="actions")
+ @Nonnull
public final List extends Action> getAllActions() {
- List _actions = new ArrayList(getActions());
- for (TransientActionFactory> taf : ExtensionList.lookup(TransientActionFactory.class)) {
- if (taf.type().isInstance(this)) {
- try {
- _actions.addAll(createFor(taf));
- } catch (Exception e) {
- LOGGER.log(Level.SEVERE, "Could not load actions from " + taf + " for " + this, e);
+ List _actions = getActions();
+ boolean adding = false;
+ for (TransientActionFactory> taf : TransientActionFactory.factoriesFor(getClass(), Action.class)) {
+ Collection extends Action> additions = createFor(taf);
+ if (!additions.isEmpty()) {
+ if (!adding) { // need to make a copy
+ adding = true;
+ _actions = new ArrayList<>(_actions);
}
+ _actions.addAll(additions);
}
}
return Collections.unmodifiableList(_actions);
}
+
private Collection extends Action> createFor(TransientActionFactory taf) {
- return taf.createFor(taf.type().cast(this));
+ try {
+ Collection extends Action> result = taf.createFor(taf.type().cast(this));
+ for (Action a : result) {
+ if (!taf.actionType().isInstance(a)) {
+ LOGGER.log(Level.WARNING, "Actions from {0} for {1} included {2} not assignable to {3}", new Object[] {taf, this, a, taf.actionType()});
+ return Collections.emptySet();
+ }
+ }
+ return result;
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Could not load actions from " + taf + " for " + this, e);
+ return Collections.emptySet();
+ }
}
/**
* Gets all actions of a specified type that contributed to this object.
*
* @param type The type of action to return.
- * @return
- * may be empty but never null.
+ * @return an unmodifiable, possible empty list
* @see #getAction(Class)
*/
+ @Nonnull
public List getActions(Class type) {
- return Util.filter(getAllActions(), type);
+ List _actions = Util.filter(getActions(), type);
+ for (TransientActionFactory> taf : TransientActionFactory.factoriesFor(getClass(), type)) {
+ _actions.addAll(Util.filter(createFor(taf), type));
+ }
+ return Collections.unmodifiableList(_actions);
}
/**
* Adds a new action.
- *
- * The default implementation calls {@code getActions().add(a)}.
+ * Note: calls to {@link #getAllActions()} that happen before calls to this method may not see the update.
+ * Note: this method will always modify the actions
*/
- public void addAction(Action a) {
- if(a==null) throw new IllegalArgumentException();
+ @SuppressWarnings({"ConstantConditions","deprecation"})
+ @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
+ public void addAction(@Nonnull Action a) {
+ if(a==null) {
+ throw new IllegalArgumentException("Action must be non-null");
+ }
getActions().add(a);
}
/**
- * Add an action, replacing any existing action of the (exact) same class.
+ * Add an action, replacing any existing actions of the (exact) same class.
+ * Note: calls to {@link #getAllActions()} that happen before calls to this method may not see the update.
+ * Note: this method does not affect transient actions contributed by a {@link TransientActionFactory}.
+ * Note: this method cannot provide concurrency control due to the backing storage being a
+ * {@link CopyOnWriteArrayList} so concurrent calls to any of the mutation methods may produce surprising results
+ * though technically consistent from the concurrency contract of {@link CopyOnWriteArrayList} (we would need
+ * some form of transactions or a different backing type).
+ *
* @param a an action to add/replace
* @since 1.548
+ * @see #addOrReplaceAction(Action) if you want to know whether the backing {@link #actions} was modified, for
+ * example in cases where the caller would need to persist the {@link Actionable} in order to persist the change
+ * and there is a desire to elide unnecessary persistence of unmodified objects.
*/
- public void replaceAction(Action a) {
+ @SuppressWarnings({"ConstantConditions", "deprecation"})
+ @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
+ public void replaceAction(@Nonnull Action a) {
+ addOrReplaceAction(a);
+ }
+
+ /**
+ * Add an action, replacing any existing actions of the (exact) same class.
+ * Note: calls to {@link #getAllActions()} that happen before calls to this method may not see the update.
+ * Note: this method does not affect transient actions contributed by a {@link TransientActionFactory}
+ * Note: this method cannot provide concurrency control due to the backing storage being a
+ * {@link CopyOnWriteArrayList} so concurrent calls to any of the mutation methods may produce surprising results
+ * though technically consistent from the concurrency contract of {@link CopyOnWriteArrayList} (we would need
+ * some form of transactions or a different backing type).
+ *
+ * @param a an action to add/replace
+ * @return {@code true} if this actions changed as a result of the call
+ * @since 2.29
+ */
+ @SuppressWarnings({"ConstantConditions", "deprecation"})
+ @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
+ public boolean addOrReplaceAction(@Nonnull Action a) {
+ if (a == null) {
+ throw new IllegalArgumentException("Action must be non-null");
+ }
// CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way:
List old = new ArrayList(1);
List current = getActions();
+ boolean found = false;
for (Action a2 : current) {
- if (a2.getClass() == a.getClass()) {
+ if (!found && a.equals(a2)) {
+ found = true;
+ } else if (a2.getClass() == a.getClass()) {
old.add(a2);
}
}
current.removeAll(old);
- addAction(a);
+ if (!found) {
+ addAction(a);
+ }
+ return !found || !old.isEmpty();
+ }
+
+ /**
+ * Remove an action.
+ * Note: calls to {@link #getAllActions()} that happen before calls to this method may not see the update.
+ * Note: this method does not affect transient actions contributed by a {@link TransientActionFactory}
+ * Note: this method cannot provide concurrency control due to the backing storage being a
+ * {@link CopyOnWriteArrayList} so concurrent calls to any of the mutation methods may produce surprising results
+ * though technically consistent from the concurrency contract of {@link CopyOnWriteArrayList} (we would need
+ * some form of transactions or a different backing type).
+ *
+ * @param a an action to remove (if {@code null} then this will be a no-op)
+ * @return {@code true} if this actions changed as a result of the call
+ * @since 2.29
+ */
+ @SuppressWarnings("deprecation")
+ public boolean removeAction(@Nullable Action a) {
+ if (a == null) {
+ return false;
+ }
+ // CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way:
+ return getActions().removeAll(Collections.singleton(a));
+ }
+
+ /**
+ * Removes any actions of the specified type.
+ * Note: calls to {@link #getAllActions()} that happen before calls to this method may not see the update.
+ * Note: this method does not affect transient actions contributed by a {@link TransientActionFactory}
+ * Note: this method cannot provide concurrency control due to the backing storage being a
+ * {@link CopyOnWriteArrayList} so concurrent calls to any of the mutation methods may produce surprising results
+ * though technically consistent from the concurrency contract of {@link CopyOnWriteArrayList} (we would need
+ * some form of transactions or a different backing type).
+ *
+ * @param clazz the type of actions to remove
+ * @return {@code true} if this actions changed as a result of the call
+ * @since 2.29
+ */
+ @SuppressWarnings({"ConstantConditions","deprecation"})
+ @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
+ public boolean removeActions(@Nonnull Class extends Action> clazz) {
+ if (clazz == null) {
+ throw new IllegalArgumentException("Action type must be non-null");
+ }
+ // CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way:
+ List old = new ArrayList();
+ List current = getActions();
+ for (Action a : current) {
+ if (clazz.isInstance(a)) {
+ old.add(a);
+ }
+ }
+ return current.removeAll(old);
+ }
+
+ /**
+ * Replaces any actions of the specified type by the supplied action.
+ * Note: calls to {@link #getAllActions()} that happen before calls to this method may not see the update.
+ * Note: this method does not affect transient actions contributed by a {@link TransientActionFactory}
+ * Note: this method cannot provide concurrency control due to the backing storage being a
+ * {@link CopyOnWriteArrayList} so concurrent calls to any of the mutation methods may produce surprising results
+ * though technically consistent from the concurrency contract of {@link CopyOnWriteArrayList} (we would need
+ * some form of transactions or a different backing type).
+ *
+ * @param clazz the type of actions to replace (note that the action you are replacing this with need not extend
+ * this class)
+ * @param a the action to replace with
+ * @return {@code true} if this actions changed as a result of the call
+ * @since 2.29
+ */
+ @SuppressWarnings({"ConstantConditions", "deprecation"})
+ @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
+ public boolean replaceActions(@Nonnull Class extends Action> clazz, @Nonnull Action a) {
+ if (clazz == null) {
+ throw new IllegalArgumentException("Action type must be non-null");
+ }
+ if (a == null) {
+ throw new IllegalArgumentException("Action must be non-null");
+ }
+ // CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way:
+ List old = new ArrayList();
+ List current = getActions();
+ boolean found = false;
+ for (Action a1 : current) {
+ if (!found) {
+ if (a.equals(a1)) {
+ found = true;
+ } else if (clazz.isInstance(a1)) {
+ old.add(a1);
+ }
+ } else if (clazz.isInstance(a1) && !a.equals(a1)) {
+ old.add(a1);
+ }
+ }
+ current.removeAll(old);
+ if (!found) {
+ addAction(a);
+ }
+ return !(old.isEmpty() && found);
}
/** @deprecated No clear purpose, since subclasses may have overridden {@link #getActions}, and does not consider {@link TransientActionFactory}. */
@@ -162,9 +323,20 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj
* @see #getActions(Class)
*/
public T getAction(Class type) {
- for (Action a : getAllActions())
- if (type.isInstance(a))
+ // Shortcut: if the persisted list has one, return it.
+ for (Action a : getActions()) {
+ if (type.isInstance(a)) {
return type.cast(a);
+ }
+ }
+ // Otherwise check transient factories.
+ for (TransientActionFactory> taf : TransientActionFactory.factoriesFor(getClass(), type)) {
+ for (Action a : createFor(taf)) {
+ if (type.isInstance(a)) {
+ return type.cast(a);
+ }
+ }
+ }
return null;
}
diff --git a/core/src/main/java/hudson/model/AdministrativeMonitor.java b/core/src/main/java/hudson/model/AdministrativeMonitor.java
index 0bdc8a97bc18b9cab09dbca8e559273e67cf2ae4..8b2d39b6cb9eb78bd1e553f884cead2ffd13df40 100644
--- a/core/src/main/java/hudson/model/AdministrativeMonitor.java
+++ b/core/src/main/java/hudson/model/AdministrativeMonitor.java
@@ -33,8 +33,12 @@ import java.util.Set;
import java.io.IOException;
import jenkins.model.Jenkins;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.interceptor.RequirePOST;
/**
* Checks the health of a subsystem of Jenkins and if there's something
@@ -74,7 +78,7 @@ import org.kohsuke.stapler.StaplerResponse;
* @see Jenkins#administrativeMonitors
*/
@LegacyInstancesAreScopedToHudson
-public abstract class AdministrativeMonitor extends AbstractModelObject implements ExtensionPoint {
+public abstract class AdministrativeMonitor extends AbstractModelObject implements ExtensionPoint, StaplerProxy {
/**
* Human-readable ID of this monitor, which needs to be unique within the system.
*
@@ -142,12 +146,21 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen
/**
* URL binding to disable this monitor.
*/
+ @RequirePOST
public void doDisable(StaplerRequest req, StaplerResponse rsp) throws IOException {
- Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
disable(true);
rsp.sendRedirect2(req.getContextPath()+"/manage");
}
+ /**
+ * Requires ADMINISTER permission for any operation in here.
+ */
+ @Restricted(NoExternalUse.class)
+ public Object getTarget() {
+ Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
+ return this;
+ }
+
/**
* All registered {@link AdministrativeMonitor} instances.
*/
diff --git a/core/src/main/java/hudson/model/AllView.java b/core/src/main/java/hudson/model/AllView.java
index 9398c307f0064fba9eaed494cee280ebcb95b74e..939c5fd2ebd8a0bbf474a0135e79ed029f558fab 100644
--- a/core/src/main/java/hudson/model/AllView.java
+++ b/core/src/main/java/hudson/model/AllView.java
@@ -23,9 +23,16 @@
*/
package hudson.model;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import jenkins.util.SystemProperties;
+import org.apache.commons.lang.StringUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
-import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
@@ -44,6 +51,21 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
* @since 1.269
*/
public class AllView extends View {
+
+ /**
+ * The name of the default {@link AllView}. An {@link AllView} with this name will get a localized display name.
+ * Other {@link AllView} instances will be assumed to have been created by the user and thus will use the
+ * name the user created them with.
+ *
+ * @since 2.37
+ */
+ public static final String DEFAULT_VIEW_NAME = "all";
+
+ /**
+ * Our logger.
+ */
+ private static final Logger LOGGER = Logger.getLogger(AllView.class.getName());
+
@DataBoundConstructor
public AllView(String name) {
super(name);
@@ -64,6 +86,11 @@ public class AllView extends View {
return true;
}
+ @Override
+ public String getDisplayName() {
+ return DEFAULT_VIEW_NAME.equals(name) ? Messages.Hudson_ViewName() : name;
+ }
+
@RequirePOST
@Override
public Item doCreateItem(StaplerRequest req, StaplerResponse rsp)
@@ -89,13 +116,73 @@ public class AllView extends View {
// noop
}
+ /**
+ * Corrects the name of the {@link AllView} if and only if the {@link AllView} is the primary view and
+ * its name is one of the localized forms of {@link Messages#_Hudson_ViewName()} and the user has not opted out of
+ * fixing the view name by setting the system property {@code hudson.mode.AllView.JENKINS-38606} to {@code false}.
+ * Use this method to round-trip the primary view name, e.g.
+ * {@code primaryView = applyJenkins38606Fixup(views, primaryView)}
+ *
+ * NOTE: we can only fix the localized name of an {@link AllView} if it is the primary view as otherwise urls
+ * would change, whereas the primary view is special and does not normally get accessed by the
+ * {@code /view/_name_} url. (Also note that there are some cases where the primary view will get accessed by
+ * its {@code /view/_name_} url which will then fall back to the primary view)
+ *
+ * @param views the list of views.
+ * @param primaryView the current primary view name.
+ * @return the primary view name - this will be the same as the provided primary view name unless a JENKINS-38606
+ * matching name is detected, in which case this will be the new name of the primary view.
+ * @since 2.37
+ */
+ @Nonnull
+ public static String migrateLegacyPrimaryAllViewLocalizedName(@Nonnull List views,
+ @Nonnull String primaryView) {
+ if (DEFAULT_VIEW_NAME.equals(primaryView)) {
+ // modern name, we are safe
+ return primaryView;
+ }
+ if (SystemProperties.getBoolean(AllView.class.getName()+".JENKINS-38606", true)) {
+ AllView allView = null;
+ for (View v : views) {
+ if (DEFAULT_VIEW_NAME.equals(v.getViewName())) {
+ // name conflict, we cannot rename the all view anyway
+ return primaryView;
+ }
+ if (StringUtils.equals(v.getViewName(), primaryView)) {
+ if (v instanceof AllView) {
+ allView = (AllView) v;
+ } else {
+ // none of our business fixing as we can only safely fix the primary view
+ return primaryView;
+ }
+ }
+ }
+ if (allView != null) {
+ // the primary view is an AllView but using a non-default name
+ for (Locale l : Locale.getAvailableLocales()) {
+ if (primaryView.equals(Messages._Hudson_ViewName().toString(l))) {
+ // bingo JENKINS-38606 detected
+ LOGGER.log(Level.INFO,
+ "JENKINS-38606 detected for AllView in {0}; renaming view from {1} to {2}",
+ new Object[]{allView.owner.getUrl(), DEFAULT_VIEW_NAME});
+ allView.name = DEFAULT_VIEW_NAME;
+ return DEFAULT_VIEW_NAME;
+ }
+ }
+ }
+ }
+ return primaryView;
+ }
+
@Extension @Symbol("all")
public static final class DescriptorImpl extends ViewDescriptor {
@Override
- public boolean isInstantiable() {
- for (View v : Stapler.getCurrentRequest().findAncestorObject(ViewGroup.class).getViews())
- if(v instanceof AllView)
+ public boolean isApplicableIn(ViewGroup owner) {
+ for (View v : owner.getViews()) {
+ if (v instanceof AllView) {
return false;
+ }
+ }
return true;
}
diff --git a/core/src/main/java/hudson/model/Api.java b/core/src/main/java/hudson/model/Api.java
index 258f48ffea95f243d6d47b2f831e356d51286f9f..6878a4284a1d884c957e1c7aefa8307272f4f504 100644
--- a/core/src/main/java/hudson/model/Api.java
+++ b/core/src/main/java/hudson/model/Api.java
@@ -169,8 +169,7 @@ public class Api extends AbstractModelObject {
}
// switch to gzipped output
- OutputStream o = rsp.getCompressedOutputStream(req);
- try {
+ try (OutputStream o = rsp.getCompressedOutputStream(req)) {
if (isSimpleOutput(result)) {
// simple output allowed
rsp.setContentType("text/plain;charset=UTF-8");
@@ -182,8 +181,6 @@ public class Api extends AbstractModelObject {
// otherwise XML
rsp.setContentType("application/xml;charset=UTF-8");
new XMLWriter(o).write(result);
- } finally {
- o.close();
}
}
diff --git a/core/src/main/java/hudson/model/AsyncAperiodicWork.java b/core/src/main/java/hudson/model/AsyncAperiodicWork.java
index 069a4bb05f2f2818e14232fd6b07746efd051bba..1d295fa7161db0abcdaffbb957773954b2ec7307 100644
--- a/core/src/main/java/hudson/model/AsyncAperiodicWork.java
+++ b/core/src/main/java/hudson/model/AsyncAperiodicWork.java
@@ -23,6 +23,7 @@
*/
package hudson.model;
+import hudson.Functions;
import hudson.security.ACL;
import hudson.util.StreamTaskListener;
import java.io.File;
@@ -119,9 +120,9 @@ public abstract class AsyncAperiodicWork extends AperiodicWork {
execute(l);
} catch (IOException e) {
- e.printStackTrace(l.fatalError(e.getMessage()));
+ Functions.printStackTrace(e, l.fatalError(e.getMessage()));
} catch (InterruptedException e) {
- e.printStackTrace(l.fatalError("aborted"));
+ Functions.printStackTrace(e, l.fatalError("aborted"));
} finally {
stopTime = System.currentTimeMillis();
try {
diff --git a/core/src/main/java/hudson/model/AsyncPeriodicWork.java b/core/src/main/java/hudson/model/AsyncPeriodicWork.java
index d88413f4127646d21972ec59884f4eac07541392..f3ffcc64976a4fa0cbe9892f8c19a3d21a364c1b 100644
--- a/core/src/main/java/hudson/model/AsyncPeriodicWork.java
+++ b/core/src/main/java/hudson/model/AsyncPeriodicWork.java
@@ -1,5 +1,6 @@
package hudson.model;
+import hudson.Functions;
import hudson.security.ACL;
import hudson.util.StreamTaskListener;
import java.io.File;
@@ -99,9 +100,9 @@ public abstract class AsyncPeriodicWork extends PeriodicWork {
execute(l);
} catch (IOException e) {
- e.printStackTrace(l.fatalError(e.getMessage()));
+ Functions.printStackTrace(e, l.fatalError(e.getMessage()));
} catch (InterruptedException e) {
- e.printStackTrace(l.fatalError("aborted"));
+ Functions.printStackTrace(e, l.fatalError("aborted"));
} finally {
stopTime = System.currentTimeMillis();
try {
diff --git a/core/src/main/java/hudson/model/Build.java b/core/src/main/java/hudson/model/Build.java
index a5dbcfcf1f1958afc9cdccb54d4074d326d84470..2b8b7649310e71250aa154dd81242833143dddb1 100644
--- a/core/src/main/java/hudson/model/Build.java
+++ b/core/src/main/java/hudson/model/Build.java
@@ -23,6 +23,7 @@
*/
package hudson.model;
+import hudson.Functions;
import hudson.Launcher;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
@@ -195,7 +196,7 @@ public abstract class Build
,B extends Build
>
performAllBuildSteps(listener, project.getPublishersList(), false);
performAllBuildSteps(listener, project.getProperties(), false);
} catch (Exception x) {
- x.printStackTrace(listener.error(Messages.Build_post_build_steps_failed()));
+ Functions.printStackTrace(x, listener.error(Messages.Build_post_build_steps_failed()));
}
super.cleanUp(listener);
}
diff --git a/core/src/main/java/hudson/model/ChoiceParameterDefinition.java b/core/src/main/java/hudson/model/ChoiceParameterDefinition.java
index 964d05a214281871518eda61469d219787fb2bd1..f175bd001bd0fe08228eb0266fd764c3e4702f32 100644
--- a/core/src/main/java/hudson/model/ChoiceParameterDefinition.java
+++ b/core/src/main/java/hudson/model/ChoiceParameterDefinition.java
@@ -105,7 +105,7 @@ public class ChoiceParameterDefinition extends SimpleParameterDefinition {
}
/**
- * Checks if parameterised build choices are valid.
+ * Checks if parameterized build choices are valid.
*/
public FormValidation doCheckChoices(@QueryParameter String value) {
if (ChoiceParameterDefinition.areValidChoices(value)) {
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index 00f52e197c8b6cb80fe3b75b1fba8a4a4f202a13..3b87e42f239a1c0fff0f5c1799d6237645b5f751 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -25,8 +25,6 @@
*/
package hudson.model;
-import edu.umd.cs.findbugs.annotations.OverrideMustInvoke;
-import edu.umd.cs.findbugs.annotations.When;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher.ProcStarter;
@@ -88,6 +86,7 @@ import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.args4j.Option;
import org.kohsuke.stapler.interceptor.RequirePOST;
+import javax.annotation.OverridingMethodsMustInvokeSuper;
import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
@@ -191,7 +190,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
protected final Object statusChangeLock = new Object();
/**
- * Keeps track of stack traces to track the tremination requests for this computer.
+ * Keeps track of stack traces to track the termination requests for this computer.
*
* @since 1.607
* @see Executor#resetWorkUnit(String)
@@ -269,13 +268,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
return Collections.unmodifiableList(result);
}
- @SuppressWarnings("deprecation")
- @Override
- public void addAction(Action a) {
- if(a==null) throw new IllegalArgumentException();
- super.getActions().add(a);
- }
-
/**
* This is where the log from the remote agent goes.
* The method also creates a log directory if required.
@@ -786,7 +778,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
public RunList getBuilds() {
- return new RunList(Jenkins.getInstance().getAllItems(Job.class)).node(getNode());
+ return RunList.fromJobs(Jenkins.getInstance().allItems(Job.class)).node(getNode());
}
/**
@@ -862,7 +854,20 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
protected void onRemoved(){
}
- private synchronized void setNumExecutors(int n) {
+ /**
+ * Calling path, *means protected by Queue.withLock
+ *
+ * Computer.doConfigSubmit -> Computer.replaceBy ->Jenkins.setNodes* ->Computer.setNode
+ * AbstractCIBase.updateComputerList->Computer.inflictMortalWound*
+ * AbstractCIBase.updateComputerList->AbstractCIBase.updateComputer* ->Computer.setNode
+ * AbstractCIBase.updateComputerList->AbstractCIBase.killComputer->Computer.kill
+ * Computer.constructor->Computer.setNode
+ * Computer.kill is called after numExecutors set to zero(Computer.inflictMortalWound) so not need the Queue.lock
+ *
+ * @param number of executors
+ */
+ @GuardedBy("hudson.model.Queue.lock")
+ private void setNumExecutors(int n) {
this.numExecutors = n;
final int diff = executors.size()-n;
@@ -1105,8 +1110,10 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
@Exported(inline=true)
public Map getMonitorData() {
Map r = new HashMap();
- for (NodeMonitor monitor : NodeMonitor.getAll())
- r.put(monitor.getClass().getName(),monitor.data(this));
+ if (hasPermission(CONNECT)) {
+ for (NodeMonitor monitor : NodeMonitor.getAll())
+ r.put(monitor.getClass().getName(), monitor.data(this));
+ }
return r;
}
@@ -1267,7 +1274,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* and then it would need to be loaded, which pulls in {@link Jenkins} and loads that
* and then that fails to load as you are not supposed to do that. Another option
* would be to export the logger over remoting, with increased complexity as a result.
- * Instead we just use a loger based on this class name and prevent any references to
+ * Instead we just use a logger based on this class name and prevent any references to
* other classes from being transferred over remoting.
*/
private static final Logger LOGGER = Logger.getLogger(ListPossibleNames.class.getName());
@@ -1365,19 +1372,19 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
checkPermission(Jenkins.ADMINISTER);
rsp.setContentType("text/plain");
- PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req));
- VirtualChannel vc = getChannel();
- if (vc instanceof Channel) {
- w.println("Master to slave");
- ((Channel)vc).dumpExportTable(w);
- w.flush(); // flush here once so that even if the dump from the agent fails, the client gets some useful info
-
- w.println("\n\n\nSlave to master");
- w.print(vc.call(new DumpExportTableTask()));
- } else {
- w.println(Messages.Computer_BadChannel());
+ try (PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req))) {
+ VirtualChannel vc = getChannel();
+ if (vc instanceof Channel) {
+ w.println("Master to slave");
+ ((Channel) vc).dumpExportTable(w);
+ w.flush(); // flush here once so that even if the dump from the agent fails, the client gets some useful info
+
+ w.println("\n\n\nSlave to master");
+ w.print(vc.call(new DumpExportTableTask()));
+ } else {
+ w.println(Messages.Computer_BadChannel());
+ }
}
- w.close();
}
private static final class DumpExportTableTask extends MasterToSlaveCallable {
@@ -1534,7 +1541,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* @see hudson.slaves.RetentionStrategy#isAcceptingTasks(Computer)
* @see hudson.model.Node#isAcceptingTasks()
*/
- @OverrideMustInvoke(When.ANYTIME)
+ @OverridingMethodsMustInvokeSuper
public boolean isAcceptingTasks() {
final Node node = getNode();
return getRetentionStrategy().isAcceptingTasks(this) && (node == null || node.isAcceptingTasks());
diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java
index 89ed31f289741f3993af35a3659c83c4b0d2e43b..b907420b5876a4ce5d34f930e228c7ea89585fab 100644
--- a/core/src/main/java/hudson/model/ComputerSet.java
+++ b/core/src/main/java/hudson/model/ComputerSet.java
@@ -413,7 +413,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
/**
* @return The list of strings of computer names (excluding master)
- * @since TODO
+ * @since 2.14
*/
@Nonnull
public static List getComputerNames() {
diff --git a/core/src/main/java/hudson/model/DependencyGraph.java b/core/src/main/java/hudson/model/DependencyGraph.java
index 41327d009d337140fc56d058ede288abc286aae7..237eba1b40f50ad180a722999aca36a9559b94f9 100644
--- a/core/src/main/java/hudson/model/DependencyGraph.java
+++ b/core/src/main/java/hudson/model/DependencyGraph.java
@@ -91,7 +91,7 @@ public class DependencyGraph implements Comparator {
SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM);
try {
this.computationalData = new HashMap, Object>();
- for( AbstractProject p : getAllProjects() )
+ for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) )
p.buildDependencyGraph(this);
forward = finalize(forward);
@@ -147,10 +147,6 @@ public class DependencyGraph implements Comparator {
topologicallySorted = Collections.unmodifiableList(topologicallySorted);
}
- Collection getAllProjects() {
- return Jenkins.getInstance().getAllItems(AbstractProject.class);
- }
-
/**
* Special constructor for creating an empty graph
*/
diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java
index 9987613407a2be01e02e3f2454c762013170ab3c..2cccca37652a4c396fa60da5118fb055dc04beda 100644
--- a/core/src/main/java/hudson/model/Descriptor.java
+++ b/core/src/main/java/hudson/model/Descriptor.java
@@ -39,6 +39,7 @@ import hudson.views.ListViewColumn;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
+import jenkins.util.io.OnMaster;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.*;
@@ -126,7 +127,7 @@ import javax.annotation.Nullable;
* @author Kohsuke Kawaguchi
* @see Describable
*/
-public abstract class Descriptor> implements Saveable {
+public abstract class Descriptor> implements Saveable, OnMaster {
/**
* The class being described by this descriptor.
*/
@@ -200,11 +201,11 @@ public abstract class Descriptor> implements Saveable {
public Descriptor getItemTypeDescriptorOrDie() {
Class it = getItemType();
if (it == null) {
- throw new AssertionError(clazz + " is not an array/collection type in " + displayName + ". See https://wiki.jenkins-ci.org/display/JENKINS/My+class+is+missing+descriptor");
+ throw new AssertionError(clazz + " is not an array/collection type in " + displayName + ". See https://jenkins.io/redirect/developer/class-is-missing-descriptor");
}
Descriptor d = Jenkins.getInstance().getDescriptor(it);
if (d==null)
- throw new AssertionError(it +" is missing its descriptor in "+displayName+". See https://wiki.jenkins-ci.org/display/JENKINS/My+class+is+missing+descriptor");
+ throw new AssertionError(it +" is missing its descriptor in "+displayName+". See https://jenkins.io/redirect/developer/class-is-missing-descriptor");
return d;
}
@@ -456,7 +457,7 @@ public abstract class Descriptor> implements Saveable {
}
/**
- * Used by Jelly to abstract away the handlign of global.jelly vs config.jelly databinding difference.
+ * Used by Jelly to abstract away the handling of global.jelly vs config.jelly databinding difference.
*/
public @CheckForNull PropertyType getPropertyType(@Nonnull Object instance, @Nonnull String field) {
// in global.jelly, instance==descriptor
@@ -639,7 +640,13 @@ public abstract class Descriptor> implements Saveable {
if (isApplicable(actualType, json)) {
LOGGER.log(Level.FINE, "switching to newInstance {0} {1}", new Object[] {actualType.getName(), json});
try {
- return Jenkins.getActiveInstance().getDescriptor(actualType).newInstance(Stapler.getCurrentRequest(), json);
+ final Descriptor descriptor = Jenkins.getActiveInstance().getDescriptor(actualType);
+ if (descriptor != null) {
+ return descriptor.newInstance(Stapler.getCurrentRequest(), json);
+ } else {
+ LOGGER.log(Level.WARNING, "Descriptor not found. Falling back to default instantiation "
+ + actualType.getName() + " " + json);
+ }
} catch (Exception x) {
LOGGER.log(Level.WARNING, "falling back to default instantiation " + actualType.getName() + " " + json, x);
// If nested objects are not using newInstance, bindJSON will wind up throwing the same exception anyway,
@@ -786,7 +793,7 @@ public abstract class Descriptor> implements Saveable {
/**
* Invoked when the global configuration page is submitted.
*
- * Can be overriden to store descriptor-specific information.
+ * Can be overridden to store descriptor-specific information.
*
* @param json
* The JSON object that captures the configuration data for this {@link Descriptor}.
@@ -812,7 +819,7 @@ public abstract class Descriptor> implements Saveable {
*
* @return never null, always the same value for a given instance of {@link Descriptor}.
*
- * @since TODO, used to be in {@link GlobalConfiguration} before that.
+ * @since 2.0, used to be in {@link GlobalConfiguration} before that.
*/
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Unclassified.class);
@@ -1139,7 +1146,7 @@ public abstract class Descriptor> implements Saveable {
.generateResponse(req, rsp, node);
} else {
// for now, we can't really use the field name that caused the problem.
- new Failure(getMessage()).generateResponse(req,rsp,node);
+ new Failure(getMessage()).generateResponse(req,rsp,node,getCause());
}
}
}
diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
index bf7885030d9bfa4e2df45ecba8143ece0bc8c86a..418efca8793d95d37b2962283ba2fbfe8a610ab0 100644
--- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
+++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
@@ -223,8 +223,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
}
if (plain) {
rsp.setContentType("text/plain;charset=UTF-8");
- OutputStream os = rsp.getOutputStream();
- try {
+ try (OutputStream os = rsp.getOutputStream()) {
for (VirtualFile kid : baseFile.list()) {
os.write(kid.getName().getBytes("UTF-8"));
if (kid.isDirectory()) {
@@ -233,8 +232,6 @@ public final class DirectoryBrowserSupport implements HttpResponse {
os.write('\n');
}
os.flush();
- } finally {
- os.close();
}
return;
}
@@ -356,33 +353,33 @@ public final class DirectoryBrowserSupport implements HttpResponse {
}
private static void zip(OutputStream outputStream, VirtualFile dir, String glob) throws IOException {
- ZipOutputStream zos = new ZipOutputStream(outputStream);
- zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter
- for (String n : dir.list(glob.length() == 0 ? "**" : glob)) {
- String relativePath;
- if (glob.length() == 0) {
- // JENKINS-19947: traditional behavior is to prepend the directory name
- relativePath = dir.getName() + '/' + n;
- } else {
- relativePath = n;
- }
- // In ZIP archives "All slashes MUST be forward slashes" (http://pkware.com/documents/casestudies/APPNOTE.TXT)
- // TODO On Linux file names can contain backslashes which should not treated as file separators.
- // Unfortunately, only the file separator char of the master is known (File.separatorChar)
- // but not the file separator char of the (maybe remote) "dir".
- ZipEntry e = new ZipEntry(relativePath.replace('\\', '/'));
- VirtualFile f = dir.child(n);
- e.setTime(f.lastModified());
- zos.putNextEntry(e);
- InputStream in = f.open();
- try {
- Util.copyStream(in, zos);
- } finally {
- IOUtils.closeQuietly(in);
+ try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
+ zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter
+ for (String n : dir.list(glob.length() == 0 ? "**" : glob)) {
+ String relativePath;
+ if (glob.length() == 0) {
+ // JENKINS-19947: traditional behavior is to prepend the directory name
+ relativePath = dir.getName() + '/' + n;
+ } else {
+ relativePath = n;
+ }
+ // In ZIP archives "All slashes MUST be forward slashes" (http://pkware.com/documents/casestudies/APPNOTE.TXT)
+ // TODO On Linux file names can contain backslashes which should not treated as file separators.
+ // Unfortunately, only the file separator char of the master is known (File.separatorChar)
+ // but not the file separator char of the (maybe remote) "dir".
+ ZipEntry e = new ZipEntry(relativePath.replace('\\', '/'));
+ VirtualFile f = dir.child(n);
+ e.setTime(f.lastModified());
+ zos.putNextEntry(e);
+ InputStream in = f.open();
+ try {
+ Util.copyStream(in, zos);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ zos.closeEntry();
}
- zos.closeEntry();
}
- zos.close();
}
/**
diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java
index 69e8dcbf9e0a3de5a4836caf13a32c2e2249caa3..d7fda884aa14fe32ec95debadd76c6bd2455f5a1 100644
--- a/core/src/main/java/hudson/model/DownloadService.java
+++ b/core/src/main/java/hudson/model/DownloadService.java
@@ -70,6 +70,11 @@ import org.kohsuke.stapler.StaplerResponse;
*/
@Extension
public class DownloadService extends PageDecorator {
+
+ /**
+ * the prefix for the signature validator name
+ */
+ private static final String signatureValidatorPrefix = "downloadable";
/**
* Builds up an HTML fragment that starts all the download jobs.
*/
@@ -163,8 +168,7 @@ public class DownloadService extends PageDecorator {
*/
@Restricted(NoExternalUse.class)
public static String loadJSON(URL src) throws IOException {
- InputStream is = ProxyConfiguration.open(src).getInputStream();
- try {
+ try (InputStream is = ProxyConfiguration.open(src).getInputStream()) {
String jsonp = IOUtils.toString(is, "UTF-8");
int start = jsonp.indexOf('{');
int end = jsonp.lastIndexOf('}');
@@ -173,8 +177,6 @@ public class DownloadService extends PageDecorator {
} else {
throw new IOException("Could not find JSON in " + src);
}
- } finally {
- is.close();
}
}
@@ -186,8 +188,7 @@ public class DownloadService extends PageDecorator {
*/
@Restricted(NoExternalUse.class)
public static String loadJSONHTML(URL src) throws IOException {
- InputStream is = ProxyConfiguration.open(src).getInputStream();
- try {
+ try (InputStream is = ProxyConfiguration.open(src).getInputStream()) {
String jsonp = IOUtils.toString(is, "UTF-8");
String preamble = "window.parent.postMessage(JSON.stringify(";
int start = jsonp.indexOf(preamble);
@@ -197,8 +198,6 @@ public class DownloadService extends PageDecorator {
} else {
throw new IOException("Could not find JSON in " + src);
}
- } finally {
- is.close();
}
}
@@ -397,7 +396,11 @@ public class DownloadService extends PageDecorator {
public FormValidation updateNow() throws IOException {
List jsonList = new ArrayList<>();
boolean toolInstallerMetadataExists = false;
- for (String site : getUrls()) {
+ for (UpdateSite updatesite : Jenkins.getActiveInstance().getUpdateCenter().getSiteList()) {
+ String site = updatesite.getMetadataUrlForDownloadable(url);
+ if (site == null) {
+ return FormValidation.warning("The update site " + site + " does not look like an update center");
+ }
String jsonString;
try {
jsonString = loadJSONHTML(new URL(site + ".html?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8")));
@@ -408,7 +411,7 @@ public class DownloadService extends PageDecorator {
}
JSONObject o = JSONObject.fromObject(jsonString);
if (signatureCheck) {
- FormValidation e = new JSONSignatureValidator("downloadable '"+id+"'").verifySignature(o);
+ FormValidation e = updatesite.getJsonSignatureValidator(signatureValidatorPrefix +" '"+id+"'").verifySignature(o);
if (e.kind!= Kind.OK) {
LOGGER.log(Level.WARNING, "signature check failed for " + site, e );
continue;
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index df27abaab76e34428ccd7a6acc93688e9708df8f..83345726be0ba8f4cb65f9702d73fbe2fecbc9da 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -24,6 +24,7 @@
package hudson.model;
import hudson.FilePath;
+import hudson.Functions;
import hudson.Util;
import hudson.model.Queue.Executable;
import hudson.model.queue.Executables;
@@ -282,19 +283,16 @@ public class Executor extends Thread implements ModelObject {
*/
private void resetWorkUnit(String reason) {
StringWriter writer = new StringWriter();
- PrintWriter pw = new PrintWriter(writer);
- try {
+ try (PrintWriter pw = new PrintWriter(writer)) {
pw.printf("%s grabbed %s from queue but %s %s. ", getName(), workUnit, owner.getDisplayName(), reason);
if (owner.getTerminatedBy().isEmpty()) {
pw.print("No termination trace available.");
} else {
pw.println("Termination trace follows:");
- for (Computer.TerminationRequest request: owner.getTerminatedBy()) {
- request.printStackTrace(pw);
+ for (Computer.TerminationRequest request : owner.getTerminatedBy()) {
+ Functions.printStackTrace(request, pw);
}
}
- } finally {
- pw.close();
}
LOGGER.log(WARNING, writer.toString());
lock.writeLock().lock();
@@ -392,6 +390,9 @@ public class Executor extends Thread implements ModelObject {
}
if (executable instanceof Actionable) {
+ if (LOGGER.isLoggable(Level.FINER)) {
+ LOGGER.log(FINER, "when running {0} from {1} we are copying {2} actions whereas the item currently has {3}", new Object[] {executable, workUnit.context.item, workUnit.context.actions, workUnit.context.item.getAllActions()});
+ }
for (Action action: workUnit.context.actions) {
((Actionable) executable).addAction(action);
}
diff --git a/core/src/main/java/hudson/model/Failure.java b/core/src/main/java/hudson/model/Failure.java
index fa948901997dc6930271206810b7ffa0da30d83f..37fc2696b54291acebf80ece0a18af4ddcd6d83f 100644
--- a/core/src/main/java/hudson/model/Failure.java
+++ b/core/src/main/java/hudson/model/Failure.java
@@ -28,6 +28,7 @@ import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
+import javax.annotation.CheckForNull;
import javax.servlet.ServletException;
import java.io.IOException;
@@ -54,6 +55,13 @@ public class Failure extends RuntimeException implements HttpResponse {
this.pre = pre;
}
+ public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node, @CheckForNull Throwable throwable) throws IOException, ServletException {
+ if (throwable != null) {
+ req.setAttribute("exception", throwable);
+ }
+ generateResponse(req, rsp, node);
+ }
+
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
req.setAttribute("message",getMessage());
if(pre)
diff --git a/core/src/main/java/hudson/model/FileParameterValue.java b/core/src/main/java/hudson/model/FileParameterValue.java
index 930eb41fd8ae4d7d0ca8c0c28ae9b6540cbad91b..16777ea6bed9c7d0c26b920f84596cb4e0f86f99 100644
--- a/core/src/main/java/hudson/model/FileParameterValue.java
+++ b/core/src/main/java/hudson/model/FileParameterValue.java
@@ -163,7 +163,7 @@ public class FileParameterValue extends ParameterValue {
/**
* Compares file parameters (existing files will be considered as different).
- * @since 1.586 Function has been modified in order to avoid JENKINS-19017 issue (wrong merge of builds in the queue).
+ * @since 1.586 Function has been modified in order to avoid JENKINS-19017 issue (wrong merge of builds in the queue).
*/
@Override
public boolean equals(Object obj) {
@@ -266,11 +266,8 @@ public class FileParameterValue extends ParameterValue {
public byte[] get() {
try {
- FileInputStream inputStream = new FileInputStream(file);
- try {
+ try (FileInputStream inputStream = new FileInputStream(file)) {
return IOUtils.toByteArray(inputStream);
- } finally {
- inputStream.close();
}
} catch (IOException e) {
throw new Error(e);
diff --git a/core/src/main/java/hudson/model/FingerprintCleanupThread.java b/core/src/main/java/hudson/model/FingerprintCleanupThread.java
index 10716d073f761bbd125be8ade55289a21d355908..59187a3a400630e63968c886541652f8c439e7b4 100644
--- a/core/src/main/java/hudson/model/FingerprintCleanupThread.java
+++ b/core/src/main/java/hudson/model/FingerprintCleanupThread.java
@@ -25,6 +25,7 @@ package hudson.model;
import hudson.Extension;
import hudson.ExtensionList;
+import hudson.Functions;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
@@ -113,7 +114,7 @@ public final class FingerprintCleanupThread extends AsyncPeriodicWork {
return fp.trim();
}
} catch (IOException e) {
- e.printStackTrace(listener.error("Failed to process " + fingerprintFile));
+ Functions.printStackTrace(e, listener.error("Failed to process " + fingerprintFile));
return false;
}
}
diff --git a/core/src/main/java/hudson/model/FreeStyleProject.java b/core/src/main/java/hudson/model/FreeStyleProject.java
index ed7b3252388147b4e880c7ac672693cfdf0899cb..aecfc5cba22d7e1451b0f50a106ecb4fbabf157f 100644
--- a/core/src/main/java/hudson/model/FreeStyleProject.java
+++ b/core/src/main/java/hudson/model/FreeStyleProject.java
@@ -25,6 +25,8 @@ package hudson.model;
import hudson.Extension;
import jenkins.model.Jenkins;
+import org.jenkins.ui.icon.Icon;
+import org.jenkins.ui.icon.IconSet;
import org.jenkinsci.Symbol;
import jenkins.model.item_category.StandaloneProjectsCategory;
import org.kohsuke.accmod.Restricted;
@@ -97,5 +99,16 @@ public class FreeStyleProject extends Project i
return (Jenkins.RESOURCE_PATH + "/images/:size/freestyleproject.png").replaceFirst("^/", "");
}
+ @Override
+ public String getIconClassName() {
+ return "icon-freestyle-project";
+ }
+
+ static {
+ IconSet.icons.addIcon(new Icon("icon-freestyle-project icon-sm", "16x16/freestyleproject.png", Icon.ICON_SMALL_STYLE));
+ IconSet.icons.addIcon(new Icon("icon-freestyle-project icon-md", "24x24/freestyleproject.png", Icon.ICON_MEDIUM_STYLE));
+ IconSet.icons.addIcon(new Icon("icon-freestyle-project icon-lg", "32x32/freestyleproject.png", Icon.ICON_LARGE_STYLE));
+ IconSet.icons.addIcon(new Icon("icon-freestyle-project icon-xlg", "48x48/freestyleproject.png", Icon.ICON_XLARGE_STYLE));
+ }
}
}
diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java
index 6706f2b492a372c87d91af73ccf38dc7faa4b639..b0e21af7908ab58bcc1b535ebd2eb9993a45e81c 100644
--- a/core/src/main/java/hudson/model/Item.java
+++ b/core/src/main/java/hudson/model/Item.java
@@ -27,6 +27,7 @@ package hudson.model;
import hudson.Functions;
import jenkins.util.SystemProperties;
import hudson.security.PermissionScope;
+import jenkins.util.io.OnMaster;
import org.kohsuke.stapler.StaplerRequest;
import java.io.IOException;
@@ -67,7 +68,7 @@ import hudson.util.Secret;
* @see Items
* @see ItemVisitor
*/
-public interface Item extends PersistenceRoot, SearchableModelObject, AccessControlled {
+public interface Item extends PersistenceRoot, SearchableModelObject, AccessControlled, OnMaster {
/**
* Gets the parent that contains this item.
*/
@@ -230,7 +231,7 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
Permission DISCOVER = new Permission(PERMISSIONS, "Discover", Messages._AbstractProject_DiscoverPermission_Description(), READ, PermissionScope.ITEM);
/**
* Ability to view configuration details.
- * If the user lacks {@link CONFIGURE} then any {@link Secret}s must be masked out, even in encrypted form.
+ * If the user lacks {@link #CONFIGURE} then any {@link Secret}s must be masked out, even in encrypted form.
* @see Secret#ENCRYPTED_VALUE_PATTERN
*/
Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._AbstractProject_ExtendedReadPermission_Description(), CONFIGURE, SystemProperties.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.ITEM});
diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java
index 3ec7d8fa5cc24d902d290fc3494bc7238859f62c..59d5394f00d36d4a116bc84c91b4367aedec1151 100644
--- a/core/src/main/java/hudson/model/ItemGroupMixIn.java
+++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java
@@ -234,6 +234,7 @@ public abstract class ItemGroupMixIn {
}
src.getDescriptor().checkApplicableIn(parent);
acl.getACL().checkCreatePermission(parent, src.getDescriptor());
+ ItemListener.checkBeforeCopy(src, parent);
T result = (T)createProject(src.getDescriptor(),name,false);
@@ -260,10 +261,7 @@ public abstract class ItemGroupMixIn {
acl.checkPermission(Item.CREATE);
Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
- if (parent.getItem(name) != null) {
- throw new IllegalArgumentException(parent.getDisplayName() + " already contains an item '" + name + "'");
- }
- // TODO what if we have no DISCOVER permission on the existing job?
+ Items.verifyItemDoesNotAlreadyExist(parent, name, null);
// place it as config.xml
File configXml = Items.getConfigFile(getRootDirFor(name)).getFile();
@@ -316,9 +314,7 @@ public abstract class ItemGroupMixIn {
acl.getACL().checkCreatePermission(parent, type);
Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
- if(parent.getItem(name)!=null)
- throw new IllegalArgumentException("Project of the name "+name+" already exists");
- // TODO problem with DISCOVER as noted above
+ Items.verifyItemDoesNotAlreadyExist(parent, name, null);
TopLevelItem item = type.newInstance(parent, name);
try {
diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java
index e828527ec10712c694001d159a761dd8653a4a92..927abc72e0bbe5e658d85c3eb14214b90fcbfbf6 100644
--- a/core/src/main/java/hudson/model/Items.java
+++ b/core/src/main/java/hudson/model/Items.java
@@ -30,29 +30,30 @@ import hudson.XmlFile;
import hudson.model.listeners.ItemListener;
import hudson.remoting.Callable;
import hudson.security.ACL;
+import hudson.security.ACLContext;
import hudson.security.AccessControlled;
import hudson.triggers.Trigger;
import hudson.util.DescriptorList;
import hudson.util.EditDistance;
import hudson.util.XStream2;
-import jenkins.model.Jenkins;
-import org.acegisecurity.Authentication;
-import org.apache.commons.lang.StringUtils;
-
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Iterator;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.StringTokenizer;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
-
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
+import jenkins.model.Jenkins;
+import org.acegisecurity.Authentication;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
/**
* Convenience methods related to {@link Item}.
@@ -79,6 +80,44 @@ public class Items {
return false;
}
};
+ /**
+ * A comparator of {@link Item} instances that uses a case-insensitive comparison of {@link Item#getName()}.
+ * If you are replacing {@link #getAllItems(ItemGroup, Class)} with {@link #allItems(ItemGroup, Class)} and
+ * need to restore the sort order of a further filtered result, you probably want {@link #BY_FULL_NAME}.
+ *
+ * @since 2.37
+ */
+ public static final Comparator BY_NAME = new Comparator() {
+ @Override public int compare(Item i1, Item i2) {
+ return name(i1).compareToIgnoreCase(name(i2));
+ }
+
+ String name(Item i) {
+ String n = i.getName();
+ if (i instanceof ItemGroup) {
+ n += '/';
+ }
+ return n;
+ }
+ };
+ /**
+ * A comparator of {@link Item} instances that uses a case-insensitive comparison of {@link Item#getFullName()}.
+ *
+ * @since 2.37
+ */
+ public static final Comparator BY_FULL_NAME = new Comparator() {
+ @Override public int compare(Item i1, Item i2) {
+ return name(i1).compareToIgnoreCase(name(i2));
+ }
+
+ String name(Item i) {
+ String n = i.getFullName();
+ if (i instanceof ItemGroup) {
+ n += '/';
+ }
+ return n;
+ }
+ };
/**
* Runs a block while making {@link #currentlyUpdatingByXml} be temporarily true.
@@ -120,7 +159,7 @@ public class Items {
* Returns all the registered {@link TopLevelItemDescriptor}s that the current security principal is allowed to
* create within the specified item group.
*
- * @since TODO
+ * @since 1.607
*/
public static List all(ItemGroup c) {
return all(Jenkins.getAuthentication(), c);
@@ -130,7 +169,7 @@ public class Items {
* Returns all the registered {@link TopLevelItemDescriptor}s that the specified security principal is allowed to
* create within the specified item group.
*
- * @since TODO
+ * @since 1.607
*/
public static List all(Authentication a, ItemGroup c) {
List result = new ArrayList();
@@ -350,7 +389,12 @@ public class Items {
/**
* Gets all the {@link Item}s recursively in the {@link ItemGroup} tree
- * and filter them by the given type.
+ * and filter them by the given type. The returned list will represent a snapshot view of the items present at some
+ * time during the call. If items are moved during the call, depending on the move, it may be possible for some
+ * items to escape the snapshot entirely.
+ *
+ * If you do not need to iterate all items, or if the order of the items is not required, consider using
+ * {@link #allItems(ItemGroup, Class)} instead.
*
* @since 1.512
*/
@@ -361,18 +405,8 @@ public class Items {
}
private static void getAllItems(final ItemGroup root, Class type, List r) {
List items = new ArrayList(((ItemGroup>) root).getItems());
- Collections.sort(items, new Comparator() {
- @Override public int compare(Item i1, Item i2) {
- return name(i1).compareToIgnoreCase(name(i2));
- }
- String name(Item i) {
- String n = i.getName();
- if (i instanceof ItemGroup) {
- n += '/';
- }
- return n;
- }
- });
+ // because we add items depth first, we can use the quicker BY_NAME comparison
+ Collections.sort(items, BY_NAME);
for (Item i : items) {
if (type.isInstance(i)) {
if (i.hasPermission(Item.READ)) {
@@ -385,6 +419,41 @@ public class Items {
}
}
+ /**
+ * Gets a read-only view of all the {@link Item}s recursively in the {@link ItemGroup} tree visible to
+ * {@link Jenkins#getAuthentication()} without concern for the order in which items are returned. Each iteration
+ * of the view will be "live" reflecting the items available between the time the iteration was started and the
+ * time the iteration was completed, however if items are moved during an iteration - depending on the move - it
+ * may be possible for such items to escape the entire iteration.
+ *
+ * @param root the root.
+ * @param type the type.
+ * @param the type.
+ * @return An {@link Iterable} for all items.
+ * @since 2.37
+ */
+ public static Iterable allItems(ItemGroup root, Class type) {
+ return allItems(Jenkins.getAuthentication(), root, type);
+ }
+
+
+ /**
+ * Gets a read-only view all the {@link Item}s recursively in the {@link ItemGroup} tree visible to the supplied
+ * authentication without concern for the order in which items are returned. Each iteration
+ * of the view will be "live" reflecting the items available between the time the iteration was started and the
+ * time the iteration was completed, however if items are moved during an iteration - depending on the move - it
+ * may be possible for such items to escape the entire iteration.
+ *
+ * @param root the root.
+ * @param type the type.
+ * @param the type.
+ * @return An {@link Iterable} for all items.
+ * @since 2.37
+ */
+ public static Iterable allItems(Authentication authentication, ItemGroup root, Class type) {
+ return new AllItemsIterable<>(root, authentication, type);
+ }
+
/**
* Finds an item whose name (when referenced from the specified context) is closest to the given name.
* @param the type of item being considered
@@ -395,10 +464,9 @@ public class Items {
* @since 1.538
*/
public static @CheckForNull T findNearest(Class type, String name, ItemGroup context) {
- List projects = Jenkins.getInstance().getAllItems(type);
- String[] names = new String[projects.size()];
- for (int i = 0; i < projects.size(); i++) {
- names[i] = projects.get(i).getRelativeNameFrom(context);
+ List names = new ArrayList<>();
+ for (T item: Jenkins.getInstance().allItems(type)) {
+ names.add(item.getRelativeNameFrom(context));
}
String nearest = EditDistance.findNearest(name, names);
return Jenkins.getInstance().getItem(nearest, context, type);
@@ -425,9 +493,7 @@ public class Items {
throw new IllegalArgumentException();
}
String name = item.getName();
- if (destination.getItem(name) != null) {
- throw new IllegalArgumentException(name + " already exists");
- }
+ verifyItemDoesNotAlreadyExist(destination, name, null);
String oldFullName = item.getFullName();
// TODO AbstractItem.renameTo has a more baroque implementation; factor it out into a utility method perhaps?
File destDir = destination.getRootDirFor(item);
@@ -440,6 +506,145 @@ public class Items {
return newItem;
}
+ private static class AllItemsIterable implements Iterable {
+
+ /**
+ * The authentication we are iterating as.
+ */
+ private final Authentication authentication;
+ /**
+ * The root we are iterating from.
+ */
+ private final ItemGroup root;
+ /**
+ * The type of item we want to return.
+ */
+ private final Class type;
+
+ private AllItemsIterable(ItemGroup root, Authentication authentication, Class type) {
+ this.root = root;
+ this.authentication = authentication;
+ this.type = type;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Iterator iterator() {
+ return new AllItemsIterator();
+ }
+
+ private class AllItemsIterator implements Iterator {
+
+ /**
+ * The stack of {@link ItemGroup}s that we have left to descend into.
+ */
+ private final Stack stack = new Stack<>();
+ /**
+ * The iterator of the current {@link ItemGroup} we are iterating.
+ */
+ private Iterator delegate = null;
+ /**
+ * The next item.
+ */
+ private T next = null;
+
+ private AllItemsIterator() {
+ // put on the stack so that hasNext() is the only place that has to worry about authentication
+ // alternative would be to impersonate and populate delegate.
+ stack.push(root);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasNext() {
+ if (next != null) {
+ return true;
+ }
+ while (true) {
+ if (delegate == null || !delegate.hasNext()) {
+ if (stack.isEmpty()) {
+ return false;
+ }
+ ItemGroup group = stack.pop();
+ // group.getItems() is responsible for performing the permission check so we will not repeat it
+ if (Jenkins.getAuthentication() == authentication) {
+ delegate = group.getItems().iterator();
+ } else {
+ // slower path because the caller has switched authentication
+ // we need to keep the original authentication so that allItems() can be used
+ // like getAllItems() without the cost of building the entire list up front
+ try (ACLContext ctx = ACL.as(authentication)) {
+ delegate = group.getItems().iterator();
+ }
+ }
+ }
+ while (delegate.hasNext()) {
+ Item item = delegate.next();
+ if (item instanceof ItemGroup) {
+ stack.push((ItemGroup) item);
+ }
+ if (type.isInstance(item)) {
+ next = type.cast(item);
+ return true;
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ try {
+ return next;
+ } finally {
+ next = null;
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Securely check for the existence of an item before trying to create one with the same name.
+ * @param parent the folder where we are about to create/rename/move an item
+ * @param newName the proposed new name
+ * @param variant if not null, an existing item which we accept could be there
+ * @throws IllegalArgumentException if there is already something there, which you were supposed to know about
+ * @throws Failure if there is already something there but you should not be told details
+ */
+ static void verifyItemDoesNotAlreadyExist(@Nonnull ItemGroup> parent, @Nonnull String newName, @CheckForNull Item variant) throws IllegalArgumentException, Failure {
+ Item existing;
+ try (ACLContext ctxt = ACL.as(ACL.SYSTEM)) {
+ existing = parent.getItem(newName);
+ }
+ if (existing != null && existing != variant) {
+ if (existing.hasPermission(Item.DISCOVER)) {
+ String prefix = parent.getFullName();
+ throw new IllegalArgumentException((prefix.isEmpty() ? "" : prefix + "/") + newName + " already exists");
+ } else {
+ // Cannot hide its existence, so at least be as vague as possible.
+ throw new Failure("");
+ }
+ }
+ }
+
/**
* Used to load/save job configuration.
*
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index f66ef21095704928fbef2b50fbfe5b57f4b7eb31..0612daa9f11c2c2426d0f49249f96d0035340dea 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -128,7 +128,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
* @author Kohsuke Kawaguchi
*/
public abstract class Job, RunT extends Run>
- extends AbstractItem implements ExtensionPoint, StaplerOverridable, ModelObjectWithChildren, OnMaster {
+ extends AbstractItem implements ExtensionPoint, StaplerOverridable, ModelObjectWithChildren {
private static final Logger LOGGER = Logger.getLogger(Job.class.getName());
@@ -568,7 +568,7 @@ public abstract class Job, RunT extends Run T getProperty(Class clazz) {
@@ -698,7 +698,7 @@ public abstract class Job, RunT extends Run getBuilds() {
- return RunList.fromRuns(_getRuns().values());
+ return RunList.fromRuns(_getRuns().values());
}
/**
@@ -730,7 +730,7 @@ public abstract class Job, RunT extends Run getBuildsAsMap() {
- return Collections.unmodifiableSortedMap(_getRuns());
+ return Collections.unmodifiableSortedMap(_getRuns());
}
/**
@@ -1224,28 +1224,27 @@ public abstract class Job, RunT extends Run, JobPropertyDescriptor> t = new DescribableList, JobPropertyDescriptor>(NOOP,getAllProperties());
- JSONObject jsonProperties = json.optJSONObject("properties");
- if (jsonProperties != null) {
- //This handles the situation when Parameterized build checkbox is checked but no parameters are selected. User will be redirected to an error page with proper error message.
- Job.checkForEmptyParameters(jsonProperties);
- t.rebuild(req,jsonProperties,JobPropertyDescriptor.getPropertyDescriptors(Job.this.getClass()));
- } else {
- t.clear();
- }
- properties.clear();
- for (JobProperty p : t) {
- p.setOwner(this);
- properties.add(p);
- }
-
- submit(req, rsp);
+ DescribableList, JobPropertyDescriptor> t = new DescribableList, JobPropertyDescriptor>(NOOP,getAllProperties());
+ JSONObject jsonProperties = json.optJSONObject("properties");
+ if (jsonProperties != null) {
+ t.rebuild(req,jsonProperties,JobPropertyDescriptor.getPropertyDescriptors(Job.this.getClass()));
+ } else {
+ t.clear();
+ }
+ properties.clear();
+ for (JobProperty p : t) {
+ p.setOwner(this);
+ properties.add(p);
+ }
- save();
+ submit(req, rsp);
+ bc.commit();
+ }
ItemListener.fireOnUpdated(this);
String newName = req.getParameter("name");
@@ -1537,18 +1536,4 @@ public abstract class Job, RunT extends Run> {
Class applicable = Types.erasure(Types.getTypeArgument(pt, 0));
return applicable.isAssignableFrom(jobType);
} else {
- throw new AssertionError(clazz+" doesn't properly parameterize JobProperty. The isApplicable() method must be overriden.");
+ throw new AssertionError(clazz+" doesn't properly parameterize JobProperty. The isApplicable() method must be overridden.");
}
}
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index 74a9fa6901619b4e5cc581aae998d1e50e439c5b..f66b0f75bdbd9c776e9293a733dda9a69c502ab3 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -56,11 +56,11 @@ import org.kohsuke.stapler.export.ExportedBean;
import java.io.StringReader;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.Collection;
import java.util.Stack;
import java.util.TreeSet;
@@ -362,10 +362,11 @@ public abstract class Label extends Actionable implements Comparable