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/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..49c5635774f67b13402028f897389aa42617adc0 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'] // TODO add '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 260ed9cafa1c81efa7c692fbf0f9b806849df0be..20a2db735b6fd3d3a0c137d1d5484d76c354f8d4 100644
--- a/changelog.html
+++ b/changelog.html
@@ -1,2556 +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.
-
- 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 0abe2d5472f42c8c3fa781ffd5ba817c3d800d43..c0e005dfb5eaa77d408a601b51c62d775110362e 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
org.jenkins-ci.mainpom
- 2.13-SNAPSHOT
+ 2.48-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,10 +93,15 @@
Messages.propertiestarget/generated-sources/localizer
+ true
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index 9eca2ec8efb8097ec8c42ab113d5f758570f3dc1..edc1ba35b62c53635ec10c6a5d1e037acc29c305 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;
@@ -80,7 +81,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 +122,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 {
@@ -414,7 +415,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());
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..98dee46cdb74f879a42521639b214ff65c537a5e 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -3,7 +3,7 @@ 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\
\n\
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 df0d2b57e1ac7b168809824f735bec1b102b27c4..acd0191d5c8bfbf854d5344b2d29f8463f3b55e9 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages_bg.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages_bg.properties
@@ -1,6 +1,6 @@
# The MIT License
#
-# Bulgarian translation copyright 2015 Alexander Shopov .
+# Bulgarian translation: Copyright (c) 2015, 2016, Alexander Shopov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -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 f3ee63e524e5764b6201175e5737095fb019a875..ccf1ef97e68ab043902cd32469e5f312a5adc50f 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.mainpom
- 2.13-SNAPSHOT
+ 2.48-SNAPSHOTjenkins-core
@@ -39,9 +39,11 @@ THE SOFTWARE.
true
- 1.243
+ 1.2502.5.6.SEC03
- 2.4.7
+ 2.4.8
+
+ true
@@ -63,7 +65,7 @@ THE SOFTWARE.
org.jenkins-civersion-number
- 1.1
+ 1.3org.jenkins-ci
@@ -157,7 +159,7 @@ THE SOFTWARE.
org.kohsuke.staplerstapler-adjunct-timeline
- 1.4
+ 1.5org.kohsuke.stapler
@@ -210,7 +212,7 @@ THE SOFTWARE.
org.jvnet.localizerlocalizer
- 1.23
+ 1.24antlr
@@ -489,12 +491,12 @@ THE SOFTWARE.
commons-collectionscommons-collections
- 3.2.1
+ 3.2.2org.jvnet.winpwinp
- 1.22
+ 1.24org.jenkins-ci
@@ -539,7 +541,7 @@ THE SOFTWARE.
net.java.sezpozsezpoz
- 1.11
+ 1.12org.kohsuke.jinterop
@@ -589,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
@@ -645,6 +646,7 @@ THE SOFTWARE.
generate-taglib-interface
+ record-core-location
@@ -694,6 +696,7 @@ THE SOFTWARE.
Messages.propertiestarget/generated-sources/localizer
+ true
@@ -769,6 +772,7 @@ THE SOFTWARE.
0.5Ctrue-XX:MaxPermSize=128m -noverify
+ false
@@ -803,6 +807,10 @@ THE SOFTWARE.
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
@@ -839,21 +847,9 @@ THE SOFTWARE.
release
-
- org.codehaus.mojo
- apt-maven-plugin
-
-
-
-
- process
-
-
-
-
@@ -878,20 +874,12 @@ THE SOFTWARE.
+
findbugs
-
-
-
- org.codehaus.mojo
- findbugs-maven-plugin
-
- Max
- High
- src/findbugs-filter.xml
-
-
-
-
+
+
+ true
+
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_bg.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_bg.properties
new file mode 100644
index 0000000000000000000000000000000000000000..95e74685ada811e8f8b8e5855fbc99b66847d304
--- /dev/null
+++ b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_bg.properties
@@ -0,0 +1,32 @@
+# The MIT License
+#
+# Bulgarian translation: Copyright (c) 2015, 2016, Alexander Shopov
+#
+# 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=\
+ \u041d\u043e\u0432\u0430\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044f \u043d\u0430 Jenkins ({0}) \u0435 \u043d\u0430\u043b\u0438\u0447\u043d\u0430 \u0437\u0430 \u0438\u0437\u0442\u0435\u0433\u043b\u044f\u043d\u0435\
+ (\u0441\u043f\u0438\u0441\u044a\u043a \u0441 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435).
+UpgradeComplete=\
+ \u041e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435\u0442\u043e \u043a\u044a\u043c \u043d\u043e\u0432\u0430\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044f Jenkins {0} \u043f\u0440\u0438\u043a\u043b\u044e\u0447\u0438. \u0422\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442\u0435.
+UpgradeCompleteRestartNotSupported=\
+\u041e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435\u0442\u043e \u043a\u044a\u043c \u043d\u043e\u0432\u0430\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044f Jenkins {0} \u043f\u0440\u0438\u043a\u043b\u044e\u0447\u0438. \u0422\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442\u0435.
+UpgradeProgress=\u0418\u0437\u043f\u044a\u043b\u043d\u044f\u0432\u0430 \u0441\u0435 \u043e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043a\u044a\u043c Jenkins {0}.
+UpgradeFailed=\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 \u043a\u044a\u043c Jenkins {0}: {1}.
diff --git a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties
index 6f5ff479b3092c96f38a93a2f6fb46a126fead29..18a2cbe3c311f03744e1c92f180bddd06a09461a 100644
--- a/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties
+++ b/core/src/filter/resources/hudson/model/UpdateCenter/CoreUpdateMonitor/message_pl.properties
@@ -1,6 +1,25 @@
-# This file is under the MIT License by authors
-
+# The MIT License
+#
+# Copyright (c) 2013-2016, Kohsuke Kawaguchi, Damian Szczepanik
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
NewVersionAvailable=Nowa wersja Jenkinsa ({0}) jest dost\u0119pna do pobrania (historia zmian).
-Or\ Upgrade\ Automatically=Albo uaktualnij automatycznie
+Or\ Upgrade\ Automatically=Uaktualnij automatycznie
UpgradeCompleteRestartNotSupported=Aktualizacja Jenkinsa do wersji {0} zako\u0144czy\u0142a si\u0119 pomy\u015Blnie, oczekiwanie na ponowne uruchomienie.
UpgradeProgress=Aktualizacja Jenkinsa do wersji {0} trwa lub nie powiod\u0142a si\u0119.
diff --git a/core/src/main/java/hudson/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 7164ffeef64d11ed1b716ea3cc142ac1ad0d647d..792e02a0b73abb959f141bfd1f227d87a25996c0 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -57,17 +57,13 @@ import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URI;
import java.net.URL;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
@@ -111,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());
@@ -180,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();
}
}
@@ -277,7 +267,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
// some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(detached.splitWhen) <= 0) {
- out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requireVersion));
+ out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requiredVersion));
LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {pluginName, detached.shortName, jenkinsVersion});
}
}
@@ -378,12 +368,12 @@ public class ClassicPluginStrategy implements PluginStrategy {
* be "1.123.*" (because 1.124 will be the first version that doesn't include the removed code.)
*/
private final VersionNumber splitWhen;
- private final String requireVersion;
+ private final String requiredVersion;
- private DetachedPlugin(String shortName, String splitWhen, String requireVersion) {
+ private DetachedPlugin(String shortName, String splitWhen, String requiredVersion) {
this.shortName = shortName;
this.splitWhen = new VersionNumber(splitWhen);
- this.requireVersion = requireVersion;
+ this.requiredVersion = requiredVersion;
}
/**
@@ -401,6 +391,16 @@ public class ClassicPluginStrategy implements PluginStrategy {
public VersionNumber getSplitWhen() {
return splitWhen;
}
+
+ /**
+ * Gets the minimum required version for the current version of Jenkins.
+ *
+ * @return the minimum required version for the current version of Jenkins.
+ * @sice 2.16
+ */
+ public VersionNumber getRequiredVersion() {
+ return new VersionNumber(requiredVersion);
+ }
}
private static final List DETACHED_LIST = Collections.unmodifiableList(Arrays.asList(
@@ -417,7 +417,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
new DetachedPlugin("windows-slaves", "1.547.*", "1.0"),
new DetachedPlugin("antisamy-markup-formatter", "1.553.*", "1.0"),
new DetachedPlugin("matrix-project", "1.561.*", "1.0"),
- new DetachedPlugin("junit", "1.577.*", "1.0")
+ new DetachedPlugin("junit", "1.577.*", "1.0"),
+ new DetachedPlugin("bouncycastle-api", "2.16.*", "2.16.0")
));
/** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
@@ -639,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
@@ -665,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/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java
index 513c557d44c7c11bcbd0c62c891d83f03997adeb..16d96ae441b0ff5a7cc4a3e59e8398db0cc595a4 100644
--- a/core/src/main/java/hudson/DescriptorExtensionList.java
+++ b/core/src/main/java/hudson/DescriptorExtensionList.java
@@ -39,6 +39,7 @@ import hudson.tasks.Publisher;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -182,6 +183,11 @@ public class DescriptorExtensionList, D extends Descrip
*/
@Override
protected List> load() {
+ if (jenkins == null) {
+ // Should never happen on the real instance
+ LOGGER.log(Level.WARNING, "Cannot load extension components, because Jenkins instance has not been assigned yet");
+ return Collections.emptyList();
+ }
return _load(jenkins.getExtensionList(Descriptor.class).getComponents());
}
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());
}
/**
@@ -1779,7 +1853,7 @@ public class Functions {
}
return ((Secret) o).getEncryptedValue();
}
- if (getIsUnitTest()) {
+ if (getIsUnitTest() && !o.equals(PasswordParameterDefinition.DEFAULT_VALUE)) {
throw new SecurityException("attempted to render plaintext ‘" + o + "’ in password field; use a getter of type Secret instead");
}
return o.toString();
diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java
index fbdb0609c7bca09b020e079f7eae7dfd3bcca5c4..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.
@@ -130,7 +133,7 @@ public abstract class Launcher {
* @deprecated since 2008-11-16.
* See the javadoc for why this is inherently unreliable. If you are trying to
* figure out the current {@link Computer} from within a build, use
- * {@link Computer#currentComputer()}
+ * {@link FilePath#toComputer()} or {@link Computer#currentComputer()}.
*/
@Deprecated
public Computer getComputer() {
@@ -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/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 1ee23f30dd66dedfb5cec4f9da795ec383b8571a..c793cef01bfc735a2b339d3712bcecbee831bbec 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -24,6 +24,8 @@
package hudson;
import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import hudson.security.ACLContext;
import jenkins.util.SystemProperties;
import hudson.PluginWrapper.Dependency;
import hudson.init.InitMilestone;
@@ -65,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;
@@ -139,6 +142,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;
@@ -464,7 +471,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
if(p.isActive())
activePlugins.add(p);
}
- } catch (CycleDetectedException e) {
+ } catch (CycleDetectedException e) { // TODO this should be impossible, since we override reactOnCycle to not throw the exception
stop(); // disable all plugins since classloading from them can lead to StackOverflow
throw e; // let Hudson fail
}
@@ -504,7 +511,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// schedule execution of loading plugins
for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
- g.followedBy().notFatal().attains(PLUGINS_PREPARED).add("Loading plugin " + p.getShortName(), new Executable() {
+ g.followedBy().notFatal().attains(PLUGINS_PREPARED).add(String.format("Loading plugin %s v%s (%s)", p.getLongName(), p.getVersion(), p.getShortName()), new Executable() {
public void run(Reactor session) throws Exception {
try {
p.resolvePluginDependencies();
@@ -570,6 +577,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
return loadPluginsFromWar(fromPath, null);
}
+ //TODO: Consider refactoring in order to avoid DMI_COLLECTION_OF_URLS
+ @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", justification = "Plugin loading happens only once on Jenkins startup")
protected @Nonnull Set loadPluginsFromWar(@Nonnull String fromPath, @CheckForNull FilenameFilter filter) {
Set names = new HashSet();
@@ -626,6 +635,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
return names;
}
+ //TODO: Consider refactoring in order to avoid DMI_COLLECTION_OF_URLS
+ @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", justification = "Plugin loading happens only once on Jenkins startup")
protected static void addDependencies(URL hpiResUrl, String fromPath, Set dependencySet) throws URISyntaxException, MalformedURLException {
if (dependencySet.contains(hpiResUrl)) {
return;
@@ -715,6 +726,34 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
new Object[] {lastExecVersion, Jenkins.VERSION, loadedDetached});
InstallUtil.saveLastExecVersion();
+ } else {
+ final Set forceUpgrade = new HashSet<>();
+ for (ClassicPluginStrategy.DetachedPlugin p : ClassicPluginStrategy.getDetachedPlugins()) {
+ VersionNumber installedVersion = getPluginVersion(rootDir, p.getShortName());
+ VersionNumber requiredVersion = p.getRequiredVersion();
+ if (installedVersion != null && installedVersion.isOlderThan(requiredVersion)) {
+ LOGGER.log(Level.WARNING,
+ "Detached plugin {0} found at version {1}, required minimum version is {2}",
+ new Object[]{p.getShortName(), installedVersion, requiredVersion});
+ forceUpgrade.add(p);
+ }
+ }
+ if (!forceUpgrade.isEmpty()) {
+ Set loadedDetached = loadPluginsFromWar("/WEB-INF/detached-plugins", new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ name = normalisePluginName(name);
+ for (ClassicPluginStrategy.DetachedPlugin detachedPlugin : forceUpgrade) {
+ if (detachedPlugin.getShortName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ LOGGER.log(INFO, "Upgraded detached plugins (and dependencies): {0}",
+ new Object[]{loadedDetached});
+ }
}
}
@@ -780,99 +819,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));
-
- // 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.
+ if (p == null) {
+ p = strategy.createPluginWrapper(arc);
+ }
+ if (p.supportsDynamicLoad()== YesNoMaybe.NO)
+ throw new RestartRequiredException(Messages._PluginManager_PluginDoesntSupportDynamicLoad_RestartRequired(sn));
- plugins.add(p);
- activePlugins.add(p);
- synchronized (((UberClassLoader) uberClassLoader).loaded) {
- ((UberClassLoader) uberClassLoader).loaded.clear();
- }
+ // 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.
- 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)
@@ -900,7 +942,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.
*/
@@ -913,7 +955,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.
@@ -924,7 +966,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
@@ -935,19 +977,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();
}
@@ -956,6 +998,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)}
@@ -1030,8 +1147,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))
@@ -1044,8 +1164,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()))
@@ -1102,7 +1225,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;
}
}
@@ -1269,7 +1392,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
* the plugin will only take effect after the reboot.
* See {@link UpdateCenter#isRestartRequiredForCompletion()}
* @return The install job list.
- * @since FIXME
+ * @since 2.0
*/
@Restricted(NoExternalUse.class)
public List> install(@Nonnull Collection plugins, boolean dynamicLoad) {
@@ -1296,7 +1419,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);
@@ -1352,12 +1475,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
updateCenter.persistInstallStatus();
if(!failures) {
- ACL.impersonate(currentAuth, new Runnable() {
- @Override
- public void run() {
- InstallUtil.proceedToNextStateFrom(InstallState.INITIAL_PLUGINS_INSTALLING);
- }
- });
+ try (ACLContext _ = ACL.as(currentAuth)) {
+ InstallUtil.proceedToNextStateFrom(InstallState.INITIAL_PLUGINS_INSTALLING);
+ }
}
}
}.start();
@@ -1413,10 +1533,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();
@@ -1444,7 +1563,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");
@@ -1457,7 +1576,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);
@@ -1493,9 +1617,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);
}
}
@@ -1839,16 +1961,21 @@ 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;
+ private transient volatile List pluginsWithCycle;
public boolean isActivated() {
if(pluginsWithCycle == null){
- pluginsWithCycle = new ArrayList();
+ pluginsWithCycle = new ArrayList<>();
for (PluginWrapper p : Jenkins.getInstance().getPluginManager().getPlugins()) {
if(p.hasCycleDependency()){
- pluginsWithCycle.add(p.getShortName());
+ pluginsWithCycle.add(p);
isActive = true;
}
}
@@ -1856,7 +1983,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
return isActive;
}
- public List getPluginsWithCycle() {
+ public List getPluginsWithCycle() {
return pluginsWithCycle;
}
}
@@ -1898,6 +2025,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 f61b96e7cf2022e28b801237abac07e9832ee0ad..5932f0777e84ffb801743192145a6c26afe77168 100644
--- a/core/src/main/java/hudson/PluginWrapper.java
+++ b/core/src/main/java/hudson/PluginWrapper.java
@@ -26,43 +26,51 @@ package hudson;
import com.google.common.collect.ImmutableSet;
import hudson.PluginManager.PluginInstanceStore;
+import hudson.model.AdministrativeMonitor;
import hudson.model.Api;
import hudson.model.ModelObject;
-import jenkins.MissingDependencyException;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
import hudson.util.VersionNumber;
+import org.jvnet.localizer.ResourceBundleHolder;
+import org.kohsuke.stapler.HttpResponse;
+import org.kohsuke.stapler.HttpResponses;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.export.Exported;
+import org.kohsuke.stapler.export.ExportedBean;
+import org.kohsuke.stapler.interceptor.RequirePOST;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.LogFactory;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.Closeable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.jar.JarFile;
import java.util.jar.Manifest;
+import java.util.logging.Level;
import java.util.logging.Logger;
+
import static java.util.logging.Level.WARNING;
import static org.apache.commons.io.FilenameUtils.getBaseName;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.logging.LogFactory;
-import org.kohsuke.stapler.HttpResponse;
-import org.kohsuke.stapler.HttpResponses;
-import org.kohsuke.stapler.export.Exported;
-import org.kohsuke.stapler.export.ExportedBean;
-import org.kohsuke.stapler.interceptor.RequirePOST;
-
-import java.util.Enumeration;
-import java.util.jar.JarFile;
-import java.util.logging.Level;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
/**
* Represents a Jenkins plug-in and associated control information
@@ -88,6 +96,12 @@ import javax.annotation.Nonnull;
*/
@ExportedBean
public class PluginWrapper implements Comparable, ModelObject {
+ /**
+ * A plugin won't be loaded unless his declared dependencies are present and match the required minimal version.
+ * This can be set to false to disable the version check (legacy behaviour)
+ */
+ private static final boolean ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK = Boolean.parseBoolean(System.getProperty(PluginWrapper.class.getName()+"." + "dependenciesVersionCheck.enabled", "true"));
+
/**
* {@link PluginManager} to which this belongs to.
*/
@@ -142,6 +156,12 @@ public class PluginWrapper implements Comparable, ModelObject {
private final List dependencies;
private final List optionalDependencies;
+ public List getDependencyErrors() {
+ return Collections.unmodifiableList(dependencyErrors);
+ }
+
+ private final transient List dependencyErrors = new ArrayList<>();
+
/**
* Is this plugin bundled in jenkins.war?
*/
@@ -229,7 +249,7 @@ public class PluginWrapper implements Comparable, ModelObject {
@Override
public String toString() {
- return shortName + " (" + version + ")";
+ return shortName + " (" + version + ")" + (optional ? " optional" : "");
}
}
@@ -394,6 +414,21 @@ public class PluginWrapper implements Comparable, ModelObject {
return "???";
}
+ /**
+ * Returns the required Jenkins core version of this plugin.
+ * @return the required Jenkins core version of this plugin.
+ * @since 2.16
+ */
+ @Exported
+ public @CheckForNull String getRequiredCoreVersion() {
+ String v = manifest.getMainAttributes().getValue("Jenkins-Version");
+ if (v!= null) return v;
+
+ v = manifest.getMainAttributes().getValue("Hudson-Version");
+ if (v!= null) return v;
+ return null;
+ }
+
/**
* Returns the version number of this plugin
*/
@@ -524,22 +559,73 @@ public class PluginWrapper implements Comparable, ModelObject {
* thrown if one or several mandatory dependencies doesn't exists.
*/
/*package*/ void resolvePluginDependencies() throws IOException {
- List missingDependencies = new ArrayList<>();
+ if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK) {
+ String requiredCoreVersion = getRequiredCoreVersion();
+ if (requiredCoreVersion == null) {
+ LOGGER.warning(shortName + " doesn't declare required core version.");
+ } else {
+ VersionNumber actualVersion = Jenkins.getVersion();
+ if (actualVersion.isOlderThan(new VersionNumber(requiredCoreVersion))) {
+ dependencyErrors.add(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion));
+ }
+ }
+ }
// make sure dependencies exist
for (Dependency d : dependencies) {
- if (parent.getPlugin(d.shortName) == null)
- missingDependencies.add(d);
- }
- if (!missingDependencies.isEmpty())
- throw new MissingDependencyException(this.shortName, missingDependencies);
+ PluginWrapper dependency = parent.getPlugin(d.shortName);
+ if (dependency == null) {
+ PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName);
+ if (failedDependency != null) {
+ dependencyErrors.add(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), failedDependency.getVersion()));
+ break;
+ } else {
+ dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version));
+ }
+ } else {
+ if (dependency.isActive()) {
+ if (isDependencyObsolete(d, dependency)) {
+ dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version));
+ }
+ } else {
+ if (isDependencyObsolete(d, dependency)) {
+ dependencyErrors.add(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version));
+ } else {
+ dependencyErrors.add(Messages.PluginWrapper_disabled(dependency.getLongName()));
+ }
+ }
+ }
+ }
// add the optional dependencies that exists
for (Dependency d : optionalDependencies) {
- if (parent.getPlugin(d.shortName) != null)
- dependencies.add(d);
+ PluginWrapper dependency = parent.getPlugin(d.shortName);
+ if (dependency != null && dependency.isActive()) {
+ if (isDependencyObsolete(d, dependency)) {
+ dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version));
+ } else {
+ dependencies.add(d);
+ }
+ }
+ }
+ if (!dependencyErrors.isEmpty()) {
+ NOTICE.addPlugin(this);
+ StringBuilder messageBuilder = new StringBuilder();
+ messageBuilder.append(Messages.PluginWrapper_failed_to_load_plugin(getLongName(), getVersion())).append(System.lineSeparator());
+ for (Iterator iterator = dependencyErrors.iterator(); iterator.hasNext(); ) {
+ String dependencyError = iterator.next();
+ messageBuilder.append(" - ").append(dependencyError);
+ if (iterator.hasNext()) {
+ messageBuilder.append(System.lineSeparator());
+ }
+ }
+ throw new IOException(messageBuilder.toString());
}
}
+ private boolean isDependencyObsolete(Dependency d, PluginWrapper dependency) {
+ return ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version));
+ }
+
/**
* If the plugin has {@link #getUpdateInfo() an update},
* returns the {@link hudson.model.UpdateSite.Plugin} object.
@@ -622,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);
@@ -645,6 +728,51 @@ public class PluginWrapper implements Comparable, ModelObject {
return false;
}
+ @Extension
+ public final static PluginWrapperAdministrativeMonitor NOTICE = new PluginWrapperAdministrativeMonitor();
+
+ /**
+ * Administrative Monitor for failed plugins
+ */
+ public static final class PluginWrapperAdministrativeMonitor extends AdministrativeMonitor {
+ private final Map plugins = new HashMap<>();
+
+ void addPlugin(PluginWrapper plugin) {
+ plugins.put(plugin.shortName, plugin);
+ }
+
+ public boolean isActivated() {
+ return !plugins.isEmpty();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.PluginWrapper_PluginWrapperAdministrativeMonitor_DisplayName();
+ }
+
+ public Collection getPlugins() {
+ return plugins.values();
+ }
+
+ public PluginWrapper getPlugin(String shortName) {
+ return plugins.get(shortName);
+ }
+
+ /**
+ * Depending on whether the user said "dismiss" or "correct", send him to the right place.
+ */
+ public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException {
+ if(req.hasParameter("correct")) {
+ rsp.sendRedirect(req.getContextPath()+"/pluginManager");
+
+ }
+ }
+
+ public static PluginWrapperAdministrativeMonitor get() {
+ return AdministrativeMonitor.all().get(PluginWrapperAdministrativeMonitor.class);
+ }
+ }
+
//
//
// Action methods
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();
@@ -412,6 +403,8 @@ public class Util {
* @return false if it is ok to continue trying to delete things, true if
* we were interrupted (and should stop now).
*/
+ @SuppressFBWarnings(value = "DM_GC", justification = "Garbage collection happens only when "
+ + "GC_AFTER_FAILED_DELETE is true. It's an experimental feature in Jenkins.")
private static boolean pauseBetweenDeletes(int numberOfAttemptsSoFar) {
long delayInMs;
if( numberOfAttemptsSoFar>=DELETION_MAX ) return false;
@@ -496,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);
@@ -507,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;
@@ -521,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();
@@ -759,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) {
@@ -797,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();
}
}
@@ -1367,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);
}
}
@@ -1598,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("//");
@@ -1623,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 2f57096464381ef979c95434ff05fb38350e9e64..2e087e0348844a34dad29148478aaa7daa6c3513 100644
--- a/core/src/main/java/hudson/WebAppMain.java
+++ b/core/src/main/java/hudson/WebAppMain.java
@@ -23,6 +23,7 @@
*/
package hudson;
+import hudson.security.ACLContext;
import jenkins.util.SystemProperties;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.core.JVM;
@@ -59,7 +60,6 @@ import javax.xml.transform.TransformerFactoryConfigurationError;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Date;
@@ -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);
@@ -370,25 +374,26 @@ public class WebAppMain implements ServletContextListener {
}
public void contextDestroyed(ServletContextEvent event) {
- try {
- ACL.impersonate(ACL.SYSTEM, new Runnable() {
- @Override
- public void run() {
- terminated = true;
- Jenkins instance = Jenkins.getInstanceOrNull();
- if (instance != null)
- instance.cleanUp();
- 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"));
- t.interrupt();
- }
-
- // Logger is in the system classloader, so if we don't do this
- // the whole web app will never be undepoyed.
- Logger.getLogger("").removeHandler(handler);
+ try (ACLContext old = ACL.as(ACL.SYSTEM)) {
+ Jenkins instance = Jenkins.getInstanceOrNull();
+ 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"));
+ t.interrupt();
+ }
+
+ // Logger is in the system classloader, so if we don't do this
+ // 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
new file mode 100644
index 0000000000000000000000000000000000000000..24b2ec301851fea81867b277c953d45ca1785097
--- /dev/null
+++ b/core/src/main/java/hudson/cli/CancelQuietDownCommand.java
@@ -0,0 +1,53 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.cli;
+
+import hudson.Extension;
+import jenkins.model.Jenkins;
+
+import java.util.logging.Logger;
+
+/**
+ * Cancel previous quiet down Jenkins - preparation for a restart
+ *
+ * @author pjanouse
+ * @since 2.14
+ */
+@Extension
+public class CancelQuietDownCommand extends CLICommand {
+
+ private static final Logger LOGGER = Logger.getLogger(CancelQuietDownCommand.class.getName());
+
+ @Override
+ public String getShortDescription() {
+ return Messages.CancelQuietDownCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ Jenkins.getActiveInstance().doCancelQuietDown();
+ return 0;
+ }
+}
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/CliManagerImpl.java b/core/src/main/java/hudson/cli/CliManagerImpl.java
index 7ff5247e854c4ae9a2cbfdb64298b6b0af7cdf65..577cf3ce132fe373b6f95e80c95ff2415e75e366 100644
--- a/core/src/main/java/hudson/cli/CliManagerImpl.java
+++ b/core/src/main/java/hudson/cli/CliManagerImpl.java
@@ -50,10 +50,11 @@ public class CliManagerImpl implements CliEntryPoint, Serializable {
private Authentication transportAuth;
+ //TODO: Migrate the code to Callable decorator
/**
* Runs callable from this CLI client with the transport authentication credential.
*/
- private final CallableFilter authenticationFilter = new CallableFilter() {
+ private transient final CallableFilter authenticationFilter = new CallableFilter() {
public V call(Callable callable) throws Exception {
SecurityContext context = SecurityContextHolder.getContext();
Authentication old = context.getAuthentication();
diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java
index aa1d031b4b8c53eb1ac22972b3787c67cfd3a151..d663957fe9debe8998bb698ece5067fa40b410bd 100644
--- a/core/src/main/java/hudson/cli/CliProtocol.java
+++ b/core/src/main/java/hudson/cli/CliProtocol.java
@@ -1,6 +1,7 @@
package hudson.cli;
import hudson.Extension;
+import hudson.Util;
import hudson.model.Computer;
import hudson.remoting.Channel;
import hudson.remoting.Channel.Mode;
@@ -31,9 +32,25 @@ public class CliProtocol extends AgentProtocol {
@Inject
NioChannelSelector nio;
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptIn() {
+ return OPT_IN;
+ }
+
@Override
public String getName() {
- return "CLI-connect";
+ return jenkins.CLI.DISABLED ? null : "CLI-connect";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return "Jenkins CLI Protocol/1";
}
@Override
@@ -85,4 +102,14 @@ public class CliProtocol extends AgentProtocol {
channel.join();
}
}
+
+ /**
+ * A/B test turning off this protocol by default.
+ */
+ private static final boolean OPT_IN;
+
+ static {
+ byte hash = Util.fromHexString(Jenkins.getInstance().getLegacyInstanceId())[0];
+ OPT_IN = (hash % 10) == 0;
+ }
}
diff --git a/core/src/main/java/hudson/cli/CliProtocol2.java b/core/src/main/java/hudson/cli/CliProtocol2.java
index a4893cfd94bb9e1ca4c7dc8786098c91a8b6f841..bb14599dbfea037d7d07517d828ffb9aa7f968b9 100644
--- a/core/src/main/java/hudson/cli/CliProtocol2.java
+++ b/core/src/main/java/hudson/cli/CliProtocol2.java
@@ -25,7 +25,23 @@ import java.security.Signature;
public class CliProtocol2 extends CliProtocol {
@Override
public String getName() {
- return "CLI2-connect";
+ return jenkins.CLI.DISABLED ? null : "CLI2-connect";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOptIn() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDisplayName() {
+ return "Jenkins CLI Protocol/2";
}
@Override
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 cd3a3bf8f96703d29dba6d3d5f1db4c397a353aa..ae20f5e60848507503cca9f3ff0d3a21cb9c53a5 100644
--- a/core/src/main/java/hudson/cli/ConnectNodeCommand.java
+++ b/core/src/main/java/hudson/cli/ConnectNodeCommand.java
@@ -26,25 +26,25 @@ package hudson.cli;
import hudson.AbortException;
import hudson.Extension;
import hudson.model.Computer;
-import org.acegisecurity.AccessDeniedException;
+import hudson.model.ComputerSet;
import hudson.util.EditDistance;
import jenkins.model.Jenkins;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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")
@@ -75,10 +75,7 @@ public class ConnectNodeCommand extends CLICommand {
if(computer == null) {
if(names == null) {
- names = new ArrayList();
- for (Computer c : jenkins.getComputers())
- if (!c.getName().isEmpty())
- names.add(c.getName());
+ names = ComputerSet.getComputerNames();
}
String adv = EditDistance.findNearest(node_s, names);
throw new IllegalArgumentException(adv == null ?
@@ -100,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 01c48e2fcd49d7c5ecb71f51d27f803b1aac015d..65c95106eb5f8653007b8d62bf8aa5c8c5e987e0 100644
--- a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
+++ b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java
@@ -26,23 +26,24 @@ package hudson.cli;
import hudson.AbortException;
import hudson.Extension;
import hudson.model.Computer;
+import hudson.model.ComputerSet;
import hudson.util.EditDistance;
import jenkins.model.Jenkins;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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")
@@ -73,12 +74,7 @@ public class DisconnectNodeCommand extends CLICommand {
if (computer == null) {
if (names == null) {
- names = new ArrayList();
- for (Computer c : jenkins.getComputers()) {
- if (!c.getName().isEmpty()) {
- names.add(c.getName());
- }
- }
+ names = ComputerSet.getComputerNames();
}
String adv = EditDistance.findNearest(node_s, names);
throw new IllegalArgumentException(adv == null ?
@@ -99,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
new file mode 100644
index 0000000000000000000000000000000000000000..e003a633b28ce80895a5257e74973918c7ee9a2b
--- /dev/null
+++ b/core/src/main/java/hudson/cli/OfflineNodeCommand.java
@@ -0,0 +1,96 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.cli;
+
+import hudson.AbortException;
+import hudson.Extension;
+import hudson.model.Computer;
+import hudson.model.ComputerSet;
+import hudson.util.EditDistance;
+import jenkins.model.Jenkins;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * CLI Command, which puts the Jenkins node offline.
+ * @author pjanouse
+ * @since 2.15
+ */
+@Extension
+public class OfflineNodeCommand extends CLICommand {
+
+ @Argument(metaVar = "NAME", usage = "Agent name, or empty string for master", required = true, multiValued = true)
+ private List nodes;
+
+ @Option(name = "-m", usage = "Record the reason about why you are disconnecting this node")
+ public String cause;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.OfflineNodeCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ boolean errorOccurred = false;
+ final Jenkins jenkins = Jenkins.getInstance();
+ final HashSet hs = new HashSet(nodes);
+ List names = null;
+
+ for (String node_s : hs) {
+ try {
+ Computer computer = jenkins.getComputer(node_s);
+ if (computer == null) {
+ if (names == null) {
+ names = ComputerSet.getComputerNames();
+ }
+ String adv = EditDistance.findNearest(node_s, names);
+ throw new IllegalArgumentException(adv == null ?
+ hudson.model.Messages.Computer_NoSuchSlaveExistsWithoutAdvice(node_s) :
+ hudson.model.Messages.Computer_NoSuchSlaveExists(node_s, adv));
+ }
+ computer.cliOffline(cause);
+ } catch (Exception e) {
+ if (hs.size() == 1) {
+ throw e;
+ }
+
+ stderr.println(node_s + ": " + e.getMessage());
+ errorOccurred = true;
+ continue;
+ }
+ }
+
+ if (errorOccurred) {
+ 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 197fa404556025097a6337e471962d531af26ade..5cb190fcd8ccf6bdb2c7f4e3e4c7e55888ad6505 100644
--- a/core/src/main/java/hudson/cli/OnlineNodeCommand.java
+++ b/core/src/main/java/hudson/cli/OnlineNodeCommand.java
@@ -27,20 +27,19 @@ package hudson.cli;
import hudson.AbortException;
import hudson.Extension;
import hudson.model.Computer;
+import hudson.model.ComputerSet;
import hudson.util.EditDistance;
import jenkins.model.Jenkins;
-import org.acegisecurity.AccessDeniedException;
import org.kohsuke.args4j.Argument;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
-import java.util.logging.Logger;
/**
+ * CLI Command, which moves the node to the online state.
* @author pjanouse
- * @since TODO
+ * @since 1.642
*/
@Extension
public class OnlineNodeCommand extends CLICommand {
@@ -67,12 +66,7 @@ public class OnlineNodeCommand extends CLICommand {
computer = jenkins.getComputer(node_s);
if (computer == null) {
if (names == null) {
- names = new ArrayList();
- for (Computer c : jenkins.getComputers()) {
- if (!c.getName().isEmpty()) {
- names.add(c.getName());
- }
- }
+ names = ComputerSet.getComputerNames();
}
String adv = EditDistance.findNearest(node_s, names);
throw new IllegalArgumentException(adv == null ?
@@ -93,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
new file mode 100644
index 0000000000000000000000000000000000000000..4d84055c8de21667dac56c35ef6397cb253e7574
--- /dev/null
+++ b/core/src/main/java/hudson/cli/QuietDownCommand.java
@@ -0,0 +1,60 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.cli;
+
+import hudson.Extension;
+import jenkins.model.Jenkins;
+import org.kohsuke.args4j.Option;
+
+import java.util.logging.Logger;
+
+/**
+ * Quiet down Jenkins - preparation for a restart
+ *
+ * @author pjanouse
+ * @since 2.14
+ */
+@Extension
+public class QuietDownCommand extends CLICommand {
+
+ private static final Logger LOGGER = Logger.getLogger(QuietDownCommand.class.getName());
+
+ @Option(name="-block",usage="Block until the system really quiets down and no builds are running")
+ public boolean block = false;
+
+ @Option(name="-timeout",usage="If non-zero, only block up to the specified number of milliseconds")
+ public int timeout = 0;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.QuietDownCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ Jenkins.getActiveInstance().doQuietDown(block, timeout);
+ return 0;
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..319e4424a9e44b8b95d1b00b19403f0a805dc994
--- /dev/null
+++ b/core/src/main/java/hudson/cli/WaitNodeOfflineCommand.java
@@ -0,0 +1,51 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.cli;
+
+import hudson.Extension;
+import hudson.model.Node;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * CLI command, which waits till the node switches to the offline state.
+ * @author pjanouse
+ * @since 2.16
+ */
+@Extension
+public class WaitNodeOfflineCommand extends CLICommand {
+
+ @Argument(metaVar="NODE", usage="Name of the node", required=true)
+ public Node node;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.WaitNodeOfflineCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ node.toComputer().waitUntilOffline();
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e9713ad6f4cd4e6034ca11f8e1ae9748d1315cf
--- /dev/null
+++ b/core/src/main/java/hudson/cli/WaitNodeOnlineCommand.java
@@ -0,0 +1,51 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.cli;
+
+import hudson.Extension;
+import hudson.model.Node;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * CLI command, which waits till the node switches to the online state.
+ * @author pjanouse
+ * @since 2.16
+ */
+@Extension
+public class WaitNodeOnlineCommand extends CLICommand {
+
+ @Argument(metaVar="NODE", usage="Name of the node", required=true)
+ public Node node;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.WaitNodeOnlineCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ node.toComputer().waitUntilOnline();
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java
index 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/GenericItemOptionHandler.java b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java
index cd6b6b9377557af47348c1e767521da18b7ff0a0..6cd1d86eba57141695824a9ec52c22a8cad074f7 100644
--- a/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java
+++ b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java
@@ -27,6 +27,7 @@ package hudson.cli.handlers;
import hudson.model.Item;
import hudson.model.Items;
import hudson.security.ACL;
+import hudson.security.ACLContext;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
@@ -61,16 +62,14 @@ public abstract class GenericItemOptionHandler extends OptionHan
T s = j.getItemByFullName(src, type());
if (s == null) {
final Authentication who = Jenkins.getAuthentication();
- ACL.impersonate(ACL.SYSTEM, new Runnable() {
- @Override public void run() {
- Item actual = j.getItemByFullName(src);
- if (actual == null) {
- LOGGER.log(Level.FINE, "really no item exists named {0}", src);
- } else {
- LOGGER.log(Level.WARNING, "running as {0} could not find {1} of {2}", new Object[] {who.getPrincipal(), actual, type()});
- }
+ try (ACLContext _ = ACL.as(ACL.SYSTEM)) {
+ Item actual = j.getItemByFullName(src);
+ if (actual == null) {
+ LOGGER.log(Level.FINE, "really no item exists named {0}", src);
+ } else {
+ LOGGER.log(Level.WARNING, "running as {0} could not find {1} of {2}", new Object[] {who.getPrincipal(), actual, type()});
}
- });
+ }
T nearest = Items.findNearest(type(), src, j);
if (nearest != null) {
throw new IllegalArgumentException("No such job '" + src + "'; perhaps you meant '" + nearest.getFullName() + "'?");
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/console/ModelHyperlinkNote.java b/core/src/main/java/hudson/console/ModelHyperlinkNote.java
index 86d7bd3fc880474eb78119df0142c41a31f12c64..784934708d62dc95389f0877fd571601d865e61c 100644
--- a/core/src/main/java/hudson/console/ModelHyperlinkNote.java
+++ b/core/src/main/java/hudson/console/ModelHyperlinkNote.java
@@ -8,6 +8,7 @@ import org.jenkinsci.Symbol;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.annotation.Nonnull;
/**
* {@link HyperlinkNote} that links to a {@linkplain ModelObject model object},
@@ -26,7 +27,7 @@ public class ModelHyperlinkNote extends HyperlinkNote {
return " class='model-link'";
}
- public static String encodeTo(User u) {
+ public static String encodeTo(@Nonnull User u) {
return encodeTo(u,u.getDisplayName());
}
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/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..8f1fc9325bfb7d7ecb0aa212be8c519d6947267f 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;
@@ -235,27 +233,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 +335,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 +656,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);
}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 8dab4cb80c369e716ccb2406380d53543e05afba..cdf98e5083f828e0263da8428c514090615f7e3c 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -1105,7 +1105,7 @@ public abstract class AbstractProject
,R extends A
*/
@Deprecated
public static class BecauseOfBuildInProgress extends BlockedBecauseOfBuildInProgress {
- public BecauseOfBuildInProgress(AbstractBuild, ?> build) {
+ public BecauseOfBuildInProgress(@Nonnull AbstractBuild, ?> build) {
super(build);
}
}
@@ -1146,7 +1146,15 @@ public abstract class AbstractProject
,R extends A
public CauseOfBlockage getCauseOfBlockage() {
// Block builds until they are done with post-production
if (isLogUpdated() && !isConcurrentBuild()) {
- return new BlockedBecauseOfBuildInProgress(getLastBuild());
+ final R lastBuild = getLastBuild();
+ if (lastBuild != null) {
+ return new BlockedBecauseOfBuildInProgress(lastBuild);
+ } else {
+ // The build has been likely deleted after the isLogUpdated() call.
+ // 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");
+ }
}
if (blockBuildWhenDownstreamBuilding()) {
AbstractProject,?> bup = getBuildingDownstream();
@@ -1362,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/BuildTimelineWidget.java b/core/src/main/java/hudson/model/BuildTimelineWidget.java
index 9b8bc849b860100e957e392b5aa4e094ef8d3716..31810b5f5e939e8409eb12422b150b3bffb2d91b 100644
--- a/core/src/main/java/hudson/model/BuildTimelineWidget.java
+++ b/core/src/main/java/hudson/model/BuildTimelineWidget.java
@@ -62,8 +62,8 @@ public class BuildTimelineWidget {
TimelineEventList result = new TimelineEventList();
for (Run r : builds.byTimestamp(min,max)) {
Event e = new Event();
- e.start = r.getTime();
- e.end = new Date(r.timestamp+r.getDuration());
+ e.start = new Date(r.getStartTimeInMillis());
+ e.end = new Date(r.getStartTimeInMillis()+r.getDuration());
e.title = r.getFullDisplayName();
// what to put in the description?
// e.description = "Longish description of event "+r.getFullDisplayName();
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 b8db6c0a3fa09f0296672e844e8e13e0f7b4d1d6..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;
@@ -68,6 +66,8 @@ import jenkins.model.Jenkins;
import jenkins.util.ContextResettingExecutorService;
import jenkins.security.MasterToSlaveCallable;
+import org.jenkins.ui.icon.Icon;
+import org.jenkins.ui.icon.IconSet;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -86,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;
@@ -189,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)
@@ -267,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.
@@ -521,10 +515,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
/**
- * CLI command to mark the node offline.
+ * @deprecated Implementation of CLI command "offline-node" moved to {@link hudson.cli.OfflineNodeCommand}.
+ *
+ * @param cause
+ * Record the note about why you are disconnecting this node
*/
- @CLIMethod(name="offline-node")
- public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
+ @Deprecated
+ public void cliOffline(String cause) throws ExecutionException, InterruptedException {
checkPermission(DISCONNECT);
setTemporarilyOffline(true, new ByCLI(cause));
}
@@ -718,6 +715,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
}
+ /**
+ * Returns the icon for this computer.
+ *
+ * It is both the recommended and default implementation to serve different icons based on {@link #isOffline}
+ *
+ * @see #getIconClassName()
+ */
@Exported
public String getIcon() {
if(isOffline())
@@ -726,6 +730,17 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
return "computer.png";
}
+ /**
+ * Returns the class name that will be used to lookup the icon.
+ *
+ * This class name will be added as a class tag to the html img tags where the icon should
+ * show up followed by a size specifier given by {@link Icon#toNormalizedIconSizeClass(String)}
+ * The conversion of class tag to src tag is registered through {@link IconSet#addIcon(Icon)}
+ *
+ * It is both the recommended and default implementation to serve different icons based on {@link #isOffline}
+ *
+ * @see #getIcon()
+ */
@Exported
public String getIconClassName() {
if(isOffline())
@@ -763,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());
}
/**
@@ -839,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;
@@ -1082,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;
}
@@ -1244,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());
@@ -1342,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 {
@@ -1471,7 +1501,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* Blocks until the node becomes online/offline.
*/
- @CLIMethod(name="wait-node-online")
public void waitUntilOnline() throws InterruptedException {
synchronized (statusChangeLock) {
while (!isOnline())
@@ -1479,7 +1508,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
}
- @CLIMethod(name="wait-node-offline")
public void waitUntilOffline() throws InterruptedException {
synchronized (statusChangeLock) {
while (!isOffline())
@@ -1513,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());
@@ -1528,11 +1556,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
Jenkins h = Jenkins.getInstance();
Computer item = h.getComputer(name);
if (item==null) {
- List names = new ArrayList();
- for (Computer c : h.getComputers())
- if (c.getName().length()>0)
- names.add(c.getName());
- throw new CmdLineException(null,Messages.Computer_NoSuchSlaveExists(name,EditDistance.findNearest(name,names)));
+ List names = ComputerSet.getComputerNames();
+ String adv = EditDistance.findNearest(name, names);
+ throw new IllegalArgumentException(adv == null ?
+ hudson.model.Messages.Computer_NoSuchSlaveExistsWithoutAdvice(name) :
+ hudson.model.Messages.Computer_NoSuchSlaveExists(name, adv));
}
return item;
}
diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java
index 7b6510ef4bda8d75fceca3efcd2d59ab3f0a3faf..b907420b5876a4ce5d34f930e228c7ea89585fab 100644
--- a/core/src/main/java/hudson/model/ComputerSet.java
+++ b/core/src/main/java/hudson/model/ComputerSet.java
@@ -49,6 +49,7 @@ import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
+import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
@@ -410,6 +411,21 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
}, 10, TimeUnit.SECONDS);
}
+ /**
+ * @return The list of strings of computer names (excluding master)
+ * @since 2.14
+ */
+ @Nonnull
+ public static List getComputerNames() {
+ final ArrayList names = new ArrayList();
+ for (Computer c : Jenkins.getInstance().getComputers()) {
+ if (!c.getName().isEmpty()) {
+ names.add(c.getName());
+ }
+ }
+ return names;
+ }
+
private static final Logger LOGGER = Logger.getLogger(ComputerSet.class.getName());
static {
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 8337195690ce6639a33edd698198f8447afc957e..2db2873fac59e9adf08f694e383cdbab03463697 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.*;
@@ -125,7 +126,7 @@ import javax.annotation.Nonnull;
* @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.
*/
@@ -199,11 +200,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;
}
@@ -455,7 +456,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
@@ -638,7 +639,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,
@@ -785,7 +792,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}.
@@ -811,7 +818,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);
@@ -1138,7 +1145,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/DescriptorVisibilityFilter.java b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
index 838e3a0caf4309bc75a568fcc727fde31b74b292..f092a8bb1094800f7627c66030cda4564dceddff 100644
--- a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
+++ b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
@@ -35,7 +35,7 @@ public abstract class DescriptorVisibilityFilter implements ExtensionPoint {
* @return true to allow the descriptor to be visible. false to hide it.
* If any of the installed {@link DescriptorVisibilityFilter} returns false,
* the descriptor is not shown.
- * @since FIXME
+ * @since 2.12
*/
public boolean filterType(@Nonnull Class> contextClass, @Nonnull Descriptor descriptor) {
return true;
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/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index e76f330a91af71b0ecdac7f8baff88f08e25234f..2e33bdf2d070c85e6fb6a6f12ff0abc9aaa7be2e 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -25,7 +25,6 @@ package hudson.model;
import com.google.common.collect.ImmutableList;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
-import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
@@ -40,6 +39,7 @@ import hudson.Extension;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SaveableListener;
import hudson.security.ACL;
+import hudson.security.ACLContext;
import hudson.util.AtomicFileWriter;
import hudson.util.HexBinaryConverter;
import hudson.util.Iterators;
@@ -822,11 +822,9 @@ public class Fingerprint implements ModelObject, Saveable {
public static final class ProjectRenameListener extends ItemListener {
@Override
public void onLocationChanged(final Item item, final String oldName, final String newName) {
- ACL.impersonate(ACL.SYSTEM, new Runnable() {
- @Override public void run() {
- locationChanged(item, oldName, newName);
- }
- });
+ try (ACLContext _ = ACL.as(ACL.SYSTEM)) {
+ locationChanged(item, oldName, newName);
+ }
}
private void locationChanged(Item item, String oldName, String newName) {
if (item instanceof AbstractProject) {
@@ -1437,36 +1435,31 @@ public class Fingerprint implements ModelObject, Saveable {
// Probably it failed due to the missing Item.DISCOVER
// We try to retrieve the job using SYSTEM user and to check permissions manually.
final Authentication userAuth = Jenkins.getAuthentication();
- final boolean[] res = new boolean[] {false};
- ACL.impersonate(ACL.SYSTEM, new Runnable() {
- @Override
- public void run() {
- final Item itemBySystemUser = jenkins.getItemByFullName(fullName);
- if (itemBySystemUser == null) {
- return;
- }
-
- // To get the item existence fact, a user needs Item.DISCOVER for the item
- // and Item.READ for all container folders.
- boolean canDiscoverTheItem = itemBySystemUser.getACL().hasPermission(userAuth, Item.DISCOVER);
- if (canDiscoverTheItem) {
- ItemGroup> current = itemBySystemUser.getParent();
- do {
- if (current instanceof Item) {
- final Item item = (Item) current;
- current = item.getParent();
- if (!item.getACL().hasPermission(userAuth, Item.READ)) {
- canDiscoverTheItem = false;
- }
- } else {
- current = null;
+ try (ACLContext _ = ACL.as(ACL.SYSTEM)) {
+ final Item itemBySystemUser = jenkins.getItemByFullName(fullName);
+ if (itemBySystemUser == null) {
+ return false;
+ }
+
+ // To get the item existence fact, a user needs Item.DISCOVER for the item
+ // and Item.READ for all container folders.
+ boolean canDiscoverTheItem = itemBySystemUser.getACL().hasPermission(userAuth, Item.DISCOVER);
+ if (canDiscoverTheItem) {
+ ItemGroup> current = itemBySystemUser.getParent();
+ do {
+ if (current instanceof Item) {
+ final Item i = (Item) current;
+ current = i.getParent();
+ if (!i.getACL().hasPermission(userAuth, Item.READ)) {
+ canDiscoverTheItem = false;
}
- } while (canDiscoverTheItem && current != null);
- }
- res[0] = canDiscoverTheItem;
+ } else {
+ current = null;
+ }
+ } while (canDiscoverTheItem && current != null);
}
- });
- return res[0];
+ return canDiscoverTheItem;
+ }
}
private static final XStream2 XSTREAM = new XStream2();
@@ -1475,7 +1468,7 @@ public class Fingerprint implements ModelObject, Saveable {
* Provides the XStream instance this class is using for serialization.
*
* @return the XStream instance
- * @since FIXME
+ * @since 1.655
*/
@Nonnull
public static XStream2 getXStream() {
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..d24c6abe7c20a6ef6805f692d42ed145465fb2a3 100644
--- a/core/src/main/java/hudson/model/ItemGroupMixIn.java
+++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java
@@ -260,10 +260,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 +313,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..a30d459bd4174c3ce76cb9e6befe27b750adda3a 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 FIXME
+ */
+ 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 FIXME
+ */
+ 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 FIXME
+ */
+ 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 FIXME
+ */
+ 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 ee6cb07a98a96968dddc3cfecfc17c67b57d68c5..0612daa9f11c2c2426d0f49249f96d0035340dea 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -25,7 +25,6 @@ package hudson.model;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.BulkChange;
-
import hudson.EnvVars;
import hudson.Extension;
import hudson.ExtensionPoint;
@@ -61,15 +60,37 @@ import hudson.util.TextFile;
import hudson.widgets.HistoryWidget;
import hudson.widgets.HistoryWidget.Adapter;
import hudson.widgets.Widget;
+import java.awt.Color;
+import java.awt.Paint;
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.servlet.ServletException;
import jenkins.model.BuildDiscarder;
+import jenkins.model.BuildDiscarderProperty;
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import jenkins.model.Jenkins;
+import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ProjectNamingStrategy;
+import jenkins.model.RunIdMigrator;
+import jenkins.model.lazy.LazyBuildMixIn;
import jenkins.security.HexStringConfidentialKey;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
-
import org.apache.commons.io.FileUtils;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
@@ -82,6 +103,8 @@ import org.jfree.chart.renderer.category.StackedAreaRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleInsets;
import org.jvnet.localizer.Localizable;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.StaplerOverridable;
@@ -90,25 +113,8 @@ import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.interceptor.RequirePOST;
-import javax.servlet.ServletException;
-
-import java.awt.*;
-import java.io.*;
-import java.net.URLEncoder;
-import java.util.*;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-
-import static javax.servlet.http.HttpServletResponse.*;
-import jenkins.model.BuildDiscarderProperty;
-import jenkins.model.ModelObjectWithChildren;
-import jenkins.model.RunIdMigrator;
-import jenkins.model.lazy.LazyBuildMixIn;
-import org.kohsuke.accmod.Restricted;
-import org.kohsuke.accmod.restrictions.NoExternalUse;
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
/**
* A job is an runnable entity under the monitoring of Hudson.
@@ -122,7 +128,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
* @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());
@@ -562,7 +568,7 @@ public abstract class Job, RunT extends Run T getProperty(Class clazz) {
@@ -692,7 +698,7 @@ public abstract class Job, RunT extends Run getBuilds() {
- return RunList.fromRuns(_getRuns().values());
+ return RunList.fromRuns(_getRuns().values());
}
/**
@@ -724,7 +730,7 @@ public abstract class Job, RunT extends Run getBuildsAsMap() {
- return Collections.unmodifiableSortedMap(_getRuns());
+ return Collections.unmodifiableSortedMap(_getRuns());
}
/**
@@ -810,7 +816,7 @@ public abstract class Job, RunT extends Run, RunT extends Run runs = _getRuns();
+ if (runs instanceof RunMap) {
+ RunMap runMap = (RunMap) runs;
+ for (int index = i.getNumber(); index > u.getNumber() && totalCount < 5; index--) {
+ if (runMap.runExists(index)) {
+ totalCount++;
+ }
+ }
+ if (totalCount < 5) {
+ // start loading from the first failure as we counted the rest
+ i = u;
+ }
+ }
+ }
while (totalCount < 5 && i != null) {
switch (i.getIconColor()) {
case BLUE:
@@ -1198,26 +1224,27 @@ public abstract class Job, RunT extends Run, 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);
- }
-
- 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");
diff --git a/core/src/main/java/hudson/model/JobPropertyDescriptor.java b/core/src/main/java/hudson/model/JobPropertyDescriptor.java
index 2af8f74bc14fe5db129b5fbc678da7c089b9a7c3..5a1676e2df60c273c2f2910147b310030cb8e832 100644
--- a/core/src/main/java/hudson/model/JobPropertyDescriptor.java
+++ b/core/src/main/java/hudson/model/JobPropertyDescriptor.java
@@ -91,7 +91,7 @@ public abstract class JobPropertyDescriptor extends Descriptor> {
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..5f47e942ee3dd7ea1772eba199e72f2ac112de3c 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -362,10 +362,11 @@ public abstract class Label extends Actionable implements Comparable