diff --git a/.gitattributes b/.gitattributes
index aa1753af324bc..63ca2fc710ef2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -15,8 +15,6 @@
*.json text
*.jelly text
*.jellytag text
-# JENKINS-68887: postcss-less fails to properly parse .less files with Windows line breaks
-*.less text eol=lf
*.properties text
*.rb text
*.sh text
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 48c812c7f9109..f26d5d1de3b59 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -40,17 +40,17 @@ For examples, see: https://www.jenkins.io/changelog/
N/A
+```[tasklist]
### Submitter checklist
-
- [ ] The Jira issue, if it exists, is well-described.
-- [ ] The changelog entries and upgrade guidelines are appropriate for the audience affected by the change (users or developers, depending on the change) and are in the imperative mood (see [examples](https://github.com/jenkins-infra/jenkins.io/blob/master/content/_data/changelogs/weekly.yml)).
- - Fill in the **Proposed upgrade guidelines** section only if there are breaking changes or changes that may require extra steps from users during upgrade.
+- [ ] The changelog entries and upgrade guidelines are appropriate for the audience affected by the change (users or developers, depending on the change) and are in the imperative mood (see [examples](https://github.com/jenkins-infra/jenkins.io/blob/master/content/_data/changelogs/weekly.yml)). Fill in the **Proposed upgrade guidelines** section only if there are breaking changes or changes that may require extra steps from users during upgrade.
- [ ] There is automated testing or an explanation as to why this change has no tests.
- [ ] New public classes, fields, and methods are annotated with `@Restricted` or have `@since TODO` Javadocs, as appropriate.
- [ ] New deprecations are annotated with `@Deprecated(since = "TODO")` or `@Deprecated(forRemoval = true, since = "TODO")`, if applicable.
- [ ] New or substantially changed JavaScript is not defined inline and does not call `eval` to ease future introduction of Content Security Policy (CSP) directives (see [documentation](https://www.jenkins.io/doc/developer/security/csp/)).
- [ ] For dependency updates, there are links to external changelogs and, if possible, full differentials.
- [ ] For new APIs and extension points, there is a link to at least one consumer.
+```
### Desired reviewers
@@ -60,13 +60,14 @@ N/A
If you need an accelerated review process by the community (e.g., for critical bugs), mention @jenkinsci/core-pr-reviewers.
-->
-### Maintainer checklist
-
Before the changes are marked as `ready-for-merge`:
+```[tasklist]
+### Maintainer checklist
- [ ] There are at least two (2) approvals for the pull request and no outstanding requests for change.
- [ ] Conversations in the pull request are over, or it is explicit that a reviewer is not blocking the change.
- [ ] Changelog entries in the pull request title and/or **Proposed changelog entries** are accurate, human-readable, and in the imperative mood.
- [ ] Proper changelog labels are set so that the changelog can be generated automatically.
- [ ] If the change needs additional upgrade steps from users, the `upgrade-guide-needed` label is set and there is a **Proposed upgrade guidelines** section in the pull request title (see [example](https://github.com/jenkinsci/jenkins/pull/4387)).
- [ ] If it would make sense to backport the change to LTS, a Jira issue must exist, be a _Bug_ or _Improvement_, and be labeled as `lts-candidate` to be considered (see [query](https://issues.jenkins.io/issues/?filter=12146)).
+```
diff --git a/.github/config.yml b/.github/config.yml
new file mode 100644
index 0000000000000..dd96853821458
--- /dev/null
+++ b/.github/config.yml
@@ -0,0 +1,25 @@
+newPRWelcomeComment: >
+ Yay, your first pull request towards Jenkins core was created successfully!
+ Thank you so much!
+
+
+ A contributor will provide feedback soon.
+ Meanwhile, you can join the [chats](https://app.gitter.im/#/room/#jenkins-ci:matrix.org) and [community forums](https://community.jenkins.io/) to connect with other Jenkins users, developers, and maintainers.
+
+firstPRMergeComment: >
+ Congratulations on getting your very first Jenkins core pull request merged 🎉🥳
+
+
+ This is a fantastic achievement, and we're thrilled to have you as part of our community!
+ Thank you for your valuable input, and we look forward to seeing more of your contributions in the future!
+
+
+ We would like to invite you to join the [community chats](https://app.gitter.im/#/room/#jenkins-ci:matrix.org) and [forums](https://community.jenkins.io/) to meet other Jenkins contributors 😊
+
+ Don't forget to check out the [participation](https://www.jenkins.io/participate/) page to learn more about how to contribute to Jenkins.
+
+
+
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index d4fc706d89542..6e7d93447f7d9 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -17,7 +17,6 @@ updates:
# version of Jetty we deliver. See:
# https://github.com/jenkinsci/jenkins/pull/5211
- dependency-name: "jakarta.servlet:jakarta.servlet-api"
- - dependency-name: "javax.servlet:javax.servlet-api"
# Jetty Maven Plugin and Winstone should be upgraded in lockstep in order
# to keep their corresponding Jetty versions aligned.
@@ -52,9 +51,14 @@ updates:
# Starting with 6.x, Spring Security requires Java 17 at a minimum.
- dependency-name: "org.springframework.security:spring-security-bom"
versions: [">=6.0.0"]
+
+ # Starting with 7.x, Guice switches from javax.* to jakarta.* bindings.
+ # See https://github.com/google/guice/wiki/Guice700
+ - dependency-name: "com.google.inject:guice-bom"
+ versions: [">=7.0.0"]
- package-ecosystem: "maven"
directory: "/"
- target-branch: "stable-2.375"
+ target-branch: "stable-2.414"
labels:
- "into-lts"
- "needs-justification"
diff --git a/.github/images/jenkins-welcome.svg b/.github/images/jenkins-welcome.svg
new file mode 100644
index 0000000000000..28aa849ebfafe
--- /dev/null
+++ b/.github/images/jenkins-welcome.svg
@@ -0,0 +1,204 @@
+
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index dea378b71a474..18da01b85864f 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -7,7 +7,7 @@ tag-template: jenkins-$NEXT_MINOR_VERSION
template: |
_This is an automatically generated changelog draft for Jenkins weekly releases.
- See https://www.jenkins.io/changelog/ for the official changelogs._
+ See https://www.jenkins.io/changelog/#v$NEXT_MINOR_VERSION for the official changelog for this release, or https://www.jenkins.io/changelog-old/#v$NEXT_MINOR_VERSION for releases older than around 7 months._
$CHANGES
diff --git a/.github/renovate.json b/.github/renovate.json
index ce2f6a867a861..b6f6d7e650907 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -14,12 +14,6 @@
"stabilityDays": 3,
"reviewers": ["team:sig-ux"]
},
- {
- "matchPackageNames": ["bootstrap"],
- "allowedVersions": "<=3.4.1",
- "description": "https://issues.jenkins.io/browse/JENKINS-69409",
- "enabled": false
- },
{
"matchPackageNames": ["node"],
"allowedVersions": "/18.[0-9]+.[0-9]+(.[0-9]+)?$/"
@@ -44,6 +38,24 @@
"matchStrings": ["ARG MAVEN_VERSION=(?.*?)\n"],
"depNameTemplate": "org.apache.maven:maven-core",
"datasourceTemplate": "maven"
+ },
+ {
+ "fileMatch": ["core/src/site/site.xml"],
+ "matchStrings": ["lit@(?.*?)/"],
+ "depNameTemplate": "lit",
+ "datasourceTemplate": "npm"
+ },
+ {
+ "fileMatch": ["core/src/site/site.xml"],
+ "matchStrings": ["webcomponentsjs@(?.*?)/"],
+ "depNameTemplate": "@webcomponents/webcomponentsjs",
+ "datasourceTemplate": "npm"
+ },
+ {
+ "fileMatch": ["core/src/site/site.xml"],
+ "matchStrings": ["(?.*?)<\/version>"],
+ "depNameTemplate": "org.apache.maven.skins:maven-fluido-skin",
+ "datasourceTemplate": "maven"
}
],
"labels": ["dependencies", "skip-changelog"],
diff --git a/.github/workflows/announce-lts-rc.yml b/.github/workflows/announce-lts-rc.yml
new file mode 100644
index 0000000000000..b544abd78f3e8
--- /dev/null
+++ b/.github/workflows/announce-lts-rc.yml
@@ -0,0 +1,30 @@
+name: Announce LTS RCs
+
+on:
+ release:
+ types: [prereleased]
+
+jobs:
+ post:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Post on Discourse
+ uses: roots/discourse-topic-github-release-action@fc9e50fa1a1ce6255ba4d03f104382845b79ad5f # v1.0.0
+ with:
+ discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }}
+ discourse-base-url: https://community.jenkins.io/
+ discourse-author-username: jenkins-release-bot
+ discourse-category: 23
+ - name: Post on mailing list
+ uses: dawidd6/action-send-mail@v3
+ with:
+ server_address: smtp.gmail.com
+ server_port: 465
+ username: ${{secrets.MAIL_USERNAME}}
+ password: ${{secrets.MAIL_PASSWORD}}
+ secure: true
+ subject: ${{ github.event.release.tag_name }} has been released
+ to: jenkinsci-dev@googlegroups.com
+ from: Jenkins Release Bot
+ html_body: ${{ github.event.release.body }}
+ convert_markdown: true
diff --git a/.github/workflows/label-lts-prs.yaml b/.github/workflows/label-lts-prs.yaml
new file mode 100644
index 0000000000000..e947bd078f0fa
--- /dev/null
+++ b/.github/workflows/label-lts-prs.yaml
@@ -0,0 +1,23 @@
+name: Label PRs targeting LTS branches
+
+on: [pull_request_target]
+
+permissions:
+ pull-requests: write
+
+jobs:
+ label:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check if PR targets LTS branch
+ if: startsWith(github.event.pull_request.base.ref, 'stable-')
+ uses: actions/github-script@v6
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ github.rest.issues.addLabels({
+ owner: context.payload.repository.owner.login,
+ repo: context.payload.repository.name,
+ issue_number: context.payload.pull_request.number,
+ labels: ['into-lts']
+ });
diff --git a/.gitpod/Dockerfile b/.gitpod/Dockerfile
index 83b644700f163..9f5ab9c02d915 100644
--- a/.gitpod/Dockerfile
+++ b/.gitpod/Dockerfile
@@ -1,6 +1,6 @@
FROM gitpod/workspace-full
-ARG MAVEN_VERSION=3.9.1
+ARG MAVEN_VERSION=3.9.4
RUN brew install gh && \
bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && sdk install maven ${MAVEN_VERSION} && sdk default maven ${MAVEN_VERSION}"
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index a7f16f208d1df..de5572116383f 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -32,9 +32,6 @@
-
-
-
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index 90787cbb2d51b..1f36364094f1d 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -2,6 +2,6 @@
io.jenkins.tools.incrementalsgit-changelist-maven-extension
- 1.6
+ 1.7
diff --git a/Jenkinsfile b/Jenkinsfile
index 1de19cbde01cf..ce0d1f068e388 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -12,12 +12,50 @@ properties([
disableConcurrentBuilds(abortPrevious: true)
])
-def builds = [:]
-
def axes = [
platforms: ['linux', 'windows'],
jdks: [11, 17, 19],
]
+
+stage('Record build') {
+ retry(conditions: [kubernetesAgent(handleNonKubernetes: true), nonresumable()], count: 2) {
+ node('maven-11') {
+ infra.checkoutSCM()
+
+ /*
+ * Record the primary build for this CI job.
+ */
+ withCredentials([string(credentialsId: 'launchable-jenkins-jenkins', variable: 'LAUNCHABLE_TOKEN')]) {
+ /*
+ * TODO Add the commits of the transitive closure of the Jenkins WAR under test to this build.
+ */
+ sh 'launchable verify && launchable record build --name ${BUILD_TAG} --source jenkinsci/jenkins=.'
+ axes.values().combinations {
+ def (platform, jdk) = it
+ if (platform == 'windows' && jdk != 17) {
+ return // unnecessary use of hardware
+ }
+ def sessionFile = "launchable-session-${platform}-jdk${jdk}.txt"
+ sh "launchable record session --build ${env.BUILD_TAG} --flavor platform=${platform} --flavor jdk=${jdk} >${sessionFile}"
+ stash name: sessionFile, includes: sessionFile
+ }
+ }
+
+ /*
+ * Record commits for use in downstream CI jobs that may consume this artifact.
+ */
+ withCredentials([string(credentialsId: 'launchable-jenkins-acceptance-test-harness', variable: 'LAUNCHABLE_TOKEN')]) {
+ sh 'launchable verify && launchable record commit'
+ }
+ withCredentials([string(credentialsId: 'launchable-jenkins-bom', variable: 'LAUNCHABLE_TOKEN')]) {
+ sh 'launchable verify && launchable record commit'
+ }
+ }
+ }
+}
+
+def builds = [:]
+
axes.values().combinations {
def (platform, jdk) = it
if (platform == 'windows' && jdk != 17) {
@@ -37,29 +75,50 @@ axes.values().combinations {
infra.checkoutSCM()
}
- def changelistF = "${pwd tmp: true}/changelist"
- def m2repo = "${pwd tmp: true}/m2repo"
+ def tmpDir = pwd(tmp: true)
+ def changelistF = "${tmpDir}/changelist"
+ def m2repo = "${tmpDir}/m2repo"
+ def session
// Now run the actual build.
stage("${platform.capitalize()} - JDK ${jdk} - Build / Test") {
timeout(time: 6, unit: 'HOURS') {
+ dir(tmpDir) {
+ def sessionFile = "launchable-session-${platform}-jdk${jdk}.txt"
+ unstash sessionFile
+ session = readFile(sessionFile).trim()
+ }
+ def mavenOptions = [
+ '-Pdebug',
+ '-Penable-jacoco',
+ '--update-snapshots',
+ "-Dmaven.repo.local=$m2repo",
+ '-Dmaven.test.failure.ignore',
+ '-DforkCount=2',
+ '-Dspotbugs.failOnError=false',
+ '-Dcheckstyle.failOnViolation=false',
+ '-Dset.changelist',
+ 'help:evaluate',
+ '-Dexpression=changelist',
+ "-Doutput=$changelistF",
+ 'clean',
+ 'install',
+ ]
+ if (env.CHANGE_ID && !pullRequest.labels.contains('full-test')) {
+ def excludesFile
+ def target = platform == 'windows' ? '30%' : '100%'
+ withCredentials([string(credentialsId: 'launchable-jenkins-jenkins', variable: 'LAUNCHABLE_TOKEN')]) {
+ if (isUnix()) {
+ excludesFile = "${tmpDir}/excludes.txt"
+ sh "launchable verify && launchable subset --session ${session} --target ${target} --get-tests-from-previous-sessions --output-exclusion-rules maven >${excludesFile}"
+ } else {
+ excludesFile = "${tmpDir}\\excludes.txt"
+ bat "launchable verify && launchable subset --session ${session} --target ${target}% --get-tests-from-previous-sessions --output-exclusion-rules maven >${excludesFile}"
+ }
+ }
+ mavenOptions.add(0, "-Dsurefire.excludesFile=${excludesFile}")
+ }
realtimeJUnit(healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/*.xml') {
- def mavenOptions = [
- '-Pdebug',
- '-Penable-jacoco',
- '--update-snapshots',
- "-Dmaven.repo.local=$m2repo",
- '-Dmaven.test.failure.ignore',
- '-DforkCount=2',
- '-Dspotbugs.failOnError=false',
- '-Dcheckstyle.failOnViolation=false',
- '-Dset.changelist',
- 'help:evaluate',
- '-Dexpression=changelist',
- "-Doutput=$changelistF",
- 'clean',
- 'install',
- ]
infra.runMaven(mavenOptions, jdk)
if (isUnix()) {
sh 'git add . && git diff --exit-code HEAD'
@@ -71,12 +130,6 @@ axes.values().combinations {
// Once we've built, archive the artifacts and the test results.
stage("${platform.capitalize()} - JDK ${jdk} - Publish") {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/target/surefire-reports/*.dumpstream'
- if (!fileExists('core/target/surefire-reports/TEST-jenkins.Junit4TestsRanTest.xml')) {
- error 'JUnit 4 tests are no longer being run for the core package'
- }
- if (!fileExists('test/target/surefire-reports/TEST-jenkins.Junit4TestsRanTest.xml')) {
- error 'JUnit 4 tests are no longer being run for the test package'
- }
// cli and war have been migrated to JUnit 5
if (failFast && currentBuild.result == 'UNSTABLE') {
error 'There were test failures; halting early'
@@ -86,17 +139,27 @@ axes.values().combinations {
if (folders.length > 1) {
discoverGitReferenceBuild(scm: folders[1])
}
- recordCoverage(tools: [[parser: 'JACOCO', pattern: 'coverage/target/site/jacoco-aggregate/jacoco.xml']], sourceCodeRetention: 'MODIFIED')
+ recordCoverage(tools: [[parser: 'JACOCO', pattern: 'coverage/target/site/jacoco-aggregate/jacoco.xml']],
+ sourceCodeRetention: 'MODIFIED', sourceDirectories: [[path: 'core/src/main/java']])
echo "Recording static analysis results for '${platform.capitalize()}'"
recordIssues(
enabledForFailure: true,
- tools: [java(), javaDoc()],
+ tools: [java()],
filters: [excludeFile('.*Assert.java')],
sourceCodeEncoding: 'UTF-8',
skipBlames: true,
trendChartType: 'TOOLS_ONLY'
)
+ recordIssues(
+ enabledForFailure: true,
+ tools: [javaDoc()],
+ filters: [excludeFile('.*Assert.java')],
+ sourceCodeEncoding: 'UTF-8',
+ skipBlames: true,
+ trendChartType: 'TOOLS_ONLY',
+ qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]
+ )
recordIssues([tool: spotBugs(pattern: '**/target/spotbugsXml.xml'),
sourceCodeEncoding: 'UTF-8',
skipBlames: true,
@@ -117,37 +180,9 @@ axes.values().combinations {
skipBlames: true,
trendChartType: 'TOOLS_ONLY',
qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]])
- launchable.install()
- withCredentials([string(credentialsId: 'launchable-jenkins-jenkins', variable: 'LAUNCHABLE_TOKEN')]) {
- launchable('verify')
- /*
- * TODO Create a Launchable build and session earlier, and replace "--no-build" with
- * "--session" to associate these test results with a particular build. The commits
- * associated with the Launchable build should be the commits of the transitive
- * closure of the Jenkins WAR under test in this build.
- */
- launchable("record tests --no-build --flavor platform=${platform} --flavor jdk=${jdk} maven './**/target/surefire-reports'")
- }
if (failFast && currentBuild.result == 'UNSTABLE') {
error 'Static analysis quality gates not passed; halting early'
}
- /*
- * If the current build was successful, we send the commits to Launchable so that
- * these commits can be consumed by a build in the Launchable BOM workspace in the
- * future.
- *
- * TODO Move this up to before the present parallel block starts (or move the ATH run
- * in this file to after joining on the present parallel block) so that these commits
- * can be consumed by a build in the Launchable ATH workspace in this file in the
- * future.
- */
- if (currentBuild.currentResult == 'SUCCESS') {
- withCredentials([string(credentialsId: 'launchable-jenkins-bom', variable: 'LAUNCHABLE_TOKEN')]) {
- launchable('verify')
- launchable('record commit')
- }
- }
-
def changelist = readFile(changelistF)
dir(m2repo) {
archiveArtifacts(
@@ -158,6 +193,13 @@ axes.values().combinations {
)
}
}
+ withCredentials([string(credentialsId: 'launchable-jenkins-jenkins', variable: 'LAUNCHABLE_TOKEN')]) {
+ if (isUnix()) {
+ sh "launchable verify && launchable record tests --session ${session} --flavor platform=${platform} --flavor jdk=${jdk} maven './**/target/surefire-reports'"
+ } else {
+ bat "launchable verify && launchable record tests --session ${session} --flavor platform=${platform} --flavor jdk=${jdk} maven ./**/target/surefire-reports"
+ }
+ }
}
}
}
@@ -166,7 +208,7 @@ axes.values().combinations {
def athAxes = [
platforms: ['linux'],
- jdks: [11],
+ jdks: [17],
browsers: ['firefox'],
]
athAxes.values().combinations {
@@ -178,21 +220,19 @@ athAxes.values().combinations {
deleteDir()
checkout scm
infra.withArtifactCachingProxy {
- sh 'bash ath.sh ' + browser
+ sh "bash ath.sh ${jdk} ${browser}"
}
junit testResults: 'target/ath-reports/TEST-*.xml', testDataPublishers: [[$class: 'AttachmentPublisher']]
- launchable.install()
- withCredentials([string(credentialsId: 'launchable-jenkins-acceptance-test-harness', variable: 'LAUNCHABLE_TOKEN')]) {
- launchable('verify')
- /*
- * TODO Create a Launchable build and session earlier, and replace "--no-build" with
- * "--session" to associate these test results with a particular build. The commits
- * associated with the Launchable build should be the commits of the transitive closure of
- * the Jenkins WAR under test in this build as well as the commits of the transitive closure
- * of the ATH JAR.
- */
- launchable("record tests --no-build --flavor platform=${platform} --flavor jdk=${jdk} --flavor browser=${browser} maven './target/ath-reports'")
- }
+ /*
+ * Currently disabled, as the fact that this is a manually created subset will confuse Launchable,
+ * which expects this to be a full build. When we implement subsetting, this can be re-enabled using
+ * Launchable's subset rather than our own.
+ */
+ /*
+ withCredentials([string(credentialsId: 'launchable-jenkins-acceptance-test-harness', variable: 'LAUNCHABLE_TOKEN')]) {
+ sh "launchable verify && launchable record tests --no-build --flavor platform=${platform} --flavor jdk=${jdk} --flavor browser=${browser} maven './target/ath-reports'"
+ }
+ */
}
}
}
diff --git a/README.md b/README.md
index 529c8234c4583..755a417ea158b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,5 @@
-
+
# About
diff --git a/ath.sh b/ath.sh
index bbb55908f25f0..08009178739ed 100644
--- a/ath.sh
+++ b/ath.sh
@@ -6,12 +6,14 @@ set -o xtrace
cd "$(dirname "$0")"
# https://github.com/jenkinsci/acceptance-test-harness/releases
-export ATH_VERSION=5545.v924953404220
+export ATH_VERSION=5686.vc3a_26a_d441a_9
if [[ $# -eq 0 ]]; then
+ export JDK=17
export BROWSER=firefox
else
- export BROWSER=$1
+ export JDK=$1
+ export BROWSER=$2
fi
MVN='mvn -B -ntp -Pquick-build -am -pl war package'
@@ -25,6 +27,7 @@ mkdir -p target/ath-reports
chmod a+rwx target/ath-reports
exec docker run --rm \
+ --env JDK \
--env ATH_VERSION \
--env BROWSER \
--shm-size 2g `# avoid selenium.WebDriverException exceptions like 'Failed to decode response from marionette' and webdriver closed` \
@@ -39,6 +42,7 @@ exec docker run --rm \
set -o pipefail
set -o xtrace
cd
+ set-java.sh "${JDK}"
# Start the VNC system provided by the image from the default user home directory
eval "$(vnc.sh)"
env | sort
diff --git a/bom/pom.xml b/bom/pom.xml
index 10bdab349ce13..9f255a92df246 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -40,7 +40,7 @@ THE SOFTWARE.
9.52.0.7
- 1781.v62372c33644e
+ 1802.v9e2750160d012.4.21
@@ -49,14 +49,14 @@ THE SOFTWARE.
com.google.injectguice-bom
- 5.1.0
+ 6.0.0pomimportorg.springframeworkspring-framework-bom
- 5.3.27
+ 5.3.29pomimport
@@ -64,7 +64,7 @@ THE SOFTWARE.
org.springframework.securityspring-security-bom
- 5.8.3
+ 5.8.5pomimport
@@ -82,7 +82,7 @@ THE SOFTWARE.
com.google.guavaguava
- 31.1-jre
+ 32.1.1-jre
@@ -113,7 +113,7 @@ THE SOFTWARE.
commons-codeccommons-codec
- 1.15
+ 1.16.0commons-collections
@@ -128,7 +128,7 @@ THE SOFTWARE.
commons-iocommons-io
- 2.11.0
+ 2.13.0commons-jelly
@@ -225,7 +225,7 @@ THE SOFTWARE.
org.jenkins-cicrypto-util
- 1.8
+ 1.9org.jenkins-ci
@@ -260,7 +260,7 @@ THE SOFTWARE.
org.jvnet.hudsoncommons-jelly-tags-define
- 1.1-jenkins-20230124
+ 1.1-jenkins-20230713org.jvnet.localizer
@@ -295,7 +295,7 @@ THE SOFTWARE.
org.kohsuke.metainf-servicesmetainf-services
- 1.9
+ 1.11org.kohsuke.stapler
diff --git a/changelog.html b/changelog.html
deleted file mode 100644
index cf08ed0484d97..0000000000000
--- a/changelog.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
diff --git a/cli/pom.xml b/cli/pom.xml
index b6b743cf3f6b4..56560342e5f41 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -15,7 +15,7 @@
https://github.com/jenkinsci/jenkins
- 2.9.2
+ 2.10.0
@@ -119,7 +119,7 @@
org.apache.maven.pluginsmaven-shade-plugin
- 3.4.1
+ 3.5.0
diff --git a/cli/src/main/java/hudson/cli/PlainCLIProtocol.java b/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
index 4f4fcfc49a664..e49daa246f889 100644
--- a/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
+++ b/cli/src/main/java/hudson/cli/PlainCLIProtocol.java
@@ -277,7 +277,7 @@ protected final boolean handle(Op op, DataInputStream dis) throws IOException {
onStart();
return true;
case STDIN:
- onStdin(IOUtils.toByteArray(dis));
+ onStdin(dis.readAllBytes());
return true;
case END_STDIN:
onEndStdin();
@@ -327,10 +327,10 @@ protected boolean handle(Op op, DataInputStream dis) throws IOException {
onExit(dis.readInt());
return true;
case STDOUT:
- onStdout(IOUtils.toByteArray(dis));
+ onStdout(dis.readAllBytes());
return true;
case STDERR:
- onStderr(IOUtils.toByteArray(dis));
+ onStderr(dis.readAllBytes());
return true;
default:
return false;
diff --git a/cli/src/main/resources/hudson/cli/client/Messages_ru.properties b/cli/src/main/resources/hudson/cli/client/Messages_ru.properties
new file mode 100644
index 0000000000000..ab55c185dc9fc
--- /dev/null
+++ b/cli/src/main/resources/hudson/cli/client/Messages_ru.properties
@@ -0,0 +1,26 @@
+CLI.Usage=Jenkins CLI\n\
+ Использование: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
+ Параметры:\n\
+ \ -s URL : URL сервера (по умолчанию используется переменная окружения JENKINS_URL)\n\
+ \ -webSocket : Подключиться с помощью WebSocket (по умолчанию; хорошо работает с большинством обратных прокси-серверов; требует Jetty)\n\
+ \ -http : Использовать пару соединений HTTP(S), а не WebSocket\n\
+ \ -ssh : Использовать протокол SSH вместо WebSocket (требуется -user; порт SSH должен быть открыт на сервере)\n\
+ \ -i KEY : Файл закрытого ключа SSH, используемый для аутентификации (для использования с -ssh)\n\
+ \ -noCertificateCheck : Полностью обойти проверку сертификата HTTPS. Используйте с осторожностью\n\
+ \ -noKeyAuth : Не пытайтесь загрузить закрытый ключ аутентификации SSH. Конфликты с -i\n\
+ \ -user : Указать пользователя (для использования с -ssh; необходимо зарегистрировать открытый ключ)\n\
+ \ -strictHostKey : Запрос строгой проверки ключей хоста (для использования с -ssh)\n\
+ \ -logger FINE : Включить подробное ведение журнала с клиента\n\
+ \ -auth [ USER:SECRET | @FILE ] : Указать имя пользователя и пароль или API-токен (или загрузить оба из файла);\n\
+ \ для использования с -http.\n\
+ \ Рекомендуется передавать учетные данные по файлам.\n\
+ \ Более подробную информацию и варианты смотрите на сайте https://www.jenkins.io/redirect/cli-http-connection-mode.\n\
+ \ -bearer [ TOKEN | @FILE ] : Указать аутентификацию с использованием токена (или загрузить токен из файла);\n\
+ \ Для использования с -http. Взаимоисключающий вариант с -auth.\n\
+ \ Рекомендуется передавать учетные данные через файл.\n\
+ \n\
+ Доступные команды зависят от сервера. Выполните команду ''help'', чтобы \
+ посмотреть список.
+CLI.NoURL=Не указаны ни -s, ни переменная окружения JENKINS_URL.
+CLI.NoSuchFileExists=Не существует такого файла: {0}
+CLI.BadAuth=Параметры окружения JENKINS_USER_ID и JENKINS_API_TOKEN должны быть оба установлены или оставлены пустыми.
diff --git a/core/pom.xml b/core/pom.xml
index a8045e8029f2e..26f9a102c81ec 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -328,9 +328,10 @@ THE SOFTWARE.
websocket-spi${project.version}
+
- javax.servlet
- javax.servlet-api
+ jakarta.servlet
+ jakarta.servlet-api
@@ -467,7 +468,7 @@ THE SOFTWARE.
org.jenkins-cicore-annotation-processors
- 1.0
+ 13.v2dcfc22a_a_b_29providedtrue
@@ -481,7 +482,7 @@ THE SOFTWARE.
org.hamcrest
- hamcrest-core
+ hamcresttest
@@ -546,6 +547,9 @@ THE SOFTWARE.
generate-sources
+
+
@@ -650,7 +654,7 @@ THE SOFTWARE.
- @{jacocoSurefireArgs} --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED
+ @{jacocoSurefireArgs} --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMEDfalse
@@ -675,8 +679,8 @@ THE SOFTWARE.
- org.kohsuke.stapler
- maven-stapler-plugin
+ io.jenkins.tools.maven
+ stapler-maven-plugin
diff --git a/core/src/main/java/hudson/AbstractMarkupText.java b/core/src/main/java/hudson/AbstractMarkupText.java
index b13eeaad3edd0..a05cbb399d81c 100644
--- a/core/src/main/java/hudson/AbstractMarkupText.java
+++ b/core/src/main/java/hudson/AbstractMarkupText.java
@@ -83,7 +83,7 @@ public final int length() {
* @since 1.349
*/
public void addHyperlink(int startPos, int endPos, String url) {
- addMarkup(startPos, endPos, "", "");
+ addMarkup(startPos, endPos, "", "");
}
/**
@@ -93,7 +93,7 @@ public void addHyperlink(int startPos, int endPos, String url) {
* @since 1.395
*/
public void addHyperlinkLowKey(int startPos, int endPos, String url) {
- addMarkup(startPos, endPos, "", "");
+ addMarkup(startPos, endPos, "", "");
}
/**
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index bc4b350c0c4ff..c16fc93e1aa91 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -40,6 +40,7 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.file.Files;
@@ -59,10 +60,7 @@
import jenkins.ClassLoaderReflectionToolkit;
import jenkins.ExtensionFilter;
import jenkins.plugins.DetachedPluginsUtil;
-import jenkins.util.AntClassLoader;
-import jenkins.util.SystemProperties;
import jenkins.util.URLClassLoader2;
-import org.apache.commons.io.output.NullOutputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Expand;
@@ -290,30 +288,17 @@ protected ClassLoader createClassLoader(List paths, ClassLoader parent, At
boolean usePluginFirstClassLoader =
atts != null && Boolean.parseBoolean(atts.getValue("PluginFirstClassLoader"));
- if (useAntClassLoader) {
- AntClassLoader classLoader;
- if (usePluginFirstClassLoader) {
- classLoader = new PluginFirstClassLoader();
- classLoader.setParentFirst(false);
- classLoader.setParent(parent);
- } else {
- classLoader = new AntClassLoader(parent, true);
- }
- classLoader.addPathFiles(paths);
- return classLoader;
+ List urls = new ArrayList<>();
+ for (File path : paths) {
+ urls.add(path.toURI().toURL());
+ }
+ URLClassLoader2 classLoader;
+ if (usePluginFirstClassLoader) {
+ classLoader = new PluginFirstClassLoader2(urls.toArray(new URL[0]), parent);
} else {
- List urls = new ArrayList<>();
- for (File path : paths) {
- urls.add(path.toURI().toURL());
- }
- URLClassLoader2 classLoader;
- if (usePluginFirstClassLoader) {
- classLoader = new PluginFirstClassLoader2(urls.toArray(new URL[0]), parent);
- } else {
- classLoader = new URLClassLoader2(urls.toArray(new URL[0]), parent);
- }
- return classLoader;
+ classLoader = new URLClassLoader2(urls.toArray(new URL[0]), parent);
}
+ return classLoader;
}
/**
@@ -432,17 +417,6 @@ private DependencyClassLoader findAncestorDependencyClassLoader(ClassLoader clas
if (classLoader instanceof DependencyClassLoader) {
return (DependencyClassLoader) classLoader;
}
-
- if (classLoader instanceof AntClassLoader) {
- // AntClassLoaders hold parents not only as AntClassLoader#getParent()
- // but also as AntClassLoader#getConfiguredParent()
- DependencyClassLoader ret = findAncestorDependencyClassLoader(
- ((AntClassLoader) classLoader).getConfiguredParent()
- );
- if (ret != null) {
- return ret;
- }
- }
}
return null;
}
@@ -529,7 +503,7 @@ private static void createClassJarFromWebInfClasses(File archive, File destDir,
final long dirTime = archive.lastModified();
// this ZipOutputStream is reused and not created for each directory
- try (ZipOutputStream wrappedZOut = new ZipOutputStream(NullOutputStream.NULL_OUTPUT_STREAM) {
+ try (OutputStream nos = OutputStream.nullOutputStream(); ZipOutputStream wrappedZOut = new ZipOutputStream(nos) {
@Override
public void putNextEntry(ZipEntry ze) throws IOException {
ze.setTime(dirTime + 1999); // roundup
@@ -716,7 +690,4 @@ protected URL findResource(String name) {
return null;
}
}
-
- @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts")
- public static /* not final */ boolean useAntClassLoader = SystemProperties.getBoolean(ClassicPluginStrategy.class.getName() + ".useAntClassLoader");
}
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index 05ebd3f974187..801735835c0fd 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -507,6 +507,7 @@ private void resolve(Class> c, Set> encountered) {
boolean foundInjectableConstructor = false;
for (Constructor constructor : cc.getDeclaredConstructors()) {
if (constructor.isAnnotationPresent(javax.inject.Inject.class)
+ || constructor.isAnnotationPresent(jakarta.inject.Inject.class)
|| constructor.isAnnotationPresent(com.google.inject.Inject.class)) {
constructor.getAnnotatedParameterTypes();
constructor.getParameterAnnotations();
@@ -529,6 +530,7 @@ private void resolve(Class> c, Set> encountered) {
// See com.google.inject.spi.InjectionPoint(TypeLiteral, Method, boolean)
for (Method method : cc.getDeclaredMethods()) {
if (method.isAnnotationPresent(javax.inject.Inject.class)
+ || method.isAnnotationPresent(jakarta.inject.Inject.class)
|| method.isAnnotationPresent(com.google.inject.Inject.class)) {
method.getAnnotatedParameterTypes();
method.getParameterAnnotations();
@@ -538,6 +540,7 @@ private void resolve(Class> c, Set> encountered) {
// See com.google.inject.spi.InjectionPoint(TypeLiteral, Field, boolean)
for (Field f : cc.getDeclaredFields()) {
if (f.isAnnotationPresent(javax.inject.Inject.class)
+ || f.isAnnotationPresent(jakarta.inject.Inject.class)
|| f.isAnnotationPresent(com.google.inject.Inject.class)) {
f.getAnnotations();
f.getAnnotatedType().getAnnotations();
diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java
index 6ea7375ff5a95..e6ba431691fb0 100644
--- a/core/src/main/java/hudson/Launcher.java
+++ b/core/src/main/java/hudson/Launcher.java
@@ -24,8 +24,6 @@
package hudson;
-import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;
-
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -63,7 +61,6 @@
import jenkins.tasks.filters.EnvVarsFilterRuleWrapper;
import jenkins.tasks.filters.EnvVarsFilterableBuilder;
import jenkins.util.MemoryReductionUtil;
-import org.apache.commons.io.input.NullInputStream;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -192,7 +189,7 @@ public final class ProcStarter {
@CheckForNull
protected FilePath pwd;
@CheckForNull
- protected OutputStream stdout = NULL_OUTPUT_STREAM, stderr;
+ protected OutputStream stdout = OutputStream.nullOutputStream(), stderr;
@CheckForNull
private TaskListener stdoutListener;
@CheckForNull
@@ -1512,7 +1509,7 @@ private static EnvVars inherit(@NonNull Map overrides) {
@SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for debugging")
public static boolean showFullPath = false;
- private static final NullInputStream NULL_INPUT_STREAM = new NullInputStream(0);
+ private static final InputStream NULL_INPUT_STREAM = InputStream.nullInputStream();
private static final Logger LOGGER = Logger.getLogger(Launcher.class.getName());
}
diff --git a/core/src/main/java/hudson/PluginFirstClassLoader.java b/core/src/main/java/hudson/PluginFirstClassLoader.java
deleted file mode 100644
index f652187a2963e..0000000000000
--- a/core/src/main/java/hudson/PluginFirstClassLoader.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Olivier Lamy
- *
- * 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;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import jenkins.util.AntClassLoader;
-
-/**
- * classLoader which use first /WEB-INF/lib/*.jar and /WEB-INF/classes before core classLoader
- * you must use the pluginFirstClassLoader true in the maven-hpi-plugin
- *
- * @author olamy
- * @since 1.371
- * @deprecated use {@link PluginFirstClassLoader2}
- */
-@Deprecated
-public class PluginFirstClassLoader
- extends AntClassLoader
-{
- public PluginFirstClassLoader() {
- super(null, false);
- }
-
- private List urls = new CopyOnWriteArrayList<>();
-
- @Override
- public void addPathFiles(Collection paths)
- throws IOException
- {
- for (File f : paths)
- {
- urls.add(f.toURI().toURL());
- addPathFile(f);
- }
- }
-
- /**
- * @return List of jar used by the plugin /WEB-INF/lib/*.jar and classes directory /WEB-INF/classes
- */
- public List getURLs()
- {
- return urls;
- }
-
- @Override
- protected Enumeration findResources(String name, boolean skipParent)
- throws IOException
- {
- return super.findResources(name, skipParent);
- }
-
- @Override
- public Enumeration findResources(String name)
- throws IOException
- {
- return super.findResources(name);
- }
-
- @Override
- public URL getResource(String name)
- {
- return super.getResource(name);
- }
-
- @Override
- public InputStream getResourceAsStream(String name)
- {
- return super.getResourceAsStream(name);
- }
-}
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index 0ea2594fad3ac..5d64485cfa1b8 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -1135,7 +1135,7 @@ protected void copyBundledPlugin(URL src, String fileName) throws IOException {
final JarEntry entry = entryName != null && jarFile != null ? jarFile.getJarEntry(entryName) : null;
if (entry != null) {
try (InputStream i = jarFile.getInputStream(entry)) {
- byte[] manifestBytes = IOUtils.toByteArray(i);
+ byte[] manifestBytes = i.readAllBytes();
in = new ByteArrayInputStream(manifestBytes);
}
} else {
diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java
index 44956a5b3f8a0..2161e501089f0 100644
--- a/core/src/main/java/hudson/PluginWrapper.java
+++ b/core/src/main/java/hudson/PluginWrapper.java
@@ -72,7 +72,6 @@
import jenkins.model.Jenkins;
import jenkins.plugins.DetachedPluginsUtil;
import jenkins.security.UpdateSiteWarningsMonitor;
-import jenkins.util.AntClassLoader;
import jenkins.util.URLClassLoader2;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;
@@ -414,12 +413,7 @@ public boolean isDeprecated() {
*/
@Restricted(Beta.class)
public void injectJarsToClasspath(File... jars) throws Exception {
- if (classLoader instanceof AntClassLoader) {
- for (File f : jars) {
- LOGGER.log(Level.CONFIG, () -> "Inserting " + f + " into " + shortName + " plugin's classpath");
- ((AntClassLoader) classLoader).addPathComponent(f);
- }
- } else if (classLoader instanceof URLClassLoader2) {
+ if (classLoader instanceof URLClassLoader2) {
for (File f : jars) {
LOGGER.log(Level.CONFIG, () -> "Inserting " + f + " into " + shortName + " plugin's classpath");
((URLClassLoader2) classLoader).addURL(f.toURI().toURL());
diff --git a/core/src/main/java/hudson/Proc.java b/core/src/main/java/hudson/Proc.java
index 61defc264f542..a36ac726ec5cb 100644
--- a/core/src/main/java/hudson/Proc.java
+++ b/core/src/main/java/hudson/Proc.java
@@ -33,7 +33,6 @@
import hudson.util.DaemonThreadFactory;
import hudson.util.ExceptionCatchingThreadFactory;
import hudson.util.NamingThreadFactory;
-import hudson.util.NullStream;
import hudson.util.ProcessTree;
import hudson.util.StreamCopyThread;
import java.io.File;
@@ -52,7 +51,6 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
-import org.apache.commons.io.input.NullInputStream;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -428,8 +426,8 @@ private static String calcName(String[] cmd) {
return String.join(" ", cmd);
}
- public static final InputStream SELFPUMP_INPUT = new NullInputStream(0);
- public static final OutputStream SELFPUMP_OUTPUT = new NullStream();
+ public static final InputStream SELFPUMP_INPUT = InputStream.nullInputStream();
+ public static final OutputStream SELFPUMP_OUTPUT = OutputStream.nullOutputStream();
}
/**
diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java
index 99c83866dff4d..3936071de2efd 100644
--- a/core/src/main/java/hudson/TcpSlaveAgentListener.java
+++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java
@@ -56,7 +56,6 @@
import jenkins.slaves.RemotingVersionInfo;
import jenkins.util.SystemProperties;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.output.NullOutputStream;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -315,6 +314,7 @@ private void respondHello(String header, Socket s) throws IOException {
if (header.startsWith("GET / ")) {
response = "HTTP/1.0 200 OK\r\n" +
"Content-Type: text/plain;charset=UTF-8\r\n" +
+ "X-Content-Type-Options: nosniff\r\n" +
"\r\n" +
"Jenkins-Agent-Protocols: " + getAgentProtocolNames() + "\r\n" +
"Jenkins-Version: " + Jenkins.VERSION + "\r\n" +
@@ -330,7 +330,9 @@ private void respondHello(String header, Socket s) throws IOException {
s.shutdownOutput();
InputStream i = s.getInputStream();
- IOUtils.copy(i, NullOutputStream.NULL_OUTPUT_STREAM);
+ try (OutputStream o = OutputStream.nullOutputStream()) {
+ IOUtils.copy(i, o);
+ }
s.shutdownInput();
}
}
diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java
index 07254e2b77e6a..6adba3022f55a 100644
--- a/core/src/main/java/hudson/Util.java
+++ b/core/src/main/java/hudson/Util.java
@@ -32,13 +32,14 @@
import hudson.model.TaskListener;
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
+import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
@@ -55,7 +56,9 @@
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
@@ -64,6 +67,7 @@
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@@ -108,9 +112,7 @@
import jenkins.util.SystemProperties;
import jenkins.util.io.PathRemover;
import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -247,15 +249,22 @@ public static String loadFile(@NonNull File logfile, @NonNull Charset charset) t
// contain unmappable and/or malformed byte sequences. We need to make
// sure that in such cases, no CharacterCodingException is thrown.
//
- // One approach that cannot be used is to call Files.newBufferedReader()
- // because there is a difference in how an InputStreamReader constructed
- // from a Charset and the reader returned by Files.newBufferedReader()
- // handle malformed and unmappable byte sequences for the specified
- // encoding; the latter is more picky and will throw an exception.
+ // One approach that cannot be used is Files.newBufferedReader, which
+ // creates its CharsetDecoder with the default behavior of reporting
+ // malformed input and unmappable character errors. The implementation
+ // of InputStreamReader(InputStream, Charset) has the desired behavior
+ // of replacing malformed input and unmappable character errors, but
+ // this implementation is not specified in the API contract. Therefore,
+ // we explicitly use a decoder with the desired behavior.
// See: https://issues.jenkins.io/browse/JENKINS-49060?focusedCommentId=325989&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-325989
- try {
- return FileUtils.readFileToString(logfile, charset);
- } catch (FileNotFoundException e) {
+ CharsetDecoder decoder = charset.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
+ try (InputStream is = Files.newInputStream(Util.fileToPath(logfile));
+ Reader isr = new InputStreamReader(is, decoder);
+ Reader br = new BufferedReader(isr)) {
+ return IOUtils.toString(br);
+ } catch (NoSuchFileException e) {
return "";
} catch (Exception e) {
throw new IOException("Failed to fully read " + logfile, e);
@@ -627,10 +636,11 @@ public static String ensureEndsWith(@CheckForNull String subject, @CheckForNull
public static String getDigestOf(@NonNull InputStream source) throws IOException {
try (source) {
MessageDigest md5 = getMd5();
- DigestInputStream in = new DigestInputStream(source, md5);
- // Note: IOUtils.copy() buffers the input internally, so there is no
- // need to use a BufferedInputStream.
- IOUtils.copy(in, NullOutputStream.NULL_OUTPUT_STREAM);
+ try (InputStream in = new DigestInputStream(source, md5); OutputStream out = OutputStream.nullOutputStream()) {
+ // Note: IOUtils.copy() buffers the input internally, so there is no
+ // need to use a BufferedInputStream.
+ IOUtils.copy(in, out);
+ }
return toHexString(md5.digest());
} catch (NoSuchAlgorithmException e) {
throw new IOException("MD5 not installed", e); // impossible
diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java
index 53af28828ddb6..61e378296f457 100644
--- a/core/src/main/java/hudson/cli/DisablePluginCommand.java
+++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java
@@ -131,7 +131,7 @@ private void printIndented(int indent, String format, String... arguments) {
System.arraycopy(arguments, 0, newArgs, 1, arguments.length);
String f = "%" + indent + "s" + format + "%n";
- stdout.format(f, newArgs);
+ stdout.format(f, (Object[]) newArgs);
}
}
diff --git a/core/src/main/java/hudson/console/AnnotatedLargeText.java b/core/src/main/java/hudson/console/AnnotatedLargeText.java
index e074044e806da..8e0a59bee2afc 100644
--- a/core/src/main/java/hudson/console/AnnotatedLargeText.java
+++ b/core/src/main/java/hudson/console/AnnotatedLargeText.java
@@ -34,6 +34,7 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.remoting.ObjectInputStreamEx;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -49,7 +50,6 @@
import javax.crypto.CipherOutputStream;
import jenkins.model.Jenkins;
import jenkins.security.CryptoConfidentialKey;
-import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jenkinsci.remoting.util.AnonymousClassWarnings;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java
index 8183934aedcec..20ded06e98625 100644
--- a/core/src/main/java/hudson/console/ConsoleNote.java
+++ b/core/src/main/java/hudson/console/ConsoleNote.java
@@ -34,8 +34,8 @@
import hudson.model.Run;
import hudson.remoting.ClassFilter;
import hudson.remoting.ObjectInputStreamEx;
-import hudson.util.IOUtils;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
@@ -54,7 +54,7 @@
import jenkins.security.HMACConfidentialKey;
import jenkins.util.JenkinsJVM;
import jenkins.util.SystemProperties;
-import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.BuildListener;
import org.jenkinsci.remoting.util.AnonymousClassWarnings;
import org.kohsuke.accmod.Restricted;
@@ -306,12 +306,12 @@ public static void skip(DataInputStream in) throws IOException {
DataInputStream decoded = new DataInputStream(Base64.getDecoder().wrap(in));
int macSz = - decoded.readInt();
if (macSz > 0) { // new format
- IOUtils.skip(decoded, macSz);
+ IOUtils.skipFully(decoded, macSz);
int sz = decoded.readInt();
- IOUtils.skip(decoded, sz);
+ IOUtils.skipFully(decoded, sz);
} else { // old format
int sz = -macSz;
- IOUtils.skip(decoded, sz);
+ IOUtils.skipFully(decoded, sz);
}
byte[] postamble = new byte[POSTAMBLE.length];
diff --git a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java
index acfa1c7867b64..875a3e4ef7e05 100644
--- a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java
+++ b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java
@@ -35,6 +35,7 @@
import hudson.model.TaskListener;
import hudson.util.StreamTaskListener;
import hudson.util.jna.DotNet;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
@@ -46,7 +47,6 @@
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Move;
diff --git a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java
index 961cf1358b52a..2cf936679a68e 100644
--- a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java
@@ -32,6 +32,7 @@
import hudson.Util;
import hudson.util.StreamTaskListener;
import hudson.util.jna.Kernel32;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
@@ -43,7 +44,6 @@
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.output.ByteArrayOutputStream;
/**
* {@link Lifecycle} for Hudson installed as Windows service.
diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index 0984690980d1d..92900e678445f 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -940,8 +940,6 @@ public Void call() throws IOException {
}
});
Jenkins.get().rebuildDependencyGraphAsync();
-
- SaveableListener.fireOnChange(this, getConfigFile());
}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 809c215a61b21..099d0d190ded3 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -953,6 +953,11 @@ public R getNearestOldBuild(int n) {
return buildMixIn.getNearestOldBuild(n);
}
+ @Override
+ protected List getEstimatedDurationCandidates() {
+ return buildMixIn.getEstimatedDurationCandidates();
+ }
+
/**
* Type token for the corresponding build type.
* The build class must have two constructors:
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index f44c97dab34ca..31e0c0bc804bd 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -110,6 +110,7 @@
import jenkins.util.ErrorLoggingExecutorService;
import jenkins.util.Listeners;
import jenkins.util.SystemProperties;
+import jenkins.widgets.HasWidgets;
import net.jcip.annotations.GuardedBy;
import org.apache.commons.lang.StringUtils;
import org.jenkins.ui.icon.Icon;
@@ -160,7 +161,7 @@
* @author Kohsuke Kawaguchi
*/
@ExportedBean
-public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener, DescriptorByNameOwner, StaplerProxy {
+public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener, DescriptorByNameOwner, StaplerProxy, HasWidgets {
private final CopyOnWriteArrayList executors = new CopyOnWriteArrayList<>();
// TODO:
diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java
index 211c795ee95ca..cad9089b776f9 100644
--- a/core/src/main/java/hudson/model/ComputerSet.java
+++ b/core/src/main/java/hudson/model/ComputerSet.java
@@ -57,6 +57,7 @@
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu.ContextMenu;
import jenkins.util.Timer;
+import jenkins.widgets.HasWidgets;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
@@ -75,7 +76,7 @@
* @author Kohsuke Kawaguchi
*/
@ExportedBean
-public final class ComputerSet extends AbstractModelObject implements Describable, ModelObjectWithChildren {
+public final class ComputerSet extends AbstractModelObject implements Describable, ModelObjectWithChildren, HasWidgets {
/**
* This is the owner that persists {@link #monitors}.
*/
diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
index b88e478740a62..6f8bf2e6ee77f 100644
--- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
+++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
@@ -869,5 +869,5 @@ private static OpenOption[] getOpenOptions() {
private static final Logger LOGGER = Logger.getLogger(DirectoryBrowserSupport.class.getName());
@Restricted(NoExternalUse.class)
- public static final String DEFAULT_CSP_VALUE = "sandbox; default-src 'none'; img-src 'self'; style-src 'self';";
+ public static final String DEFAULT_CSP_VALUE = "sandbox allow-same-origin; default-src 'none'; img-src 'self'; style-src 'self';";
}
diff --git a/core/src/main/java/hudson/model/FileParameterValue.java b/core/src/main/java/hudson/model/FileParameterValue.java
index e11d31c891fae..e9e30e63463bf 100644
--- a/core/src/main/java/hudson/model/FileParameterValue.java
+++ b/core/src/main/java/hudson/model/FileParameterValue.java
@@ -46,7 +46,6 @@
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.util.FileItemHeadersImpl;
import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -288,9 +287,7 @@ public long getSize() {
@Override
public byte[] get() {
try {
- try (InputStream inputStream = Files.newInputStream(file.toPath())) {
- return IOUtils.toByteArray(inputStream);
- }
+ return Files.readAllBytes(file.toPath());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
diff --git a/core/src/main/java/hudson/model/JDK.java b/core/src/main/java/hudson/model/JDK.java
index 50de1401f0299..addb9202f3ce6 100644
--- a/core/src/main/java/hudson/model/JDK.java
+++ b/core/src/main/java/hudson/model/JDK.java
@@ -34,11 +34,11 @@
import hudson.tools.ToolInstaller;
import hudson.tools.ToolProperty;
import hudson.util.FormValidation;
-import hudson.util.NullStream;
import hudson.util.StreamTaskListener;
import hudson.util.XStream2;
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
@@ -164,7 +164,7 @@ public JDK forEnvironment(EnvVars environment) {
*/
public static boolean isDefaultJDKValid(Node n) {
try {
- TaskListener listener = new StreamTaskListener(new NullStream());
+ TaskListener listener = new StreamTaskListener(OutputStream.nullOutputStream());
Launcher launcher = n.createLauncher(listener);
return launcher.launch().cmds("java", "-fullversion").stdout(listener).join() == 0;
} catch (IOException | InterruptedException e) {
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index dd9d88ff25c9c..9ae6056092922 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -98,6 +98,7 @@
import jenkins.scm.RunWithSCM;
import jenkins.security.HexStringConfidentialKey;
import jenkins.triggers.SCMTriggerItem;
+import jenkins.widgets.HasWidgets;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.apache.commons.io.FileUtils;
@@ -135,7 +136,7 @@
* @author Kohsuke Kawaguchi
*/
public abstract class Job, RunT extends Run>
- extends AbstractItem implements ExtensionPoint, StaplerOverridable, ModelObjectWithChildren {
+ extends AbstractItem implements ExtensionPoint, StaplerOverridable, ModelObjectWithChildren, HasWidgets {
private static final Logger LOGGER = Logger.getLogger(Job.class.getName());
@@ -611,15 +612,10 @@ public Collection> getOverrides() {
return r;
}
- public List getWidgets() {
- ArrayList r = new ArrayList<>();
- r.add(createHistoryWidget());
- return r;
- }
-
/**
- * @see LazyBuildMixIn#createHistoryWidget
+ * @deprecated see {@link LazyBuildMixIn#createHistoryWidget()}
*/
+ @Deprecated(forRemoval = true, since = "2.410")
protected HistoryWidget createHistoryWidget() {
return new HistoryWidget(this, getBuilds(), HISTORY_ADAPTER);
}
@@ -1013,6 +1009,7 @@ public List getLastBuildsOverThreshold(int numberOfBuilds, Result threshol
* Failing to find 3 of those, it will return up to 3 last unsuccessful builds.
*
* In any case it will not go more than 6 builds into the past to avoid costly build loading.
+ * @see LazyBuildMixIn#getEstimatedDurationCandidates
*/
protected List getEstimatedDurationCandidates() {
List candidates = new ArrayList<>(3);
diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java
index 3476b81cc035b..77852e994ecea 100644
--- a/core/src/main/java/hudson/model/ListView.java
+++ b/core/src/main/java/hudson/model/ListView.java
@@ -452,7 +452,7 @@ protected void submit(StaplerRequest req) throws ServletException, FormException
}
for (TopLevelItem item : items) {
String relativeNameFrom = item.getRelativeNameFrom(getOwner().getItemGroup());
- if (req.getParameter(relativeNameFrom) != null) {
+ if (req.getParameter("item_" + relativeNameFrom) != null) {
jobNames.add(relativeNameFrom);
}
}
diff --git a/core/src/main/java/hudson/model/ManageJenkinsAction.java b/core/src/main/java/hudson/model/ManageJenkinsAction.java
index a0eda48159dbf..c6c37a57662a0 100644
--- a/core/src/main/java/hudson/model/ManageJenkinsAction.java
+++ b/core/src/main/java/hudson/model/ManageJenkinsAction.java
@@ -79,13 +79,13 @@ public ContextMenu doContextMenu(StaplerRequest request, StaplerResponse respons
* menu.
*/
@Restricted(NoExternalUse.class)
- public void addContextMenuItem(ContextMenu menu, String url, String icon, String iconXml, String text, boolean post, boolean requiresConfirmation, Badge badge) {
+ public void addContextMenuItem(ContextMenu menu, String url, String icon, String iconXml, String text, boolean post, boolean requiresConfirmation, Badge badge, String message) {
if (Stapler.getCurrentRequest().findAncestorObject(this.getClass()) != null || !Util.isSafeToRedirectTo(url)) {
// Default behavior if the URL is absolute or scheme-relative, or the current object is an ancestor (i.e. would resolve correctly)
- menu.add(url, icon, iconXml, text, post, requiresConfirmation, badge);
+ menu.add(url, icon, iconXml, text, post, requiresConfirmation, badge, message);
return;
}
// If neither is the case, rewrite the relative URL to point to inside the /manage/ URL space
- menu.add("manage/" + url, icon, iconXml, text, post, requiresConfirmation, badge);
+ menu.add("manage/" + url, icon, iconXml, text, post, requiresConfirmation, badge, message);
}
}
diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java
index 7a4c7c7a93dd4..910d4637f28fa 100644
--- a/core/src/main/java/hudson/model/Queue.java
+++ b/core/src/main/java/hudson/model/Queue.java
@@ -26,6 +26,7 @@
package hudson.model;
import static hudson.init.InitMilestone.JOB_CONFIG_ADAPTED;
+import static hudson.model.Item.CANCEL;
import static hudson.util.Iterators.reverse;
import com.google.common.annotations.VisibleForTesting;
@@ -112,6 +113,7 @@
import jenkins.model.Jenkins;
import jenkins.model.queue.AsynchronousExecution;
import jenkins.model.queue.CompositeCauseOfBlockage;
+import jenkins.model.queue.QueueItem;
import jenkins.security.QueueItemAuthenticator;
import jenkins.security.QueueItemAuthenticatorProvider;
import jenkins.security.stapler.StaplerAccessibleType;
@@ -1929,25 +1931,34 @@ default CauseOfBlockage getCauseOfBlockage() {
* Checks the permission to see if the current user can abort this executable.
* Returns normally from this method if it's OK.
*
- * NOTE: If you have implemented {@link AccessControlled} this should just be
+ * NOTE: If you have implemented {@link AccessControlled} this defaults to
* {@code checkPermission(hudson.model.Item.CANCEL);}
*
* @throws AccessDeniedException if the permission is not granted.
*/
- void checkAbortPermission();
+ default void checkAbortPermission() {
+ if (this instanceof AccessControlled) {
+ ((AccessControlled) this).checkPermission(CANCEL);
+ }
+ }
/**
* Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
* instead of exception.
* Also used by default for {@link hudson.model.Queue.Item#hasCancelPermission}.
*
- * NOTE: If you have implemented {@link AccessControlled} this should just be
+ * NOTE: If you have implemented {@link AccessControlled} this returns by default
* {@code return hasPermission(hudson.model.Item.CANCEL);}
*
* @return false
* if the user doesn't have the permission.
*/
- boolean hasAbortPermission();
+ default boolean hasAbortPermission() {
+ if (this instanceof AccessControlled) {
+ return ((AccessControlled) this).hasPermission(CANCEL);
+ }
+ return true;
+ }
/**
* Returns the URL of this task relative to the context root of the application.
@@ -2117,7 +2128,7 @@ default long getEstimatedDuration() {
* Item in a queue.
*/
@ExportedBean(defaultVisibility = 999)
- public abstract static class Item extends Actionable {
+ public abstract static class Item extends Actionable implements QueueItem {
private final long id;
@@ -2128,6 +2139,7 @@ public abstract static class Item extends Actionable {
* @since 1.601
*/
@Exported
+ @Override
public long getId() {
return id;
}
@@ -2147,12 +2159,19 @@ public int getIdLegacy() {
* Project to be built.
*/
@Exported
+ @NonNull
public final Task task;
private /*almost final*/ transient FutureImpl future;
private final long inQueueSince;
+ @Override
+ @NonNull
+ public Task getTask() {
+ return task;
+ }
+
/**
* Build is blocked because another build is in progress,
* required {@link Resource}s are not available, or otherwise blocked
@@ -2173,6 +2192,7 @@ public int getIdLegacy() {
* True if the item is starving for an executor for too long.
*/
@Exported
+ @Override
public boolean isStuck() { return false; }
/**
@@ -2188,6 +2208,7 @@ public long getInQueueSince() {
* Returns a human readable presentation of how long this item is already in the queue.
* E.g. something like '3 minutes 40 seconds'
*/
+ @Override
public String getInQueueForString() {
long duration = System.currentTimeMillis() - this.inQueueSince;
return Util.getTimeSpanString(duration);
@@ -2250,6 +2271,7 @@ public final List getCauses() {
}
@Restricted(DoNotUse.class) // used from Jelly
+ @Override
public String getCausesDescription() {
List causes = getCauses();
StringBuilder s = new StringBuilder();
@@ -2259,15 +2281,11 @@ public String getCausesDescription() {
return s.toString();
}
- protected Item(Task task, List actions, long id, FutureImpl future) {
- this.task = task;
- this.id = id;
- this.future = future;
- this.inQueueSince = System.currentTimeMillis();
- for (Action action : actions) addAction(action);
+ protected Item(@NonNull Task task, @NonNull List actions, long id, FutureImpl future) {
+ this(task, actions, id, future, System.currentTimeMillis());
}
- protected Item(Task task, List actions, long id, FutureImpl future, long inQueueSince) {
+ protected Item(@NonNull Task task, @NonNull List actions, long id, FutureImpl future, long inQueueSince) {
this.task = task;
this.id = id;
this.future = future;
@@ -2297,6 +2315,7 @@ public String getUrl() {
* Gets a human-readable status message describing why it's in the queue.
*/
@Exported
+ @Override
public final String getWhy() {
CauseOfBlockage cob = getCauseOfBlockage();
return cob != null ? cob.getShortDescription() : null;
@@ -2312,6 +2331,7 @@ public final String getWhy() {
* @return String
*/
@Exported
+ @Override
public String getParams() {
StringBuilder s = new StringBuilder();
for (ParametersAction pa : getActions(ParametersAction.class)) {
@@ -2322,19 +2342,6 @@ public String getParams() {
return s.toString();
}
- /**
- * Checks whether a scheduled item may be canceled.
- * @return by default, the same as {@link hudson.model.Queue.Task#hasAbortPermission}
- */
- public boolean hasCancelPermission() {
- return task.hasAbortPermission();
- }
-
- @Override
- public String getDisplayName() {
- return null;
- }
-
@Override
public String getSearchUrl() {
return null;
diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java
index 113b5a742efb5..87644b3aa343b 100644
--- a/core/src/main/java/hudson/model/Run.java
+++ b/core/src/main/java/hudson/model/Run.java
@@ -1242,12 +1242,7 @@ private static int addArtifacts(@NonNull VirtualFile dir,
/**
* Maximum number of artifacts to list before using switching to the tree view.
*/
- public static final int LIST_CUTOFF = Integer.parseInt(SystemProperties.getString("hudson.model.Run.ArtifactList.listCutoff", "16"));
-
- /**
- * Maximum number of artifacts to show in tree view before just showing a link.
- */
- public static final int TREE_CUTOFF = Integer.parseInt(SystemProperties.getString("hudson.model.Run.ArtifactList.treeCutoff", "40"));
+ public static final int LIST_CUTOFF = Integer.parseInt(SystemProperties.getString("hudson.model.Run.ArtifactList.listCutoff", "20"));
// ..and then "too many"
diff --git a/core/src/main/java/hudson/model/Slave.java b/core/src/main/java/hudson/model/Slave.java
index d6727ad260587..22eb63cd2fc6f 100644
--- a/core/src/main/java/hudson/model/Slave.java
+++ b/core/src/main/java/hudson/model/Slave.java
@@ -71,7 +71,6 @@
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.WorkspaceLocator;
import jenkins.util.SystemProperties;
-import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -470,7 +469,7 @@ public URL getURL() throws IOException {
public byte[] readFully() throws IOException {
try (InputStream in = connect().getInputStream()) {
- return IOUtils.toByteArray(in);
+ return in.readAllBytes();
}
}
diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java
index b9ca1d2001b8e..7ea06f20191e4 100644
--- a/core/src/main/java/hudson/model/UpdateCenter.java
+++ b/core/src/main/java/hudson/model/UpdateCenter.java
@@ -1665,7 +1665,7 @@ public void run() {
if (e.getMessage().contains("Connection timed out")) {
// Google can't be down, so this is probably a proxy issue
connectionStates.put(ConnectionStatus.INTERNET, ConnectionStatus.FAILED);
- statuses.add(Messages.UpdateCenter_Status_ConnectionFailed(Functions.xmlEscape(connectionCheckUrl)));
+ statuses.add(Messages.UpdateCenter_Status_ConnectionFailed(Functions.xmlEscape(connectionCheckUrl), Jenkins.get().getRootUrl()));
return;
}
}
@@ -1687,7 +1687,7 @@ public void run() {
statuses.add(Messages.UpdateCenter_Status_Success());
} catch (UnknownHostException e) {
connectionStates.put(ConnectionStatus.UPDATE_SITE, ConnectionStatus.FAILED);
- statuses.add(Messages.UpdateCenter_Status_UnknownHostException(Functions.xmlEscape(e.getMessage())));
+ statuses.add(Messages.UpdateCenter_Status_UnknownHostException(Functions.xmlEscape(e.getMessage()), Jenkins.get().getRootUrl()));
addStatus(e);
error = e;
} catch (Exception e) {
diff --git a/core/src/main/java/hudson/model/UsageStatistics.java b/core/src/main/java/hudson/model/UsageStatistics.java
index 0a13c0983b160..cbc356aa3b773 100644
--- a/core/src/main/java/hudson/model/UsageStatistics.java
+++ b/core/src/main/java/hudson/model/UsageStatistics.java
@@ -35,6 +35,7 @@
import hudson.node_monitors.ArchitectureMonitor;
import hudson.security.Permission;
import hudson.util.Secret;
+import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
@@ -65,7 +66,6 @@
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
-import org.apache.commons.io.output.ByteArrayOutputStream;
import org.kohsuke.stapler.StaplerRequest;
/**
diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java
index 91c34d8c44875..3f9c2765ed896 100644
--- a/core/src/main/java/hudson/model/View.java
+++ b/core/src/main/java/hudson/model/View.java
@@ -59,7 +59,6 @@
import hudson.util.RunList;
import hudson.util.XStream2;
import hudson.views.ListViewColumn;
-import hudson.widgets.Widget;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -103,6 +102,7 @@
import jenkins.security.stapler.StaplerAccessibleType;
import jenkins.util.ProgressiveRendering;
import jenkins.util.xml.XMLUtils;
+import jenkins.widgets.HasWidgets;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
@@ -147,7 +147,7 @@
* @see ViewGroup
*/
@ExportedBean
-public abstract class View extends AbstractModelObject implements AccessControlled, Describable, ExtensionPoint, Saveable, ModelObjectWithChildren, DescriptorByNameOwner {
+public abstract class View extends AbstractModelObject implements AccessControlled, Describable, ExtensionPoint, Saveable, ModelObjectWithChildren, DescriptorByNameOwner, HasWidgets {
/**
* Container of this view. Set right after the construction
@@ -412,16 +412,6 @@ public boolean isFilterQueue() {
return filterQueue;
}
- /**
- * Gets the {@link Widget}s registered on this object.
- *
- *
- * For now, this just returns the widgets registered to Hudson.
- */
- public List getWidgets() {
- return Collections.unmodifiableList(Jenkins.get().getWidgets());
- }
-
/**
* If this view uses {@code } for rendering, this method returns columns to be displayed.
*/
diff --git a/core/src/main/java/hudson/security/FullControlOnceLoggedInAuthorizationStrategy.java b/core/src/main/java/hudson/security/FullControlOnceLoggedInAuthorizationStrategy.java
index 1ff59f295569c..634fc43b9b660 100644
--- a/core/src/main/java/hudson/security/FullControlOnceLoggedInAuthorizationStrategy.java
+++ b/core/src/main/java/hudson/security/FullControlOnceLoggedInAuthorizationStrategy.java
@@ -28,9 +28,9 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.model.Descriptor;
+import jakarta.inject.Inject;
import java.util.Collections;
import java.util.List;
-import javax.inject.Inject;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
diff --git a/core/src/main/java/hudson/slaves/Cloud.java b/core/src/main/java/hudson/slaves/Cloud.java
index 8ab456817a5a1..6ec3ed3b489d5 100644
--- a/core/src/main/java/hudson/slaves/Cloud.java
+++ b/core/src/main/java/hudson/slaves/Cloud.java
@@ -46,13 +46,24 @@
import hudson.security.PermissionScope;
import hudson.slaves.NodeProvisioner.PlannedNode;
import hudson.util.DescriptorList;
+import hudson.util.FormApply;
+import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.Future;
+import javax.servlet.ServletException;
import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+import org.apache.commons.lang.Validate;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.HttpRedirect;
+import org.kohsuke.stapler.HttpResponse;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.interceptor.RequirePOST;
+import org.kohsuke.stapler.verb.POST;
/**
* Creates {@link Node}s to dynamically expand/shrink the agents attached to Hudson.
@@ -104,9 +115,10 @@ public abstract class Cloud extends Actionable implements ExtensionPoint, Descri
* This is expected to be short ID-like string that does not contain any character unsafe as variable name or
* URL path token.
*/
- public final String name;
+ public String name;
protected Cloud(String name) {
+ Validate.notEmpty(name, Messages.Cloud_RequiredName());
this.name = name;
}
@@ -122,7 +134,7 @@ public String getDisplayName() {
* @return Jenkins relative URL.
*/
public @NonNull String getUrl() {
- return "cloud/" + Util.rawEncode(name);
+ return "cloud/" + Util.rawEncode(name) + "/";
}
@Override
@@ -275,6 +287,58 @@ public static void registerPermissions() {
Objects.hash(PERMISSION_SCOPE, PROVISION);
}
+ public String getIcon() {
+ return "symbol-cloud";
+ }
+
+ public String getIconClassName() {
+ return "symbol-cloud";
+ }
+
+ @SuppressWarnings("unused") // stapler
+ public String getIconAltText() {
+ return getClass().getSimpleName().replace("Cloud", "");
+ }
+
+ /**
+ * Deletes the cloud.
+ */
+ @RequirePOST
+ public HttpResponse doDoDelete() throws IOException {
+ checkPermission(Jenkins.ADMINISTER);
+ Jenkins.get().clouds.remove(this);
+ return new HttpRedirect("..");
+ }
+
+ /**
+ * Accepts the update to the node configuration.
+ */
+ @POST
+ public HttpResponse doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException {
+ checkPermission(Jenkins.ADMINISTER);
+
+ Jenkins j = Jenkins.get();
+ Cloud cloud = j.getCloud(this.name);
+ if (cloud == null) {
+ throw new ServletException("No such cloud " + this.name);
+ }
+ Cloud result = cloud.reconfigure(req, req.getSubmittedForm());
+ String proposedName = result.name;
+ if (!proposedName.equals(this.name)
+ && j.getCloud(proposedName) != null) {
+ throw new Descriptor.FormException(jenkins.agents.Messages.CloudSet_CloudAlreadyExists(proposedName), "name");
+ }
+ j.clouds.replace(this, result);
+ j.save();
+ // take the user back to the cloud top page.
+ return FormApply.success(".");
+ }
+
+ public Cloud reconfigure(@NonNull final StaplerRequest req, JSONObject form) throws Descriptor.FormException {
+ if (form == null) return null;
+ return getDescriptor().newInstance(req, form);
+ }
+
/**
* Parameter object for {@link hudson.slaves.Cloud}.
* @since 2.259
diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java
index ccc90ec0873b2..b7610683be6de 100644
--- a/core/src/main/java/hudson/slaves/SlaveComputer.java
+++ b/core/src/main/java/hudson/slaves/SlaveComputer.java
@@ -56,7 +56,6 @@
import hudson.security.ACLContext;
import hudson.slaves.OfflineCause.ChannelTermination;
import hudson.util.Futures;
-import hudson.util.NullStream;
import hudson.util.RingBufferLogHandler;
import hudson.util.StreamTaskListener;
import hudson.util.VersionNumber;
@@ -330,10 +329,11 @@ protected Future> _connect(boolean forceReconnect) {
@Override
public void taskAccepted(Executor executor, Queue.Task task) {
+ LOGGER.log(Level.FINER, "Accepted {0} on {1}", new Object[] {task.toString(), executor.getOwner().getDisplayName()});
+
if (launcher instanceof ExecutorListener) {
((ExecutorListener) launcher).taskAccepted(executor, task);
}
-
//getNode() can return null at indeterminate times when nodes go offline
Slave node = getNode();
if (node != null && node.getRetentionStrategy() instanceof ExecutorListener) {
@@ -343,6 +343,7 @@ public void taskAccepted(Executor executor, Queue.Task task) {
@Override
public void taskStarted(Executor executor, Queue.Task task) {
+ LOGGER.log(Level.FINER, "Started {0} on {1}", new Object[] {task.toString(), executor.getOwner().getDisplayName()});
if (launcher instanceof ExecutorListener) {
((ExecutorListener) launcher).taskStarted(executor, task);
}
@@ -354,6 +355,7 @@ public void taskStarted(Executor executor, Queue.Task task) {
@Override
public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
+ LOGGER.log(Level.FINE, "Completed {0} on {1}", new Object[] {task.toString(), executor.getOwner().getDisplayName()});
if (launcher instanceof ExecutorListener) {
((ExecutorListener) launcher).taskCompleted(executor, task, durationMS);
}
@@ -365,6 +367,7 @@ public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
@Override
public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
+ LOGGER.log(Level.FINE, "Completed with problems {0} on {1}", new Object[] {task.toString(), executor.getOwner().getDisplayName()});
if (launcher instanceof ExecutorListener) {
((ExecutorListener) launcher).taskCompletedWithProblems(executor, task, durationMS, problems);
}
@@ -386,7 +389,7 @@ public OutputStream openLogFile() {
return log;
} catch (IOException e) {
logger.log(Level.SEVERE, "Failed to create log file " + getLogFile(), e);
- return new NullStream();
+ return OutputStream.nullOutputStream();
}
}
diff --git a/core/src/main/java/hudson/tasks/Maven.java b/core/src/main/java/hudson/tasks/Maven.java
index db05022bc9b99..4230da15d6f21 100644
--- a/core/src/main/java/hudson/tasks/Maven.java
+++ b/core/src/main/java/hudson/tasks/Maven.java
@@ -51,7 +51,6 @@
import hudson.tools.ToolProperty;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
-import hudson.util.NullStream;
import hudson.util.StreamTaskListener;
import hudson.util.VariableResolver;
import hudson.util.VariableResolver.ByMap;
@@ -59,6 +58,7 @@
import hudson.util.XStream2;
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -665,7 +665,7 @@ private static File getExeFile(String execName, String home) {
*/
public boolean getExists() {
try {
- return getExecutable(new LocalLauncher(new StreamTaskListener(new NullStream()))) != null;
+ return getExecutable(new LocalLauncher(new StreamTaskListener(OutputStream.nullOutputStream()))) != null;
} catch (IOException | InterruptedException e) {
return false;
}
diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java
index f7415597d1a72..c5cb6bb751f97 100644
--- a/core/src/main/java/hudson/triggers/Trigger.java
+++ b/core/src/main/java/hudson/triggers/Trigger.java
@@ -91,6 +91,7 @@ public abstract class Trigger implements Describable>
* @see Items#currentlyUpdatingByXml
*/
public void start(J project, boolean newInstance) {
+ LOGGER.finer(() -> "Starting " + this + " on " + project);
this.job = project;
try { // reparse the tabs with the job as the hash
@@ -206,6 +207,10 @@ protected Object readResolve() throws ObjectStreamException {
return this;
}
+ @Override
+ public String toString() {
+ return super.toString() + "[" + spec + "]";
+ }
/**
* Runs every minute to check {@link TimerTrigger} and schedules build.
@@ -283,7 +288,9 @@ public void run(AbstractProject p) {
// Process all triggers, except SCMTriggers when synchronousPolling is set
for (TriggeredItem p : inst.allItems(TriggeredItem.class)) {
+ LOGGER.finer(() -> "considering " + p);
for (Trigger t : p.getTriggers().values()) {
+ LOGGER.finer(() -> "found trigger " + t);
if (!(p instanceof AbstractProject && t instanceof SCMTrigger && scmd.synchronousPolling)) {
if (t != null && t.spec != null && t.tabs != null) {
LOGGER.log(Level.FINE, "cron checking {0} with spec ‘{1}’", new Object[]{p, t.spec.trim()});
@@ -292,6 +299,9 @@ public void run(AbstractProject p) {
LOGGER.log(Level.CONFIG, "cron triggered {0}", p);
try {
long begin_time = System.currentTimeMillis();
+ if (t.job == null) {
+ LOGGER.fine(() -> t + " not yet started on " + p + " but trying to run anyway");
+ }
t.run();
long end_time = System.currentTimeMillis();
if (end_time - begin_time > CRON_THRESHOLD * 1000) {
diff --git a/core/src/main/java/hudson/util/DescribableList.java b/core/src/main/java/hudson/util/DescribableList.java
index 0fa5e9fa70f18..a73dac196b2ca 100644
--- a/core/src/main/java/hudson/util/DescribableList.java
+++ b/core/src/main/java/hudson/util/DescribableList.java
@@ -100,7 +100,11 @@ public void setOwner(Owner owner) {
* Removes all instances of the same type, then add the new one.
*/
public void replace(T item) throws IOException {
- removeAll((Class) item.getClass());
+ for (T t : data) {
+ if (t.getClass() == item.getClass()) {
+ data.remove(t);
+ }
+ }
data.add(item);
onModified();
}
diff --git a/core/src/main/java/hudson/util/DoubleLaunchChecker.java b/core/src/main/java/hudson/util/DoubleLaunchChecker.java
index d541452ca141d..6fac015c02245 100644
--- a/core/src/main/java/hudson/util/DoubleLaunchChecker.java
+++ b/core/src/main/java/hudson/util/DoubleLaunchChecker.java
@@ -24,27 +24,22 @@
package hudson.util;
-import static hudson.init.InitMilestone.JOB_CONFIG_ADAPTED;
-import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.Main;
import hudson.Util;
-import hudson.init.Initializer;
-import hudson.triggers.SafeTimerTask;
+import hudson.model.AdministrativeMonitor;
+import hudson.model.AperiodicWork;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.time.Duration;
import java.util.Random;
-import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.servlet.ServletException;
import jenkins.model.Jenkins;
-import jenkins.util.Timer;
-import org.kohsuke.stapler.StaplerRequest;
-import org.kohsuke.stapler.StaplerResponse;
-import org.kohsuke.stapler.interceptor.RequirePOST;
/**
* Makes sure that no other Hudson uses our {@code JENKINS_HOME} directory,
@@ -63,21 +58,15 @@
* @author Kohsuke Kawaguchi
* @since 1.178
*/
-@SuppressFBWarnings(value = "PREDICTABLE_RANDOM", justification = "The random is just used for load distribution.")
-public class DoubleLaunchChecker {
+@Extension
+public class DoubleLaunchChecker extends AdministrativeMonitor {
/**
* The timestamp of the owner file when we updated it for the last time.
* 0 to indicate that there was no update before.
*/
private long lastWriteTime = 0L;
- /**
- * Once the error is reported, the user can choose to ignore and proceed anyway,
- * in which case the flag is set to true.
- */
- private boolean ignore = false;
-
- private final Random random = new Random();
+ private boolean activated;
public final File home;
@@ -91,20 +80,29 @@ public DoubleLaunchChecker() {
home = Jenkins.get().getRootDir();
}
+ @Override
+ public String getDisplayName() {
+ return Messages.DoubleLaunchChecker_duplicate_jenkins_checker();
+ }
+
+ @Override
+ public boolean isActivated() {
+ return activated;
+ }
+
protected void execute() {
+ LOGGER.fine("running detector");
File timestampFile = new File(home, ".owner");
long t = timestampFile.lastModified();
- if (t != 0 && lastWriteTime != 0 && t != lastWriteTime && !ignore) {
+ if (t != 0 && lastWriteTime != 0 && t != lastWriteTime && isEnabled()) {
try {
collidingId = Files.readString(Util.fileToPath(timestampFile), Charset.defaultCharset());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to read collision file", e);
}
// we noticed that someone else have updated this file.
- // switch GUI to display this error.
- // TODO seems drastic; could this just be switched to an AdministrativeMonitor?
- Jenkins.get().servletContext.setAttribute("app", this);
+ activated = true;
LOGGER.severe("Collision detected. timestamp=" + t + ", expected=" + lastWriteTime);
// we need to continue updating this file, so that the other Hudson would notice the problem, too.
}
@@ -113,11 +111,10 @@ protected void execute() {
Files.writeString(Util.fileToPath(timestampFile), getId(), Charset.defaultCharset());
lastWriteTime = timestampFile.lastModified();
} catch (IOException e) {
+ LOGGER.log(Level.FINE, null, e);
// if failed to write, err on the safe side and assume things are OK.
lastWriteTime = 0;
}
-
- schedule();
}
/**
@@ -131,44 +128,29 @@ public String getCollidingId() {
return collidingId;
}
- /**
- * Schedules the next execution.
- */
- public void schedule() {
- // randomize the scheduling so that multiple Hudson instances will write at the file at different time
- long MINUTE = 1000 * 60; // TODO use TimeUnit.MINUTE.toMillis
-
- Timer.get()
- .schedule(new SafeTimerTask() {
- @Override
- protected void doRun() {
- execute();
- }
- }, (random.nextInt(30) + 60) * MINUTE, TimeUnit.MILLISECONDS);
- }
+ @SuppressFBWarnings(value = "PREDICTABLE_RANDOM", justification = "The random is just used for load distribution.")
+ @Extension
+ public static final class Schedule extends AperiodicWork {
- @Initializer(after = JOB_CONFIG_ADAPTED)
- public static void init() {
- // TODO AperiodicWork would be more idiomatic
- new DoubleLaunchChecker().schedule();
- }
+ private final Random random = new Random();
- /**
- * Serve all URLs with the index view.
- */
- public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
- rsp.setStatus(SC_INTERNAL_SERVER_ERROR);
- req.getView(this, "index.jelly").forward(req, rsp);
- }
+ @Override
+ public AperiodicWork getNewInstance() {
+ // Awkward to use DoubleLaunchChecker itself as the AperiodicWork since it is stateful, and we may not return this.
+ return new Schedule();
+ }
+
+ @Override
+ public long getRecurrencePeriod() {
+ // randomize the scheduling so that multiple Jenkins instances will write at the file at different time
+ return (Main.isUnitTest ? Duration.ofSeconds(random.nextInt(10) + 20) : Duration.ofMinutes(random.nextInt(30) + 60)).toMillis();
+ }
+
+ @Override
+ protected void doAperiodicRun() {
+ ExtensionList.lookupSingleton(DoubleLaunchChecker.class).execute();
+ }
- /**
- * Ignore the problem and go back to using Hudson.
- */
- @RequirePOST
- public void doIgnore(StaplerRequest req, StaplerResponse rsp) throws IOException {
- ignore = true;
- Jenkins.get().servletContext.setAttribute("app", Jenkins.get());
- rsp.sendRedirect2(req.getContextPath() + '/');
}
private static final Logger LOGGER = Logger.getLogger(DoubleLaunchChecker.class.getName());
diff --git a/core/src/main/java/hudson/util/HeadBufferingStream.java b/core/src/main/java/hudson/util/HeadBufferingStream.java
index e5a2de574edc0..491ef6e56b7d8 100644
--- a/core/src/main/java/hudson/util/HeadBufferingStream.java
+++ b/core/src/main/java/hudson/util/HeadBufferingStream.java
@@ -24,10 +24,10 @@
package hudson.util;
+import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
-import org.apache.commons.io.output.ByteArrayOutputStream;
/**
* {@link FilterInputStream} that buffers the first N bytes to a byte array on the side.
diff --git a/core/src/main/java/hudson/util/HudsonIsRestarting.java b/core/src/main/java/hudson/util/HudsonIsRestarting.java
index a0dccc545a713..580a9713a08df 100644
--- a/core/src/main/java/hudson/util/HudsonIsRestarting.java
+++ b/core/src/main/java/hudson/util/HudsonIsRestarting.java
@@ -41,8 +41,29 @@
* @author Kohsuke Kawaguchi
*/
public class HudsonIsRestarting {
+ private boolean safeRestart;
+
+ /**
+ * @since TODO
+ */
+ public HudsonIsRestarting(boolean safeRestart) {
+ this.safeRestart = safeRestart;
+ }
+
+ @Deprecated
+ public HudsonIsRestarting() {
+ this.safeRestart = false;
+ }
+
public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
rsp.setStatus(SC_SERVICE_UNAVAILABLE);
req.getView(this, "index.jelly").forward(req, rsp);
}
+
+ /**
+ * @since TODO
+ */
+ public boolean isSafeRestart() {
+ return safeRestart;
+ }
}
diff --git a/core/src/main/java/hudson/util/IOUtils.java b/core/src/main/java/hudson/util/IOUtils.java
index bf16104d4c451..7bb9121661226 100644
--- a/core/src/main/java/hudson/util/IOUtils.java
+++ b/core/src/main/java/hudson/util/IOUtils.java
@@ -35,8 +35,9 @@ public class IOUtils {
* Drains the input stream and closes it.
*/
public static void drain(InputStream in) throws IOException {
- org.apache.commons.io.IOUtils.copy(in, new NullStream());
- in.close();
+ try (in; OutputStream out = OutputStream.nullOutputStream()) {
+ org.apache.commons.io.IOUtils.copy(in, out);
+ }
}
public static void copy(File src, OutputStream out) throws IOException {
@@ -81,7 +82,9 @@ public static File mkdirs(File dir) throws IOException {
* So to reliably skip just the N bytes, we'll actually read all those bytes.
*
* @since 1.349
+ * @deprecated use {@link org.apache.commons.io.IOUtils#skipFully(InputStream, long)}
*/
+ @Deprecated
public static InputStream skip(InputStream in, long size) throws IOException {
DataInputStream di = new DataInputStream(in);
@@ -149,49 +152,11 @@ public static String readFirstLine(InputStream is, String encoding) throws IOExc
}
}
-
- /**
- * @deprecated Use instead {@link org.apache.commons.io.IOUtils#DIR_SEPARATOR_UNIX}
- */
- @Deprecated
- public static final char DIR_SEPARATOR_UNIX = org.apache.commons.io.IOUtils.DIR_SEPARATOR_UNIX;
-
- /**
- * @deprecated Use instead {@link org.apache.commons.io.IOUtils#DIR_SEPARATOR_WINDOWS}
- */
- @Deprecated
- public static final char DIR_SEPARATOR_WINDOWS = org.apache.commons.io.IOUtils.DIR_SEPARATOR_WINDOWS;
-
- /**
- * @deprecated Use instead {@link org.apache.commons.io.IOUtils#DIR_SEPARATOR}
- */
- @Deprecated
- public static final char DIR_SEPARATOR = org.apache.commons.io.IOUtils.DIR_SEPARATOR;
-
- /**
- * @deprecated Use instead {@link org.apache.commons.io.IOUtils#LINE_SEPARATOR_UNIX}
- */
- @Deprecated
- public static final String LINE_SEPARATOR_UNIX = org.apache.commons.io.IOUtils.LINE_SEPARATOR_UNIX;
-
- /**
- * @deprecated Use instead {@link org.apache.commons.io.IOUtils#LINE_SEPARATOR_WINDOWS}
- */
- @Deprecated
- public static final String LINE_SEPARATOR_WINDOWS = org.apache.commons.io.IOUtils.LINE_SEPARATOR_WINDOWS;
-
- /**
- * @deprecated Use instead {@link org.apache.commons.io.IOUtils#LINE_SEPARATOR}
- */
- @Deprecated
- public static final String LINE_SEPARATOR;
-
static {
// avoid security issues
StringWriter buf = new StringWriter(4);
PrintWriter out = new PrintWriter(buf);
out.println();
- LINE_SEPARATOR = buf.toString();
}
/**
diff --git a/core/src/main/java/hudson/util/NullStream.java b/core/src/main/java/hudson/util/NullStream.java
index 41ce21803ec47..c6256d5a17bed 100644
--- a/core/src/main/java/hudson/util/NullStream.java
+++ b/core/src/main/java/hudson/util/NullStream.java
@@ -28,7 +28,9 @@
/**
* @author Kohsuke Kawaguchi
+ * @deprecated use {@link OutputStream#nullOutputStream}
*/
+@Deprecated
public final class NullStream extends OutputStream {
public NullStream() {}
diff --git a/core/src/main/java/hudson/util/PluginServletFilter.java b/core/src/main/java/hudson/util/PluginServletFilter.java
index a7673eaf03d4d..037b7010d51a0 100644
--- a/core/src/main/java/hudson/util/PluginServletFilter.java
+++ b/core/src/main/java/hudson/util/PluginServletFilter.java
@@ -43,6 +43,7 @@
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import jenkins.model.Jenkins;
+import jenkins.util.HttpServletFilter;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -96,6 +97,11 @@ public void init(FilterConfig config) throws ServletException {
config.getServletContext().setAttribute(KEY, this);
}
+ /**
+ * Dynamically register a new filter.
+ * May be paired with {@link #removeFilter}.
+ *
For most purposes you can instead use {@link HttpServletFilter}.
+ */
public static void addFilter(Filter filter) throws ServletException {
Jenkins j = Jenkins.getInstanceOrNull();
diff --git a/core/src/main/java/hudson/util/PrettyPrintWriter.java b/core/src/main/java/hudson/util/PrettyPrintWriter.java
new file mode 100644
index 0000000000000..4ad71f0f5959b
--- /dev/null
+++ b/core/src/main/java/hudson/util/PrettyPrintWriter.java
@@ -0,0 +1,340 @@
+// TODO adapted from https://github.com/x-stream/xstream/blob/32e52a6519a25366bbb5774bb536b5e290b64a42/xstream/src/java/com/thoughtworks/xstream/io/xml/PrettyPrintWriter.java pending release of https://github.com/jenkinsci/jenkins/pull/7924
+
+/*
+ * Copyright (C) 2004, 2005, 2006 Joe Walnes.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013, 2014, 2015 XStream Committers.
+ * All rights reserved.
+ *
+ * The software in this package is published under the terms of the BSD
+ * style license a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ *
+ * Created on 07. March 2004 by Joe Walnes
+ */
+
+package hudson.util;
+
+import com.thoughtworks.xstream.core.util.FastStack;
+import com.thoughtworks.xstream.core.util.QuickWriter;
+import com.thoughtworks.xstream.io.StreamException;
+import com.thoughtworks.xstream.io.naming.NameCoder;
+import com.thoughtworks.xstream.io.xml.AbstractXmlWriter;
+import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
+import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer;
+import java.io.Writer;
+
+
+/**
+ * A simple writer that outputs XML in a pretty-printed indented stream.
+ *
+ * By default, the chars
+ * & < > " ' \r
+ * are escaped and replaced with a suitable XML entity. To alter this behavior, override the
+ * {@link #writeText(com.thoughtworks.xstream.core.util.QuickWriter, String)} and
+ * {@link #writeAttributeValue(com.thoughtworks.xstream.core.util.QuickWriter, String)} methods.
+ *
+ *
+ * The XML specification requires XML parsers to drop CR characters completely. This implementation will therefore use
+ * only a LF for line endings, never the platform encoding. You can overwrite the {@link #getNewLine()} method for a
+ * different behavior.
+ *
+ *
+ * Note: Depending on the XML version some characters cannot be written. Especially a 0 character is never valid in XML,
+ * neither directly nor as entity nor within CDATA. However, this writer works by default in a quirks mode, where it
+ * will write any character at least as character entity (even a null character). You may switch into XML_1_1 mode
+ * (which supports most characters) or XML_1_0 that does only support a very limited number of control characters. See
+ * XML specification for version 1.0 or 1.1. If a character is not supported, a
+ * {@link StreamException} is thrown. Select a proper parser implementation that respects the version in the XML header
+ * (the Xpp3 parser will also read character entities of normally invalid characters).
+ *
+ *
+ * @author Joe Walnes
+ * @author Jörg Schaible
+ */
+class PrettyPrintWriter extends AbstractXmlWriter {
+
+ public static int XML_QUIRKS = -1;
+ public static int XML_1_0 = 0;
+ public static int XML_1_1 = 1;
+
+ private final QuickWriter writer;
+ private final FastStack elementStack = new FastStack(16);
+ private final char[] lineIndenter;
+ private final int mode;
+
+ private boolean tagInProgress;
+ protected int depth;
+ private boolean readyForNewLine;
+ private boolean tagIsEmpty;
+
+ private static final char[] NULL = "".toCharArray();
+ private static final char[] AMP = "&".toCharArray();
+ private static final char[] LT = "<".toCharArray();
+ private static final char[] GT = ">".toCharArray();
+ private static final char[] CR = "
".toCharArray();
+ private static final char[] QUOT = """.toCharArray();
+ private static final char[] APOS = "'".toCharArray();
+ private static final char[] CLOSE = "".toCharArray();
+
+ /**
+ * @since 1.4
+ */
+ PrettyPrintWriter(final Writer writer, final int mode, final char[] lineIndenter, final NameCoder nameCoder) {
+ super(nameCoder);
+ this.writer = new QuickWriter(writer);
+ this.lineIndenter = lineIndenter;
+ this.mode = mode;
+ if (mode < XML_QUIRKS || mode > XML_1_1) {
+ throw new IllegalArgumentException("Not a valid XML mode");
+ }
+ }
+
+ /**
+ * @since 1.3
+ * @deprecated As of 1.4 use {@link PrettyPrintWriter#PrettyPrintWriter(Writer, int, char[], NameCoder)} instead
+ */
+ @Deprecated
+ PrettyPrintWriter(
+ final Writer writer, final int mode, final char[] lineIndenter, final XmlFriendlyReplacer replacer) {
+ this(writer, mode, lineIndenter, (NameCoder) replacer);
+ }
+
+ /**
+ * @since 1.3
+ */
+ PrettyPrintWriter(final Writer writer, final int mode, final char[] lineIndenter) {
+ this(writer, mode, lineIndenter, new XmlFriendlyNameCoder());
+ }
+
+ PrettyPrintWriter(final Writer writer, final char[] lineIndenter) {
+ this(writer, XML_QUIRKS, lineIndenter);
+ }
+
+ /**
+ * @since 1.3
+ */
+ PrettyPrintWriter(final Writer writer, final int mode, final String lineIndenter) {
+ this(writer, mode, lineIndenter.toCharArray());
+ }
+
+ PrettyPrintWriter(final Writer writer, final String lineIndenter) {
+ this(writer, lineIndenter.toCharArray());
+ }
+
+ /**
+ * @since 1.4
+ */
+ PrettyPrintWriter(final Writer writer, final int mode, final NameCoder nameCoder) {
+ this(writer, mode, new char[]{' ', ' '}, nameCoder);
+ }
+
+ /**
+ * @since 1.3
+ * @deprecated As of 1.4 use {@link PrettyPrintWriter#PrettyPrintWriter(Writer, int, NameCoder)} instead
+ */
+ @Deprecated
+ PrettyPrintWriter(final Writer writer, final int mode, final XmlFriendlyReplacer replacer) {
+ this(writer, mode, new char[]{' ', ' '}, replacer);
+ }
+
+ /**
+ * @since 1.4
+ */
+ PrettyPrintWriter(final Writer writer, final NameCoder nameCoder) {
+ this(writer, XML_QUIRKS, new char[]{' ', ' '}, nameCoder);
+ }
+
+ /**
+ * @deprecated As of 1.4 use {@link PrettyPrintWriter#PrettyPrintWriter(Writer, NameCoder)} instead.
+ */
+ @Deprecated
+ PrettyPrintWriter(final Writer writer, final XmlFriendlyReplacer replacer) {
+ this(writer, XML_QUIRKS, new char[]{' ', ' '}, replacer);
+ }
+
+ /**
+ * @since 1.3
+ */
+ PrettyPrintWriter(final Writer writer, final int mode) {
+ this(writer, mode, new char[]{' ', ' '});
+ }
+
+ PrettyPrintWriter(final Writer writer) {
+ this(writer, new char[]{' ', ' '});
+ }
+
+ @Override
+ public void startNode(final String name) {
+ final String escapedName = encodeNode(name);
+ tagIsEmpty = false;
+ finishTag();
+ writer.write('<');
+ writer.write(escapedName);
+ elementStack.push(escapedName);
+ tagInProgress = true;
+ depth++;
+ readyForNewLine = true;
+ tagIsEmpty = true;
+ }
+
+ @Override
+ public void startNode(final String name, final Class clazz) {
+ startNode(name);
+ }
+
+ @Override
+ public void setValue(final String text) {
+ readyForNewLine = false;
+ tagIsEmpty = false;
+ finishTag();
+
+ writeText(writer, text);
+ }
+
+ @Override
+ public void addAttribute(final String key, final String value) {
+ writer.write(' ');
+ writer.write(encodeAttribute(key));
+ writer.write('=');
+ writer.write('\"');
+ writeAttributeValue(writer, value);
+ writer.write('\"');
+ }
+
+ protected void writeAttributeValue(final QuickWriter writer, final String text) {
+ writeText(text, true);
+ }
+
+ protected void writeText(final QuickWriter writer, final String text) {
+ writeText(text, false);
+ }
+
+ private void writeText(final String text, final boolean isAttribute) {
+ text.codePoints().forEach(c -> {
+ switch (c) {
+ case '\0':
+ if (mode == XML_QUIRKS) {
+ writer.write(NULL);
+ } else {
+ throw new StreamException("Invalid character 0x0 in XML stream");
+ }
+ break;
+ case '&':
+ writer.write(AMP);
+ break;
+ case '<':
+ writer.write(LT);
+ break;
+ case '>':
+ writer.write(GT);
+ break;
+ case '"':
+ writer.write(QUOT);
+ break;
+ case '\'':
+ writer.write(APOS);
+ break;
+ case '\r':
+ writer.write(CR);
+ break;
+ case '\t':
+ case '\n':
+ if (!isAttribute) {
+ writer.write(Character.toChars(c));
+ break;
+ }
+ //$FALL-THROUGH$
+ default:
+ if (Character.isDefined(c) && !Character.isISOControl(c)) {
+ if (mode != XML_QUIRKS) {
+ if (c > '\ud7ff' && c < '\ue000') {
+ throw new StreamException("Invalid character 0x"
+ + Integer.toHexString(c)
+ + " in XML stream");
+ }
+ }
+ writer.write(Character.toChars(c));
+ } else {
+ if (mode == XML_1_0) {
+ if (c < 9 || c == '\u000b' || c == '\u000c' || c == '\u000e' || c >= '\u000f' && c <= '\u001f') {
+ throw new StreamException("Invalid character 0x"
+ + Integer.toHexString(c)
+ + " in XML 1.0 stream");
+ }
+ }
+ if (mode != XML_QUIRKS) {
+ if (c == '\ufffe' || c == '\uffff') {
+ throw new StreamException("Invalid character 0x"
+ + Integer.toHexString(c)
+ + " in XML stream");
+ }
+ }
+ writer.write("");
+ writer.write(Integer.toHexString(c));
+ writer.write(';');
+ }
+ }
+ });
+ }
+
+ @Override
+ public void endNode() {
+ depth--;
+ if (tagIsEmpty) {
+ writer.write('/');
+ readyForNewLine = false;
+ finishTag();
+ elementStack.popSilently();
+ } else {
+ finishTag();
+ writer.write(CLOSE);
+ writer.write((String) elementStack.pop());
+ writer.write('>');
+ }
+ readyForNewLine = true;
+ if (depth == 0) {
+ writer.flush();
+ }
+ }
+
+ private void finishTag() {
+ if (tagInProgress) {
+ writer.write('>');
+ }
+ tagInProgress = false;
+ if (readyForNewLine) {
+ endOfLine();
+ }
+ readyForNewLine = false;
+ tagIsEmpty = false;
+ }
+
+ protected void endOfLine() {
+ writer.write(getNewLine());
+ for (int i = 0; i < depth; i++) {
+ writer.write(lineIndenter);
+ }
+ }
+
+ @Override
+ public void flush() {
+ writer.flush();
+ }
+
+ @Override
+ public void close() {
+ writer.close();
+ }
+
+ /**
+ * Retrieve the line terminator. This method returns always a line feed, since according the XML specification any
+ * parser must ignore a carriage return. Overload this method, if you need different behavior.
+ *
+ * @return the line terminator
+ * @since 1.3
+ */
+ protected String getNewLine() {
+ return "\n";
+ }
+}
diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java
index e9689bde2f178..8f995738683bc 100644
--- a/core/src/main/java/hudson/util/ProcessTree.java
+++ b/core/src/main/java/hudson/util/ProcessTree.java
@@ -53,7 +53,6 @@
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.io.ObjectStreamException;
import java.io.RandomAccessFile;
import java.io.Serializable;
@@ -75,7 +74,6 @@
import jenkins.agents.AgentComputerUtil;
import jenkins.security.SlaveToMasterCallable;
import jenkins.util.SystemProperties;
-import org.apache.commons.io.FileUtils;
import org.jenkinsci.remoting.SerializableOnlyOverRemoting;
import org.jvnet.winp.WinProcess;
import org.jvnet.winp.WinpException;
@@ -914,7 +912,7 @@ public synchronized List getArguments() {
return arguments;
arguments = new ArrayList<>();
try {
- byte[] cmdline = readFileToByteArray(getFile("cmdline"));
+ byte[] cmdline = Files.readAllBytes(Util.fileToPath(getFile("cmdline")));
int pos = 0;
for (int i = 0; i < cmdline.length; i++) {
byte b = cmdline[i];
@@ -938,7 +936,7 @@ public synchronized EnvVars getEnvironmentVariables() {
return envVars;
envVars = new EnvVars();
try {
- byte[] environ = readFileToByteArray(getFile("environ"));
+ byte[] environ = Files.readAllBytes(Util.fileToPath(getFile("environ")));
int pos = 0;
for (int i = 0; i < environ.length; i++) {
byte b = environ[i];
@@ -954,12 +952,6 @@ public synchronized EnvVars getEnvironmentVariables() {
return envVars;
}
}
-
- public byte[] readFileToByteArray(File file) throws IOException {
- try (InputStream in = FileUtils.openInputStream(file)) {
- return org.apache.commons.io.IOUtils.toByteArray(in);
- }
- }
}
/**
diff --git a/core/src/main/java/hudson/util/StreamTaskListener.java b/core/src/main/java/hudson/util/StreamTaskListener.java
index 4abe31d3ec512..389a57c724de3 100644
--- a/core/src/main/java/hudson/util/StreamTaskListener.java
+++ b/core/src/main/java/hudson/util/StreamTaskListener.java
@@ -149,7 +149,7 @@ public StreamTaskListener(@NonNull Writer w) throws IOException {
*/
@Deprecated
public StreamTaskListener() throws IOException {
- this(new NullStream());
+ this(OutputStream.nullOutputStream());
}
public static StreamTaskListener fromStdout() {
diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java
index 98c1934c4127f..5c6ebc566731d 100644
--- a/core/src/main/java/hudson/util/XStream2.java
+++ b/core/src/main/java/hudson/util/XStream2.java
@@ -46,7 +46,6 @@
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.ReaderWrapper;
-import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.StandardStaxDriver;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
import com.thoughtworks.xstream.mapper.Mapper;
@@ -138,7 +137,7 @@ private static class StaxDriver extends StandardStaxDriver {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
- return new PrettyPrintWriter(out, getNameCoder());
+ return new PrettyPrintWriter(out, PrettyPrintWriter.XML_1_1, getNameCoder());
}
@Override
@@ -470,8 +469,18 @@ public Class realClass(String elementName) {
*/
private static final class AssociatedConverterImpl implements Converter {
private final XStream xstream;
- private final ConcurrentHashMap, Converter> cache =
- new ConcurrentHashMap<>();
+ private static final ClassValue> classCache = new ClassValue>() {
+ @Override
+ protected Class extends ConverterMatcher> computeValue(Class> type) {
+ return computeConverterClass(type);
+ }
+ };
+ private final ClassValue cache = new ClassValue() {
+ @Override
+ protected Converter computeValue(Class> type) {
+ return computeConverter(type);
+ }
+ };
private AssociatedConverterImpl(XStream xstream) {
this.xstream = xstream;
@@ -482,17 +491,33 @@ private Converter findConverter(@CheckForNull Class> t) {
if (t == null) {
return null;
}
+ return cache.get(t);
+ }
- Converter result = cache.get(t);
- if (result != null)
- // ConcurrentHashMap does not allow null, so use this object to represent null
- return result == this ? null : result;
+ @CheckForNull
+ private static Class extends ConverterMatcher> computeConverterClass(@NonNull Class> t) {
try {
final ClassLoader classLoader = t.getClassLoader();
if (classLoader == null) {
return null;
}
- Class> cl = classLoader.loadClass(t.getName() + "$ConverterImpl");
+ String name = t.getName() + "$ConverterImpl";
+ if (classLoader.getResource(name.replace('.', '/') + ".class") == null) {
+ return null;
+ }
+ return classLoader.loadClass(name).asSubclass(ConverterMatcher.class);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ @CheckForNull
+ private Converter computeConverter(@NonNull Class> t) {
+ Class extends ConverterMatcher> cl = classCache.get(t);
+ if (cl == null) {
+ return null;
+ }
+ try {
Constructor> c = cl.getConstructors()[0];
Class>[] p = c.getParameterTypes();
@@ -507,14 +532,9 @@ else if (p[i] == Mapper.class)
}
ConverterMatcher cm = (ConverterMatcher) c.newInstance(args);
- result = cm instanceof SingleValueConverter
+ return cm instanceof SingleValueConverter
? new SingleValueConverterWrapper((SingleValueConverter) cm)
: (Converter) cm;
- cache.put(t, result);
- return result;
- } catch (ClassNotFoundException e) {
- cache.put(t, this); // See above.. this object in cache represents null
- return null;
} catch (IllegalAccessException e) {
IllegalAccessError x = new IllegalAccessError();
x.initCause(e);
diff --git a/core/src/main/java/hudson/widgets/BuildHistoryWidget.java b/core/src/main/java/hudson/widgets/BuildHistoryWidget.java
index da3576771416f..2c171b594f5a3 100644
--- a/core/src/main/java/hudson/widgets/BuildHistoryWidget.java
+++ b/core/src/main/java/hudson/widgets/BuildHistoryWidget.java
@@ -24,12 +24,22 @@
package hudson.widgets;
-import hudson.model.Queue.Item;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Job;
+import hudson.model.Queue;
import hudson.model.Queue.Task;
+import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import jenkins.model.Jenkins;
+import jenkins.model.queue.QueueItem;
import jenkins.widgets.HistoryPageFilter;
+import jenkins.widgets.WidgetFactory;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Displays the build history on the side panel.
@@ -52,17 +62,17 @@ public BuildHistoryWidget(Task owner, Iterable baseList, Adapter super T> a
/**
* Returns the first queue item if the owner is scheduled for execution in the queue.
*/
- public Item getQueuedItem() {
+ public QueueItem getQueuedItem() {
return Jenkins.get().getQueue().getItem(owner);
}
/**
* Returns the queue item if the owner is scheduled for execution in the queue, in REVERSE ORDER
*/
- public List getQueuedItems() {
- LinkedList list = new LinkedList<>();
- for (Item item : Jenkins.get().getQueue().getItems()) {
- if (item.task == owner) {
+ public List getQueuedItems() {
+ LinkedList list = new LinkedList<>();
+ for (QueueItem item : Jenkins.get().getQueue().getItems()) {
+ if (item.getTask() == owner) {
list.addFirst(item);
}
}
@@ -78,4 +88,28 @@ public HistoryPageFilter getHistoryPageFilter() {
return updateFirstTransientBuildKey(historyPageFilter);
}
+
+ @Extension
+ @Restricted(DoNotUse.class)
+ @Symbol("buildHistory")
+ public static final class FactoryImpl extends WidgetFactory {
+ @Override
+ public Class type() {
+ return Job.class;
+ }
+
+ @Override
+ public Class widgetType() {
+ return BuildHistoryWidget.class;
+ }
+
+ @NonNull
+ @Override
+ public Collection createFor(@NonNull Job target) {
+ if (target instanceof Queue.Task) {
+ return List.of(new BuildHistoryWidget<>((Queue.Task) target, target.getBuilds(), Job.HISTORY_ADAPTER));
+ }
+ return Collections.emptySet();
+ }
+ }
}
diff --git a/core/src/main/java/hudson/widgets/HistoryWidget.java b/core/src/main/java/hudson/widgets/HistoryWidget.java
index aba195ab7c744..c7a7f12a1d76f 100644
--- a/core/src/main/java/hudson/widgets/HistoryWidget.java
+++ b/core/src/main/java/hudson/widgets/HistoryWidget.java
@@ -25,11 +25,16 @@
package hudson.widgets;
import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
import hudson.Functions;
+import hudson.model.Job;
import hudson.model.ModelObject;
+import hudson.model.Queue;
import hudson.model.Run;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -37,6 +42,10 @@
import jenkins.util.SystemProperties;
import jenkins.widgets.HistoryPageEntry;
import jenkins.widgets.HistoryPageFilter;
+import jenkins.widgets.WidgetFactory;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.Header;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
@@ -97,6 +106,11 @@ public HistoryWidget(O owner, Iterable baseList, Adapter super T> adapter)
this.searchString = currentRequest.getParameter("search");
}
+ @Override
+ protected String getOwnerUrl() {
+ return baseUrl;
+ }
+
/**
* Title of the widget.
*/
@@ -292,4 +306,29 @@ private Long getPagingParam(@CheckForNull StaplerRequest currentRequest, @CheckF
return null;
}
}
+
+ @Extension
+ @Restricted(DoNotUse.class)
+ @Symbol("history")
+ public static final class FactoryImpl extends WidgetFactory {
+ @Override
+ public Class type() {
+ return Job.class;
+ }
+
+ @Override
+ public Class widgetType() {
+ return HistoryWidget.class;
+ }
+
+ @NonNull
+ @Override
+ public Collection createFor(@NonNull Job target) {
+ // e.g. hudson.model.ExternalJob
+ if (!(target instanceof Queue.Task)) {
+ return List.of(new HistoryWidget<>(target, target.getBuilds(), Job.HISTORY_ADAPTER));
+ }
+ return Collections.emptySet();
+ }
+ }
}
diff --git a/core/src/main/java/hudson/widgets/Widget.java b/core/src/main/java/hudson/widgets/Widget.java
index 81bc9e4e4cac8..a26d904cbe572 100644
--- a/core/src/main/java/hudson/widgets/Widget.java
+++ b/core/src/main/java/hudson/widgets/Widget.java
@@ -24,7 +24,7 @@
package hudson.widgets;
-import hudson.ExtensionPoint;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.model.View;
/**
@@ -47,14 +47,14 @@
* @since 1.146
* @see jenkins.model.Jenkins#getWidgets()
*/
-public abstract class Widget implements ExtensionPoint {
+public abstract class Widget {
/**
* Gets the URL path name.
*
*
* For example, if this method returns "xyz", and if the parent object
* (that this widget is associated with) is bound to /foo/bar/zot,
- * then this widget object will be exposed to /foo/bar/zot/xyz.
+ * then this widget object will be exposed to /foo/bar/zot/widget/xyz.
*
*
* This method is useful when the widget needs to expose additional URLs,
@@ -67,4 +67,21 @@ public abstract class Widget implements ExtensionPoint {
public String getUrlName() {
return getClass().getSimpleName();
}
+
+ /**
+ * @return The URL of the owner of this widget relative to context path. Always ends with a trailing slash.
+ * Can be null for backward compatibility with widgets annotated with @Extension.
+ */
+ @CheckForNull
+ protected String getOwnerUrl() {
+ return null;
+ }
+
+ /**
+ * @return the URL relative to the context path. Always ends with a trailing '/'.
+ */
+ public String getUrl() {
+ String ownerUrl = getOwnerUrl();
+ return (ownerUrl == null ? "" : ownerUrl) + "widget/" + getUrlName() + '/';
+ }
}
diff --git a/core/src/main/java/jenkins/agents/CloudSet.java b/core/src/main/java/jenkins/agents/CloudSet.java
new file mode 100644
index 0000000000000..5229dd06f18b4
--- /dev/null
+++ b/core/src/main/java/jenkins/agents/CloudSet.java
@@ -0,0 +1,272 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2023, CloudBees Inc, and other contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.agents;
+
+import hudson.Extension;
+import hudson.Functions;
+import hudson.Util;
+import hudson.model.AbstractModelObject;
+import hudson.model.AutoCompletionCandidates;
+import hudson.model.Describable;
+import hudson.model.Descriptor;
+import hudson.model.Failure;
+import hudson.model.RootAction;
+import hudson.model.UpdateCenter;
+import hudson.slaves.Cloud;
+import hudson.util.FormValidation;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.servlet.ServletException;
+import jenkins.model.Jenkins;
+import jenkins.model.ModelObjectWithChildren;
+import jenkins.model.ModelObjectWithContextMenu;
+import net.sf.json.JSONObject;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.StaplerProxy;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.interceptor.RequirePOST;
+import org.kohsuke.stapler.verb.POST;
+
+@Restricted(NoExternalUse.class)
+public class CloudSet extends AbstractModelObject implements Describable, ModelObjectWithChildren, RootAction, StaplerProxy {
+ private static final Logger LOGGER = Logger.getLogger(CloudSet.class.getName());
+
+ @Override
+ public Descriptor getDescriptor() {
+ return Jenkins.get().getDescriptorOrDie(CloudSet.class);
+ }
+
+ public Cloud getDynamic(String token) {
+ return Jenkins.get().getCloud(token);
+ }
+
+ @Override
+ @Restricted(NoExternalUse.class)
+ public Object getTarget() {
+ Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);
+ return this;
+ }
+
+ @Override
+ public String getIconFileName() {
+ return null;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.CloudSet_DisplayName();
+ }
+
+ @Override
+ public String getUrlName() {
+ return "cloud";
+ }
+
+ @Override
+ public String getSearchUrl() {
+ return "/cloud/";
+ }
+
+ @SuppressWarnings("unused") // stapler
+ @Restricted(DoNotUse.class) // stapler
+ public String getCloudUrl(StaplerRequest request, Jenkins jenkins, Cloud cloud) {
+ String context = Functions.getNearestAncestorUrl(request, jenkins);
+ if (Jenkins.get().getCloud(cloud.name) != cloud) { // this cloud is not the first occurrence with this name
+ return context + "/cloud/cloudByIndex/" + getClouds().indexOf(cloud) + "/";
+ } else {
+ return context + "/" + cloud.getUrl();
+ }
+ }
+
+ @SuppressWarnings("unused") // stapler
+ @Restricted(DoNotUse.class) // stapler
+ public Cloud getCloudByIndex(int index) {
+ return Jenkins.get().clouds.get(index);
+ }
+
+ @SuppressWarnings("unused") // stapler
+ public boolean isCloudAvailable() {
+ return !Cloud.all().isEmpty();
+ }
+
+ @SuppressWarnings("unused") // stapler
+ public String getCloudUpdateCenterCategoryLabel() {
+ return URLEncoder.encode(UpdateCenter.getCategoryDisplayName("cloud"), StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public ModelObjectWithContextMenu.ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
+ ModelObjectWithContextMenu.ContextMenu m = new ModelObjectWithContextMenu.ContextMenu();
+ Jenkins.get().clouds.stream().forEach(c -> m.add(c));
+ return m;
+ }
+
+ public Cloud getDynamic(String name, StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
+ return Jenkins.get().clouds.getByName(name);
+ }
+
+ @SuppressWarnings("unused") // stapler
+ @Restricted(DoNotUse.class) // stapler
+ public Jenkins.CloudList getClouds() {
+ return Jenkins.get().clouds;
+ }
+
+ @SuppressWarnings("unused") // stapler
+ @Restricted(DoNotUse.class) // stapler
+ public boolean hasClouds() {
+ return !Jenkins.get().clouds.isEmpty();
+ }
+
+ /**
+ * Makes sure that the given name is good as an agent name.
+ * @return trimmed name if valid; throws ParseException if not
+ */
+ public String checkName(String name) throws Failure {
+ if (name == null)
+ throw new Failure("Query parameter 'name' is required");
+
+ name = name.trim();
+ Jenkins.checkGoodName(name);
+
+ if (Jenkins.get().getCloud(name) != null)
+ throw new Failure(Messages.CloudSet_CloudAlreadyExists(name));
+
+ // looks good
+ return name;
+ }
+
+ @SuppressWarnings("unused") // stapler
+ public FormValidation doCheckName(@QueryParameter String value) {
+ Jenkins.get().checkPermission(Jenkins.ADMINISTER);
+ if (Util.fixEmpty(value) == null) {
+ return FormValidation.ok();
+ }
+ try {
+ checkName(value);
+ return FormValidation.ok();
+ } catch (Failure e) {
+ return FormValidation.error(e.getMessage());
+ }
+ }
+
+ /**
+ * First check point in creating a new cloud.
+ */
+ @RequirePOST
+ public synchronized void doCreate(StaplerRequest req, StaplerResponse rsp,
+ @QueryParameter String name, @QueryParameter String mode,
+ @QueryParameter String from) throws IOException, ServletException, Descriptor.FormException {
+ final Jenkins jenkins = Jenkins.get();
+ jenkins.checkPermission(Jenkins.ADMINISTER);
+
+ if (mode != null && mode.equals("copy")) {
+ name = checkName(name);
+
+ Cloud src = jenkins.getCloud(from);
+ if (src == null) {
+ if (Util.fixEmpty(from) == null) {
+ throw new Failure(Messages.CloudSet_SpecifyCloudToCopy());
+ } else {
+ throw new Failure(Messages.CloudSet_NoSuchCloud(from));
+ }
+ }
+
+ // copy through XStream
+ String xml = Jenkins.XSTREAM.toXML(src);
+ // Not great, but cloud name is final
+ xml = xml.replace("" + src.name + "", "" + name + "");
+ Cloud result = (Cloud) Jenkins.XSTREAM.fromXML(xml);
+ jenkins.clouds.add(result);
+ // send the browser to the config page
+ rsp.sendRedirect2(Functions.getNearestAncestorUrl(req, jenkins) + "/" + result.getUrl() + "configure");
+ } else {
+ // proceed to step 2
+ if (mode == null) {
+ throw new Failure("No mode given");
+ }
+
+ Descriptor d = Cloud.all().findByName(mode);
+ if (d == null) {
+ throw new Failure("No node type ‘" + mode + "’ is known");
+ }
+ handleNewCloudPage(d, name, req, rsp);
+ }
+ }
+
+ private void handleNewCloudPage(Descriptor descriptor, String name, StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException {
+ checkName(name);
+ JSONObject formData = req.getSubmittedForm();
+ formData.put("name", name);
+ formData.put("cloudName", name); // ec2 uses that field name
+ formData.remove("mode"); // Cloud descriptors won't have this field.
+ req.setAttribute("instance", formData);
+ req.setAttribute("descriptor", descriptor);
+ req.getView(this, "_new.jelly").forward(req, rsp);
+ }
+
+ /**
+ * Really creates a new agent.
+ */
+ @POST
+ public synchronized void doDoCreate(StaplerRequest req, StaplerResponse rsp,
+ @QueryParameter String type) throws IOException, ServletException, Descriptor.FormException {
+ Jenkins.get().checkPermission(Jenkins.ADMINISTER);
+ Cloud cloud = Cloud.all().find(type).newInstance(req, req.getSubmittedForm());
+ if (!Jenkins.get().clouds.add(cloud)) {
+ LOGGER.log(Level.WARNING, () -> "Creating duplicate cloud name " + cloud.name + ". Plugin " + Jenkins.get().getPluginManager().whichPlugin(cloud.getClass()) + " should be updated to support user provided name.");
+ }
+ // take the user back to the cloud list top page
+ rsp.sendRedirect2(".");
+ }
+
+ @Extension
+ public static class DescriptorImpl extends Descriptor implements StaplerProxy {
+
+ /**
+ * Auto-completion for the "copy from" field in the new cloud page.
+ */
+ @SuppressWarnings("unused") // stapler
+ public AutoCompletionCandidates doAutoCompleteCopyNewItemFrom(@QueryParameter final String value) {
+ final AutoCompletionCandidates r = new AutoCompletionCandidates();
+ Jenkins.get().clouds.stream()
+ .filter(c -> c.name.startsWith(value))
+ .forEach(c -> r.add(c.name));
+ return r;
+ }
+
+ @Override
+ public Object getTarget() {
+ Jenkins.get().checkPermission(Jenkins.ADMINISTER);
+ return this;
+ }
+ }
+}
diff --git a/core/src/main/java/jenkins/agents/CloudsLink.java b/core/src/main/java/jenkins/agents/CloudsLink.java
new file mode 100644
index 0000000000000..38293b0ba3aef
--- /dev/null
+++ b/core/src/main/java/jenkins/agents/CloudsLink.java
@@ -0,0 +1,69 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2023, CloudBees Inc, and other contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.agents;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.ManagementLink;
+import hudson.security.Permission;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+
+@Extension
+@Symbol("clouds")
+public class CloudsLink extends ManagementLink {
+
+ @Override
+ public String getDisplayName() {
+ return Messages.CloudsLink_DisplayName();
+ }
+
+ @Override
+ public String getDescription() {
+ return Messages.CloudsLink_Description();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return "symbol-cloud";
+ }
+
+ @Override
+ public String getUrlName() {
+ return "cloud";
+ }
+
+ @NonNull
+ @Override
+ public Category getCategory() {
+ return Category.CONFIGURATION;
+ }
+
+ @NonNull
+ @Override
+ public Permission getRequiredPermission() {
+ return Jenkins.SYSTEM_READ;
+ }
+}
diff --git a/core/src/main/java/jenkins/cli/SafeRestartCommand.java b/core/src/main/java/jenkins/cli/SafeRestartCommand.java
new file mode 100644
index 0000000000000..9f83627ba844c
--- /dev/null
+++ b/core/src/main/java/jenkins/cli/SafeRestartCommand.java
@@ -0,0 +1,59 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2023, Jan Meiswinkel
+ *
+ * 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 jenkins.cli;
+
+import hudson.Extension;
+import hudson.cli.CLICommand;
+import hudson.cli.Messages;
+import java.util.logging.Logger;
+import jenkins.model.Jenkins;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.args4j.Option;
+
+/**
+ * Safe Restart Jenkins - do not accept any new jobs and try to pause existing.
+ *
+ * @since TODO
+ */
+@Extension
+@Restricted(NoExternalUse.class)
+public class SafeRestartCommand extends CLICommand {
+ private static final Logger LOGGER = Logger.getLogger(SafeRestartCommand.class.getName());
+
+ @Option(name = "-message", usage = "Message for safe restart that will be visible to users")
+ public String message = null;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.SafeRestartCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ Jenkins.get().doSafeRestart(null, message);
+ return 0;
+ }
+}
diff --git a/core/src/main/java/jenkins/diagnostics/ControllerExecutorsNoAgents.java b/core/src/main/java/jenkins/diagnostics/ControllerExecutorsNoAgents.java
index 5170d60327391..d06c3c7bff69a 100644
--- a/core/src/main/java/jenkins/diagnostics/ControllerExecutorsNoAgents.java
+++ b/core/src/main/java/jenkins/diagnostics/ControllerExecutorsNoAgents.java
@@ -57,7 +57,7 @@ public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException {
disable(true);
rsp.sendRedirect(req.getContextPath() + "/manage");
} else if (req.hasParameter("cloud")) {
- rsp.sendRedirect(req.getContextPath() + "/configureClouds");
+ rsp.sendRedirect(req.getContextPath() + "/manage/cloud/");
} else if (req.hasParameter("agent")) {
rsp.sendRedirect(req.getContextPath() + "/computer/new");
}
diff --git a/core/src/main/java/jenkins/install/InstallStateFilter.java b/core/src/main/java/jenkins/install/InstallStateFilter.java
index af34991aa163a..f5114717d0eec 100644
--- a/core/src/main/java/jenkins/install/InstallStateFilter.java
+++ b/core/src/main/java/jenkins/install/InstallStateFilter.java
@@ -2,8 +2,8 @@
import hudson.ExtensionList;
import hudson.ExtensionPoint;
+import jakarta.inject.Provider;
import java.util.List;
-import javax.inject.Provider;
/**
* Allows plugging in to the lifecycle when determining InstallState
diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java
index 0572d48a5209c..ff6949eaefd8c 100644
--- a/core/src/main/java/jenkins/install/InstallUtil.java
+++ b/core/src/main/java/jenkins/install/InstallUtil.java
@@ -37,6 +37,7 @@
import hudson.model.UpdateCenter.InstallationJob;
import hudson.model.UpdateCenter.UpdateCenterJob;
import hudson.util.VersionNumber;
+import jakarta.inject.Provider;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -51,7 +52,6 @@
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.inject.Provider;
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import jenkins.util.xml.XMLUtils;
diff --git a/core/src/main/java/jenkins/management/NodesLink.java b/core/src/main/java/jenkins/management/NodesLink.java
index e0058a24304bc..7a6f73f205f83 100644
--- a/core/src/main/java/jenkins/management/NodesLink.java
+++ b/core/src/main/java/jenkins/management/NodesLink.java
@@ -39,7 +39,7 @@ public class NodesLink extends ManagementLink {
@Override
public String getIconFileName() {
- return "symbol-cloud";
+ return "symbol-computer";
}
@Override
diff --git a/core/src/main/java/jenkins/model/GlobalCloudConfiguration.java b/core/src/main/java/jenkins/model/GlobalCloudConfiguration.java
index 2731e12649e51..022474b496ba9 100644
--- a/core/src/main/java/jenkins/model/GlobalCloudConfiguration.java
+++ b/core/src/main/java/jenkins/model/GlobalCloudConfiguration.java
@@ -3,29 +3,21 @@
import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.Extension;
import hudson.RestrictedSince;
-import hudson.model.Descriptor;
import hudson.model.RootAction;
-import hudson.slaves.Cloud;
-import hudson.util.FormApply;
-import java.io.IOException;
-import javax.servlet.ServletException;
-import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
-import org.kohsuke.stapler.StaplerRequest;
-import org.kohsuke.stapler.StaplerResponse;
-import org.kohsuke.stapler.verb.POST;
/**
- * Provides a configuration form for {@link Jenkins#clouds}.
- *
- * Has been overhauled in Jenkins 2.XXX to no longer contribute to Configure System, but be a standalone form.
+ * Redirects from /configureClouds to /cloud/.
+ * Previously was the form for clouds.
+ * @deprecated Replaced by {@link jenkins.agents.CloudsLink} and {@link jenkins.agents.CloudSet}.
*/
@Extension
@Symbol("cloud")
@Restricted(NoExternalUse.class)
@RestrictedSince("2.205")
+@Deprecated
public class GlobalCloudConfiguration implements RootAction {
@CheckForNull
@@ -44,12 +36,4 @@ public String getDisplayName() {
public String getUrlName() {
return "configureClouds";
}
-
- @POST
- public void doConfigure(StaplerRequest req, StaplerResponse rsp) throws Descriptor.FormException, IOException, ServletException {
- Jenkins.get().checkPermission(Jenkins.ADMINISTER);
- JSONObject json = req.getSubmittedForm();
- Jenkins.get().clouds.rebuildHetero(req, json, Cloud.all(), "cloud");
- FormApply.success(req.getContextPath() + "/manage").generateResponse(req, rsp, null);
- }
}
diff --git a/core/src/main/java/jenkins/model/GlobalConfiguration.java b/core/src/main/java/jenkins/model/GlobalConfiguration.java
index a1eb2523c7743..4a5aa76ba1178 100644
--- a/core/src/main/java/jenkins/model/GlobalConfiguration.java
+++ b/core/src/main/java/jenkins/model/GlobalConfiguration.java
@@ -22,7 +22,7 @@
* An option to present a single section for your plugin in the Jenkins global configuration page is
* to use this class to manage the configuration for your plugin and its extension points. To access
* properties defined in your GlobalConfiguration subclass, here are two possibilities:
- *
@{@link javax.inject.Inject} into your other {@link hudson.Extension}s (so this does not work
+ *
@{@link jakarta.inject.Inject} into your other {@link hudson.Extension}s (so this does not work
* for classes not annotated with {@link hudson.Extension})
*
access it via a call to {@code ExtensionList.lookupSingleton(.class)}
*
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index dda1ef9d81f5c..9bf94cf9389a5 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -225,6 +225,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -264,6 +265,7 @@
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner;
+import jenkins.agents.CloudSet;
import jenkins.diagnostics.URICheckEncodingMonitor;
import jenkins.install.InstallState;
import jenkins.install.SetupWizard;
@@ -481,6 +483,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
@CheckForNull
private transient volatile QuietDownInfo quietDownInfo;
+
private transient volatile boolean terminating;
@GuardedBy("Jenkins.class")
private transient boolean cleanUpStarted;
@@ -1575,6 +1578,14 @@ public ComputerSet getComputer() {
return new ComputerSet();
}
+ /**
+ * Only there to bind to /cloud/ URL. Otherwise /cloud/new gets resolved to getCloud("new") by stapler which is not what we want.
+ */
+ @Restricted(DoNotUse.class)
+ public CloudSet getCloud() {
+ return new CloudSet();
+ }
+
/**
* Exposes {@link Descriptor} by its name to URL.
*
@@ -2039,13 +2050,7 @@ public boolean isUpgradedFromBefore(VersionNumber v) {
* Gets the read-only list of all {@link Computer}s.
*/
public Computer[] getComputers() {
- Computer[] r = computers.values().toArray(new Computer[0]);
- Arrays.sort(r, (lhs, rhs) -> {
- if (lhs.getNode() == Jenkins.this) return -1;
- if (rhs.getNode() == Jenkins.this) return 1;
- return lhs.getName().compareTo(rhs.getName());
- });
- return r;
+ return computers.values().stream().sorted(Comparator.comparing(Computer::getName)).toArray(Computer[]::new);
}
@CLIResolver
@@ -2934,6 +2939,20 @@ public boolean isQuietingDown() {
return quietDownInfo != null;
}
+ /**
+ * Returns if the quietingDown is a safe restart.
+ * @since TODO
+ */
+ @Restricted(NoExternalUse.class)
+ @NonNull
+ public boolean isPreparingSafeRestart() {
+ QuietDownInfo quietDownInfo = this.quietDownInfo;
+ if (quietDownInfo != null) {
+ return quietDownInfo.isSafeRestart();
+ }
+ return false;
+ }
+
/**
* Returns quiet down reason if it was indicated.
* @return
@@ -2944,7 +2963,7 @@ public boolean isQuietingDown() {
@CheckForNull
public String getQuietDownReason() {
final QuietDownInfo info = quietDownInfo;
- return info != null ? info.reason : null;
+ return info != null ? info.message : null;
}
/**
@@ -4084,7 +4103,7 @@ public synchronized HttpRedirect doQuietDown() {
*
* @param block Block until the system really quiets down and no builds are running
* @param timeout If non-zero, only block up to the specified number of milliseconds
- * @deprecated since 2.267; use {@link #doQuietDown(boolean, int, String)} instead.
+ * @deprecated since 2.267; use {@link #doQuietDown(boolean, int, String, boolean)} instead.
*/
@Deprecated
public synchronized HttpRedirect doQuietDown(boolean block, int timeout) {
@@ -4100,16 +4119,34 @@ public synchronized HttpRedirect doQuietDown(boolean block, int timeout) {
*
* @param block Block until the system really quiets down and no builds are running
* @param timeout If non-zero, only block up to the specified number of milliseconds
- * @param reason Quiet reason that will be visible to user
- * @since 2.267
+ * @param message Quiet reason that will be visible to user
+ * @deprecated use {@link #doQuietDown(boolean, int, String, boolean)} instead.
+ */
+ @Deprecated(since = "TODO")
+ public HttpRedirect doQuietDown(boolean block,
+ int timeout,
+ @CheckForNull String message) throws InterruptedException, IOException {
+
+ return doQuietDown(block, timeout, message, false);
+ }
+
+ /**
+ * Quiet down Jenkins - preparation for a restart
+ *
+ * @param block Block until the system really quiets down and no builds are running
+ * @param timeout If non-zero, only block up to the specified number of milliseconds
+ * @param message Quiet reason that will be visible to user
+ * @param safeRestart If the quietDown is for a safeRestart
+ * @since TODO
*/
@RequirePOST
public HttpRedirect doQuietDown(@QueryParameter boolean block,
@QueryParameter int timeout,
- @QueryParameter @CheckForNull String reason) throws InterruptedException, IOException {
+ @QueryParameter @CheckForNull String message,
+ @QueryParameter boolean safeRestart) throws InterruptedException, IOException {
synchronized (this) {
checkPermission(MANAGE);
- quietDownInfo = new QuietDownInfo(reason);
+ quietDownInfo = new QuietDownInfo(message, safeRestart);
}
if (block) {
long waitUntil = timeout;
@@ -4504,20 +4541,35 @@ public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
}
/**
- * Queues up a restart of Jenkins for when there are no builds running, if we can.
+ * Queues up a safe restart of Jenkins.
+ * Builds that cannot continue while the controller is not running have to finish or pause before it can proceed.
+ * No new builds will be started. No new jobs are accepted.
*
- * This first replaces "app" to {@link HudsonIsRestarting}
+ * @deprecated use {@link #doSafeRestart(StaplerRequest, String)} instead.
*
- * @since 1.332
*/
- @CLIMethod(name = "safe-restart")
+ @Deprecated(since = "TODO")
public HttpResponse doSafeRestart(StaplerRequest req) throws IOException, ServletException, RestartNotSupportedException {
+ return doSafeRestart(req, null);
+ }
+
+ /**
+ * Queues up a safe restart of Jenkins. Jobs have to finish or pause before it can proceed. No new jobs are accepted.
+ *
+ * @since TODO
+ */
+ public HttpResponse doSafeRestart(StaplerRequest req, @QueryParameter("message") String message) throws IOException, ServletException, RestartNotSupportedException {
checkPermission(MANAGE);
- if (req != null && req.getMethod().equals("GET"))
+ if (req != null && req.getMethod().equals("GET")) {
return HttpResponses.forwardToView(this, "_safeRestart.jelly");
+ }
+
+ if (req != null && req.getParameter("cancel") != null) {
+ return doCancelQuietDown();
+ }
if (req == null || req.getMethod().equals("POST")) {
- safeRestart();
+ safeRestart(message);
}
return HttpResponses.redirectToDot();
@@ -4563,11 +4615,22 @@ public void run() {
/**
* Queues up a restart to be performed once there are no builds currently running.
* @since 1.332
+ * @deprecated use {@link #safeRestart(String)} instead.
*/
+ @Deprecated(since = "TODO")
public void safeRestart() throws RestartNotSupportedException {
+ safeRestart(null);
+ }
+
+ /**
+ * Queues up a restart to be performed once there are no builds currently running.
+ * @param message the message to show to users in the shutdown banner.
+ * @since TODO
+ */
+ public void safeRestart(String message) throws RestartNotSupportedException {
final Lifecycle lifecycle = restartableLifecycle();
// Quiet down so that we won't launch new builds.
- quietDownInfo = new QuietDownInfo();
+ quietDownInfo = new QuietDownInfo(message, true);
new Thread("safe-restart thread") {
final String exitUser = getAuthentication2().getName();
@@ -4576,11 +4639,10 @@ public void run() {
try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) {
// Wait 'til we have no active executors.
- doQuietDown(true, 0, null);
-
+ doQuietDown(true, 0, message, true);
// Make sure isQuietingDown is still true.
if (isQuietingDown()) {
- servletContext.setAttribute("app", new HudsonIsRestarting());
+ servletContext.setAttribute("app", new HudsonIsRestarting(true));
// give some time for the browser to load the "reloading" page
lifecycle.onStatusUpdate("Restart in 10 seconds");
Thread.sleep(TimeUnit.SECONDS.toMillis(10));
@@ -5752,16 +5814,31 @@ private static void _setJenkinsJVM(boolean jenkinsJVM) {
}
private static final class QuietDownInfo {
-
@CheckForNull
- final String reason;
+ final String message;
+
+ private boolean safeRestart;
QuietDownInfo() {
- this(null);
+ this(null, false);
+ }
+
+ QuietDownInfo(final String message) {
+ this(message, false);
+ }
+
+ QuietDownInfo(final String message, final boolean safeRestart) {
+ this.message = message;
+ this.safeRestart = safeRestart;
+ }
+
+
+ boolean isSafeRestart() {
+ return safeRestart;
}
- QuietDownInfo(final String reason) {
- this.reason = reason;
+ void setSafeRestart(boolean safeRestart) {
+ this.safeRestart = safeRestart;
}
}
}
diff --git a/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java b/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java
index 52b49131ad792..01ac4ca37242c 100644
--- a/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java
+++ b/core/src/main/java/jenkins/model/ModelObjectWithContextMenu.java
@@ -2,7 +2,6 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Functions;
-import hudson.Util;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.BallColor;
@@ -10,6 +9,7 @@
import hudson.model.Job;
import hudson.model.ModelObject;
import hudson.model.Node;
+import hudson.slaves.Cloud;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -150,7 +150,7 @@ public ContextMenu add(String url, String icon, String iconXml, String text, boo
return this;
}
- /** @since TODO */
+ /** @since 2.401 */
public ContextMenu add(String url, String icon, String iconXml, String text, boolean post, boolean requiresConfirmation, Badge badge) {
if (text != null && icon != null && url != null) {
MenuItem item = new MenuItem(url, icon, text);
@@ -163,6 +163,20 @@ public ContextMenu add(String url, String icon, String iconXml, String text, boo
return this;
}
+ /** @since TODO */
+ public ContextMenu add(String url, String icon, String iconXml, String text, boolean post, boolean requiresConfirmation, Badge badge, String message) {
+ if (text != null && icon != null && url != null) {
+ MenuItem item = new MenuItem(url, icon, text);
+ item.iconXml = iconXml;
+ item.post = post;
+ item.requiresConfirmation = requiresConfirmation;
+ item.badge = badge;
+ item.message = message;
+ items.add(item);
+ }
+ return this;
+ }
+
/**
* Add a header row (no icon, no URL, rendered in header style).
*
@@ -221,6 +235,13 @@ public ContextMenu add(Computer c) {
.withContextRelativeUrl(c.getUrl()));
}
+ public ContextMenu add(Cloud c) {
+ return add(new MenuItem()
+ .withDisplayName(c.getDisplayName())
+ .withIconClass(c.getIconClassName())
+ .withContextRelativeUrl(c.getUrl()));
+ }
+
/**
* Adds a child item when rendering context menu of its parent.
*
@@ -333,6 +354,8 @@ class MenuItem {
private Badge badge;
+ private String message;
+
/**
* The type of menu item
* @since 2.340
@@ -355,13 +378,18 @@ public String getIconXml() {
/**
* The badge to display for the context menu item
- * @since TODO
+ * @since 2.401
*/
@Exported
public Badge getBadge() {
return badge;
}
+ @Exported
+ public String getMessage() {
+ return message;
+ }
+
public MenuItem(String url, String icon, String displayName) {
withUrl(url).withIcon(icon).withDisplayName(displayName);
}
@@ -414,7 +442,7 @@ public MenuItem withIconClass(String iconClass) {
}
public MenuItem withDisplayName(String displayName) {
- this.displayName = Util.escape(displayName);
+ this.displayName = displayName;
return this;
}
diff --git a/core/src/main/java/jenkins/model/TransientActionFactory.java b/core/src/main/java/jenkins/model/TransientActionFactory.java
index 73b211751cb91..6eb1ed52692f2 100644
--- a/core/src/main/java/jenkins/model/TransientActionFactory.java
+++ b/core/src/main/java/jenkins/model/TransientActionFactory.java
@@ -24,10 +24,8 @@
package jenkins.model;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionListListener;
import hudson.ExtensionPoint;
@@ -80,59 +78,53 @@ public abstract class TransientActionFactory implements ExtensionPoint {
*/
public abstract @NonNull Collection extends Action> createFor(@NonNull T target);
- /** @see no pairs/tuples in Java */
- private static class CacheKey {
- private final Class> type;
- private final Class extends Action> actionType;
+ @Restricted(NoExternalUse.class)
+ @Extension
+ public static final class Cache extends ExtensionListListener {
- CacheKey(Class> type, Class extends Action> actionType) {
- this.type = type;
- this.actionType = actionType;
- }
+ @SuppressWarnings("rawtypes")
+ private ExtensionList allFactories;
- @Override
- public boolean equals(Object obj) {
- return obj instanceof CacheKey && type == ((CacheKey) obj).type && actionType == ((CacheKey) obj).actionType;
- }
+ private ClassValue>>> cache;
- @Override
- public int hashCode() {
- return type.hashCode() ^ actionType.hashCode();
+ private synchronized ClassValue>>> cache() {
+ if (allFactories == null) {
+ allFactories = ExtensionList.lookup(TransientActionFactory.class);
+ allFactories.addListener(this);
+ }
+ if (cache == null) {
+ cache = new ClassValue<>() {
+ @Override
+ protected ClassValue>> computeValue(Class> type) {
+ return new ClassValue<>() {
+ @Override
+ protected List> computeValue(Class> actionType) {
+ List> factories = new ArrayList<>();
+ for (TransientActionFactory> taf : allFactories) {
+ if (taf.type().isAssignableFrom(type) && (actionType.isAssignableFrom(taf.actionType()) || taf.actionType().isAssignableFrom(actionType))) {
+ factories.add(taf);
+ }
+ }
+ return factories;
+ }
+ };
+ }
+ };
+ }
+ return cache;
}
- }
- @SuppressWarnings("rawtypes")
- private static final LoadingCache, LoadingCache>>> cache =
- CacheBuilder.newBuilder().weakKeys().build(new CacheLoader<>() {
+
@Override
- public LoadingCache>> load(final ExtensionList allFactories) throws Exception {
- final LoadingCache>> perJenkinsCache =
- CacheBuilder.newBuilder().build(new CacheLoader<>() {
- @Override
- public List> load(CacheKey key) throws Exception {
- List> factories = new ArrayList<>();
- for (TransientActionFactory> taf : allFactories) {
- Class extends Action> actionType = taf.actionType();
- if (taf.type().isAssignableFrom(key.type) && (key.actionType.isAssignableFrom(actionType) || actionType.isAssignableFrom(key.actionType))) {
- factories.add(taf);
- }
- }
- return factories;
- }
- });
- allFactories.addListener(new ExtensionListListener() {
- @Override
- public void onChange() {
- perJenkinsCache.invalidateAll();
- }
- });
- return perJenkinsCache;
+ public synchronized void onChange() {
+ cache = null;
}
- });
+
+ }
@Restricted(NoExternalUse.class) // pending a need for it outside Actionable
public static Iterable extends TransientActionFactory>> factoriesFor(Class> type, Class extends Action> actionType) {
- return cache.getUnchecked(ExtensionList.lookup(TransientActionFactory.class)).getUnchecked(new CacheKey(type, actionType));
+ return ExtensionList.lookupSingleton(Cache.class).cache().get(type).get(actionType);
}
}
diff --git a/test/src/test/java/jenkins/util/AntClassLoaderTest.java b/core/src/main/java/jenkins/model/experimentalflags/RemovePrototypeUserExperimentalFlag.java
similarity index 58%
rename from test/src/test/java/jenkins/util/AntClassLoaderTest.java
rename to core/src/main/java/jenkins/model/experimentalflags/RemovePrototypeUserExperimentalFlag.java
index c8c882ed81ac1..a2773986cfda8 100644
--- a/test/src/test/java/jenkins/util/AntClassLoaderTest.java
+++ b/core/src/main/java/jenkins/model/experimentalflags/RemovePrototypeUserExperimentalFlag.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright 2020 CloudBees, Inc.
+ * Copyright (c) 2023, 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
@@ -22,26 +22,33 @@
* THE SOFTWARE.
*/
-package jenkins.util;
+package jenkins.model.experimentalflags;
-import static org.junit.Assert.assertNotNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import hudson.Extension;
-import org.junit.Rule;
-import org.junit.Test;
-import org.jvnet.hudson.test.Issue;
-import org.jvnet.hudson.test.JenkinsRule;
-import org.jvnet.hudson.test.recipes.WithPlugin;
+@Extension
+public final class RemovePrototypeUserExperimentalFlag extends BooleanUserExperimentalFlag {
-public class AntClassLoaderTest {
+ public RemovePrototypeUserExperimentalFlag() {
+ super("remove-prototype.flag");
+ }
- @Rule
- public JenkinsRule r = new JenkinsRule();
+ @NonNull
+ @Override
+ public Boolean getDefaultValue() {
+ return false;
+ }
- @Issue("JENKINS-60644")
- @WithPlugin("loads-resource.jpi")
- @Test
- public void loadsResource() throws Exception {
- assertNotNull(r.jenkins.pluginManager.getPlugin("loads-resource").classLoader.getResourceAsStream("io/jenkins/plugins/loads_resource/stuff"));
+ @Override
+ public String getDisplayName() {
+ return "Remove Prototype.js";
}
+ @Nullable
+ @Override
+ public String getShortDescription() {
+ return "Remove Prototype.js from all Jenkins UI pages. This will break anything that depends on Prototype.js.";
+ }
}
diff --git a/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java b/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java
index 79984477a02aa..d9771de36576a 100644
--- a/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java
+++ b/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java
@@ -3,7 +3,6 @@
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.util.AdaptedIterator;
import hudson.util.Iterators;
-import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
@@ -192,25 +191,16 @@ protected R adapt(BuildReference ref) {
@Override
public Object[] toArray() {
- List