Repository: jenkinsci/docker Branch: master Commit: 2167b62427c1 Files: 88 Total size: 229.7 KB Directory structure: gitextract_gxy0y0_6/ ├── .ci/ │ └── publish.sh ├── .git-blame-ignore-revs ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── dependabot.yml │ ├── release-drafter.yml │ └── workflows/ │ ├── release-drafter.yml │ └── updatecli.yaml ├── .gitignore ├── .gitmodules ├── .hadolint.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── HACKING.adoc ├── Jenkinsfile ├── LICENSE.txt ├── Makefile ├── README.md ├── SECURITY.md ├── alpine/ │ └── hotspot/ │ └── Dockerfile ├── debian/ │ └── Dockerfile ├── docker-bake.hcl ├── jdk-download-url.sh ├── jdk-download.sh ├── jenkins-plugin-cli.ps1 ├── jenkins-plugin-cli.sh ├── jenkins-support ├── jenkins-support.psm1 ├── jenkins.io-2026.key ├── jenkins.ps1 ├── jenkins.sh ├── make.ps1 ├── rhel/ │ └── Dockerfile ├── tests/ │ ├── bake.bats │ ├── functions/ │ │ ├── .ssh/ │ │ │ └── config │ │ ├── Dockerfile │ │ └── Dockerfile-windows │ ├── functions.Tests.ps1 │ ├── functions.bats │ ├── golden/ │ │ ├── expected_env_vars_except_hostname.txt │ │ ├── expected_platforms.txt │ │ ├── expected_tags.txt │ │ ├── expected_tags_latest_lts.txt │ │ └── expected_tags_latest_weekly.txt │ ├── jenkinsfile.bats │ ├── plugins-cli/ │ │ ├── Dockerfile │ │ ├── Dockerfile-windows │ │ ├── custom-war/ │ │ │ ├── Dockerfile │ │ │ ├── Dockerfile-windows │ │ │ └── WEB-INF/ │ │ │ └── plugins/ │ │ │ └── my-happy-plugin.hpi │ │ ├── java-opts/ │ │ │ └── Dockerfile │ │ ├── no-war/ │ │ │ ├── Dockerfile │ │ │ ├── Dockerfile-windows │ │ │ └── plugins.txt │ │ ├── pluginsfile/ │ │ │ ├── Dockerfile │ │ │ ├── Dockerfile-windows │ │ │ └── plugins.txt │ │ ├── ref/ │ │ │ ├── Dockerfile │ │ │ └── Dockerfile-windows │ │ └── update/ │ │ ├── Dockerfile │ │ └── Dockerfile-windows │ ├── plugins-cli.Tests.ps1 │ ├── plugins-cli.bats │ ├── runtime.Tests.ps1 │ ├── runtime.bats │ ├── test_helpers.bash │ ├── test_helpers.psm1 │ ├── update-golden-file.sh │ └── upgrade-plugins/ │ ├── Dockerfile │ └── Dockerfile-windows ├── tini_pub.gpg ├── tools/ │ ├── hadolint │ └── shellcheck ├── updatecli/ │ ├── scripts/ │ │ └── rhel-latest-tag.sh │ ├── updatecli.d/ │ │ ├── alpine.yaml │ │ ├── debian.yaml │ │ ├── git-lfs.yaml │ │ ├── hadolint.yaml │ │ ├── jdk17.yaml │ │ ├── jdk21.yaml │ │ ├── jdk25.yaml │ │ ├── jenkins-version-simulated-lts.yaml │ │ ├── jenkins-version.yaml │ │ ├── plugin-installation-manager-tool.yaml │ │ ├── rhel.yaml │ │ └── shellcheck.yaml │ └── values.github-action.yaml └── windows/ └── windowsservercore/ └── hotspot/ └── Dockerfile ================================================ FILE CONTENTS ================================================ ================================================ FILE: .ci/publish.sh ================================================ #!/bin/bash -eu # Publish any versions of the docker image not yet pushed to ${JENKINS_REPO} # Arguments: # -n dry run, do not build or publish images # -d debug : "${JENKINS_VERSION:?Variable \$JENKINS_VERSION not set or empty.}" set -eu -o pipefail : "${DOCKERHUB_ORGANISATION:=jenkins}" : "${DOCKERHUB_REPO:=jenkins}" : "${BAKE_TARGET:=linux}" export JENKINS_REPO="${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}" function sort-versions() { if [ "$(uname)" == 'Darwin' ]; then gsort --version-sort else sort --version-sort fi } # Process arguments dry_run=false debug=false while [[ $# -gt 0 ]]; do key="$1" case "${key}" in -n) dry_run=true ;; -d) debug=true ;; *) echo "ERROR: Unknown option: ${key}" exit 1 ;; esac shift done if [[ "${debug}" = true ]]; then echo "Debug mode enabled" set -x fi if [[ "${dry_run}" = true ]]; then echo "Dry run, will not publish images" fi # Retrieve all the Jenkins versions from Artifactory all_jenkins_versions="$(curl --disable --fail --silent --show-error --location \ https://repo.jenkins-ci.org/releases/org/jenkins-ci/main/jenkins-war/maven-metadata.xml \ | grep '.*')" latest_lts_version="$(echo "${all_jenkins_versions}" | grep -E -o '[0-9]\.[0-9]+\.[0-9]' | sort-versions | tail -n1)" latest_weekly_version="$(echo "${all_jenkins_versions}" | grep -E -o '[0-9]\.[0-9]+' | sort-versions | tail -n 1)" if [[ "${JENKINS_VERSION}" == "${latest_weekly_version}" ]] then LATEST_WEEKLY="true" else LATEST_WEEKLY="false" fi if [[ "${JENKINS_VERSION}" == "${latest_lts_version}" ]] then LATEST_LTS="true" else LATEST_LTS="false" fi build_opts=("--pull") metadata_suffix="publish" if test "${dry_run}" == "true"; then build_opts+=("--set=*.output=type=cacheonly") metadata_suffix="dry-run" else build_opts+=("--push") fi # Save build result metadata mkdir -p target BUILD_METADATA_PATH="target/build-result-metadata_${BAKE_TARGET}_${metadata_suffix}.json" build_opts+=("--metadata-file=${BUILD_METADATA_PATH}") COMMIT_SHA=$(git rev-parse HEAD) export COMMIT_SHA JENKINS_VERSION LATEST_WEEKLY LATEST_LTS BUILD_METADATA_PATH cat < ## 📦 Jenkins Core updates * Update to Jenkins $NEXT_MINOR_VERSION ([changelog](https://www.jenkins.io/changelog/$NEXT_MINOR_VERSION)) $CHANGES ================================================ FILE: .github/workflows/release-drafter.yml ================================================ # Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc name: Release Drafter on: push: branches: - "master" workflow_dispatch: jobs: update_release_draft: runs-on: ubuntu-latest if: github.repository_owner == 'jenkinsci' steps: # https://github.com/release-drafter/release-drafter/issues/871#issuecomment-3686135188 - name: Wait for 15 seconds to ensure GraphQL consistency shell: bash run: sleep 15s # Drafts your next Release notes as Pull Requests are merged into "master" - name: Release Drafter uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7.1.1 with: token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/updatecli.yaml ================================================ name: updatecli on: # Allow to be run manually workflow_dispatch: schedule: # Run once per week (to avoid alert fatigue) - cron: '0 2 * * 1' # Every Monday at 2am UTC push: branches: - master pull_request: branches: - master jobs: updatecli: runs-on: ubuntu-latest if: github.repository_owner == 'jenkinsci' steps: - name: Checkout uses: actions/checkout@v6 - name: Install Updatecli in the runner uses: updatecli/updatecli-action@v2.100.0 - name: Run Updatecli in Dry Run mode run: updatecli diff --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml env: UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run Updatecli in Apply mode if: github.ref == 'refs/heads/master' run: updatecli apply --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml env: UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ *.tmp bats/ target/ tests/functions/init.groovy.d/ tests/functions/java_cp/ tests/functions/copy_reference_file.log tests/**/work-*/ manifest-tool multiarch/qemu-* multiarch/Dockerfile-* /docker.iml work-pester-jenkins-windows/ /.idea/ /**/windows/**/jenkins.ps1 /**/windows/**/jenkins-plugin-cli.ps1 /**/windows/**/jenkins-support.psm1 build-windows_*.yaml tests/**/Dockerfile\.* ================================================ FILE: .gitmodules ================================================ [submodule "tests/test_helper/bats-support"] path = tests/test_helper/bats-support url = https://github.com/ztombol/bats-support [submodule "tests/test_helper/bats-assert"] path = tests/test_helper/bats-assert url = https://github.com/ztombol/bats-assert ================================================ FILE: .hadolint.yml ================================================ # Hadolint configuration file --- # configure ignore rules # see https://github.com/hadolint/hadolint#rules for a list of available rules. ignored: # Exclusions in this section have been triaged and determined to be false # positives. - DL3008 # Pin versions in apt-get install - DL3018 # Pin versions in apk add - DL3033 # Specify version with yum install -y - - DL3041 # Specify version with dnf install -y - # Here lies technical debt. Exclusions in this section have not yet been # triaged. When working on on this section, pick an exclusion to triage, then: # - If it is a false positive, add a "# hadolint ignore=" suppression, # then remove the exclusion from this section. # - If it is not a false positive, fix the bug, then remove the exclusion from # this section. - DL3006 # Always tag the version of an image explicitly ================================================ FILE: CHANGELOG.md ================================================ Changelog ========= | See [GitHub releases](https://github.com/jenkinsci/docker/releases) | | --- | These release notes represent changes in the controller image content or packaging, but not in the bundled WAR files. Please refer to the [weekly changelog](https://jenkins.io/changelog/) and [LTS changelog](https://jenkins.io/changelog-stable/) for WAR file changelogs. ## Version scheme The repository follows the scheme of Jenkins, 2-digit for [Weekly releases](https://jenkins.io/download/weekly/) and 3-digit for [LTS releases](https://jenkins.io/download/lts/). ================================================ FILE: CONTRIBUTING.md ================================================ # Issues and Contributing Please note that only issues related to this Docker image will be addressed here. * If you have Docker related issues, please ask in the [Docker user mailing list](https://groups.google.com/forum/#!forum/docker-user). * If you have Jenkins related issues, please ask in the [Jenkins mailing lists](https://jenkins-ci.org/content/mailing-lists). * If you are not sure, then this is probably not the place to create an issue and you should use any of the previously mentioned mailing lists. If after going through the previous checklist you still think you should create an issue here please provide: * Docker commands that you execute * Actual result * Expected outcome * Have you tried a non-dockerized Jenkins and get the expected outcome? * Output of `docker version` * Other relevant information If you are interested to provide fixes by yourself, you might want to check out the [dedicated documentation](HACKING.adoc). ================================================ FILE: HACKING.adoc ================================================ = Hacking documentation This document explains how to develop on this repository. == Requirements * A Bourne-Again-Shell compatible prompt (bash) * GNU `make` 3.80+ * Docker with https://github.com/docker/buildx[BuildX] capability ** Docker 20.10+ is recommended as it is usually packaged with Buildx ** Docker 19.03+ is required ** BuildX v0.5.1+ is needed (manual installation of the plugin can be done if you don't have it: https://github.com/docker/buildx) * https://git-scm.com/[git] 1.6+ (git 2+ is recommended) * https://stedolan.github.io/jq/[jq] 1.6+ * https://curl.se/[curl] 7+ We recommend https://www.gnu.org/software/parallel/[GNU Parallel] for parallel test execution, but it is not required. // In case the link breaks, and the bug hasn't been fixed yet: // On Apple Silicon in native arm64 containers, older versions of libssl in // debian:buster and ubuntu:20.04 will segfault when connected to some TLS // servers, for example curl https://dl.yarnpkg.com. The bug is fixed in newer versions // of libssl in debian:bookworm, ubuntu:21.04 and fedora:35. Tests currently do not work on Mac M1 due to a link:https://docs.docker.com/docker-for-mac/release-notes/#known-issues[known issue] in 'Docker Desktop 3.4.0'. == Building === Linux [source,bash] -- ## build all linux platforms make build ## only build a specific linux image make build-debian_jdk17 # or build-alpine_jdk17 build-debian_slim_jdk17 build-debian_jdk17 ... -- == Testing === Linux Tests for Linux images are written using https://github.com/bats-core/bats-core[bats] under the `tests/` directory. Tests pre-requisites are automatically managed by the `make prepare-test` target (dependency of any `make test*` target) which: - Ensures that the `bats` command is installed in the `bats/bin/` directory (along with all the bats project in `./bats/`) - Ensures that the additional bats helper are installed as git sub-modules in `./tests/test_helper/` For efficiency, the tests are executed in parallel. [IMPORTANT] Due to the parallel execution, each test should be self-contained and not depend on another test, even inside a given test harness. Please note that: - You can disable the parallel execution by setting the environment variable `DISABLE_PARALLEL_TESTS` to the value `true` - Parallel execution is disabled if the commands `docker` or (GNU) `parallel` are not installed. You can restrict the execution to only a subset of test harness files. By setting the environment variable `TEST_SUITES` to the path of the bats test harness file to execute alone. [source,bash] -- ## Run tests for all linux platforms make test ## Run tests for a specific linux platform make test-debian_jdk17 # or test-alpine_jdk17 test-debian_slim_jdk17 test-debian_jdk17 ... ## Run tests for Alpine Linux JDK17 platform in sequential mode DISABLE_PARALLEL_TESTS=true make test-alpine_jdk17 ## Only run the test suite `functions.bats` for the Debian JDK21 platform TEST_SUITES=./tests/functions.bats make test-debian_jdk21 -- You can also pass extra parameters to `bats` by setting `BATS_FLAGS`. Example: [source,bash] -- ## Run tests except those with a "test-type:golden-file" `bats` tag make test BATS_FLAGS="--filter-tags '\!test-type:golden-file'" ## Add an extra parameter make test BATS_FLAGS="--filter-tags '\!test-type:golden-file' --verbose-run" -- See https://bats-core.readthedocs.io/en/stable/usage.html for the list of `bats` options. === Golden files A golden file (sometimes called a snapshot) is a file that contains the expected output of a program or function. Tests compare the current output of the code against this "golden" reference to detect regressions or unintended changes. They are treated as contract artifacts, not test fixtures. Golden files may be updated only when: * Output behavior is intentionally changed * A bug fix corrects previously incorrect output * A new test case is added Golden updates must be reviewed like code. If your work implies golden file changes, those changes must be committed: * In the same commit as the behavior change * With a commit message explaining why output changed Golden updates must never be automatic. ==== How to update a golden file * Reproduce output manually * Inspect output * Update the golden file explicitly * Run tests To update a golden file, you can use the dedicated ./tests/update-golden-file.sh helper script. Example if there are new LTS tags: [source,bash] -- ./tests/update-golden-file.sh expected_tags_latest_lts make tags LATEST_LTS=true -- Then ensure corresponding tests are passing with: [source,bash] -- bats ./tests/tags.bats -- == Multiarch support The buildx tool is used to build our multiarch images, this relies on either QEMU for emulating the architecture being built, or a remote builder configured for the required platform(s). Planned supported architectures: * amd64 * arm64 * s390x == Debugging In order to debug the controller, use the `-e DEBUG=true -p 5005:5005` when starting the container. Jenkins will be suspended on the startup in such case, and then it will be possible to attach a debugger from IDE to it. == Test images publication You can test the script used for publication in dry-run by setting an existing Jenkins Core version and by passing the `-n` option: [source,bash] -- $ export JENKINS_VERSION=2.528.3 $ ./.ci/publish.sh -n Dry run, will not publish images Using the following settings: * JENKINS_REPO: jenkins/jenkins * JENKINS_VERSION: 2.528.3 * COMMIT_SHA: 1c72a9383191562eb3c44838aeeadad0839c2c92 * LATEST_WEEKLY: false * LATEST_LTS: true * BUILD_METADATA_PATH: target/build-result-metadata_linux_dry-run.json [+] Building 104.6s (59/73) <...snip...> -- Note that you can set `BAKE_TARGET` to test the publication of a single target instead of the default "linux" multiarch (heavy) build: [source,bash] -- $ export BAKE_TARGET=debian_jdk25 $ ./.ci/publish.sh -n Using the following settings: * JENKINS_REPO: jenkins/jenkins * JENKINS_VERSION: 2.528.3 * COMMIT_SHA: aaf4e7faf887b7ac4879c3bf540ede48220cca9f * LATEST_WEEKLY: false * LATEST_LTS: true * BUILD_METADATA_PATH: target/build-result-metadata_debian_jdk25_dry-run.json * BAKE TARGET: debian_jdk25 * BUILDX OPTIONS: --pull --set=*.output=type=cacheonly --metadata-file=target/build-result-metadata_debian_jdk25_dry-run.json * RESOLVED BAKE CONFIG: { "group": { "default": { "targets": [ "debian_jdk25" ] } }, "target": { "debian_jdk25": { "context": ".", "dockerfile": "debian/Dockerfile", "args": { "COMMIT_SHA": "aaf4e7faf887b7ac4879c3bf540ede48220cca9f", "DEBIAN_RELEASE_LINE": "trixie", "DEBIAN_VARIANT": "", "DEBIAN_VERSION": "20251117", "JAVA_VERSION": "25.0.1_8", "JENKINS_VERSION": "2.528.3", "PLUGIN_CLI_VERSION": "2.14.0", "WAR_URL": "https://get.jenkins.io/war-stable/2.528.3/jenkins.war" }, "tags": [ "docker.io/jenkins/jenkins:2.528.3-jdk25", "docker.io/jenkins/jenkins:lts-jdk25", "docker.io/jenkins/jenkins:2.528.3-lts-jdk25" ], "platforms": [ "linux/amd64", "linux/arm64", "linux/s390x", "linux/ppc64le" ] } } } [+] Building 104.6s (59/73) ... -- You can also pass the `-d` option (debug) to see traces from the script. === Using an overridden target repository on Docker Hub Create a new dedicated target repository in your Docker Hub account, and use it like follows (note the absence of `-d` option): [source,bash] -- $ export DOCKERHUB_ORGANISATION=jenkins4eval $ export DOCKERHUB_REPO=test-jenkins # The log below will help confirm this override was taken in account: $ ./.ci/publish.sh Using the following settings: * JENKINS_REPO: jenkins4eval/test-jenkins * JENKINS_VERSION: 2.528.3 * WAR_SHA: bfa31f1e3aacebb5bce3d5076c73df97bf0c0567eeb8d8738f54f6bac48abd74 * COMMIT_SHA: aaf4e7faf887b7ac4879c3bf540ede48220cca9f * LATEST_WEEKLY: false * LATEST_LTS: true * BUILD_METADATA_PATH: target/build-result-metadata_linux_publish.json * BAKE TARGET: linux * BUILDX OPTIONS: --pull --push --metadata-file=target/build-result-metadata_linux_publish.json * RESOLVED BAKE CONFIG: { ... -- ================================================ FILE: Jenkinsfile ================================================ #!/usr/bin/env groovy def listOfProperties = [] listOfProperties << buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '5')) // Only master branch will run on a timer basis if (env.BRANCH_NAME.trim() == 'master') { listOfProperties << pipelineTriggers([cron('''H H/6 * * 0-2,4-6 H 6,21 * * 3''')]) } properties(listOfProperties) // Default environment variable set to allow images publication def envVars = ['PUBLISH=true'] // List of architectures and corresponding ci.jenkins.io agent labels def architecturesAndCiJioAgentLabels = [ 'amd64': 'docker && amd64', 'arm64': 'arm64docker', // Using qemu 'ppc64le': 'docker && amd64', 'riscv64': 'docker && amd64', 's390x': 'docker && amd64', ] // Set to true in a replay to simulate a LTS build on ci.jenkins.io // It will set the environment variables needed for a LTS // and disable images publication out of caution def SIMULATE_LTS_BUILD = false if (SIMULATE_LTS_BUILD) { envVars = [ 'PUBLISH=false', 'TAG_NAME=2.504.3', // TODO: replace by the first LTS based on 2.534+ when available 'JENKINS_VERSION=2.541.1', // Filter out golden file based testing // To filter out all tests, set BATS_FLAGS="--filter-tags none" 'BATS_FLAGS=--filter-tags "\\!test-type:golden-file"' ] } stage('Build') { def builds = [:] withEnv(envVars) { echo '= bake target: linux' def windowsImageTypes = [ 'windowsservercore-ltsc2019', 'windowsservercore-ltsc2022' ] for (anImageType in windowsImageTypes) { def imageType = anImageType builds[imageType] = { def windowsVersionNumber = imageType.split('-')[1].replace('ltsc', '') def windowsLabel = "windows-${windowsVersionNumber}" nodeWithTimeout(windowsLabel) { stage('Checkout') { checkout scm } withEnv(["IMAGE_TYPE=${imageType}"]) { if (!infra.isTrusted()) { /* Outside of the trusted.ci environment, we're building and testing * the Dockerfile in this repository, but not publishing to docker hub */ stage("Build ${imageType}") { powershell './make.ps1 build -ImageType ${env:IMAGE_TYPE}' archiveArtifacts artifacts: 'build-windows_*.yaml', allowEmptyArchive: true } stage("Test ${imageType}") { def windowsTestStatus = powershell(script: './make.ps1 test -ImageType ${env:IMAGE_TYPE}', returnStatus: true) junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results.xml') if (windowsTestStatus > 0) { // If something bad happened let's clean up the docker images error('Windows test stage failed.') } } // disable until we get the parallel changes merged in // def branchName = "${env.BRANCH_NAME}" // if (branchName ==~ 'master'){ // stage('Publish Experimental') { // infra.withDockerCredentials { // withEnv(['DOCKERHUB_ORGANISATION=jenkins4eval','DOCKERHUB_REPO=jenkins']) { // powershell './make.ps1 publish' // } // } // } // } } else { // Only publish when a tag triggered the build & the publication is enabled (ie not simulating a LTS) if (env.TAG_NAME && (env.PUBLISH == 'true')) { // Split to ensure any suffix is not taken in account (but allow suffix tags to trigger rebuilds) String jenkins_version = env.TAG_NAME.split('-')[0] // Setting WAR_URL to download war from Artifactory instead of mirrors on publication from trusted.ci.jenkins.io withEnv([ "JENKINS_VERSION=${jenkins_version}", "WAR_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${jenkins_version}/jenkins-war-${jenkins_version}.war" ]) { stage('Publish') { infra.withDockerCredentials { withEnv(['DOCKERHUB_ORGANISATION=jenkins', 'DOCKERHUB_REPO=jenkins']) { powershell './make.ps1 build -ImageType ${env:IMAGE_TYPE}' powershell './make.ps1 publish -ImageType ${env:IMAGE_TYPE}' } } } } } } } } } } if (!infra.isTrusted()) { // An up to date list can be obtained with make list-linux def images = [ 'alpine_jdk21', 'alpine_jdk25', 'debian_jdk21', 'debian_jdk25', 'debian-slim_jdk21', 'debian-slim_jdk25', 'rhel_jdk21', 'rhel_jdk25', ] for (i in images) { def imageToBuild = i builds[imageToBuild] = { nodeWithTimeout(architecturesAndCiJioAgentLabels["amd64"]) { deleteDir() stage('Checkout') { checkout scm } stage('Static analysis') { sh 'make hadolint shellcheck' } /* Outside of the trusted.ci environment, we're building and testing * the Dockerfile in this repository, but not publishing to docker hub */ stage("Build linux-${imageToBuild}") { sh "make build-${imageToBuild}" archiveArtifacts artifacts: 'target/build-result-metadata_*.json', allowEmptyArchive: true } stage("Test linux-${imageToBuild}") { sh 'make prepare-test' try { sh "make test-${imageToBuild}" } catch (err) { error("${err.toString()}") } finally { junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/*.xml') } } } } } // Building every other architectures than amd64 on agents with the corresponding labels if available architecturesAndCiJioAgentLabels.findAll { arch, _ -> arch != 'amd64' }.each { architecture, labels -> builds[architecture] = { nodeWithTimeout(labels) { stage('Checkout') { deleteDir() checkout scm } // sanity check that proves all images build on declared platforms not already built in other stages stage("Multi arch build - ${architecture}") { sh "make docker-init buildarch-${architecture}" archiveArtifacts artifacts: 'target/build-result-metadata_*.json', allowEmptyArchive: true } } } } } else { // Only publish when a tag triggered the build if (env.TAG_NAME) { // Split to ensure any suffix is not taken in account (but allow suffix tags to trigger rebuilds) String jenkins_version = env.TAG_NAME.split('-')[0] builds['linux'] = { // Setting WAR_URL to download war from Artifactory instead of mirrors on publication from trusted.ci.jenkins.io withEnv([ "JENKINS_VERSION=${jenkins_version}", "WAR_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${jenkins_version}/jenkins-war-${jenkins_version}.war" ]) { nodeWithTimeout('docker') { stage('Checkout') { checkout scm } stage('Publish') { // Publication is enabled by default, disabled when simulating a LTS if (env.PUBLISH == 'true') { infra.withDockerCredentials { sh 'make docker-init' sh 'make publish' archiveArtifacts artifacts: 'target/build-result-metadata_*.json', allowEmptyArchive: true } } } } } } } } parallel builds } } void nodeWithTimeout(String label, def body) { node(label) { timeout(time: 60, unit: 'MINUTES') { body.call() } } } ================================================ FILE: LICENSE.txt ================================================ The MIT License Copyright (c) 2014-, Michael Neale, Nicolas de Loof, Carlos Sanchez, and a number of other of contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ ROOT_DIR="$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/" ## For Docker <=20.04 export DOCKER_BUILDKIT=1 ## For Docker <=20.04 export DOCKER_CLI_EXPERIMENTAL=enabled ## Required to have docker build output always printed on stdout export BUILDKIT_PROGRESS=plain ## Required to have the commit SHA added as a Docker image label export COMMIT_SHA=$(shell git rev-parse HEAD) current_os := $(shell uname -s) current_arch := $(shell uname -m) export OS ?= $(shell \ case "$(current_os)" in \ (Linux) echo linux ;; \ (Darwin) echo linux ;; \ (MINGW*|MSYS*|CYGWIN*) echo windows ;; \ (*) echo unknown ;; \ esac) export ARCH ?= $(shell \ case $(current_arch) in \ (x86_64) echo "amd64" ;; \ (aarch64|arm64) echo "arm64" ;; \ (s390*|riscv*|ppc64le) echo $(current_arch);; \ (*) echo "UNKNOWN-CPU";; \ esac) all: hadolint shellcheck build test # Set to 'true' to disable parallel tests DISABLE_PARALLEL_TESTS ?= false # Set to the path of a specific test suite to restrict execution only to this # default is "all test suites in the "tests/" directory TEST_SUITES ?= $(CURDIR)/tests ##### Macros ## Check the presence of a CLI in the current PATH check_cli = type "$(1)" >/dev/null 2>&1 || { echo "Error: command '$(1)' required but not found. Exiting." ; exit 1 ; } ## Check if a given image exists in the current manifest docker-bake.hcl check_image = make --silent list | grep -w '$(1)' >/dev/null 2>&1 || { echo "Error: the image '$(1)' does not exist in manifest for the current platform '$(OS)/$(ARCH)'. Please check the output of 'make list'. Exiting." ; exit 1 ; } ## Base "docker buildx base" command to be reused everywhere bake_base_cli := docker buildx bake -f docker-bake.hcl --load ## Default bake target bake_default_target := all check-reqs: ## Build requirements @$(call check_cli,bash) @$(call check_cli,git) @$(call check_cli,docker) @docker info | grep 'buildx:' >/dev/null 2>&1 || { echo "Error: Docker BuildX plugin required but not found. Exiting." ; exit 1 ; } ## Test requirements @$(call check_cli,curl) @$(call check_cli,jq) ## This function is specific to Jenkins infrastructure and isn't required in other contexts docker-init: check-reqs ifeq ($(CI),true) ifeq ($(wildcard /etc/buildkitd.toml),) @echo 'WARNING: /etc/buildkitd.toml not found, using default configuration.' docker buildx create --use --bootstrap --driver docker-container else docker buildx create --use --bootstrap --driver docker-container --config /etc/buildkitd.toml endif else docker buildx create --use --bootstrap --driver docker-container endif # There is only an amd64 qemu image ifeq ($(ARCH),amd64) docker run --rm --privileged multiarch/qemu-user-static --reset -p yes endif # Lint check on all Dockerfiles hadolint: find . -type f -name 'Dockerfile*' -not -path "./bats/*" -print0 | xargs -0 $(ROOT_DIR)/tools/hadolint # Shellcheck on all bash scripts shellcheck: @$(ROOT_DIR)/tools/shellcheck -e SC1091 jenkins-support *.sh tests/test_helpers.bash tools/hadolint tools/shellcheck .ci/publish.sh # Build all targets with the current OS and architecture build: check-reqs target @set -x; $(bake_base_cli) --metadata-file=target/build-result-metadata_$(bake_default_target).json --set '*.platform=$(OS)/$(ARCH)' $(shell make --silent list) # Build targets depending on the architecture (Linux only, no multiarch for Windows) buildarch-%: check-reqs target showarch-% @set -x; $(bake_base_cli) --metadata-file=target/build-result-metadata_$*.json --set '*.platform=linux/$*' $(shell make --silent listarch-$*) # Build a specific target with the current OS and architecture build-%: check-reqs target show-% @$(call check_image,$*) @set -x; $(bake_base_cli) --metadata-file=target/build-result-metadata_$*.json --set '*.platform=$(OS)/$(ARCH)' '$*' # Show all targets show: @set -x; make --silent show-$(bake_default_target) # Show a specific target show-%: @set -x; $(bake_base_cli) --progress=quiet '$*' --print | jq # Show all targets depending on the architecture showarch-%: @set -x; make --silent show | jq --arg arch "$(OS)/$*" '.target |= with_entries(select(.value.platforms | index($$arch)))' # List tags of all targets tags: @set -x; make tags-$(bake_default_target) # List tags of a specific target tags-%: @set -x; make show-$* | jq -r ' .target | to_entries[] | .key as $$name | .value.tags[] | "\(.) (\($$name))"' | LC_ALL=C sort -u # List all platforms platforms: @set -x; make platforms-$(bake_default_target) # List platforms of a specific target platforms-%: @set -x; make show-$* | jq -r ' .target | to_entries[] | .key as $$name | .value.platforms[] | "\($$name):\(.)"' | LC_ALL=C sort -u # Return the list of targets depending on the current OS and architecture list: check-reqs @set -x; make --silent listarch-$(ARCH) # Return the list of targets of a specific "target" (can be a docker bake group) list-%: check-reqs @set -x; make --silent show-$* | jq -r '.target | keys[]' # Return the list of targets depending on the architecture (Linux only, no multiarch for Windows) listarch-%: check-reqs @set -x; make --silent showarch-$* | jq -r '.target | keys[]' # Ensure bats exists in the current folder bats: git clone https://github.com/bats-core/bats-core bats ;\ cd bats ;\ git checkout 3bca150ec86275d6d9d5a4fd7d48ab8b6c6f3d87; # v1.13.0 # Ensure all bats submodules are up to date prepare-test: bats check-reqs target git submodule update --init --recursive # Ensure tests and build metadata "target" folder exist target: mkdir -p target ## Define bats options based on environment # common flags for all tests bats_flags := $(TEST_SUITES) # if DISABLE_PARALLEL_TESTS true, then disable parallel execution ifneq (true,$(DISABLE_PARALLEL_TESTS)) # If the GNU 'parallel' command line is absent, then disable parallel execution parallel_cli := $(shell command -v parallel 2>/dev/null) ifneq (,$(parallel_cli)) # If parallel execution is enabled, then set 2 tests per core available for the Docker Engine test-%: PARALLEL_JOBS ?= $(shell echo $$(( $(shell docker run --rm alpine grep -c processor /proc/cpuinfo) * 2))) test-%: bats_flags += --jobs $(PARALLEL_JOBS) endif endif # Optional bats flags (see https://bats-core.readthedocs.io/en/stable/usage.html) ifneq (,$(BATS_FLAGS)) test-%: bats_flags += $(BATS_FLAGS) endif test-%: prepare-test # Check that the image exists in the manifest @$(call check_image,$*) # Ensure that the image is built @make --silent build-$* # Show bats version @bats/bin/bats --version ifeq ($(CI), true) # Execute the test harness and write result to a TAP file IMAGE=$* bats/bin/bats $(bats_flags) --formatter junit | tee target/junit-results-$*.xml else # Execute the test harness IMAGE=$* bats/bin/bats $(bats_flags) --timing endif # Test targets depending on the current architecture test: prepare-test @make --silent list | while read image; do make --silent "test-$${image}"; done # Set all required variables and publish all targets # Calling publish.sh with `-n` (dry-run) arg in case `PUBLISH` is not set to true publish: target ifeq ($(PUBLISH),true) ./.ci/publish.sh else ./.ci/publish.sh -n endif clean: rm -rf tests/test_helper/bats-*; \ rm -rf bats .PHONY: hadolint shellcheck check-reqs build clean test list show ================================================ FILE: README.md ================================================ # Official Jenkins Docker image [![Docker Stars](https://img.shields.io/docker/stars/jenkins/jenkins.svg)](https://hub.docker.com/r/jenkins/jenkins/) [![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/jenkins.svg)](https://hub.docker.com/r/jenkins/jenkins/) [![Join the chat at https://gitter.im/jenkinsci/docker](https://badges.gitter.im/jenkinsci/docker.svg)](https://gitter.im/jenkinsci/docker?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) The Jenkins Continuous Integration and Delivery server [available on Docker Hub](https://hub.docker.com/r/jenkins/jenkins). This is a fully functional Jenkins server. [https://jenkins.io/](https://jenkins.io/). # Usage ``` docker run -p 8080:8080 -p 50000:50000 --restart=on-failure jenkins/jenkins:lts-jdk21 ``` NOTE: read the section [_Connecting agents_](#connecting-agents) below for the role of the `50000` port mapping. NOTE: read the section [_DNS Configuration_](#dns-configuration) in case you see the message "This Jenkins instance appears to be offline." This will store the workspace in `/var/jenkins_home`. All Jenkins data lives in there - including plugins and configuration. You will probably want to make that an explicit volume so you can manage it and attach to another container for upgrades : ``` docker run -p 8080:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk21 ``` This will automatically create a 'jenkins_home' [docker volume](https://docs.docker.com/storage/volumes/) on the host machine. Docker volumes retain their content even when the container is stopped, started, or deleted. NOTE: Avoid using a [bind mount](https://docs.docker.com/storage/bind-mounts/) from a folder on the host machine into `/var/jenkins_home`, as this might result in file permission issues (the user used inside the container might not have rights to the folder on the host machine). If you _really_ need to bind mount jenkins_home, ensure that the directory on the host is accessible by the jenkins user inside the container (jenkins user - uid 1000) or use `-u some_other_user` parameter with `docker run`. ``` docker run -d -v jenkins_home:/var/jenkins_home -p 8080:8080 -p 50000:50000 --restart=on-failure jenkins/jenkins:lts-jdk21 ``` This will run Jenkins in detached mode with port forwarding and volume added. You can access logs with command 'docker logs CONTAINER_ID' in order to check first login token. ID of container will be returned from output of command above. Or, directly print the initial admin password using: ``` docker exec cat /var/jenkins_home/secrets/initialAdminPassword ``` Replace with your actual Jenkins container id or name. To access Jenkins and complete the initial setup, follow the instructions in the [installation guide](https://www.jenkins.io/doc/book/installing/docker/#setup-wizard). ## Backing up data If you bind mount in a volume - you can simply back up that directory (which is jenkins_home) at any time. Using a bind mount is not recommended since it can lead to permission issues. Treat the jenkins_home directory as you would a database - in Docker you would generally put a database on a volume. If your volume is inside a container - you can use `docker cp $ID:/var/jenkins_home` command to extract the data, or other options to find where the volume data is. Note that some symlinks on some OSes may be converted to copies (this can confuse jenkins with lastStableBuild links, etc) For more info check Docker docs section on [Use volumes](https://docs.docker.com/storage/volumes/) ## Setting the number of executors You can define the number of executors on the Jenkins built-in node using a groovy script. By default it is set to 2 executors, but you can extend the image and change it to your desired number of executors (recommended 0 executors on the built-in node) : `executors.groovy` ``` import jenkins.model.* Jenkins.instance.setNumExecutors(0) // Recommended to not run builds on the built-in node ``` and `Dockerfile` ``` FROM jenkins/jenkins:lts COPY --chown=jenkins:jenkins executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy ``` ## Connecting agents You can run builds on the controller out of the box. The Jenkins project recommends that no executors be enabled on the controller. In order to connect agents **through an inbound TCP connection**, map the port: `-p 50000:50000`. That port will be used when you connect agents to the controller. If you are only using [SSH (outbound) build agents](https://plugins.jenkins.io/ssh-slaves/), this port is not required, as connections are established from the controller. If you connect agents using web sockets (since Jenkins 2.217), the TCP agent port is not used either. ## Passing JVM parameters You might need to customize the JVM running Jenkins, typically to adjust [system properties](https://www.jenkins.io/doc/book/managing/system-properties/) or tweak heap memory settings. Use the `JAVA_OPTS` or `JENKINS_JAVA_OPTS` environment variables for this purpose : ``` docker run --name myjenkins -p 8080:8080 -p 50000:50000 --restart=on-failure --env JAVA_OPTS=-Dhudson.footerURL=http://mycompany.com jenkins/jenkins:lts-jdk21 ``` JVM options specifically for the Jenkins controller should be set through `JENKINS_JAVA_OPTS`, as other tools might also respond to the `JAVA_OPTS` environment variable. ## Configuring logging Jenkins logging can be configured through a properties file and `java.util.logging.config.file` Java property. For example: ``` mkdir data cat > data/log.properties < If this property is already set in **JAVA_OPTS** or **JENKINS_JAVA_OPTS**, then the value of > `JENKINS_SLAVE_AGENT_PORT` will be ignored. # Installing more tools You can run your container as root - and install via apt-get, install as part of build steps via jenkins tool installers, or you can create your own Dockerfile to customise, for example: ``` FROM jenkins/jenkins:lts-jdk21 # if we want to install via apt USER root RUN apt-get update && apt-get install -y ruby make more-thing-here # drop back to the regular jenkins user - good practice USER jenkins ``` In such a derived image, you can customize your jenkins instance with hook scripts or additional plugins. For this purpose, use `/usr/share/jenkins/ref` as a place to define the default JENKINS_HOME content you wish the target installation to look like : ``` FROM jenkins/jenkins:lts-jdk21 COPY --chown=jenkins:jenkins custom.groovy /usr/share/jenkins/ref/init.groovy.d/custom.groovy ``` If you need to maintain the entire init.groovy.d directory and have a persistent JENKINS_HOME you may run the docker image with `-e PRE_CLEAR_INIT_GROOVY_D=true` ## Preinstalling plugins ### Install plugins You can rely on [the plugin manager CLI](https://github.com/jenkinsci/plugin-installation-manager-tool/) to pass a set of plugins to download with their dependencies. This tool will perform downloads from update centers, and internet access is required for the default update centers. ### Setting update centers During the download, the CLI will use update centers defined by the following environment variables: - `JENKINS_UC` - Main update center. This update center may offer plugin versions depending on the Jenkins LTS Core versions. Default value: https://updates.jenkins.io - `JENKINS_UC_EXPERIMENTAL` - [Experimental Update Center](https://jenkins.io/blog/2013/09/23/experimental-plugins-update-center/). This center offers Alpha and Beta versions of plugins. Default value: https://updates.jenkins.io/experimental - `JENKINS_INCREMENTALS_REPO_MIRROR` - Defines Maven mirror to be used to download plugins from the [Incrementals repo](https://jenkins.io/blog/2018/05/15/incremental-deployment/). Default value: https://repo.jenkins-ci.org/incrementals - `JENKINS_UC_DOWNLOAD` - Download url of the Update Center. Default value: `$JENKINS_UC/download` - `JENKINS_PLUGIN_INFO` - Location of plugin information. Default value: https://updates.jenkins.io/current/plugin-versions.json It is possible to override the environment variables in images. :exclamation: Note that changing update center variables **will not** change the Update Center being used by Jenkins runtime, it concerns only the plugin manager CLI. ### Installing Custom Plugins Installing prebuilt, custom plugins can be accomplished by copying the plugin HPI file into `/usr/share/jenkins/ref/plugins/` within the `Dockerfile`: ``` COPY --chown=jenkins:jenkins path/to/custom.hpi /usr/share/jenkins/ref/plugins/ ``` ### Usage You can run the CLI manually in Dockerfile: ```Dockerfile FROM jenkins/jenkins:lts-jdk21 RUN jenkins-plugin-cli --plugins pipeline-model-definition github-branch-source:1.8 ``` Furthermore it is possible to pass a file that contains this set of plugins (with or without line breaks). ```Dockerfile FROM jenkins/jenkins:lts-jdk21 COPY --chown=jenkins:jenkins plugins.txt /usr/share/jenkins/ref/plugins.txt RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt ``` When jenkins container starts, it will check `JENKINS_HOME` has this reference content, and copy them there if required. It will not override such files, so if you upgraded some plugins from UI they won't be reverted on next start. In case you _do_ want to override, append '.override' to the name of the reference file. E.g. a file named `/usr/share/jenkins/ref/config.xml.override` will overwrite an existing `config.xml` file in JENKINS_HOME. Also see [JENKINS-24986](https://issues.jenkins.io/browse/JENKINS-24986) Here is an example to get the list of plugins from an existing server: ``` JENKINS_HOST=username:password@myhost.com:port curl -sSL "http://$JENKINS_HOST/pluginManager/api/xml?depth=1&xpath=/*/*/shortName|/*/*/version&wrapper=plugins" | perl -pe 's/.*?([\w-]+).*?([^<]+)()(<\/\w+>)+/\1 \2\n/g'|sed 's/ /:/' ``` Example Output: ``` cucumber-testresult-plugin:0.8.2 pam-auth:1.1 matrix-project:1.4.1 script-security:1.13 ... ``` For 2.x-derived images, you may also want to RUN echo 2.0 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state to indicate that this Jenkins installation is fully configured. Otherwise a banner will appear prompting the user to install additional plugins, which may be inappropriate. ### Access logs To enable Jenkins user access logs from Jenkins home directory inside a docker container, set the `JENKINS_OPTS` environment variable value to `--accessLoggerClassName=winstone.accesslog.SimpleAccessLogger --simpleAccessLogger.format=combined --simpleAccessLogger.file=/var/jenkins_home/logs/access_log` ### Naming convention in tags The naming convention for the tags on Docker Hub follows the format `:`, where the repository name is jenkins/jenkins and where the tag specifies the image version. In the case of the LTS and latest versions, the tags are `lts` and `latest`, respectively. You can use these tags to pull the corresponding Jenkins images from Docker Hub and run them on your system. For example, to pull the LTS version of the Jenkins image use this command: `docker pull jenkins/jenkins:lts` ### Docker Compose with Jenkins To use Docker Compose with Jenkins, you can define a docker-compose.yml file including a Jenkins instance and any other services it depends on. For example, the following docker-compose.yml file defines a Jenkins controller and a Jenkins SSH agent: ```yaml services: jenkins: image: jenkins/jenkins:lts ports: - "8080:8080" volumes: - jenkins_home:/var/jenkins_home ssh-agent: image: jenkins/ssh-agent volumes: jenkins_home: ``` This `docker-compose.yml` file creates two containers: one for Jenkins and one for the Jenkins SSH agent. The Jenkins container is based on the `jenkins/jenkins:lts` image and exposes the Jenkins web interface on port 8080. The `jenkins_home` volume is a [named volume](https://docs.docker.com/storage/volumes/) that is created and managed by Docker. It is mounted at `/var/jenkins_home` in the Jenkins container, and it will persist the Jenkins configuration and data. The ssh-agent container is based on the `jenkins/ssh-agent` image and runs an SSH server to execute [Jenkins SSH Build Agent](https://plugins.jenkins.io/ssh-slaves/). To start the Jenkins instance and the other services defined in the `docker-compose.yml` file, run the `docker compose up -d`. This will pull the necessary images from Docker Hub if they are not already present on your system, and start the services in the background. You can then access the Jenkins web interface on `http://localhost:8080` on your host system to configure and manage your Jenkins instance (where `localhost` points to the published port by your Docker Engine). NOTE: read the section [_DNS Configuration_](#dns-configuration) in case you see the message "This Jenkins instance appears to be offline." In that case add the dns configuration to the yaml: ```yaml services: jenkins: # ... other config dns: - 1.1.1.1 - 8.8.8.8 # ... other config ``` ### Updating plugins file The [plugin-installation-manager-tool](https://github.com/jenkinsci/plugin-installation-manager-tool) supports updating the plugin file for you. Example command: ```command JENKINS_IMAGE=jenkins/jenkins:lts-jdk21 docker run -it ${JENKINS_IMAGE} bash -c "stty -onlcr && jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt --available-updates --output txt" > plugins2.txt mv plugins2.txt plugins.txt ``` ## Upgrading All the data needed is in the /var/jenkins_home directory - so depending on how you manage that - depends on how you upgrade. Generally - you can copy it out - and then "docker pull" the image again - and you will have the latest LTS - you can then start up with -v pointing to that data (/var/jenkins_home) and everything will be as you left it. As always - please ensure that you know how to drive docker - especially volume handling! If you mount the Jenkins home directory to a [Docker named volume](https://docs.docker.com/storage/volumes/), then the upgrade consists of `docker pull` and nothing more. We recommend using `docker compose`, especially in cases where the user is also running a parallel nginx/apache container as a reverse proxy for the Jenkins container. ### Upgrading plugins By default, plugins will be upgraded if they haven't been upgraded manually and if the version from the docker image is newer than the version in the container. Versions installed by the docker image are tracked through a marker file. To force upgrades of plugins that have been manually upgraded, run the docker image with `-e PLUGINS_FORCE_UPGRADE=true`. The default behaviour when upgrading from a docker image that didn't write marker files is to leave existing plugins in place. If you want to upgrade existing plugins without marker you may run the docker image with `-e TRY_UPGRADE_IF_NO_MARKER=true`. Then plugins will be upgraded if the version provided by the docker image is newer. # Hacking If you wish to contribute fixes to this repository, please refer to the [dedicated documentation](HACKING.adoc). # Security For information related to the security of this Docker image, please refer to the [dedicated documentation](SECURITY.md). # Questions? We're on Gitter, https://gitter.im/jenkinsci/docker ================================================ FILE: SECURITY.md ================================================ # Security Policy The Jenkins project takes security seriously. We make every possible effort to ensure users can adequately secure their automation infrastructure. You can find more information in the [general Security Policy](https://github.com/jenkinsci/.github/blob/master/SECURITY.md), this policy is specific to our Docker images. ## Docker Image Publication When an image is published, the latest image and the latest available packages are used. We rely on the base image provider for the security of the system libraries. The default base image is Debian but multiple other variants are proposed, that could potentially better fit your needs. ## Reporting Security Vulnerabilities If you have identified a security vulnerability and would like to report it, please be aware of those requirements. For findings from a **Software Composition Analysis (SCA) scanner report**, all of the following points must be satisfied: - If the finding is coming from the system (Docker layer): - The scan must have been done on the latest version of the image. Vulnerabilities are discovered in a continuous way, so it is expected that past releases could contain some. - The package should have a fixed version provided in the base image that is not yet included in our image. We rely on the base image provider to propose the corrections. - The correction should have existed at the time the image was created. Normally our update workflow ensures that the latest available versions are used. - If the finding is coming from the application dependencies: - Proof of exploitation or sufficiently good explanation about why you think it's impacting the application. For all "valid" findings from SCA, your report must contain: - The path to the library (there are ~2000 components in the ecosystem, we don't want to have to guess) - The version and variant of the Docker image you scanned. - The scanner name and version as well. - The publicly accessible information about the vulnerability (ideally CVE). For private vulnerability database, please provide all the information at your disposal. The objective is to reduce the number of reports we receive that are not relevant to the security of the project. For findings from a **manual audit**, the report must contain either reproduction steps or a sufficiently well described proof to demonstrate the impact. Once the report is ready, please follow the process about [Reporting Security Vulnerabilities](https://jenkins.io/security/reporting/). We will reject reports that are not satisfying those requirements. ## Vulnerability Management Once the report is considered legitimate, a new image is published with the latest packages. In the case the adjustment has to be done in the building process (e.g. in the Dockerfile), the correction will be prioritized and applied as soon as possible. By default we do not plan to publish advisories for vulnerabilities at the Docker level. There may be exceptions. ================================================ FILE: alpine/hotspot/Dockerfile ================================================ ARG ALPINE_TAG=3.23.3 FROM alpine:"${ALPINE_TAG}" AS jre-and-war ARG JAVA_VERSION=17.0.18_8 SHELL ["/bin/ash", "-o", "pipefail", "-c"] COPY jdk-download-url.sh /usr/bin/jdk-download-url.sh COPY jdk-download.sh /usr/bin/jdk-download.sh RUN apk add --no-cache \ ca-certificates \ gnupg \ jq \ curl \ && rm -fr /var/cache/apk/* \ && /usr/bin/jdk-download.sh alpine ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}" # Generate smaller java runtime without unneeded files # for now we include the full module path to maintain compatibility # while still saving space (approx 200mb from the full distribution) # hadolint ignore=SC2086 RUN java_major_version="$(jlink --version 2>&1 | cut -c1-2)"; \ if [ "$java_major_version" = "25" ]; then \ cp -r "/opt/jdk-${JAVA_VERSION}" /javaruntime; \ else \ case "$java_major_version" in \ "17") options="--compress=2" ;; \ "21") options="--compress=zip-6" ;; \ *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \ esac; \ jlink \ --strip-java-debug-attributes \ ${options} \ --add-modules ALL-MODULE-PATH \ --no-man-pages \ --no-header-files \ --output /javaruntime; \ fi # Jenkins version being bundled in this docker image ARG JENKINS_VERSION=2.555 # Can be used to customize where jenkins.war get downloaded from ARG WAR_URL=https://get.jenkins.io/war/${JENKINS_VERSION}/jenkins.war COPY jenkins.io-2026.key /war/jenkins-key.pub # Not using ADD as it does not check Last-Modified header # see https://github.com/docker/docker/issues/8331 RUN curl -fsSL "${WAR_URL}" -o /war/jenkins.war \ && curl -fsSL "${WAR_URL}.asc" -o /war/jenkins.war.asc \ && gpg --import /war/jenkins-key.pub \ && gpg --verify --trust-model direct /war/jenkins.war.asc /war/jenkins.war FROM alpine:"${ALPINE_TAG}" AS controller RUN apk add --no-cache \ bash \ coreutils \ curl \ git \ git-lfs \ musl-locales \ musl-locales-lang \ openssh-client \ tini \ ttf-dejavu \ tzdata \ unzip \ && git lfs install ENV LANG=C.UTF-8 ARG TARGETARCH ARG COMMIT_SHA ARG user=jenkins ARG group=jenkins ARG uid=1000 ARG gid=1000 ARG http_port=8080 ARG agent_port=50000 ARG JENKINS_HOME=/var/jenkins_home ARG REF=/usr/share/jenkins/ref ENV JENKINS_HOME=$JENKINS_HOME ENV JENKINS_SLAVE_AGENT_PORT=${agent_port} ENV REF=$REF # Jenkins is run with user `jenkins`, uid = 1000 # If you bind mount a volume from the host or a data container, # ensure you use the same uid RUN mkdir -p $JENKINS_HOME \ && chown ${uid}:${gid} $JENKINS_HOME \ && addgroup -g ${gid} ${group} \ && adduser -h "$JENKINS_HOME" -u ${uid} -G ${group} -s /bin/bash -D ${user} # Jenkins home directory is a volume, so configuration and build history # can be persisted and survive image upgrades VOLUME $JENKINS_HOME # $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want # to set on a fresh new installation. Use it to bundle additional plugins # or config file with your custom jenkins Docker image. RUN mkdir -p ${REF}/init.groovy.d ENV JENKINS_UC=https://updates.jenkins.io ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals RUN chown -R ${user} "$JENKINS_HOME" "$REF" ARG PLUGIN_CLI_VERSION=2.14.0 ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar RUN curl -fsSL ${PLUGIN_CLI_URL} -o /opt/jenkins-plugin-manager.jar \ && echo "$(curl -fsSL "${PLUGIN_CLI_URL}.sha256") /opt/jenkins-plugin-manager.jar" >/tmp/jpm_sha \ && sha256sum -c --strict /tmp/jpm_sha \ && rm -f /tmp/jpm_sha # for main web interface: EXPOSE ${http_port} # will be used by attached agents: EXPOSE ${agent_port} ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log ENV JAVA_HOME=/opt/java/openjdk ENV PATH="${JAVA_HOME}/bin:${PATH}" COPY --from=jre-and-war /javaruntime $JAVA_HOME COPY --from=jre-and-war /war/jenkins.war /usr/share/jenkins/jenkins.war USER ${user} COPY jenkins-support /usr/local/bin/jenkins-support COPY jenkins.sh /usr/local/bin/jenkins.sh COPY jenkins-plugin-cli.sh /bin/jenkins-plugin-cli ARG JENKINS_VERSION=2.555 ENV JENKINS_VERSION=${JENKINS_VERSION} ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"] # metadata labels LABEL \ org.opencontainers.image.vendor="Jenkins project" \ org.opencontainers.image.title="Official Jenkins Docker image" \ org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" \ org.opencontainers.image.version="${JENKINS_VERSION}" \ org.opencontainers.image.url="https://www.jenkins.io/" \ org.opencontainers.image.source="https://github.com/jenkinsci/docker" \ org.opencontainers.image.revision="${COMMIT_SHA}" \ org.opencontainers.image.licenses="MIT" ================================================ FILE: debian/Dockerfile ================================================ ARG TRIXIE_TAG=20251103 ARG DEBIAN_RELEASE_LINE=trixie ARG DEBIAN_VERSION=20251117 ARG DEBIAN_VARIANT="-slim" FROM debian:"${DEBIAN_RELEASE_LINE}-${DEBIAN_VERSION}${DEBIAN_VARIANT}" AS jre-and-war ARG JAVA_VERSION=17.0.18_8 SHELL ["/bin/bash", "-o", "pipefail", "-c"] COPY jdk-download-url.sh /usr/bin/jdk-download-url.sh COPY jdk-download.sh /usr/bin/jdk-download.sh RUN apt-get update \ && apt-get install --no-install-recommends -y \ ca-certificates \ curl \ gnupg \ jq \ && rm -rf /var/lib/apt/lists/* \ && /usr/bin/jdk-download.sh ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}" # Generate smaller java runtime without unneeded files # for now we include the full module path to maintain compatibility # while still saving space (approx 200mb from the full distribution) # hadolint ignore=SC2086 RUN java_major_version="$(jlink --version 2>&1 | cut -c1-2)"; \ if [ "$java_major_version" = "25" ]; then \ cp -r "/opt/jdk-${JAVA_VERSION}" /javaruntime; \ else \ case "$java_major_version" in \ "17") options="--compress=2" ;; \ "21") options="--compress=zip-6" ;; \ *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \ esac; \ jlink \ --strip-java-debug-attributes \ ${options} \ --add-modules ALL-MODULE-PATH \ --no-man-pages \ --no-header-files \ --output /javaruntime; \ fi # Jenkins version being bundled in this docker image ARG JENKINS_VERSION=2.555 # Can be used to customize where jenkins.war get downloaded from ARG WAR_URL=https://get.jenkins.io/war/${JENKINS_VERSION}/jenkins.war COPY jenkins.io-2026.key /war/jenkins-key.pub # Not using ADD as it does not check Last-Modified header # see https://github.com/docker/docker/issues/8331 RUN curl -fsSL "${WAR_URL}" -o /war/jenkins.war \ && curl -fsSL "${WAR_URL}.asc" -o /war/jenkins.war.asc \ && gpg --import /war/jenkins-key.pub \ && gpg --verify --trust-model direct /war/jenkins.war.asc /war/jenkins.war FROM debian:"${DEBIAN_RELEASE_LINE}-${DEBIAN_VERSION}${DEBIAN_VARIANT}" AS controller RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ git \ libfontconfig1 \ libfreetype6 \ procps \ ssh-client \ tini \ unzip \ tzdata \ && rm -rf /var/lib/apt/lists/* # Git LFS is not available from a package manager on all the platforms we support # Download and unpack the tar.gz distribution ARG GIT_LFS_VERSION=3.7.1 # hadolint ignore=DL4006 RUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g') \ && curl -L -s -o git-lfs.tgz "https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/git-lfs-linux-${arch}-v${GIT_LFS_VERSION}.tar.gz" \ && tar xzf git-lfs.tgz \ && bash git-lfs-*/install.sh \ && rm -rf git-lfs* ENV LANG=C.UTF-8 ARG TARGETARCH ARG COMMIT_SHA ARG user=jenkins ARG group=jenkins ARG uid=1000 ARG gid=1000 ARG http_port=8080 ARG agent_port=50000 ARG JENKINS_HOME=/var/jenkins_home ARG REF=/usr/share/jenkins/ref ENV JENKINS_HOME=$JENKINS_HOME ENV JENKINS_SLAVE_AGENT_PORT=${agent_port} ENV REF=$REF # Jenkins is run with user `jenkins`, uid = 1000 # If you bind mount a volume from the host or a data container, # ensure you use the same uid RUN mkdir -p $JENKINS_HOME \ && chown ${uid}:${gid} $JENKINS_HOME \ && groupadd -g ${gid} ${group} \ && useradd -d "$JENKINS_HOME" -u ${uid} -g ${gid} -l -m -s /bin/bash ${user} # Jenkins home directory is a volume, so configuration and build history # can be persisted and survive image upgrades VOLUME $JENKINS_HOME # $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want # to set on a fresh new installation. Use it to bundle additional plugins # or config file with your custom jenkins Docker image. RUN mkdir -p ${REF}/init.groovy.d ENV JENKINS_UC=https://updates.jenkins.io ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals RUN chown -R ${user} "$JENKINS_HOME" "$REF" ARG PLUGIN_CLI_VERSION=2.14.0 ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar RUN curl -fsSL ${PLUGIN_CLI_URL} -o /opt/jenkins-plugin-manager.jar \ && echo "$(curl -fsSL "${PLUGIN_CLI_URL}.sha256") /opt/jenkins-plugin-manager.jar" >/tmp/jpm_sha \ && sha256sum -c --strict /tmp/jpm_sha \ && rm -f /tmp/jpm_sha # for main web interface: EXPOSE ${http_port} # will be used by attached agents: EXPOSE ${agent_port} ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log ENV JAVA_HOME=/opt/java/openjdk ENV PATH="${JAVA_HOME}/bin:${PATH}" COPY --from=jre-and-war /javaruntime $JAVA_HOME COPY --from=jre-and-war /war/jenkins.war /usr/share/jenkins/jenkins.war USER ${user} COPY jenkins-support /usr/local/bin/jenkins-support COPY jenkins.sh /usr/local/bin/jenkins.sh COPY jenkins-plugin-cli.sh /bin/jenkins-plugin-cli ARG JENKINS_VERSION=2.555 ENV JENKINS_VERSION=${JENKINS_VERSION} ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/jenkins.sh"] # metadata labels LABEL \ org.opencontainers.image.vendor="Jenkins project" \ org.opencontainers.image.title="Official Jenkins Docker image" \ org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" \ org.opencontainers.image.version="${JENKINS_VERSION}" \ org.opencontainers.image.url="https://www.jenkins.io/" \ org.opencontainers.image.source="https://github.com/jenkinsci/docker" \ org.opencontainers.image.revision="${COMMIT_SHA}" \ org.opencontainers.image.licenses="MIT" ================================================ FILE: docker-bake.hcl ================================================ ## Variables variable "jdks_to_build" { default = [21, 25] } variable "windows_version_to_build" { default = ["ltsc2019", "ltsc2022"] } variable "default_jdk" { default = 21 } variable "JENKINS_VERSION" { default = "2.555" } variable "WAR_URL" { default = "" } variable "REGISTRY" { default = "docker.io" } variable "JENKINS_REPO" { default = "jenkins/jenkins" } variable "LATEST_WEEKLY" { default = "false" } variable "LATEST_LTS" { default = "false" } variable "PLUGIN_CLI_VERSION" { default = "2.14.0" } variable "COMMIT_SHA" { default = "" } variable "ALPINE_FULL_TAG" { default = "3.23.3" } variable "ALPINE_SHORT_TAG" { default = regex_replace(ALPINE_FULL_TAG, "\\.\\d+$", "") } variable "JAVA17_VERSION" { default = "17.0.18_8" } variable "JAVA21_VERSION" { default = "21.0.10_7" } variable "JAVA25_VERSION" { default = "25.0.2_10" } variable "DEBIAN_RELEASE_LINE" { default = "trixie" } variable "DEBIAN_VERSION" { default = "20251117" } variable "RHEL_TAG" { default = "9.7-1773204657" } variable "RHEL_RELEASE_LINE" { default = "ubi9" } # Set this value to a specific Windows version to override Windows versions to build returned by windowsversions function variable "WINDOWS_VERSION_OVERRIDE" { default = "" } ## Internal variables variable "jdk_versions" { default = { 17 = JAVA17_VERSION 21 = JAVA21_VERSION 25 = JAVA25_VERSION } } variable "debian_variants" { default = ["debian", "debian-slim"] } variable "current_rhel" { default = "rhel-${RHEL_RELEASE_LINE}" } ## Targets target "alpine" { matrix = { jdk = jdks_to_build } name = "alpine_jdk${jdk}" dockerfile = "alpine/hotspot/Dockerfile" context = "." args = { JENKINS_VERSION = JENKINS_VERSION WAR_URL = war_url() COMMIT_SHA = COMMIT_SHA PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION JAVA_VERSION = javaversion(jdk) ALPINE_TAG = ALPINE_FULL_TAG } tags = linux_tags("alpine", jdk) platforms = platforms("alpine", jdk) } target "debian" { matrix = { jdk = jdks_to_build variant = debian_variants } name = "${variant}_jdk${jdk}" dockerfile = "debian/Dockerfile" context = "." args = { JENKINS_VERSION = JENKINS_VERSION WAR_URL = war_url() COMMIT_SHA = COMMIT_SHA PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION JAVA_VERSION = javaversion(jdk) DEBIAN_RELEASE_LINE = DEBIAN_RELEASE_LINE DEBIAN_VERSION = DEBIAN_VERSION DEBIAN_VARIANT = is_debian_slim(variant) ? "-slim" : "" } tags = linux_tags(variant, jdk) platforms = platforms(variant, jdk) } target "rhel" { matrix = { jdk = jdks_to_build } name = "rhel_jdk${jdk}" dockerfile = "rhel/Dockerfile" context = "." args = { JENKINS_VERSION = JENKINS_VERSION WAR_URL = war_url() COMMIT_SHA = COMMIT_SHA PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION JAVA_VERSION = javaversion(jdk) RHEL_TAG = RHEL_TAG RHEL_RELEASE_LINE = RHEL_RELEASE_LINE } tags = linux_tags(current_rhel, jdk) platforms = platforms(current_rhel, jdk) } target "windowsservercore" { matrix = { jdk = jdks_to_build windows_version = windowsversions() } name = "windowsservercore-${windows_version}_jdk${jdk}" dockerfile = "windows/windowsservercore/hotspot/Dockerfile" context = "." args = { JENKINS_VERSION = JENKINS_VERSION WAR_URL = war_url() COMMIT_SHA = COMMIT_SHA PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION JAVA_VERSION = javaversion(jdk) JAVA_HOME = "C:/openjdk-${jdk}" WINDOWS_VERSION = windows_version } tags = windows_tags("windowsservercore-${windows_version}", jdk) platforms = ["windows/amd64"] } ## Groups group "linux" { targets = [ "alpine", "debian", "rhel", ] } group "windows" { targets = [ "windowsservercore" ] } group "all" { targets = [ "linux", "windows", ] } ## Common functions # return true if JENKINS_VERSION is a Weekly (one sequence of digits with a trailing literal '.') function "is_jenkins_version_weekly" { # If JENKINS_VERSION has more than one sequence of digits with a trailing literal '.', this is LTS # 2.523 has only one sequence of digits with a trailing literal '.' # 2.516.1 has two sequences of digits with a trailing literal '.' params = [] result = length(regexall("[0-9]+[.]", JENKINS_VERSION)) < 2 ? true : false } # return a tag prefixed by the Jenkins version function "_tag_jenkins_version" { params = [tag] result = notequal(tag, "") ? "${REGISTRY}/${JENKINS_REPO}:${JENKINS_VERSION}-${tag}" : "${REGISTRY}/${JENKINS_REPO}:${JENKINS_VERSION}" } # return a tag optionally prefixed by the Jenkins version function "tag" { params = [prepend_jenkins_version, tag] result = equal(prepend_jenkins_version, true) ? _tag_jenkins_version(tag) : "${REGISTRY}/${JENKINS_REPO}:${tag}" } # return a weekly optionally prefixed by the Jenkins version function "tag_weekly" { params = [prepend_jenkins_version, tag] result = equal(LATEST_WEEKLY, "true") ? tag(prepend_jenkins_version, tag) : "" } # return a LTS optionally prefixed by the Jenkins version function "tag_lts" { params = [prepend_jenkins_version, tag] result = equal(LATEST_LTS, "true") ? tag(prepend_jenkins_version, tag) : "" } # return WAR_URL if not empty, get.jenkins.io URL depending on JENKINS_VERSION release line otherwise function "war_url" { params = [] result = (notequal(WAR_URL, "") ? WAR_URL : (is_jenkins_version_weekly() ? "https://get.jenkins.io/war/${JENKINS_VERSION}/jenkins.war" : "https://get.jenkins.io/war-stable/${JENKINS_VERSION}/jenkins.war")) } # Return "true" if the jdk passed as parameter is the same as the default jdk, "false" otherwise function "is_default_jdk" { params = [jdk] result = equal(default_jdk, jdk) ? true : false } # Return the complete Java version corresponding to the jdk passed as parameter function "javaversion" { params = [jdk] result = lookup(jdk_versions, jdk, "Unsupported JDK version") } # Return an array of platforms depending on the distribution and the jdk function "platforms" { params = [distribution, jdk] result = ( # Alpine is_alpine(distribution) ? (equal(17, jdk) ? ["linux/amd64"] : ["linux/amd64", "linux/arm64"]) # Debian slim : is_debian_slim(distribution) ? (equal(17, jdk) ? ["linux/amd64"] : ["linux/amd64", "linux/arm64", "linux/riscv64"]) # RHEL : is_rhel(distribution) ? ["linux/amd64", "linux/arm64", "linux/ppc64le"] # Default (Debian) : ["linux/amd64", "linux/arm64", "linux/s390x", "linux/ppc64le", "linux/riscv64"] ) } # Return an array of tags for linux images depending on the distribution and the jdk function "linux_tags" { params = [distribution, jdk] result = ( ## Debian variants is_debian_variant(distribution) ? debian_tags(distribution, jdk) : [ ## Always publish explicit jdk tag tag(true, "${distribution}-jdk${jdk}"), tag_weekly(false, "${distribution}-jdk${jdk}"), tag_lts(false, "lts-${distribution}-jdk${jdk}"), # Special case for Alpine is_alpine(distribution) ? tag_weekly(false, "alpine${ALPINE_SHORT_TAG}-jdk${jdk}") : "", # Special case for RHEL is_rhel(distribution) ? tag_lts(true, "lts-${distribution}-jdk${jdk}") : "", ## Default JDK extra short tags (except for current rhel) is_default_jdk(jdk) && !is_rhel(distribution) ? tag(true, distribution) : "", is_default_jdk(jdk) && !is_rhel(distribution) ? tag_weekly(false, distribution) : "", is_default_jdk(jdk) && !is_rhel(distribution) ? tag_lts(false, "lts-${distribution}") : "", is_default_jdk(jdk) && !is_rhel(distribution) ? tag_lts(true, "lts-${distribution}") : "", ] ) } # Return an array of tags depending on the agent type, the jdk # and the flavor and version of Windows passed as parameters (ex: windowsservercore-ltsc2022) function "windows_tags" { params = [distribution, jdk] result = [ ## Always publish explicit jdk tag tag(true, "jdk${jdk}-hotspot-${distribution}"), tag_weekly(false, "jdk${jdk}-hotspot-${distribution}"), tag_lts(false, "lts-jdk${jdk}-hotspot-${distribution}"), ## Default JDK extra short tags is_default_jdk(jdk) ? tag(true, "hotspot-${distribution}") : "", is_default_jdk(jdk) ? tag_weekly(false, distribution) : "", is_default_jdk(jdk) ? tag_weekly(true, distribution) : "", is_default_jdk(jdk) ? tag_lts(false, "lts-${distribution}") : "", is_default_jdk(jdk) ? tag_lts(true, distribution) : "", ] } # Return if the distribution passed in parameter is Alpine function "is_alpine" { params = [distribution] result = equal("alpine", distribution) } # Return if the distribution passed in parameter is Alpine function "is_rhel" { params = [distribution] result = equal(current_rhel, distribution) } # Return if the distribution passed in parameter is a debian variant function "is_debian_variant" { params = [distribution] result = contains(debian_variants, distribution) } # Return if the variant passed in parameter is the debian slim one function "is_debian_slim" { params = [variant] result = equal("debian-slim", variant) } # Return text prefixed with "slim-" if the variant passed in parameter is the slim one # Return only "slim" if the text passed in parameter is empty or "latest" function "slim_prefix" { params = [variant, text] result = (is_debian_slim(variant) ? (equal("", text) || equal("latest", text) ? "slim" : "slim-${text}") : text) } # Return text suffixed with "-slim" if the variant passed in parameter is the slim one # Return only "slim" if the text passed in parameter is empty function "slim_suffix" { params = [variant, text] result = (is_debian_slim(variant) ? (equal("", text) ? "slim" : "${text}-slim") : text) } # Return an array of tags for debian images depending on the variant and the jdk passed as parameters function "debian_tags" { params = [variant, jdk] result = [ ## Default tags including jdk tag(true, slim_prefix(variant, "jdk${jdk}")), tag_weekly(false, slim_prefix(variant, "jdk${jdk}")), tag_lts(false, "${slim_suffix(variant, "lts")}-jdk${jdk}"), # Tags for debian only is_debian_slim(variant) ? "" : tag_weekly(false, slim_prefix(variant, "latest-jdk${jdk}")), is_debian_slim(variant) ? "" : tag_lts(true, "${slim_suffix(variant, "lts")}-jdk${jdk}"), ## If default jdk, short tags is_default_jdk(jdk) ? tag(true, slim_prefix(variant, "")) : "", is_default_jdk(jdk) ? tag_weekly(false, slim_prefix(variant, "latest")) : "", is_default_jdk(jdk) ? tag_lts(false, slim_suffix(variant, "lts")) : "", is_default_jdk(jdk) ? tag_lts(true, slim_suffix(variant, "lts")) : "", ] } # Return array of Windows version(s) to build # Can be overridden by setting WINDOWS_VERSION_OVERRIDE to a specific Windows version # Ex: WINDOWS_VERSION_OVERRIDE=ltsc2025 docker buildx bake windows function "windowsversions" { params = [] result = notequal(WINDOWS_VERSION_OVERRIDE, "") ? [WINDOWS_VERSION_OVERRIDE] : windows_version_to_build } ================================================ FILE: jdk-download-url.sh ================================================ #!/bin/sh # Check if at least one argument was passed to the script # If one argument was passed and JAVA_VERSION is set, assign the argument to OS # If two arguments were passed, assign them to JAVA_VERSION and OS respectively # If three arguments were passed, assign them to JAVA_VERSION, OS and ARCHS respectively # If not, check if JAVA_VERSION and OS are already set. If they're not set, exit the script with an error message if [ $# -eq 1 ] && [ -n "$JAVA_VERSION" ]; then OS=$1 elif [ $# -eq 2 ]; then JAVA_VERSION=$1 OS=$2 elif [ $# -eq 3 ]; then JAVA_VERSION=$1 OS=$2 ARCHS=$3 elif [ -z "$JAVA_VERSION" ] && [ -z "$OS" ]; then echo "Error: No Java version and OS specified. Please set the JAVA_VERSION and OS environment variables or pass them as arguments." >&2 exit 1 elif [ -z "$JAVA_VERSION" ]; then echo "Error: No Java version specified. Please set the JAVA_VERSION environment variable or pass it as an argument." >&2 exit 1 elif [ -z "$OS" ]; then OS=$1 if [ -z "$OS" ]; then echo "Error: No OS specified. Please set the OS environment variable or pass it as an argument." >&2 exit 1 fi fi # Check if ARCHS is set. If it's not set, assign the current architecture to it if [ -z "$ARCHS" ]; then ARCHS=$(uname -m | sed -e 's/x86_64/x64/' -e 's/armv7l/arm/') else # Convert ARCHS to an array OLD_IFS=$IFS IFS=',' set -- "$ARCHS" ARCHS="" for arch in "$@"; do ARCHS="$ARCHS $arch" done IFS=$OLD_IFS fi # Check if jq and curl are installed # If they are not installed, exit the script with an error message if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then echo "jq and curl are required but not installed. Exiting with status 1." >&2 exit 1 fi # Replace underscores with plus signs in JAVA_VERSION ARCHIVE_DIRECTORY=$(echo "$JAVA_VERSION" | tr '_' '+') # URL encode ARCHIVE_DIRECTORY ENCODED_ARCHIVE_DIRECTORY=$(echo "$ARCHIVE_DIRECTORY" | xargs -I {} printf %s {} | jq "@uri" -jRr) # Determine the OS type for the URL OS_TYPE="linux" if [ "$OS" = "alpine" ]; then OS_TYPE="alpine-linux" fi if [ "$OS" = "windows" ]; then OS_TYPE="windows" fi # Initialize a variable to store the URL for the first architecture FIRST_ARCH_URL="" # Loop over the array of architectures for ARCH in $ARCHS; do # Fetch the download URL from the Adoptium API URL="https://api.adoptium.net/v3/binary/version/jdk-${ENCODED_ARCHIVE_DIRECTORY}/${OS_TYPE}/${ARCH}/jdk/hotspot/normal/eclipse?project=jdk" if ! RESPONSE=$(curl -fsI "$URL"); then echo "Error: Failed to fetch the URL for architecture ${ARCH} from ${URL}. Exiting with status 1." >&2 echo "Response: $RESPONSE" >&2 exit 1 fi # Extract the redirect URL from the HTTP response REDIRECTED_URL=$(echo "$RESPONSE" | grep -i location | awk '{print $2}' | tr -d '\r') # If no redirect URL was found, exit the script with an error message if [ -z "$REDIRECTED_URL" ]; then echo "Error: No redirect URL found for architecture ${ARCH} from ${URL}. Exiting with status 1." >&2 echo "Response: $RESPONSE" >&2 exit 1 fi # Use curl to check if the URL is reachable # If the URL is not reachable, print an error message and exit the script with status 1 if ! curl -v -fs "$REDIRECTED_URL" >/dev/null 2>&1; then echo "${REDIRECTED_URL}" is not reachable for architecture "${ARCH}". >&2 exit 1 fi # If FIRST_ARCH_URL is empty, store the current URL if [ -z "$FIRST_ARCH_URL" ]; then FIRST_ARCH_URL=$REDIRECTED_URL fi done # If all downloads are successful, print the URL for the first architecture echo "$FIRST_ARCH_URL" ================================================ FILE: jdk-download.sh ================================================ #!/bin/sh set -x # Check if curl and tar are installed if ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1 ; then echo "curl and tar are required but not installed. Exiting with status 1." >&2 exit 1 fi # Set the OS to "standard" by default OS="standard" # If a second argument is provided, use it as the OS if [ $# -eq 1 ]; then OS=$1 fi # Call jdk-download-url.sh with JAVA_VERSION and OS as arguments # The two scripts should be in the same directory. # That's why we're trying to find the directory of the current script and use it to call the other script. SCRIPT_DIR=$(cd "$(dirname "$0")" || exit; pwd) if ! DOWNLOAD_URL=$("${SCRIPT_DIR}"/jdk-download-url.sh "${JAVA_VERSION}" "${OS}"); then echo "Error: Failed to fetch the URL. Exiting with status 1." >&2 exit 1 fi # Use curl to download the JDK archive from the URL if ! curl --silent --location --output /tmp/jdk.tar.gz "${DOWNLOAD_URL}"; then echo "Error: Failed to download the JDK archive. Exiting with status 1." >&2 exit 1 fi # Extract the archive to the /opt/ directory if ! tar -xzf /tmp/jdk.tar.gz -C /opt/; then echo "Error: Failed to extract the JDK archive. Exiting with status 1." >&2 exit 1 fi # Get the name of the extracted directory EXTRACTED_DIR=$(tar -tzf /tmp/jdk.tar.gz | head -n 1 | cut -f1 -d"/") # Rename the extracted directory to /opt/jdk-${JAVA_VERSION} if ! mv "/opt/${EXTRACTED_DIR}" "/opt/jdk-${JAVA_VERSION}"; then echo "Error: Failed to rename the extracted directory. Exiting with status 1." >&2 exit 1 fi # Remove the downloaded archive if ! rm -f /tmp/jdk.tar.gz; then echo "Error: Failed to remove the downloaded archive. Exiting with status 1." >&2 exit 1 fi ================================================ FILE: jenkins-plugin-cli.ps1 ================================================ & java "$env:JAVA_OPTS" -jar C:/ProgramData/Jenkins/jenkins-plugin-manager.jar $args ================================================ FILE: jenkins-plugin-cli.sh ================================================ #!/bin/bash # read JAVA_OPTS into array to avoid need for eval (and associated vulnerabilities) java_opts_array=() while IFS= read -r -d '' item; do java_opts_array+=( "$item" ) done < <([[ $JAVA_OPTS ]] && xargs printf '%s\0' <<<"$JAVA_OPTS") exec java "${java_opts_array[@]}" -jar /opt/jenkins-plugin-manager.jar "$@" ================================================ FILE: jenkins-support ================================================ #!/bin/bash -eu : "${REF:="/usr/share/jenkins/ref"}" # compare if version1 < version2 versionLT() { local normalized_version1 normalized_version2 first_part_of_1 first_char_other_part_of_1 first_part_of_2 # Quick check for equality if [ "$1" = "$2" ]; then return 1 fi # Convert '-' to '.' to ease comparison normalized_version1=$(echo "$1" | tr '-' '.') normalized_version2=$(echo "$2" | tr '-' '.') first_part_of_1=${normalized_version1%%.*} other_part_of_1=${normalized_version1#*.} first_char_other_part_of_1=${other_part_of_1:0:1} first_part_of_2=${normalized_version2%%.*} # Security fix backport special case # Ex: 3894.vd0f0248b_a_fc4 < 3894.3896.vca_2c931e7935 # -> normal incrementals version includes a "v" as first char of second part # -> security fix backport adds the backport source first part as second part # https://github.com/jenkinsci/workflow-cps-plugin/releases/tag/3894.vd0f0248b_a_fc4 # https://github.com/jenkinsci/workflow-cps-plugin/releases/tag/3894.3896.vca_2c931e7935 if [[ "$first_part_of_1" = "$first_part_of_2" ]]; then # If the second part of $version1 starts with a "v", then $version1 is older if [[ "$first_char_other_part_of_1" == "v" ]]; then return 0 fi fi if [ "$normalized_version1" = "$(printf '%s\n%s\n' "$normalized_version1" "$normalized_version2" | sort --version-sort | head -n1)" ]; then return 0 else return 1 fi } # returns a plugin version from a plugin archive get_plugin_version() { local archive; archive=$1 local version; version=$(unzip -p "$archive" META-INF/MANIFEST.MF | grep "^Plugin-Version: " | sed -e 's#^Plugin-Version: ##') version=${version%%[[:space:]]} echo "$version" } # Copy files from /usr/share/jenkins/ref into $JENKINS_HOME # So the initial JENKINS-HOME is set with expected content. # Don't override, as this is just a reference setup, and use from UI # can then change this, upgrade plugins, etc. copy_reference_file() { f="${1%/}" b="${f%.override}" rel="${b#"$REF/"}" version_marker="${rel}.version_from_image" dir=$(dirname "${rel}") local action; local reason; local container_version; local image_version; local marker_version; local log; log=false if [[ ${rel} == plugins/*.jpi ]]; then container_version=$(get_plugin_version "$JENKINS_HOME/${rel}") image_version=$(get_plugin_version "${f}") if [[ -e $JENKINS_HOME/${version_marker} ]]; then marker_version=$(cat "$JENKINS_HOME/${version_marker}") if versionLT "$marker_version" "$container_version"; then if ( versionLT "$container_version" "$image_version" && [[ -n $PLUGINS_FORCE_UPGRADE ]]); then action="UPGRADED" reason="Manually upgraded version ($container_version) is older than image version $image_version" log=true else action="SKIPPED" reason="Installed version ($container_version) has been manually upgraded from initial version ($marker_version)" log=true fi else if [[ "$image_version" == "$container_version" ]]; then action="SKIPPED" reason="Version from image is the same as the installed version $image_version" else if versionLT "$image_version" "$container_version"; then action="SKIPPED" log=true reason="Image version ($image_version) is older than installed version ($container_version)" else action="UPGRADED" log=true reason="Image version ($image_version) is newer than installed version ($container_version)" fi fi fi else if [[ -n "$TRY_UPGRADE_IF_NO_MARKER" ]]; then if [[ "$image_version" == "$container_version" ]]; then action="SKIPPED" reason="Version from image is the same as the installed version $image_version (no marker found)" # Add marker for next time echo "$image_version" > "$JENKINS_HOME/${version_marker}" else if versionLT "$image_version" "$container_version"; then action="SKIPPED" log=true reason="Image version ($image_version) is older than installed version ($container_version) (no marker found)" else action="UPGRADED" log=true reason="Image version ($image_version) is newer than installed version ($container_version) (no marker found)" fi fi fi fi if [[ ! -e $JENKINS_HOME/${rel} || "$action" == "UPGRADED" || $f = *.override ]]; then action=${action:-"INSTALLED"} log=true mkdir -p "$JENKINS_HOME/${dir}" cp -pr "${f}" "$JENKINS_HOME/${rel}"; # pin plugins on initial copy touch "$JENKINS_HOME/${rel}.pinned" echo "$image_version" > "$JENKINS_HOME/${version_marker}" reason=${reason:-$image_version} else action=${action:-"SKIPPED"} fi else if [[ ! -e $JENKINS_HOME/${rel} || $f = *.override ]] then action="INSTALLED" log=true mkdir -p "$JENKINS_HOME/${dir}" cp -pr "$(realpath "${f}")" "$JENKINS_HOME/${rel}"; else action="SKIPPED" fi fi if [[ -n "$VERBOSE" || "$log" == "true" ]]; then if [ -z "$reason" ]; then echo "$action $rel" >> "$COPY_REFERENCE_FILE_LOG" else echo "$action $rel : $reason" >> "$COPY_REFERENCE_FILE_LOG" fi fi } ================================================ FILE: jenkins-support.psm1 ================================================ # compare if version1 < version2 function Compare-VersionLessThan([string] $version1 = '', [string] $version2 = '') { # Quick check for equality if($version1 -eq $version2) { return $false } # Convert '-' to '.' to ease comparison $normalizedVersion1 = $version1 -replace '-', '.' $normalizedVersion2 = $version2 -replace '-', '.' $version1Parts = $normalizedVersion1.Split('.') $version2Parts = $normalizedVersion2.Split('.') # Compare major versions if ($version1Parts[0] -lt $version2parts[0]) { return $true } if ($version1Parts[0] -gt $version2parts[0]) { return $false } $maxLength = [Math]::Max($version1Parts.Length, $version2parts.Length) # First parts are equal, compare subsequent parts for ($i = 1; $i -lt $maxLength; $i++) { $version1part = if ($i -lt $version1Parts.Length) { $version1Parts[$i] } else { '0' } $version2part = if ($i -lt $version2Parts.Length) { $version2Parts[$i] } else { '0' } if ($version1part -eq $version2part) { continue } # Security fix backport special case # Ex: 3894.vd0f0248b_a_fc4 < 3894.3896.vca_2c931e7935 # -> normal incrementals version includes a "v" as first char of second part # -> security fix backport adds the backport source first part as second part # https://github.com/jenkinsci/workflow-cps-plugin/releases/tag/3894.vd0f0248b_a_fc4 # https://github.com/jenkinsci/workflow-cps-plugin/releases/tag/3894.3896.vca_2c931e7935 # If only the nth part of $version1 starts with a "v", then $version1 is older if ($version1part.Substring(0,1) -eq 'v' -and $version2part.Substring(0,1) -ne 'v') { return $true } # Try numeric comparison first, fall back to string comparison $num1 = 0 $num2 = 0 $isNum1 = [int]::TryParse($version1part, [ref]$num1) $isNum2 = [int]::TryParse($version2part, [ref]$num2) if ($isNum1 -and $isNum2) { # Both are numeric, compare as integers return ($num1 -lt $num2) } else { # At least one is not numeric, use string comparison return ($version1part -lt $version2part) } } } function Get-EnvOrDefault($name, $def) { $entry = Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Select-Object -First 1 if(($null -ne $entry) -and ![System.String]::IsNullOrWhiteSpace($entry.Value)) { return $entry.Value } return $def } function Expand-Zip($archive, $file) { # load ZIP methods Add-Type -AssemblyName System.IO.Compression.FileSystem Write-Verbose "Unzipping $file from $archive" $contents = "" if(Test-Path $archive) { # open ZIP archive for reading $zip = [System.IO.Compression.ZipFile]::OpenRead($archive) if($null -ne $zip) { $entry = $zip.GetEntry($file) if($null -ne $entry) { $reader = New-Object -TypeName System.IO.StreamReader -ArgumentList $entry.Open() $contents = $reader.ReadToEnd() $reader.Dispose() } # close ZIP file $zip.Dispose() } } return $contents } # returns a plugin version from a plugin archive function Get-PluginVersion($archive) { $archive = $archive.Trim() Write-Verbose "Getting plugin version for $archive" if(-not (Test-Path $archive)) { return "" } $version = Expand-Zip $archive "META-INF/MANIFEST.MF" | ForEach-Object {$_ -split "`n"} | Select-String -Pattern "^Plugin-Version:\s+" | ForEach-Object {$_ -replace "^Plugin-Version:\s+(.*)", '$1'} | Select-Object -First 1 | Out-String return $version.Trim() } # Copy files from C:/ProgramData/Jenkins/Reference/ into $JENKINS_HOME # So the initial JENKINS-HOME is set with expected content. # Don't override, as this is just a reference setup, and use from UI # can then change this, upgrade plugins, etc. function Copy-ReferenceFile($file) { $action = "" $reason = "" $log = $false $refDir = Get-EnvOrDefault 'REF' 'C:/ProgramData/Jenkins/Reference' if(-not (Test-Path $refDir)) { return } Push-Location $refDir $rel = Resolve-Path -Relative -Path $file Pop-Location $dir = Split-Path -Parent $rel if($file -match "plugins[\\/].*\.jpi") { $fileName = Split-Path -Leaf $file $versionMarker = (Join-Path $env:JENKINS_HOME (Join-Path "plugins" "${fileName}.version_from_image")) $containerVersion = Get-PluginVersion (Join-Path $env:JENKINS_HOME $rel) $imageVersion = Get-PluginVersion $file if(Test-Path $versionMarker) { $markerVersion = (Get-Content -Raw $versionMarker).Trim() if(Compare-VersionLessThan $markerVersion $containerVersion) { if((Compare-VersionLessThan $containerVersion $imageVersion) -and ![System.String]::IsNullOrWhiteSpace($env:PLUGINS_FORCE_UPGRADE)) { $action = "UPGRADED" $reason="Manually upgraded version ($containerVersion) is older than image version $imageVersion" $log=$true } else { $action="SKIPPED" $reason="Installed version ($containerVersion) has been manually upgraded from initial version ($markerVersion)" $log=$true } } else { if($imageVersion -eq $containerVersion) { $action = "SKIPPED" $reason = "Version from image is the same as the installed version $imageVersion" } else { if(Compare-VersionLessThan $imageVersion $containerVersion) { $action = "SKIPPED" $log = $true $reason = "Image version ($imageVersion) is older than installed version ($containerVersion)" } else { $action="UPGRADED" $log=$true $reason="Image version ($imageVersion) is newer than installed version ($containerVersion)" } } } } else { if(![System.String]::IsNullOrWhiteSpace($env:TRY_UPGRADE_IF_NO_MARKER)) { if($imageVersion -eq $containerVersion) { $action = "SKIPPED" $reason = "Version from image is the same as the installed version $imageVersion (no marker found)" # Add marker for next time Add-Content -Path $versionMarker -Value $imageVersion } else { if(Compare-VersionLessThan $imageVersion $containerVersion) { $action = "SKIPPED" $log = $true $reason = "Image version ($imageVersion) is older than installed version ($containerVersion) (no marker found)" } else { $action = "UPGRADED" $log = $true $reason = "Image version ($imageVersion) is newer than installed version ($containerVersion) (no marker found)" } } } } if((-not (Test-Path (Join-Path $env:JENKINS_HOME $rel))) -or ($action -eq "UPGRADED") -or ($file -match "\.override")) { if([System.String]::IsNullOrWhiteSpace($action)) { $action = "INSTALLED" } $log=$true if(-not (Test-Path (Join-Path $env:JENKINS_HOME $dir))) { New-Item -ItemType Directory -Path (Join-Path $env:JENKINS_HOME $dir) } Copy-Item $file (Join-Path $env:JENKINS_HOME $rel) # pin plugins on initial copy Write-Output $null >> (Join-Path $env:JENKINS_HOME "${rel}.pinned") Add-Content -Path $versionMarker -Value $imageVersion if([System.String]::IsNullOrWhiteSpace($reason)) { $reason = $imageVersion } } else { if([System.String]::IsNullOrWhiteSpace($action)) { $action = "SKIPPED" } } } else { if((-not (Test-Path (Join-Path $env:JENKINS_HOME $rel))) -or ($file -match "\.override")) { $action = "INSTALLED" $log = $true if(-not (Test-Path (Join-Path $env:JENKINS_HOME (Split-Path -Parent $rel)))) { New-Item -ItemType Directory (Join-Path $env:JENKINS_HOME (Split-Path -Parent $rel)) } Copy-Item $file (Join-Path $env:JENKINS_HOME $rel) } else { $action="SKIPPED" } } if(![System.String]::IsNullOrWhiteSpace($env:VERBOSE) -or $log) { if([System.String]::IsNullOrWhiteSpace($reason)) { Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "$action $rel" } else { Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "$action $rel : $reason" } } } ================================================ FILE: jenkins.io-2026.key ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBGlJRoMBEADGTw4Jms5rD1Wd0evqpTkNBgAIvCzvsjgGXHevmNIsDmm/niiE gKJlrl73T9d8GZeoacsAqwGTIq29ZA1jEt1lUZ8YkVxD3VxoL0RBhgMcy3qhiu37 mQN1mzuItob8P2pft5pPqCWQDojXRpnMB/BTHgbtIH3i4chKVLJoCEX/Gw7abDbj cUpoKMTByd0+Zv2OBtdm7ZOYXHObPmSqRoiYNiCsW3mZRsgN1LkwTl5IwJQ7Xpj8 9J4DK1Y6Fuyxi+QTbZk9Z3inrTx3pbARPd91MylIsOtuXkUFNQkA/ZWnKHTFgWQA qx//KrsCKLe6r3+CQ4/1R4F7jHjBB01qHrxofEzGo0LB/+QNwf1ISqD7piw20IMt vhlOqdsF2MQQAeyg8fv4nuLglI9ueh4T5FJabp6oL0QDozx1toa5Q58n0nX8gSBq 3VTd8FkzTTsaihyypWmzbdVPwAAfXhRh7sNAUvALkq4vj/EWjPruQElWyP8DwmiC Aq8iduFb66oN58vlT1rf3z/jJH3FeByVEHEymz4E9rhBN1oOUQ++ONqCMOZHwnpY K549A+mHrK12RDQTYjgbi9BH2ktPqPUE37rZDoGN9hzZ9dqG8dMEEz5qVMzsGhuw nm1d86yQRUzscHwgPELc7xiIuV3taLf2KI4qSHTDmq6nRFxcgKI2LGFfcwARAQAB tDJKZW5raW5zIFByb2plY3QgPGplbmtpbnNjaS1ib2FyZEBnb29nbGVncm91cHMu Y29tPokCVwQTAQgAQRYhBF44bq21XwFQTK6Lz3GY9LcUq/xoBQJpSUaDAhsDBQkF o5qABQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEHGY9LcUq/xouboP/1Zd KxZXkTj20jnBn8MJ9scr17wzGLy2/EaAelbfeIYmsWJ6A7ZuuUw/41dUbTuI3k3D Ta1Ft0oO5K63sJqvTQzUdas6x3HMsjYSo+YtbRZnMmR/KO4//5Lewm3LPQnCV662 8ZI73T22msQAbyxa8do56dmBT4N/NO6oGFZI6JBFnkiIlXmKDzm3aiEZi//piN3X PZgtu8wHqpFleJXUbCpk8Db69xTjdXhnFpaYg29VrzvD/0jBEZE47Bekrl6YgjJ8 CKyhaPWZfxYxNeuVRTn+yxlAcDc8o9tboSKnlZ8HSOBPbf36qmLKbD4rPQmTAVgJ hwBY2mxDUT5hTVom25KeyueIyN4l6OZEoLxcq5GxN85RkU2Zfq1jodpnm/PnF47Y 7qS4zu8bOOeUCFpJXG3kDYo34tkFKk5CT8PJLHdjgLWGvhQeL95ytPvrTLkEj4yk 6SXHH4EcKimgi0c/zotnzv997kGCpoMZoeIXpkhrTJoZvSQqFpeCamFRwl/AfM/l ppyH905Cm/GcB+W0hQqTsA0wm+6ZQn4fAR/rhqRk4Ka1TuX2ow3OQKlyoA4EgvdI 41MQEw4y9spjH2RgyJpOAgIagidECrFJbqNcyzHUZUxcD7fKMRaiv5LepxVLXZ0/ XDDBGd3AXh6nv2BTDhoE+ZI1suWZAMwvxyoFDDFO =8CuH -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: jenkins.ps1 ================================================ Import-Module -Force -DisableNameChecking C:/ProgramData/Jenkins/jenkins-support.psm1 $JENKINS_WAR = Get-EnvOrDefault 'JENKINS_WAR' 'C:/ProgramData/Jenkins/jenkins.war' $JENKINS_HOME = Get-EnvOrDefault 'JENKINS_HOME' 'C:/ProgramData/Jenkins/JenkinsHome' $COPY_REFERENCE_FILE_LOG = Get-EnvOrDefault 'COPY_REFERENCE_FILE_LOG' "$($JENKINS_HOME)/copy_reference_file.log" try { [System.IO.File]::OpenWrite($COPY_REFERENCE_FILE_LOG).Close() } catch { Write-Error "Can not write to $COPY_REFERENCE_FILE_LOG. Wrong volume permissions?`n`n$_" exit 1 } Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "--- Copying files at $(Get-Date)" Get-ChildItem -Recurse -File -Path 'C:/ProgramData/Jenkins/Reference' | ForEach-Object { Copy-ReferenceFile $_.FullName } Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "--- Copied files finished at $(Get-Date)" # if `docker run` first argument starts with `--` the user is passing jenkins launcher arguments if(($args.Count -eq 0) -or ($args[0] -match "^--.*")) { # read JAVA_OPTS and JENKINS_OPTS into arrays to avoid need for eval (and associated vulnerabilities) $java_opts_array = ($env:JAVA_OPTS -split ' ') + ($env:JENKINS_JAVA_OPTS -split ' ') $agent_port_property='jenkins.model.Jenkins.slaveAgentPort' if(![System.String]::IsNullOrWhiteSpace($env:JENKINS_AGENT_PORT) -and !($java_opts_array -match "$agent_port_property")) { $java_opts_array += "-D`"$agent_port_property=$env:JENKINS_AGENT_PORT`"" } if($null -ne $env:DEBUG) { $java_opts_array += '-Xdebug' $java_opts_array += '-Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=y' } $jenkins_opts_array = $env:JENKINS_OPTS -split ' ' $proc = Start-Process -NoNewWindow -Wait -PassThru -FilePath 'java.exe' -ArgumentList "-D`"user.home=$JENKINS_HOME`" $java_opts_array -jar $JENKINS_WAR $jenkins_opts_array $args" if($null -ne $proc) { $proc.WaitForExit() } } else { # As argument is not jenkins, assume user wants to run their own process, for example a `powershell` shell to explore this image Invoke-Expression "$args" exit $lastExitCode } ================================================ FILE: jenkins.sh ================================================ #! /bin/bash -e : "${JENKINS_WAR:="/usr/share/jenkins/jenkins.war"}" : "${JENKINS_HOME:="/var/jenkins_home"}" if [[ -n "${PRE_CLEAR_INIT_GROOVY_D}" ]]; then rm -rf "${JENKINS_HOME}/init.groovy.d" fi : "${COPY_REFERENCE_FILE_LOG:="${JENKINS_HOME}/copy_reference_file.log"}" : "${REF:="/usr/share/jenkins/ref"}" if ! [ -r "${JENKINS_HOME}" ] || ! [ -w "${JENKINS_HOME}" ]; then echo "INSTALL WARNING: User: ${USER} missing rw permissions on JENKINS_HOME: ${JENKINS_HOME}" fi touch "${COPY_REFERENCE_FILE_LOG}" || { echo "Can not write to ${COPY_REFERENCE_FILE_LOG}. Wrong volume permissions?"; exit 1; } echo "--- Copying files at $(date)" >> "$COPY_REFERENCE_FILE_LOG" find "${REF}" \( -type f -o -type l \) -exec bash -c '. /usr/local/bin/jenkins-support; for arg; do copy_reference_file "$arg"; done' _ {} + echo "--- Copied files finished at $(date)" >> "$COPY_REFERENCE_FILE_LOG" # if `docker run` first argument start with `--` the user is passing jenkins launcher arguments if [[ $# -lt 1 ]] || [[ "$1" == "--"* ]]; then # shellcheck disable=SC2001 effective_java_opts=$(sed -e 's/^ $//' <<<"$JAVA_OPTS $JENKINS_JAVA_OPTS") # read JAVA_OPTS and JENKINS_OPTS into arrays to avoid need for eval (and associated vulnerabilities) java_opts_array=() while IFS= read -r -d '' item; do java_opts_array+=( "$item" ) done < <([[ $effective_java_opts ]] && xargs printf '%s\0' <<<"$effective_java_opts") readonly agent_port_property='jenkins.model.Jenkins.slaveAgentPort' if [ -n "${JENKINS_SLAVE_AGENT_PORT:-}" ] && [[ "${effective_java_opts:-}" != *"${agent_port_property}"* ]]; then java_opts_array+=( "-D${agent_port_property}=${JENKINS_SLAVE_AGENT_PORT}" ) fi readonly lifecycle_property='hudson.lifecycle' if [[ "${JAVA_OPTS:-}" != *"${lifecycle_property}"* ]]; then java_opts_array+=( "-D${lifecycle_property}=hudson.lifecycle.ExitLifecycle" ) fi if [[ "$DEBUG" ]] ; then java_opts_array+=( \ '-Xdebug' \ '-Xrunjdwp:server=y,transport=dt_socket,address=*:5005,suspend=y' \ ) fi jenkins_opts_array=( ) while IFS= read -r -d '' item; do jenkins_opts_array+=( "$item" ) done < <([[ $JENKINS_OPTS ]] && xargs printf '%s\0' <<<"$JENKINS_OPTS") exec java -Duser.home="$JENKINS_HOME" "${java_opts_array[@]}" -jar "${JENKINS_WAR}" "${jenkins_opts_array[@]}" "$@" fi # As argument is not jenkins, assume user wants to run a different process, for example a `bash` shell to explore this image exec "$@" ================================================ FILE: make.ps1 ================================================ [CmdletBinding()] Param( [Parameter(Position = 1)] # Default script target [String] $Target = 'build', # Jenkins version to include [String] $JenkinsVersion = '2.555', # Windows flavor and windows version to build [String] $ImageType = 'windowsservercore-ltsc2022', # Generate a docker compose file even if it already exists [switch] $OverwriteDockerComposeFile = $false, # Print the build and publish command instead of executing them if set [switch] $DryRun = $false, # Output debug info for tests: 'empty' (no additional test output), 'debug' (test cmd & stderr output), 'verbose' (test cmd, stderr, stdout output) [String] $TestsDebug = '' ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # Disable Progress bar for faster downloads $Repository = 'jenkins' $Organisation = 'jenkins' if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO)) { $Repository = $env:DOCKERHUB_REPO } if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_ORGANISATION)) { $Organisation = $env:DOCKERHUB_ORGANISATION } if(![String]::IsNullOrWhiteSpace($env:JENKINS_VERSION)) { $JenkinsVersion = $env:JENKINS_VERSION } if(![String]::IsNullOrWhiteSpace($env:IMAGE_TYPE)) { $ImageType = $env:IMAGE_TYPE } $env:DOCKERHUB_ORGANISATION = "$Organisation" $env:DOCKERHUB_REPO = "$Repository" $env:JENKINS_VERSION = "$JenkinsVersion" $env:COMMIT_SHA = git rev-parse HEAD # Check for required commands Function Test-CommandExists { Param ( [String] $command ) $oldPreference = $ErrorActionPreference $ErrorActionPreference = 'stop' try { # Special case to test "docker buildx" if ($command.Contains(' ')) { Invoke-Expression $command | Out-Null Write-Debug "$command exists" } else { if(Get-Command $command){ Write-Debug "$command exists" } } } Catch { "$command does not exist" } Finally { $ErrorActionPreference = $oldPreference } } function Test-Image { param ( [String] $ImageName ) Write-Host "= TEST: Received ${ImageName} image name" $items = $ImageName.split(':') $orgRepo = $items[0] -replace 'docker.io/', '' $tag = $items[1] Write-Host "= TEST: Testing ${tag} tag of ${orgRepo} repository" $env:DOCKERHUB_ORG_REPO = $orgRepo $env:CONTROLLER_TAG = $tag $targetPath = '.\target\{0}' -f $tag if (Test-Path $targetPath) { Remove-Item -Recurse -Force $targetPath } New-Item -Path $targetPath -Type Directory | Out-Null $configuration.TestResult.OutputPath = '{0}\junit-results.xml' -f $targetPath $TestResults = Invoke-Pester -Configuration $configuration $failed = $false if ($TestResults.FailedCount -gt 0) { Write-Host "There were $($TestResults.FailedCount) failed tests in $tag" $failed = $true } else { Write-Host "There were $($TestResults.PassedCount) passed tests out of $($TestResults.TotalCount) in $tag" } Remove-Item env:\DOCKERHUB_ORG_REPO Remove-Item env:\CONTROLLER_TAG return $failed } function Test-IsLatestJenkinsRelease { param ( [String] $Version ) Write-Host "= PREPARE: Checking if $env:JENKINS_VERSION is latest Weekly or LTS..." $metadataUrl = "https://repo.jenkins-ci.org/releases/org/jenkins-ci/main/jenkins-war/maven-metadata.xml" try { [xml]$metadata = Invoke-WebRequest $metadataUrl -UseBasicParsing } catch { Write-Error "Failed to retrieve Jenkins versions from Artifactory" exit 1 } $allVersions = $metadata.metadata.versioning.versions.version # Weekly $weeklyVersions = $allVersions | Where-Object { $_ -match '^\d+\.\d+$' } | ForEach-Object { [version]$_ } | Sort-Object # LTS $ltsVersions = $allVersions | Where-Object { $_ -match '^\d+\.\d+\.\d+$' } | ForEach-Object { [version]$_ } | Sort-Object $latestWeeklyVersion = $weeklyVersions[-1] Write-Host "latest Weekly version: $latestWeeklyVersion" $latestLTSVersion = $ltsVersions[-1] Write-Host "latest LTS version: $latestLTSVersion" $latest = $false if ($Version -eq $latestWeeklyVersion) { $latest = $true } if ($Version -eq $latestLTSVersion) { $latest = $true } if (!$latest) { Write-Host "WARNING: $JenkinsVersion is neither the lastest Weekly nor the latest LTS version" } return $latest } function Initialize-DockerComposeFile { param ( [String] $ImageType, [String] $DockerComposeFile ) Write-Host "= PREPARE: Docker compose file generation for $ImageType" $items = $ImageType.Split('-') $windowsFlavor = $items[0] $windowsVersion = $items[1] # Override the list of Windows versions taken defined in docker-bake.hcl by the version from image type $env:WINDOWS_VERSION_OVERRIDE = $windowsVersion # Retrieve the targets from docker buildx bake --print output # Remove the 'output' section (unsupported by docker compose) # For each target name as service key, return a map consisting of: # - 'image' set to the first tag value # - 'build' set to the content of the bake target $yqMainQuery = '.target[] | del(.output) | {(. | key): {"image": .tags[0], "build": .}}' # Encapsulate under a top level 'services' map $yqServicesQuery = '{"services": .}' if ($PSVersionTable.PSVersion.Major -eq 5) { $yqMainQuery = $yqMainQuery -replace '"', '\"' $yqServicesQuery = $yqServicesQuery -replace '"', '\"' } # - Use docker buildx bake to output image definitions from the "" bake target # - Convert with yq to the format expected by docker compose # - Store the result in the docker compose file docker buildx bake --progress=quiet --file=docker-bake.hcl $windowsFlavor --print | yq --prettyPrint $yqMainQuery | yq $yqServicesQuery | Out-File -FilePath $DockerComposeFile # Remove override Remove-Item env:\WINDOWS_VERSION_OVERRIDE } Test-CommandExists 'docker' Test-CommandExists 'docker-compose' Test-CommandExists 'docker buildx' Test-CommandExists 'yq' # Sanity check yq --version # Add 'lts-' prefix to LTS tags not including Jenkins version # Compared to weekly releases, LTS releases include an additional build number in their version $releaseLine = 'war' # Determine if the current JENKINS_VERSION corresponds to the latest Weekly or LTS version from Artifactory $isJenkinsVersionLatest = Test-IsLatestJenkinsRelease -Version $JenkinsVersion if ($JenkinsVersion.Split('.').Count -eq 3) { $releaseLine = 'war-stable' $env:LATEST_LTS = If ($isJenkinsVersionLatest) { "true" } Else { "false" } } else { $env:LATEST_WEEKLY = If ($isJenkinsVersionLatest) { "true" } Else { "false" } } # If there is no WAR_URL set, using get.jenkins.io URL depending on the release line if([String]::IsNullOrWhiteSpace($env:WAR_URL)) { $env:WAR_URL = 'https://get.jenkins.io/{0}/{1}/jenkins.war' -f $releaseLine, $JenkinsVersion } $dockerComposeFile = 'build-windows_{0}.yaml' -f $ImageType $baseDockerCmd = 'docker-compose --file={0}' -f $dockerComposeFile $baseDockerBuildCmd = '{0} build --parallel --pull' -f $baseDockerCmd # Generate the docker compose file if it doesn't exists or if the parameter OverwriteDockerComposeFile is set if ((Test-Path $dockerComposeFile) -and -not $OverwriteDockerComposeFile) { Write-Host "= PREPARE: The docker compose file '$dockerComposeFile' containing the image definitions already exists." } else { Write-Host "= PREPARE: Initialize the docker compose file '$dockerComposeFile' containing the image definitions." Initialize-DockerComposeFile -ImageType $ImageType -DockerComposeFile $dockerComposeFile } Write-Host '= PREPARE: List of images and tags to be processed:' Invoke-Expression "$baseDockerCmd config" if ($target -eq 'build') { Write-Host '= BUILD: Building all images...' switch ($DryRun) { $true { Write-Host "(dry-run) $baseDockerBuildCmd" } $false { Invoke-Expression $baseDockerBuildCmd } } if ($lastExitCode -ne 0) { exit $lastExitCode } Write-Host '= BUILD: Finished building all images.' } if ($target -eq 'test') { if ($DryRun) { Write-Host '= TEST: (dry-run) test harness skipped' } else { Write-Host '= TEST: Starting test harness' $mod = Get-InstalledModule -Name Pester -MinimumVersion 5.3.0 -MaximumVersion 5.3.3 -ErrorAction SilentlyContinue if ($null -eq $mod) { Write-Host '= TEST: Pester 5.3.x not found: installing...' Install-Module -Force -Name Pester -MaximumVersion 5.3.3 -Scope CurrentUser } Import-Module Pester Write-Host '= TEST: Setting up Pester environment...' $configuration = [PesterConfiguration]::Default $configuration.Run.PassThru = $true $configuration.Run.Path = '.\tests' $configuration.Run.Exit = $true $configuration.TestResult.Enabled = $true $configuration.TestResult.OutputFormat = 'JUnitXml' $configuration.Output.Verbosity = 'Diagnostic' $configuration.CodeCoverage.Enabled = $false Write-Host '= TEST: Testing all images...' # Only fail the run afterwards in case of any test failures $testFailed = $false $imageDefinitions = Invoke-Expression "$baseDockerCmd config" | yq --unwrapScalar --output-format json '.services' | ConvertFrom-Json foreach ($imageDefinition in $imageDefinitions.PSObject.Properties) { $testFailed = $testFailed -or (Test-Image -ImageName $imageDefinition.Value.image) } # Fail if any test failures if ($testFailed -ne $false) { Write-Error '= TEST: Test stage failed' exit 1 } else { Write-Host '= TEST: Test stage passed!' } } } if ($target -eq 'publish') { Write-Host '= PUBLISH: push all images and tags' switch($DryRun) { $true { Write-Host "(dry-run) $baseDockerCmd push" } $false { Invoke-Expression "$baseDockerCmd push" } } # Fail if any issues when publishing the docker images if ($lastExitCode -ne 0) { Write-Error '= PUBLISH: failed!' exit 1 } } if ($lastExitCode -ne 0 -and !$DryRun) { Write-Error 'Build failed!' } else { Write-Host 'Build finished successfully' } exit $lastExitCode ================================================ FILE: rhel/Dockerfile ================================================ ARG RHEL_TAG=9.7-1773204657 ARG RHEL_RELEASE_LINE=ubi9 FROM registry.access.redhat.com/${RHEL_RELEASE_LINE}/ubi:${RHEL_TAG} AS jre-and-war ARG JAVA_VERSION=17.0.18_8 SHELL ["/bin/bash", "-o", "pipefail", "-c"] COPY jdk-download-url.sh /usr/bin/jdk-download-url.sh COPY jdk-download.sh /usr/bin/jdk-download.sh RUN dnf install --disableplugin=subscription-manager --setopt=install_weak_deps=0 --setopt=tsflags=nodocs --allowerasing -y \ ca-certificates \ curl \ jq \ && dnf clean --disableplugin=subscription-manager all \ && /usr/bin/jdk-download.sh ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}" # Generate smaller java runtime without unneeded files # for now we include the full module path to maintain compatibility # while still saving space (approx 200mb from the full distribution) # hadolint ignore=SC2086 RUN java_major_version="$(jlink --version 2>&1 | cut -c1-2)"; \ if [ "$java_major_version" = "25" ]; then \ cp -r "/opt/jdk-${JAVA_VERSION}" /javaruntime; \ else \ case "$java_major_version" in \ "17") options="--compress=2" ;; \ "21") options="--compress=zip-6" ;; \ *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \ esac; \ jlink \ --strip-java-debug-attributes \ ${options} \ --add-modules ALL-MODULE-PATH \ --no-man-pages \ --no-header-files \ --output /javaruntime; \ fi # Jenkins version being bundled in this docker image ARG JENKINS_VERSION=2.555 # Can be used to customize where jenkins.war get downloaded from ARG WAR_URL=https://get.jenkins.io/war/${JENKINS_VERSION}/jenkins.war COPY jenkins.io-2026.key /war/jenkins-key.pub # Not using ADD as it does not check Last-Modified header # see https://github.com/docker/docker/issues/8331 RUN curl -fsSL "${WAR_URL}" -o /war/jenkins.war \ && curl -fsSL "${WAR_URL}.asc" -o /war/jenkins.war.asc \ && gpg --import /war/jenkins-key.pub \ && gpg --verify --trust-model direct /war/jenkins.war.asc /war/jenkins.war FROM registry.access.redhat.com/${RHEL_RELEASE_LINE}/ubi:${RHEL_TAG} AS controller ENV LANG=C.UTF-8 ARG TARGETARCH ARG COMMIT_SHA RUN dnf install --disableplugin=subscription-manager --setopt=install_weak_deps=0 --setopt=tsflags=nodocs -y \ fontconfig \ freetype \ git \ git-lfs \ unzip \ which \ tzdata \ && dnf clean --disableplugin=subscription-manager all ARG user=jenkins ARG group=jenkins ARG uid=1000 ARG gid=1000 ARG http_port=8080 ARG agent_port=50000 ARG JENKINS_HOME=/var/jenkins_home ARG REF=/usr/share/jenkins/ref ENV JENKINS_HOME=$JENKINS_HOME ENV JENKINS_SLAVE_AGENT_PORT=${agent_port} ENV REF=$REF # Jenkins is run with user `jenkins`, uid = 1000 # If you bind mount a volume from the host or a data container, # ensure you use the same uid RUN mkdir -p $JENKINS_HOME \ && chown ${uid}:${gid} $JENKINS_HOME \ && groupadd -g ${gid} ${group} \ && useradd -N -d "$JENKINS_HOME" -u ${uid} -g ${gid} -l -m -s /bin/bash ${user} # Jenkins home directory is a volume, so configuration and build history # can be persisted and survive image upgrades VOLUME $JENKINS_HOME # $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want # to set on a fresh new installation. Use it to bundle additional plugins # or config file with your custom jenkins Docker image. RUN mkdir -p ${REF}/init.groovy.d # Use tini as subreaper in Docker container to adopt zombie processes ARG TINI_VERSION=v0.19.0 COPY tini_pub.gpg "${JENKINS_HOME}/tini_pub.gpg" RUN curl -fsSL "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TARGETARCH}" -o /sbin/tini \ && curl -fsSL "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TARGETARCH}.asc" -o /sbin/tini.asc \ && gpg --no-tty --import "${JENKINS_HOME}/tini_pub.gpg" \ && gpg --verify /sbin/tini.asc \ && rm -rf /sbin/tini.asc /root/.gnupg \ && chmod +x /sbin/tini ENV JENKINS_UC=https://updates.jenkins.io ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals RUN chown -R ${user} "$JENKINS_HOME" "$REF" ARG PLUGIN_CLI_VERSION=2.14.0 ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar RUN curl -fsSL ${PLUGIN_CLI_URL} -o /opt/jenkins-plugin-manager.jar \ && echo "$(curl -fsSL "${PLUGIN_CLI_URL}.sha256") /opt/jenkins-plugin-manager.jar" >/tmp/jpm_sha \ && sha256sum -c --strict /tmp/jpm_sha \ && rm -f /tmp/jpm_sha # for main web interface: EXPOSE ${http_port} # will be used by attached agents: EXPOSE ${agent_port} ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log ENV JAVA_HOME=/opt/java/openjdk ENV PATH="${JAVA_HOME}/bin:${PATH}" COPY --from=jre-and-war /javaruntime $JAVA_HOME COPY --from=jre-and-war /war/jenkins.war /usr/share/jenkins/jenkins.war USER ${user} COPY jenkins-support /usr/local/bin/jenkins-support COPY jenkins.sh /usr/local/bin/jenkins.sh COPY jenkins-plugin-cli.sh /bin/jenkins-plugin-cli ARG JENKINS_VERSION=2.555 ENV JENKINS_VERSION=${JENKINS_VERSION} ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"] # metadata labels LABEL \ org.opencontainers.image.vendor="Jenkins project" \ org.opencontainers.image.title="Official Jenkins Docker image" \ org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" \ org.opencontainers.image.version="${JENKINS_VERSION}" \ org.opencontainers.image.url="https://www.jenkins.io/" \ org.opencontainers.image.source="https://github.com/jenkinsci/docker" \ org.opencontainers.image.revision="${COMMIT_SHA}" \ org.opencontainers.image.licenses="MIT" ================================================ FILE: tests/bake.bats ================================================ #!/usr/bin/env bats # bats file_tags=test-suite:bake # bats file_tags=test-type:golden-file load test_helpers SUT_DESCRIPTION="docker bake" LTS_JENKINS_VERSION="2.541.1" @test "[${SUT_DESCRIPTION}: tags] Default tags unchanged" { assert_matches_golden expected_tags make --silent tags } @test "[${SUT_DESCRIPTION}: tags] Latest weekly tags unchanged" { assert_matches_golden expected_tags_latest_weekly make --silent tags LATEST_WEEKLY=true } @test "[${SUT_DESCRIPTION}: tags] Latest LTS tags unchanged" { assert_matches_golden expected_tags_latest_lts make --silent tags LATEST_LTS=true JENKINS_VERSION="${LTS_JENKINS_VERSION}" } @test "[${SUT_DESCRIPTION}: platforms] Platforms per target unchanged" { assert_matches_golden expected_platforms make --silent platforms } ================================================ FILE: tests/functions/.ssh/config ================================================ ================================================ FILE: tests/functions/Dockerfile ================================================ FROM bats-jenkins RUN mkdir -p /usr/share/jenkins/ref/.ssh && touch /usr/share/jenkins/ref/.ssh/config.override RUN chmod 600 /usr/share/jenkins/ref/.ssh/config.override ================================================ FILE: tests/functions/Dockerfile-windows ================================================ FROM bats-jenkins # hadolint shell=powershell RUN mkdir C:/ProgramData/Jenkins/Reference/pester ; echo $null >> C:/ProgramData/Jenkins/Reference/pester/test.override ================================================ FILE: tests/functions.Tests.ps1 ================================================ Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1 Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 $global:SUT_IMAGE=Get-SutImage $global:SUT_CONTAINER=Get-SutImage $global:TEST_TAG=$global:SUT_IMAGE.Replace('pester-jenkins-', '') Describe "[functions > $global:TEST_TAG] build image" { BeforeEach { Push-Location -StackName 'jenkins' -Path "$PSScriptRoot/.." } It 'builds image' { $exitCode, $stdout, $stderr = Build-Docker $global:SUT_IMAGE $exitCode | Should -Be 0 } AfterEach { Pop-Location -StackName 'jenkins' } } # Only test on Java 21, one JDK is enough to test all versions Describe "[functions > $global:TEST_TAG] Check-VersionLessThan" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'exit codes work' { docker run --rm $global:SUT_IMAGE "exit -1" $LastExitCode | Should -Be -1 } It 'has same version' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.0' '1.0')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be -1 } It 'has right side greater' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.0' '1.1')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be 0 } It 'has left side greater' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.1' '1.0')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be -1 } ## Real world examples from https://github.com/jenkinsci/docker/issues/1456 It 'has left side greater (commons-lang3-api-plugin)' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '3.12.0.0' '3.12.0-36.vd97de6465d5b_')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be 0 } It 'has left side greater (map-db-plugin)' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.0.9.0' '1.0.9-28.vf251ce40855d')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be 0 } It 'has left side greater (role-strategy-plugin, security fix backport)' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '587.v2872c41fa_e51' '587.588.v850a_20a_30162')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be 0 } It 'has left side greater (workflow-cps-plugin, security fix backport)' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '3894.vd0f0248b_a_fc4' '3894.3896.vca_2c931e7935')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be 0 } It 'has left side greater (workflow-cps-plugin, security fix backport of an older release, published later than a newer normal release)' { docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '4106.4108.v841a_e1819d4d' '4151.v5406e29e3c90')) { exit 0 } else { exit -1 }" $LastExitCode | Should -Be 0 } } # Only test on Java 21, one JDK is enough to test all versions Describe "[functions > $global:TEST_TAG] Copy-ReferenceFile" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'build test image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE $PSScriptRoot/functions $exitCode | Should -Be 0 } It 'start container' { $exitCode, $stdout, $stderr = Run-Program 'docker' "run -d --name $global:SUT_CONTAINER -P $global:SUT_IMAGE" $exitCode | Should -Be 0 } It 'wait for running' { # give time to eventually fail to initialize Start-Sleep -Seconds 5 Retry-Command -RetryCount 3 -Delay 1 -ScriptBlock { docker inspect -f "{{.State.Running}}" $global:SUT_CONTAINER ; if($lastExitCode -ne 0) { throw('Docker inspect failed') } } -Verbose | Should -BeTrue } It 'is initialized' { Retry-Command -RetryCount 30 -Delay 5 -ScriptBlock { Test-Url $global:SUT_CONTAINER "/api/json" } -Verbose | Should -BeTrue } It 'check files in JENKINS_HOME' { $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:SUT_CONTAINER powershell -C `"Get-ChildItem `$env:JENKINS_HOME`" | Select-Object -Property 'Name'" $exitCode | Should -Be 0 $stdout | Should -Match "pester" $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:SUT_CONTAINER powershell -C `"Get-ChildItem `$env:JENKINS_HOME/pester`" | Select-Object -Property 'Name'" $exitCode | Should -Be 0 $stdout | Should -Match "test.override" } It 'cleanup container' { Cleanup $global:SUT_CONTAINER | Out-Null } } ================================================ FILE: tests/functions.bats ================================================ #!/usr/bin/env bats # bats file_tags=test-suite:functions load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load test_helpers SUT_IMAGE=$(get_sut_image) SUT_DESCRIPTION="${IMAGE}-functions" . $BATS_TEST_DIRNAME/../jenkins-support @test "[${SUT_DESCRIPTION}] versionLT" { run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0 1.0" assert_failure run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0 1.1" assert_success run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.1 1.0" assert_failure ## Real world examples from https://github.com/jenkinsci/docker/issues/1456 # commons-lang3-api-plugin/releases run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 3.12.0.0 3.12.0-36.vd97de6465d5b_" assert_success # map-db-plugin run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0.9.0 1.0.9-28.vf251ce40855d" assert_success # role-strategy-plugin, security fix backport run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 587.v2872c41fa_e51 587.588.v850a_20a_30162" assert_success # workflow-cps-plugin, security fix backport run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 3894.vd0f0248b_a_fc4 3894.3896.vca_2c931e7935" assert_success # workflow-cps-plugin, security fix backport of an older release, published later than a newer normal release run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 4106.4108.v841a_e1819d4d 4151.v5406e29e3c90" assert_success } @test "[${SUT_DESCRIPTION}] permissions are propagated from override file" { local sut_image="${SUT_IMAGE}-functions-${BATS_TEST_NUMBER}" run docker_build_child "${SUT_IMAGE}" "${sut_image}" $BATS_TEST_DIRNAME/functions assert_success # Create a predefined named volume and fill it with a file in an unexpected file mode local volume_name volume_name="functions_${BATS_TEST_NUMBER}" run bash -c "docker volume rm ${volume_name}; docker volume create ${volume_name}" run docker run --rm --volume "${volume_name}:/sut_data" --user=0 "${sut_image}" \ bash -c "mkdir -p /sut_data/.ssh && touch /sut_data/.ssh/config && chmod 644 /sut_data/.ssh/config && chown -R 1000:1000 /sut_data" # replace DOS line endings \r\n run bash -c "docker run --rm --volume "${volume_name}:/var/jenkins_home:rw" "${sut_image}" stat -c '%a' /var/jenkins_home/.ssh/config" assert_success assert_line '600' # Cleanup run docker volume rm "${volume_name}" } ================================================ FILE: tests/golden/expected_env_vars_except_hostname.txt ================================================ COPY_REFERENCE_FILE_LOG=/var/jenkins_home/copy_reference_file.log HOME=/var/jenkins_home JAVA_HOME=/opt/java/openjdk JENKINS_HOME=/var/jenkins_home JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals JENKINS_SLAVE_AGENT_PORT=50000 JENKINS_UC=https://updates.jenkins.io JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental JENKINS_VERSION=2.555 LANG=C.UTF-8 PATH=/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ REF=/usr/share/jenkins/ref SHLVL=1 _=/usr/bin/env ================================================ FILE: tests/golden/expected_platforms.txt ================================================ alpine_jdk21:linux/amd64 alpine_jdk21:linux/arm64 alpine_jdk25:linux/amd64 alpine_jdk25:linux/arm64 debian-slim_jdk21:linux/amd64 debian-slim_jdk21:linux/arm64 debian-slim_jdk21:linux/riscv64 debian-slim_jdk25:linux/amd64 debian-slim_jdk25:linux/arm64 debian-slim_jdk25:linux/riscv64 debian_jdk21:linux/amd64 debian_jdk21:linux/arm64 debian_jdk21:linux/ppc64le debian_jdk21:linux/riscv64 debian_jdk21:linux/s390x debian_jdk25:linux/amd64 debian_jdk25:linux/arm64 debian_jdk25:linux/ppc64le debian_jdk25:linux/riscv64 debian_jdk25:linux/s390x rhel_jdk21:linux/amd64 rhel_jdk21:linux/arm64 rhel_jdk21:linux/ppc64le rhel_jdk25:linux/amd64 rhel_jdk25:linux/arm64 rhel_jdk25:linux/ppc64le windowsservercore-ltsc2019_jdk21:windows/amd64 windowsservercore-ltsc2019_jdk25:windows/amd64 windowsservercore-ltsc2022_jdk21:windows/amd64 windowsservercore-ltsc2022_jdk25:windows/amd64 ================================================ FILE: tests/golden/expected_tags.txt ================================================ docker.io/jenkins/jenkins:2.555 (debian_jdk21) docker.io/jenkins/jenkins:2.555-alpine (alpine_jdk21) docker.io/jenkins/jenkins:2.555-alpine-jdk21 (alpine_jdk21) docker.io/jenkins/jenkins:2.555-alpine-jdk25 (alpine_jdk25) docker.io/jenkins/jenkins:2.555-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.555-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:2.555-jdk21 (debian_jdk21) docker.io/jenkins/jenkins:2.555-jdk21-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.555-jdk21-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:2.555-jdk25 (debian_jdk25) docker.io/jenkins/jenkins:2.555-jdk25-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk25) docker.io/jenkins/jenkins:2.555-jdk25-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk25) docker.io/jenkins/jenkins:2.555-rhel-ubi9-jdk21 (rhel_jdk21) docker.io/jenkins/jenkins:2.555-rhel-ubi9-jdk25 (rhel_jdk25) docker.io/jenkins/jenkins:2.555-slim (debian-slim_jdk21) docker.io/jenkins/jenkins:2.555-slim-jdk21 (debian-slim_jdk21) docker.io/jenkins/jenkins:2.555-slim-jdk25 (debian-slim_jdk25) ================================================ FILE: tests/golden/expected_tags_latest_lts.txt ================================================ docker.io/jenkins/jenkins:2.541.1 (debian_jdk21) docker.io/jenkins/jenkins:2.541.1-alpine (alpine_jdk21) docker.io/jenkins/jenkins:2.541.1-alpine-jdk21 (alpine_jdk21) docker.io/jenkins/jenkins:2.541.1-alpine-jdk25 (alpine_jdk25) docker.io/jenkins/jenkins:2.541.1-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.541.1-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:2.541.1-jdk21 (debian_jdk21) docker.io/jenkins/jenkins:2.541.1-jdk21-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.541.1-jdk21-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:2.541.1-jdk25 (debian_jdk25) docker.io/jenkins/jenkins:2.541.1-jdk25-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk25) docker.io/jenkins/jenkins:2.541.1-jdk25-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk25) docker.io/jenkins/jenkins:2.541.1-lts (debian_jdk21) docker.io/jenkins/jenkins:2.541.1-lts-alpine (alpine_jdk21) docker.io/jenkins/jenkins:2.541.1-lts-jdk21 (debian_jdk21) docker.io/jenkins/jenkins:2.541.1-lts-jdk25 (debian_jdk25) docker.io/jenkins/jenkins:2.541.1-lts-rhel-ubi9-jdk21 (rhel_jdk21) docker.io/jenkins/jenkins:2.541.1-lts-rhel-ubi9-jdk25 (rhel_jdk25) docker.io/jenkins/jenkins:2.541.1-lts-slim (debian-slim_jdk21) docker.io/jenkins/jenkins:2.541.1-rhel-ubi9-jdk21 (rhel_jdk21) docker.io/jenkins/jenkins:2.541.1-rhel-ubi9-jdk25 (rhel_jdk25) docker.io/jenkins/jenkins:2.541.1-slim (debian-slim_jdk21) docker.io/jenkins/jenkins:2.541.1-slim-jdk21 (debian-slim_jdk21) docker.io/jenkins/jenkins:2.541.1-slim-jdk25 (debian-slim_jdk25) docker.io/jenkins/jenkins:2.541.1-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.541.1-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:lts (debian_jdk21) docker.io/jenkins/jenkins:lts-alpine (alpine_jdk21) docker.io/jenkins/jenkins:lts-alpine-jdk21 (alpine_jdk21) docker.io/jenkins/jenkins:lts-alpine-jdk25 (alpine_jdk25) docker.io/jenkins/jenkins:lts-jdk21 (debian_jdk21) docker.io/jenkins/jenkins:lts-jdk21-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:lts-jdk21-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:lts-jdk25 (debian_jdk25) docker.io/jenkins/jenkins:lts-jdk25-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk25) docker.io/jenkins/jenkins:lts-jdk25-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk25) docker.io/jenkins/jenkins:lts-rhel-ubi9-jdk21 (rhel_jdk21) docker.io/jenkins/jenkins:lts-rhel-ubi9-jdk25 (rhel_jdk25) docker.io/jenkins/jenkins:lts-slim (debian-slim_jdk21) docker.io/jenkins/jenkins:lts-slim-jdk21 (debian-slim_jdk21) docker.io/jenkins/jenkins:lts-slim-jdk25 (debian-slim_jdk25) docker.io/jenkins/jenkins:lts-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:lts-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) ================================================ FILE: tests/golden/expected_tags_latest_weekly.txt ================================================ docker.io/jenkins/jenkins:2.555 (debian_jdk21) docker.io/jenkins/jenkins:2.555-alpine (alpine_jdk21) docker.io/jenkins/jenkins:2.555-alpine-jdk21 (alpine_jdk21) docker.io/jenkins/jenkins:2.555-alpine-jdk25 (alpine_jdk25) docker.io/jenkins/jenkins:2.555-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.555-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:2.555-jdk21 (debian_jdk21) docker.io/jenkins/jenkins:2.555-jdk21-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.555-jdk21-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:2.555-jdk25 (debian_jdk25) docker.io/jenkins/jenkins:2.555-jdk25-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk25) docker.io/jenkins/jenkins:2.555-jdk25-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk25) docker.io/jenkins/jenkins:2.555-rhel-ubi9-jdk21 (rhel_jdk21) docker.io/jenkins/jenkins:2.555-rhel-ubi9-jdk25 (rhel_jdk25) docker.io/jenkins/jenkins:2.555-slim (debian-slim_jdk21) docker.io/jenkins/jenkins:2.555-slim-jdk21 (debian-slim_jdk21) docker.io/jenkins/jenkins:2.555-slim-jdk25 (debian-slim_jdk25) docker.io/jenkins/jenkins:2.555-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:2.555-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:alpine (alpine_jdk21) docker.io/jenkins/jenkins:alpine-jdk21 (alpine_jdk21) docker.io/jenkins/jenkins:alpine-jdk25 (alpine_jdk25) docker.io/jenkins/jenkins:alpine3.23-jdk21 (alpine_jdk21) docker.io/jenkins/jenkins:alpine3.23-jdk25 (alpine_jdk25) docker.io/jenkins/jenkins:jdk21 (debian_jdk21) docker.io/jenkins/jenkins:jdk21-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:jdk21-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) docker.io/jenkins/jenkins:jdk25 (debian_jdk25) docker.io/jenkins/jenkins:jdk25-hotspot-windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk25) docker.io/jenkins/jenkins:jdk25-hotspot-windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk25) docker.io/jenkins/jenkins:latest (debian_jdk21) docker.io/jenkins/jenkins:latest-jdk21 (debian_jdk21) docker.io/jenkins/jenkins:latest-jdk25 (debian_jdk25) docker.io/jenkins/jenkins:rhel-ubi9-jdk21 (rhel_jdk21) docker.io/jenkins/jenkins:rhel-ubi9-jdk25 (rhel_jdk25) docker.io/jenkins/jenkins:slim (debian-slim_jdk21) docker.io/jenkins/jenkins:slim-jdk21 (debian-slim_jdk21) docker.io/jenkins/jenkins:slim-jdk25 (debian-slim_jdk25) docker.io/jenkins/jenkins:windowsservercore-ltsc2019 (windowsservercore-ltsc2019_jdk21) docker.io/jenkins/jenkins:windowsservercore-ltsc2022 (windowsservercore-ltsc2022_jdk21) ================================================ FILE: tests/jenkinsfile.bats ================================================ #!/usr/bin/env bats # bats file_tags=test-suite:jenkinsfile load test_helpers SUT_DESCRIPTION="Jenkinsfile" @test "[${SUT_DESCRIPTION}] Default (weekly) Linux targets from docker bake are taken in account in Jenkinsfile" { [ "$(get_default_docker_bake_linux_targets)" == "$(get_targets_from_jenkinsfile)" ] } ================================================ FILE: tests/plugins-cli/Dockerfile ================================================ FROM bats-jenkins RUN jenkins-plugin-cli --plugins junit:1.6 ant:1.3 mesos:0.13.0 git:latest filesystem_scm:experimental docker-plugin:1.1.6 ================================================ FILE: tests/plugins-cli/Dockerfile-windows ================================================ FROM bats-jenkins # hadolint shell=powershell RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose --plugins junit:1.6 ant:1.3 mesos:0.13.0 git:latest filesystem_scm:experimental docker-plugin:1.1.6 ================================================ FILE: tests/plugins-cli/custom-war/Dockerfile ================================================ FROM bats-jenkins # Define a custom location for the war ENV JENKINS_WAR=/test-custom-dockerfile/my-custom-jenkins.war WORKDIR /test-custom-dockerfile # Add there a new weird plugin to assert COPY --chown=jenkins:jenkins WEB-INF/ WEB-INF/ USER root RUN chown jenkins:jenkins /test-custom-dockerfile USER jenkins # Copy the original jenkins.war to the custom location, add the weird plugin to # the new custom WAR, and run the jenkins-plugin-cli script. RUN cp /usr/share/jenkins/jenkins.war $JENKINS_WAR \ && chown jenkins:jenkins $JENKINS_WAR \ && jar -uf my-custom-jenkins.war WEB-INF/* \ && jenkins-plugin-cli --war $JENKINS_WAR --plugins junit:1.6 ================================================ FILE: tests/plugins-cli/custom-war/Dockerfile-windows ================================================ FROM bats-jenkins # hadolint shell=powershell # Define a custom location for the war ENV JENKINS_WAR=C:/ProgramData/TestCustomDockerfile/my-custom-jenkins.war WORKDIR C:/ProgramData/TestCustomDockerfile # Add there a new weird plugin to assert COPY WEB-INF/ WEB-INF/ # Copy the original jenkins.war to the custom location RUN Copy-Item C:/ProgramData/Jenkins/jenkins.war $env:JENKINS_WAR # Add the weird plugin to the new custom war # hadolint ignore=DL3059 RUN jar -uf my-custom-jenkins.war WEB-INF/* # Run the jenkins-plugin-cli script # hadolint ignore=DL3059 RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --war $env:JENKINS_WAR --plugins junit:1.6 ================================================ FILE: tests/plugins-cli/java-opts/Dockerfile ================================================ FROM bats-jenkins-plugins-cli ENV JAVA_OPTS="-Djava.opts.test=true -XshowSettings:properties" RUN jenkins-plugin-cli --version ================================================ FILE: tests/plugins-cli/no-war/Dockerfile ================================================ FROM bats-jenkins COPY plugins.txt /usr/share/jenkins/ref/plugins.txt USER root RUN rm -rf /usr/share/jenkins/jenkins.war USER jenkins RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt ================================================ FILE: tests/plugins-cli/no-war/Dockerfile-windows ================================================ FROM bats-jenkins # hadolint shell=powershell COPY plugins.txt C:/ProgramData/Jenkins/Reference/plugins.txt RUN Remove-Item -Force C:/ProgramData/Jenkins/jenkins.war # hadolint ignore=DL3059 RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 -f C:/ProgramData/Jenkins/Reference/plugins.txt ================================================ FILE: tests/plugins-cli/no-war/plugins.txt ================================================ # comment line should be skipped # simple case ant:1.3 # trailing spaces junit:1.6 # leading spaces mesos:0.13.0 # leading spaces, and trailing spaces git:latest # with comments at the end filesystem_scm:experimental # comment at the end # empty line # # empty line ================================================ FILE: tests/plugins-cli/pluginsfile/Dockerfile ================================================ FROM bats-jenkins COPY plugins.txt /usr/share/jenkins/ref/plugins.txt RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt ================================================ FILE: tests/plugins-cli/pluginsfile/Dockerfile-windows ================================================ FROM bats-jenkins # hadolint shell=powershell COPY plugins.txt C:/ProgramData/Jenkins/Reference/plugins.txt RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose -f C:/ProgramData/Jenkins/Reference/plugins.txt ================================================ FILE: tests/plugins-cli/pluginsfile/plugins.txt ================================================ # comment line should be skipped # simple case ant:1.3 # trailing spaces junit:1.6 # leading spaces mesos:0.13.0 # leading spaces, and trailing spaces git:latest # with comments at the end filesystem_scm:experimental # comment at the end # empty line # # empty line # from url subversion:::https://updates.jenkins.io/download/plugins/subversion/2.12.1/subversion.hpi ================================================ FILE: tests/plugins-cli/ref/Dockerfile ================================================ FROM bats-jenkins-plugins-cli RUN rm -rf /usr/share/jenkins/ref ; jenkins-plugin-cli --plugins junit:1.28 ant:1.3 ================================================ FILE: tests/plugins-cli/ref/Dockerfile-windows ================================================ FROM bats-jenkins # hadolint shell=powershell RUN Remove-Item -Recurse -Force C:/ProgramData/Jenkins/Reference ; C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose --plugins junit:1.28 ant:1.3 ================================================ FILE: tests/plugins-cli/update/Dockerfile ================================================ FROM bats-jenkins-plugins-cli RUN jenkins-plugin-cli --verbose --plugins junit:1.28 ant:1.3 ================================================ FILE: tests/plugins-cli/update/Dockerfile-windows ================================================ FROM bats-jenkins-plugins-cli # hadolint shell=powershell RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose --plugins junit:1.28 ant:1.3 ================================================ FILE: tests/plugins-cli.Tests.ps1 ================================================ Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1 Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 $global:SUT_IMAGE=Get-SutImage $global:SUT_CONTAINER=Get-SutImage $global:TEST_TAG=$global:SUT_IMAGE.Replace('pester-jenkins-', '') Describe "[plugins-cli > $global:TEST_TAG] build image" { BeforeEach { Push-Location -StackName 'jenkins' -Path "$PSScriptRoot/.." } It 'builds image' { $exitCode, $stdout, $stderr = Build-Docker $global:SUT_IMAGE $exitCode | Should -Be 0 } AfterEach { Pop-Location -StackName 'jenkins' } } Describe "[plugins-cli > $global:TEST_TAG] cleanup container" { It 'cleanup' { Cleanup $global:SUT_CONTAINER | Out-Null } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli $exitCode | Should -Be 0 $stdout | Should -Not -Match "Skipping already installed dependency" } It 'has correct plugins' { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli gci `$env:JENKINS_HOME/plugins | Select-Object -Property Name" $exitCode | Should -Be 0 $stdout | Should -Match 'junit.jpi' $stdout | Should -Match 'junit.jpi.pinned' $stdout | Should -Match 'ant.jpi' $stdout | Should -Match 'ant.jpi.pinned' $stdout | Should -Match 'credentials.jpi' $stdout | Should -Match 'credentials.jpi.pinned' $stdout | Should -Match 'mesos.jpi' $stdout | Should -Match 'mesos.jpi.pinned' # optional dependencies $stdout | Should -Not -Match 'metrics.jpi' $stdout | Should -Not -Match 'metrics.jpi.pinned' # plugins bundled but under detached-plugins, so need to be installed $stdout | Should -Match 'mailer.jpi' $stdout | Should -Match 'mailer.jpi.pinned' $stdout | Should -Match 'git.jpi' $stdout | Should -Match 'git.jpi.pinned' $stdout | Should -Match 'filesystem_scm.jpi' $stdout | Should -Match 'filesystem_scm.jpi.pinned' $stdout | Should -Match 'docker-plugin.jpi' $stdout | Should -Match 'docker-plugin.jpi.pinned' } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli with non-default REF" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-ref $PSScriptRoot/plugins-cli/ref $exitCode | Should -Be 0 $stdout | Should -Not -Match "Skipping already installed dependency" } It 'has correct plugins' { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli-ref -e REF=C:/ProgramData/JenkinsDir/Reference gci C:/ProgramData/JenkinsDir/Reference" $exitCode | Should -Be 0 $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli gci `$env:JENKINS_HOME/plugins | Select-Object -Property Name" $exitCode | Should -Be 0 $stdout | Should -Match 'junit.jpi' $stdout | Should -Match 'junit.jpi.pinned' $stdout | Should -Match 'ant.jpi' $stdout | Should -Match 'ant.jpi.pinned' $stdout | Should -Match 'credentials.jpi' $stdout | Should -Match 'credentials.jpi.pinned' $stdout | Should -Match 'mesos.jpi' $stdout | Should -Match 'mesos.jpi.pinned' # optional dependencies $stdout | Should -Not -Match 'metrics.jpi' $stdout | Should -Not -Match 'metrics.jpi.pinned' # plugins bundled but under detached-plugins, so need to be installed $stdout | Should -Match 'mailer.jpi' $stdout | Should -Match 'mailer.jpi.pinned' $stdout | Should -Match 'git.jpi' $stdout | Should -Match 'git.jpi.pinned' $stdout | Should -Match 'filesystem_scm.jpi' $stdout | Should -Match 'filesystem_scm.jpi.pinned' $stdout | Should -Match 'docker-plugin.jpi' $stdout | Should -Match 'docker-plugin.jpi.pinned' } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli from a plugins file" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli $exitCode | Should -Be 0 } It 'builds grandchild image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-pluginsfile $PSScriptRoot/plugins-cli/pluginsfile $exitCode | Should -Be 0 $stdout | Should -Not -Match "Skipping already installed dependency" } It 'has correct plugins' { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli gci `$env:JENKINS_HOME/plugins | Select-Object -Property Name" $exitCode | Should -Be 0 $stdout | Should -Match 'junit.jpi' $stdout | Should -Match 'junit.jpi.pinned' $stdout | Should -Match 'ant.jpi' $stdout | Should -Match 'ant.jpi.pinned' $stdout | Should -Match 'credentials.jpi' $stdout | Should -Match 'credentials.jpi.pinned' $stdout | Should -Match 'mesos.jpi' $stdout | Should -Match 'mesos.jpi.pinned' # optional dependencies $stdout | Should -Not -Match 'metrics.jpi' $stdout | Should -Not -Match 'metrics.jpi.pinned' # plugins bundled but under detached-plugins, so need to be installed $stdout | Should -Match 'mailer.jpi' $stdout | Should -Match 'mailer.jpi.pinned' $stdout | Should -Match 'git.jpi' $stdout | Should -Match 'git.jpi.pinned' $stdout | Should -Match 'filesystem_scm.jpi' $stdout | Should -Match 'filesystem_scm.jpi.pinned' } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli even when already exist" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli $exitCode | Should -Be 0 } It 'builds grandchild image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-update $PSScriptRoot/plugins-cli/update --no-cache $exitCode | Should -Be 0 } It 'has the correct version of junit' { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli-update Import-Module -Force -DisableNameChecking C:/ProgramData/Jenkins/jenkins-support.psm1 ; Expand-Zip `$env:JENKINS_HOME/plugins/junit.jpi 'META-INF/MANIFEST.MF'" $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.28' } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] plugins are getting upgraded but not downgraded" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { BeforeAll { $dockerVolume = (New-Guid).Guid docker volume rm -f $dockerVolume } It 'builds child image' { # Initial execution $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli $exitCode | Should -Be 0 } It 'has correct version of junit and ant plugins' { # Image contains junit 1.6 and ant-plugin 1.3 $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v ${dockerVolume}:C:\ProgramData\Jenkins\JenkinsHome --rm $global:SUT_IMAGE-plugins-cli exit 0" $exitCode | Should -Be 0 $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE "junit.jpi" $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.6' $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE "ant.jpi" $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.3' } It 'upgrades plugins' { # Upgrade to new image with different plugins $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-upgrade-plugins $PSScriptRoot/upgrade-plugins $exitCode | Should -Be 0 # Images contains junit 1.28 and ant-plugin 1.2 $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v ${dockerVolume}:C:\ProgramData\Jenkins\JenkinsHome --rm $global:SUT_IMAGE-upgrade-plugins exit 0" $exitCode | Should -Be 0 $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $dockerVolume $exitCode | Should -Be 0 # Should be updated $stdout | Should -Match 'Plugin-Version: 1.28' $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $dockerVolume $exitCode | Should -Be 0 # 1.2 is older than the existing 1.3, so keep 1.3 $stdout | Should -Match 'Plugin-Version: 1.3' } AfterAll { docker volume rm -f $dockerVolume } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] do not upgrade if plugin has been manually updated" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { BeforeAll { $dockerVolume = (New-Guid).Guid docker volume rm -f $dockerVolume } It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli $exitCode | Should -Be 0 } It 'updates plugin manually and then via plugin-cli' { # Image contains junit 1.8 and ant-plugin 1.3 $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v ${dockerVolume}:C:\ProgramData\Jenkins\JenkinsHome --rm $global:SUT_IMAGE-plugins-cli curl.exe --connect-timeout 20 --retry 5 --retry-delay 0 --retry-max-time 60 -s -f -L https://updates.jenkins.io/download/plugins/junit/1.8/junit.hpi -o C:/ProgramData/Jenkins/JenkinsHome/plugins/junit.jpi" $exitCode | Should -Be 0 $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.8' $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.3' $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-upgrade-plugins $PSScriptRoot/upgrade-plugins $exitCode | Should -Be 0 # Images contains junit 1.28 and ant-plugin 1.2 $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v ${dockerVolume}:C:\ProgramData\Jenkins\JenkinsHome --rm $global:SUT_IMAGE-upgrade-plugins exit 0" $exitCode | Should -Be 0 # junit shouldn't be upgraded $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.8' $stdout | Should -Not -Match 'Plugin-Version: 1.28' # ant shouldn't be downgraded $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.3' $stdout | Should -Not -Match 'Plugin-Version: 1.2' } AfterAll { docker volume rm -f $dockerVolume } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] upgrade plugin even if it has been manually updated when PLUGINS_FORCE_UPGRADE=true" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { BeforeAll { $dockerVolume = (New-Guid).Guid docker volume rm -f $dockerVolume } It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli $exitCode | Should -Be 0 } It 'upgrades plugins' { # Image contains junit 1.6 and ant-plugin 1.3 $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v ${dockerVolume}:C:\ProgramData\Jenkins\JenkinsHome --rm $global:SUT_IMAGE-plugins-cli curl.exe --connect-timeout 20 --retry 5 --retry-delay 0 --retry-max-time 60 -s -f -L https://updates.jenkins.io/download/plugins/junit/1.8/junit.hpi -o C:/ProgramData/Jenkins/JenkinsHome/plugins/junit.jpi" $exitCode | Should -Be 0 $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.8' $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-upgrade-plugins $PSScriptRoot/upgrade-plugins $exitCode | Should -Be 0 # Images contains junit 1.28 and ant-plugin 1.2 $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -e PLUGINS_FORCE_UPGRADE=true -v ${dockerVolume}:C:\ProgramData\Jenkins\JenkinsHome --rm $global:SUT_IMAGE-upgrade-plugins exit 0" true $exitCode | Should -Be 0 # junit should be upgraded $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Not -Match 'Plugin-Version: 1.8' $stdout | Should -Match 'Plugin-Version: 1.28' # ant shouldn't be downgraded $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $dockerVolume $exitCode | Should -Be 0 $stdout | Should -Match 'Plugin-Version: 1.3' $stdout | Should -Not -Match 'Plugin-Version: 1.2' } AfterAll { docker volume rm -f $dockerVolume } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli and no war" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-no-war $PSScriptRoot/plugins-cli/no-war $exitCode | Should -Be 0 } } # Only test on Java 21, one JDK is enough to test all versions Describe "[plugins-cli > $global:TEST_TAG] Use a custom jenkins.war" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'builds child image' { $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-custom-war $PSScriptRoot/plugins-cli/custom-war --no-cache $exitCode | Should -Be 0 } } ================================================ FILE: tests/plugins-cli.bats ================================================ #!/usr/bin/env bats # bats file_tags=test-suite:plugin-cli load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load test_helpers SUT_IMAGE=$(get_sut_image) SUT_DESCRIPTION="${IMAGE}-plugins-cli" teardown() { clean_work_directory "${BATS_TEST_DIRNAME}" "${SUT_IMAGE}" } @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli" { local custom_sut_image custom_sut_image="$(get_test_image)" run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli" assert_success refute_line --partial 'Skipping already installed dependency' run docker run --rm "${custom_sut_image}" ls --color=never -1 /var/jenkins_home/plugins assert_success assert_line 'junit.jpi' assert_line 'junit.jpi.pinned' assert_line 'ant.jpi' assert_line 'ant.jpi.pinned' assert_line 'credentials.jpi' assert_line 'credentials.jpi.pinned' assert_line 'mesos.jpi' assert_line 'mesos.jpi.pinned' # optional dependencies refute_line 'metrics.jpi' refute_line 'metrics.jpi.pinned' # plugins bundled but under detached-plugins, so need to be installed assert_line 'mailer.jpi' assert_line 'mailer.jpi.pinned' assert_line 'git.jpi' assert_line 'git.jpi.pinned' assert_line 'filesystem_scm.jpi' assert_line 'filesystem_scm.jpi.pinned' assert_line 'docker-plugin.jpi' assert_line 'docker-plugin.jpi.pinned' } @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli with non-default REF" { local custom_sut_image custom_ref custom_sut_image="$(get_test_image)" custom_ref=/var/lib/jenkins/ref # Build a custom image to validate the build time behavior run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/ref" --build-arg REF="${custom_ref}" assert_success refute_line --partial 'Skipping already installed dependency' volume_name="$(docker volume create)" # Start an image with the default entrypoint to test the runtime behavior run docker run --volume "${volume_name}:/var/jenkins_home" --rm "${custom_sut_image}" true assert_success # Check the content of the resulting data volume (expecting installed plugins as present and pinned) run bash -c "docker run --rm --volume ${volume_name}:/var/jenkins_home ${custom_sut_image} ls --color=never -1 /var/jenkins_home/plugins \ | tr -d '\r' `# replace DOS line endings \r\n`" assert_success assert_line 'junit.jpi' assert_line 'junit.jpi.pinned' assert_line 'ant.jpi' assert_line 'ant.jpi.pinned' } @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli from a plugins file" { local custom_sut_image custom_sut_image="$(get_test_image)" # Then proceed with child run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/pluginsfile" assert_success refute_line --partial 'Skipping already installed dependency' # replace DOS line endings \r\n run bash -c "docker run --rm ${custom_sut_image} ls --color=never -1 /var/jenkins_home/plugins | tr -d '\r'" assert_success assert_line 'junit.jpi' assert_line 'junit.jpi.pinned' assert_line 'ant.jpi' assert_line 'ant.jpi.pinned' assert_line 'credentials.jpi' assert_line 'credentials.jpi.pinned' assert_line 'mesos.jpi' assert_line 'mesos.jpi.pinned' # optional dependencies refute_line 'metrics.jpi' refute_line 'metrics.jpi.pinned' # plugins bundled but under detached-plugins, so need to be installed assert_line 'mailer.jpi' assert_line 'mailer.jpi.pinned' assert_line 'git.jpi' assert_line 'git.jpi.pinned' assert_line 'filesystem_scm.jpi' assert_line 'filesystem_scm.jpi.pinned' } @test "[${SUT_DESCRIPTION}] plugins are getting upgraded but not downgraded" { local custom_sut_image_first custom_sut_image_second custom_sut_image_first="$(get_test_image)" custom_sut_image_second="${custom_sut_image_first}-2" # Build first image with junit 1.6 and ant-plugin 1.3 run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_first}" "${BATS_TEST_DIRNAME}/plugins-cli" assert_success local volume_name volume_name="$(docker volume create)" # Generates a jenkins home (in the volume) with the plugins junit 1.6 and ant-plugin 1.3 from first image's reference run docker run --volume "$volume_name:/var/jenkins_home" --rm "${custom_sut_image_first}" true assert_success run unzip_manifest junit.jpi "$volume_name" assert_line 'Plugin-Version: 1.6' run unzip_manifest ant.jpi "$volume_name" assert_line 'Plugin-Version: 1.3' # Build second image with junit 1.28 and ant 1.2 run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_second}" "${BATS_TEST_DIRNAME}/upgrade-plugins" assert_success # Execute the second image with the existing jenkins volume: junit plugin should be updated, and ant should NOT be downgraded run docker run --volume "$volume_name:/var/jenkins_home" --rm "${custom_sut_image_second}" true assert_success run unzip_manifest junit.jpi "$volume_name" assert_success # Should be updated assert_line 'Plugin-Version: 1.28' run unzip_manifest ant.jpi "$volume_name" # 1.2 is older than the existing 1.3, so keep 1.3 assert_line 'Plugin-Version: 1.3' } @test "[${SUT_DESCRIPTION}] do not upgrade if plugin has been manually updated" { local custom_sut_image_first custom_sut_image_second custom_sut_image_first="$(get_test_image)" custom_sut_image_second="${custom_sut_image_first}-2" ## Generates an image with the plugin junit 1.6 run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_first}" "${BATS_TEST_DIRNAME}/plugins-cli" assert_success ## Image contains junit 1.6, which is manually upgraded to 1.8 local volume_name volume_name="$(docker volume create)" run docker run --volume "${volume_name}:/var/jenkins_home" --rm "${custom_sut_image_first}" \ curl --connect-timeout 20 --retry 5 --retry-delay 0 --retry-max-time 60 --silent \ --fail --location https://updates.jenkins.io/download/plugins/junit/1.8/junit.hpi \ --output /var/jenkins_home/plugins/junit.jpi assert_success run unzip_manifest junit.jpi "$volume_name" assert_line 'Plugin-Version: 1.8' ## Generates an image with the plugin junit 1.28 (upgraded) run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_second}" "${BATS_TEST_DIRNAME}/upgrade-plugins" assert_success # The image with junit 1.28 should not upgrade the version 1.8 in the volume (jenkins_home) run docker run --volume "${volume_name}:/var/jenkins_home" --rm ${custom_sut_image_second} true assert_success # junit shouldn't be upgraded run unzip_manifest junit.jpi "$volume_name" assert_success assert_line 'Plugin-Version: 1.8' refute_line 'Plugin-Version: 1.28' } @test "[${SUT_DESCRIPTION}] upgrade plugin even if it has been manually updated when PLUGINS_FORCE_UPGRADE=true" { local custom_sut_image_first custom_sut_image_second custom_sut_image_first="$(get_test_image)" custom_sut_image_second="${custom_sut_image_first}-2" ## Generates an image with the plugin junit 1.6 run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_first}" "${BATS_TEST_DIRNAME}/plugins-cli" assert_success ## Image contains junit 1.6, which is manually upgraded to 1.8 local volume_name volume_name="$(docker volume create)" run docker run --volume "${volume_name}:/var/jenkins_home" --rm "${custom_sut_image_first}" \ curl --connect-timeout 20 --retry 5 --retry-delay 0 --retry-max-time 60 --silent \ --fail --location https://updates.jenkins.io/download/plugins/junit/1.8/junit.hpi \ --output /var/jenkins_home/plugins/junit.jpi assert_success run unzip_manifest junit.jpi "$volume_name" assert_line 'Plugin-Version: 1.8' ## Generates an image with the plugin junit 1.28 (upgraded) run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_second}" "${BATS_TEST_DIRNAME}/upgrade-plugins" assert_success # The image with junit 1.28 should force-upgrade junit in the volume (jenkins_home) run docker run --volume "${volume_name}:/var/jenkins_home" --env PLUGINS_FORCE_UPGRADE=true --rm ${custom_sut_image_second} true assert_success # junit shouldn't be upgraded run unzip_manifest junit.jpi "$volume_name" assert_success refute_line 'Plugin-Version: 1.8' assert_line 'Plugin-Version: 1.28' } @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli and no war" { local custom_sut_image custom_sut_image="$(get_test_image)" run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/no-war" assert_success } @test "[${SUT_DESCRIPTION}] Use a custom jenkins.war" { local custom_sut_image custom_sut_image="$(get_test_image)" # Build the image using the right Dockerfile setting a new war with JENKINS_WAR env and with a weird plugin inside run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/custom-war" assert_success } @test "[${SUT_DESCRIPTION}] JAVA_OPTS environment variable is used with jenkins-plugin-cli" { local custom_sut_image custom_sut_image="$(get_test_image)" run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/java-opts" assert_success # Assert JAVA_OPTS has been used and 'java.opts.test' has been set to JVM assert_line --regexp 'java.opts.test.*=.*true' } ================================================ FILE: tests/runtime.Tests.ps1 ================================================ Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1 Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 $global:SUT_IMAGE=Get-SutImage $global:SUT_CONTAINER=Get-SutImage $global:TEST_TAG=$global:SUT_IMAGE.Replace('pester-jenkins-', '') Describe "[runtime > $global:TEST_TAG] build image" { BeforeEach { Push-Location -StackName 'jenkins' -Path "$PSScriptRoot/.." } It 'builds image' { $exitCode, $stdout, $stderr = Build-Docker $global:SUT_IMAGE $exitCode | Should -Be 0 } AfterEach { Pop-Location -StackName 'jenkins' } } Describe "[runtime > $global:TEST_TAG] cleanup container" { It 'cleanup' { Cleanup $global:SUT_CONTAINER | Out-Null } } # Only test on Java 21, one JDK is enough to test all versions Describe "[runtime > $global:TEST_TAG] test multiple JENKINS_OPTS" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It '"--help --version" should return the version, not the help' { # need the last line of output $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm -e JENKINS_OPTS=`"--help --version`" --name $global:SUT_CONTAINER -P $global:SUT_IMAGE" $exitCode | Should -Be 0 $stdout -split '`n' | %{$_.Trim()} | Select-Object -Last 1 | Should -Be $env:JENKINS_VERSION } } # Only test on Java 21, one JDK is enough to test all versions Describe "[runtime > $global:TEST_TAG] test jenkins arguments" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { It 'running --help --version should return the version, not the help' { # need the last line of output $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm --name $global:SUT_CONTAINER -P $global:SUT_IMAGE --help --version" $exitCode | Should -Be 0 $stdout -split '`n' | %{$_.Trim()} | Select-Object -Last 1 | Should -Be $env:JENKINS_VERSION } It 'version in docker metadata' { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{index .Config.Labels \`"org.opencontainers.image.version\`"}}`" $global:SUT_IMAGE" $exitCode | Should -Be 0 $stdout.Trim() | Should -Match $env:JENKINS_VERSION } It 'commit SHA in docker metadata' { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{index .Config.Labels \`"org.opencontainers.image.revision\`"}}`" $global:SUT_IMAGE" $exitCode | Should -Be 0 $stdout.Trim() | Should -Match $env:COMMIT_SHA } } # Only test on Java 21, one JDK is enough to test all versions Describe "[runtime > $global:TEST_TAG] passing JVM parameters" -Skip:(-not $global:TEST_TAG.Contains('jdk21-')) { BeforeAll { $tzSetting = '-Duser.timezone=Europe/Madrid' $tzRegex = [regex]::Escape("Europe/Madrid") $cspSetting = @' -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\" '@ $cspRegex = [regex]::Escape("default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';") function Start-With-Opts() { Param ( [string] $JAVA_OPTS, [string] $JENKINS_JAVA_OPTS ) $cmd = "docker --% run -d --name $global:SUT_CONTAINER -P" if ($JAVA_OPTS.length -gt 0) { $cmd += " -e JAVA_OPTS=`"$JAVA_OPTS`"" } if ($JENKINS_JAVA_OPTS.length -gt 0) { $cmd += " -e JENKINS_JAVA_OPTS=`"$JENKINS_JAVA_OPTS`"" } $cmd += " $global:SUT_IMAGE" Invoke-Expression $cmd $lastExitCode | Should -Be 0 # give time to eventually fail to initialize Start-Sleep -Seconds 5 Retry-Command -RetryCount 3 -Delay 1 -ScriptBlock { docker inspect -f "{{.State.Running}}" $global:SUT_CONTAINER ; if($lastExitCode -ne 0) { throw('Docker inspect failed') } } -Verbose | Should -BeTrue # it takes a while for jenkins to be up enough Retry-Command -RetryCount 30 -Delay 5 -ScriptBlock { Test-Url $global:SUT_CONTAINER "/api/json" } -Verbose | Should -BeTrue } function Get-Csp-Value() { return (Run-In-Script-Console $global:SUT_CONTAINER "System.getProperty('hudson.model.DirectoryBrowserSupport.CSP')") } function Get-Timezone-Value() { return (Run-In-Script-Console $global:SUT_CONTAINER "System.getProperty('user.timezone')") } } It 'passes JAVA_OPTS' { Start-With-Opts -JAVA_OPTS "$tzSetting $cspSetting" Get-Csp-Value | Should -Match $cspRegex Get-Timezone-Value | Should -Match $tzRegex } It 'passes JENKINS_JAVA_OPTS' { Start-With-Opts -JENKINS_JAVA_OPTS "$tzSetting $cspSetting" Get-Csp-Value | Should -Match $cspRegex Get-Timezone-Value | Should -Match $tzRegex } It 'JENKINS_JAVA_OPTS overrides JAVA_OPTS' { Start-With-Opts -JAVA_OPTS "$tzSetting -Dhudson.model.DirectoryBrowserSupport.CSP=\`"default-src 'self';\`"" -JENKINS_JAVA_OPTS "$cspSetting" Get-Csp-Value | Should -Match $cspRegex Get-Timezone-Value | Should -Match $tzRegex } AfterEach { Cleanup $global:SUT_CONTAINER | Out-Null } } ================================================ FILE: tests/runtime.bats ================================================ #!/usr/bin/env bats # bats file_tags=test-suite:runtime load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' load test_helpers IMAGE=${IMAGE:-debian_jdk17} SUT_IMAGE=$(get_sut_image) SUT_DESCRIPTION="${IMAGE}-runtime" teardown() { cleanup "$(get_sut_container_name)" } @test "[${SUT_DESCRIPTION}] test version in docker metadata" { local version version=$(get_jenkins_version) assert "${version}" docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.version"}}' $SUT_IMAGE } @test "[${SUT_DESCRIPTION}] test commit SHA in docker metadata is not empty" { run docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision"}}' $SUT_IMAGE refute_output "" } @test "[${SUT_DESCRIPTION}] test commit SHA in docker metadata" { local revision revision=$(get_commit_sha) assert "${revision}" docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision"}}' $SUT_IMAGE } @test "[${SUT_DESCRIPTION}] test multiple JENKINS_OPTS" { local container_name version # running --help --version should return the version, not the help version=$(get_jenkins_version) container_name="$(get_sut_container_name)" cleanup "${container_name}" # need the last line of output assert "${version}" docker run --rm --env JENKINS_OPTS="--help --version" --name "${container_name}" -P $SUT_IMAGE | tail -n 1 } # bats test_tags=test-type:golden-file @test "[${SUT_DESCRIPTION}] ensure expected environment variables are set" { local container_name container_name="$(get_sut_container_name)" cleanup "${container_name}" # Excluding HOSTNAME as its value is variable, and 'container=oci' existing only in RHEL images assert_matches_golden expected_env_vars_except_hostname docker run --rm --name "${container_name}" "${SUT_IMAGE}" bash -c "env | sort | grep -v -e HOSTNAME -e container=oci" } @test "[${SUT_DESCRIPTION}] test jenkins arguments" { local container_name version # running --help --version should return the version, not the help version=$(get_jenkins_version) container_name="$(get_sut_container_name)" cleanup "${container_name}" # need the last line of output assert "${version}" docker run --rm --name "${container_name}" -P $SUT_IMAGE --help --version | tail -n 1 } @test "[${SUT_DESCRIPTION}] timezones are handled correctly" { local timezone1 timezone2 container_name container_name="$(get_sut_container_name)" cleanup "${container_name}" run docker run --rm --name "${container_name}" $SUT_IMAGE bash -c "date +'%Z %z'" timezone1="${output}" assert_equal "${timezone1}" "UTC +0000" run docker run --rm --name "${container_name}" --env "TZ=Europe/Luxembourg" $SUT_IMAGE bash -c "date +'%Z %z'" timezone1="${output}" run docker run --rm --name "${container_name}" --env "TZ=Australia/Sydney" $SUT_IMAGE bash -c "date +'%Z %z'" timezone2="${output}" refute [ "${timezone1}" = "${timezone2}" ] } @test "[${SUT_DESCRIPTION}] has utf-8 locale" { run docker run --rm "${SUT_IMAGE}" locale charmap assert_equal "${output}" "UTF-8" } # parameters are passed as docker run parameters start-jenkins-with-jvm-opts() { local container_name container_name="$(get_sut_container_name)" cleanup "${container_name}" run docker run --detach --name "${container_name}" --publish-all "$@" $SUT_IMAGE assert_success # Container is running sleep 1 # give time to eventually fail to initialize retry 3 1 assert "true" docker inspect -f '{{.State.Running}}' "${container_name}" # Jenkins is initialized retry 30 5 test_url /api/json } get-csp-value() { runInScriptConsole "System.getProperty('hudson.model.DirectoryBrowserSupport.CSP')" } get-timezone-value() { runInScriptConsole "System.getProperty('user.timezone')" } runInScriptConsole() { SERVER="$(get_jenkins_url)" COOKIEJAR="$(mktemp)" PASSWORD="$(get_jenkins_password)" CRUMB=$(curl -u "admin:$PASSWORD" --cookie-jar "$COOKIEJAR" "$SERVER/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)") bash -c "curl -fssL -X POST -u \"admin:$PASSWORD\" --cookie \"$COOKIEJAR\" -H \"$CRUMB\" \"$SERVER\"/scriptText -d script=\"$1\" | sed -e 's/Result: //'" } # bats test_tags=use:start-jenkins-with-jvm-opts @test "[${SUT_DESCRIPTION}] passes JAVA_OPTS as JVM options" { start-jenkins-with-jvm-opts --env JAVA_OPTS="-Duser.timezone=Europe/Madrid -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\"" # JAVA_OPTS are used assert "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" get-csp-value assert 'Europe/Madrid' get-timezone-value } # bats test_tags=use:start-jenkins-with-jvm-opts @test "[${SUT_DESCRIPTION}] passes JENKINS_JAVA_OPTS as JVM options" { start-jenkins-with-jvm-opts --env JENKINS_JAVA_OPTS="-Duser.timezone=Europe/Madrid -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\"" # JENKINS_JAVA_OPTS are used assert "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" get-csp-value assert 'Europe/Madrid' get-timezone-value } # bats test_tags=use:start-jenkins-with-jvm-opts @test "[${SUT_DESCRIPTION}] JENKINS_JAVA_OPTS overrides JAVA_OPTS" { start-jenkins-with-jvm-opts \ --env JAVA_OPTS="-Duser.timezone=Europe/Madrid -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'\"" \ --env JENKINS_JAVA_OPTS="-Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\"" # JAVA_OPTS and JENKINS_JAVA_OPTS are used assert "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" get-csp-value assert 'Europe/Madrid' get-timezone-value } @test "[${SUT_DESCRIPTION}] ensure that 'ps' command is available" { command -v ps # Check for binary presence in the current PATH } ================================================ FILE: tests/test_helpers.bash ================================================ #!/bin/bash set -euo pipefail # Assert that $1 is the outputof a command $2 function assert { local expected_output=$1 shift local actual_output actual_output=$("$@") actual_output="${actual_output//[$'\t\r\n']}" # remove newlines if ! [ "$actual_output" = "$expected_output" ]; then echo "expected: \"$expected_output\"" echo "actual: \"$actual_output\"" false fi } # Assert that golden file $1 matches the output of a command $2 assert_matches_golden() { local golden="$1" shift local golden_path="tests/golden/${golden}.txt" if [[ ! -f "${golden_path}" ]]; then echo "Golden file '${golden_path}' does not exist" return 1 fi # Run the command passed as arguments and capture its output local output output="$(mktemp)" "$@" > "${output}" # Compare with golden file diff -u "${golden_path}" <(cat "${output}") } # Retry a command $1 times until it succeeds. Wait $2 seconds between retries. function retry { local attempts=$1 shift local delay=$1 shift local i for ((i=0; i < attempts; i++)); do run "$@" # shellcheck disable=SC2154 if [ "$status" -eq 0 ]; then return 0 fi sleep "${delay}" done # shellcheck disable=SC2154 echo "Command \"$*\" failed $attempts times. Status: $status. Output: $output" >&2 false } function get_sut_image { test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" ## Retrieve the SUT image name from buildx # Option --print for 'docker buildx bake' prints the JSON configuration on the stdout # Option --silent for 'make' suppresses the echoing of command so the output is valid JSON # The image name is the 1st of the "tags" array, on the first "image" found make --silent show | jq -r '.target."'"${IMAGE}"'".tags[0]' } function get_jenkins_version() { test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" make --silent show | jq -r '.target."'"${IMAGE}"'".args.JENKINS_VERSION' } function get_commit_sha() { test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" make --silent show | jq -r '.target."'"${IMAGE}"'".args.COMMIT_SHA' } function get_test_image { test -n "${BATS_TEST_NUMBER:?"[get_test_image] Please set the variable BATS_TEST_NUMBER."}" test -n "${SUT_DESCRIPTION:?"[get_test_image] Please set the variable SUT_DESCRIPTION."}" echo "${SUT_DESCRIPTION}-${BATS_TEST_NUMBER}" } function get_sut_container_name { echo "$(get_test_image)-container" } function docker_build_child { local parent=$1; shift local tag=$1; shift local dir=$1; shift local build_opts=("$@") local tmp tmp=$(mktemp "$dir/Dockerfile.XXXXXX") sed -e "s#FROM bats-jenkins.*#FROM ${parent}#g" "$dir/Dockerfile" > "$tmp" docker build --tag "$tag" --no-cache "${build_opts[@]}" --file "${tmp}" "${dir}" 2>&1 rm "$tmp" } function get_jenkins_url { docker_host="${DOCKER_HOST:-}" if [ -z "${docker_host}" ]; then DOCKER_IP=localhost else # shellcheck disable=SC2001 DOCKER_IP=$(echo "${docker_host}" | sed -e 's|tcp://\(.*\):[0-9]*|\1|') fi echo "http://$DOCKER_IP:$(docker port "$(get_sut_container_name)" 8080 | cut -d: -f2)" } function get_jenkins_password { docker exec "$(get_sut_container_name)" cat /var/jenkins_home/secrets/initialAdminPassword } function get_targets_from_jenkinsfile { sed -n '/def images = \[/,/]/p' Jenkinsfile `# retrieve images array from Jenkinsfile` \ | grep "'" `# keep only its items` \ | tr -d "', " `# cleanup output` \ | sort `# ensure constant output sort` } function get_default_docker_bake_linux_targets { make --silent show-linux | jq -r '.target | keys[]' | sort } function test_url { run curl --user "admin:$(get_jenkins_password)" --output /dev/null --silent --head --fail --connect-timeout 30 --max-time 60 "$(get_jenkins_url)$1" if [ "$status" -eq 0 ]; then true else echo "URL $(get_jenkins_url)$1 failed" >&2 echo "output: $output" >&2 false fi } function cleanup { docker kill "$1" &>/dev/null ||: docker rm -fv "$1" &>/dev/null ||: } function unzip_manifest { local plugin=$1 local volume_name=$2 export SUT_IMAGE docker run --rm --volume "${volume_name}:/var/jenkins_home" --entrypoint unzip "${SUT_IMAGE}" \ -p "/var/jenkins_home/plugins/${plugin}" META-INF/MANIFEST.MF | tr -d '\r' } function clean_work_directory { local workdir=$1 local sut_image=$2 rm -rf "${workdir}/upgrade-plugins/work-${sut_image}" } ================================================ FILE: tests/test_helpers.psm1 ================================================ Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1 function Test-CommandExists($command) { $oldPreference = $ErrorActionPreference $ErrorActionPreference = 'stop' $res = $false try { if(Get-Command $command) { $res = $true } } catch { $res = $false } finally { $ErrorActionPreference=$oldPreference } return $res } # check dependencies if(-Not (Test-CommandExists docker)) { Write-Error "docker is not available" } function Retry-Command { [CmdletBinding()] param ( [parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [scriptblock] $ScriptBlock, [int] $RetryCount = 3, [int] $Delay = 30, [string] $SuccessMessage = "Command executed successfully!", [string] $FailureMessage = "Failed to execute the command" ) process { $Attempt = 1 $Flag = $true do { try { $PreviousPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' Invoke-Command -NoNewScope -ScriptBlock $ScriptBlock -OutVariable Result 4>&1 $ErrorActionPreference = $PreviousPreference # flow control will execute the next line only if the command in the scriptblock executed without any errors # if an error is thrown, flow control will go to the 'catch' block Write-Verbose "$SuccessMessage `n" $Flag = $false } catch { if ($Attempt -gt $RetryCount) { Write-Verbose "$FailureMessage! Total retry attempts: $RetryCount" Write-Verbose "[Error Message] $($_.exception.message) `n" $Flag = $false } else { Write-Verbose "[$Attempt/$RetryCount] $FailureMessage. Retrying in $Delay seconds..." Start-Sleep -Seconds $Delay $Attempt = $Attempt + 1 } } } While ($Flag) } } function Get-SutImage { # TODO: don't hardcode Windows flavor $DOCKERFILE = 'windows/windowsservercore/hotspot/Dockerfile' $IMAGETAG = Get-EnvOrDefault 'CONTROLLER_TAG' '' $REAL_DOCKERFILE=Resolve-Path -Path "$PSScriptRoot/../${DOCKERFILE}" if(!($DOCKERFILE -match '^(?.+)[\\/](?.+)[\\/](?.+)[\\/]Dockerfile$') -or !(Test-Path $REAL_DOCKERFILE)) { Write-Error "Wrong Dockerfile path format or file does not exist: $DOCKERFILE" exit 1 } return "pester-jenkins-$IMAGETAG" } function Run-Program($cmd, $params, $verbose=$false) { if($verbose) { Write-Host "$cmd $params" } $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.CreateNoWindow = $true $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.WorkingDirectory = (Get-Location) $psi.FileName = $cmd $psi.Arguments = $params $proc = New-Object System.Diagnostics.Process $proc.StartInfo = $psi [void]$proc.Start() $stdout = $proc.StandardOutput.ReadToEnd() $stderr = $proc.StandardError.ReadToEnd() $proc.WaitForExit() if($proc.ExitCode -ne 0) { Write-Host "`n`nstdout:`n$stdout`n`nstderr:`n$stderr`n`n" } return $proc.ExitCode, $stdout, $stderr } function Build-Docker($tag) { $windowsVersion = '2019' if ($tag -match 'ltsc(\d+)$') { $windowsVersion = $matches[1] } $composeParams = '--file=build-windows_windowsservercore-ltsc{0}.yaml build --parallel' -f $windowsVersion $exitCode, $stdout, $stderr = Run-Program 'docker-compose' $composeParams if($exitCode -ne 0) { return $exitCode, $stdout, $stderr } return(Run-Program 'docker' $('tag {0}:{1} {2}' -f $env:DOCKERHUB_ORG_REPO, $env:CONTROLLER_TAG, $tag)) } function Build-DockerChild($tag, $dir) { Get-Content "$dir/Dockerfile-windows" | ForEach-Object{$_ -replace "FROM bats-jenkins","FROM $(Get-SutImage)" } | Out-File -FilePath "$dir/Dockerfile-windows.tmp" -Encoding ASCII return (Run-Program 'docker.exe' "build -t `"$tag`" $args -f `"$dir/Dockerfile-windows.tmp`" `"$dir`"") } function Get-JenkinsUrl($Container) { $DOCKER_IP=(Get-EnvOrDefault 'DOCKER_HOST' 'localhost') | %{$_ -replace 'tcp://(.*):[0-9]*','$1'} | Select-Object -First 1 $port = (docker port "$CONTAINER" 8080 | %{$_ -split ':'})[1] return "http://$($DOCKER_IP):$($port)" } function Get-JenkinsPassword($Container) { $res = docker exec $Container powershell.exe -c 'if(Test-Path "C:\ProgramData\Jenkins\JenkinsHome\secrets\initialAdminPassword") { Get-Content "C:\ProgramData\Jenkins\JenkinsHome\secrets\initialAdminPassword" ; exit 0 } else { exit -1 }' if($lastExitCode -eq 0) { return $res } return $null } function Run-In-Script-Console($Container, $Script) { $jenkinsPassword = Get-JenkinsPassword $Container $jenkinsUrl = Get-JenkinsUrl $Container if($null -ne $jenkinsPassword) { $pair = "admin:$($jenkinsPassword)" $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) $basicAuthValue = "Basic $encodedCreds" $Headers = @{ Authorization = $basicAuthValue } $crumb = (Invoke-RestMethod -Uri $('{0}{1}' -f $jenkinsUrl, '/crumbIssuer/api/json') -Headers $Headers -TimeoutSec 60 -Method Get -SessionVariable session -UseBasicParsing).crumb if ($null -ne $crumb) { $Headers += @{ "Jenkins-Crumb" = $crumb } } $body = @{ script = $Script } $res = Invoke-WebRequest -Uri $('{0}{1}' -f $jenkinsUrl, '/scriptText') -Headers $Headers -TimeoutSec 60 -Method Post -WebSession $session -UseBasicParsing -Body $body if ($res.StatusCode -eq 200) { return $res.Content.replace('Result: ', '') } } return $null } function Test-Url($Container, $Url) { $jenkinsPassword = Get-JenkinsPassword $Container $jenkinsUrl = Get-JenkinsUrl $Container if($null -ne $jenkinsPassword) { $pair = "admin:$($jenkinsPassword)" $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) $basicAuthValue = "Basic $encodedCreds" $Headers = @{ Authorization = $basicAuthValue } $res = Invoke-WebRequest -Uri $('{0}{1}' -f $jenkinsUrl, $Url) -Headers $Headers -TimeoutSec 60 -Method Head -UseBasicParsing if($res.StatusCode -eq 200) { return $true } } Write-Error "URL $(Get-JenkinsUrl $Container)$Url failed" return $false } function Cleanup($image) { docker kill "$image" 2>&1 | Out-Null docker rm -fv "$image" 2>&1 | Out-Null } function Unzip-Manifest($Container, $Plugin, $DockerVolume) { return (Run-Program "docker.exe" "run --rm -v `"${DockerVolume}:C:\ProgramData\Jenkins\JenkinsHome`" $Container mkdir C:/ProgramData/Jenkins/temp | Out-Null ; Copy-Item C:/ProgramData/Jenkins/JenkinsHome/plugins/$Plugin C:/ProgramData/Jenkins/temp/$Plugin.zip ; Expand-Archive C:/ProgramData/Jenkins/temp/$Plugin.zip -Destinationpath C:/ProgramData/Jenkins/temp ; `$content = Get-Content C:/ProgramData/Jenkins/temp/META-INF/MANIFEST.MF ; Remove-Item -Force -Recurse C:/ProgramData/Jenkins/temp ; Write-Host `$content ; exit 0") } ================================================ FILE: tests/update-golden-file.sh ================================================ #!/usr/bin/env bash set -euo pipefail # This script runs a specified command, captures its output, # and compares it against a "golden file" representing the expected output. # If the output differs, the script shows a diff and allows the user to update the golden file. # If the output matches, it reports that the golden file is up-to-date. # # Usage: # ./update-golden-file.sh # # Arguments: # Name of the test, used to determine the golden file path. # The corresponding golden file will be stored as: # golden/.txt # # Command to run, whose stdout will be compared to the golden file. # This can include arguments, e.g.: # ./update-golden-file.sh expected_tags_latest_lts make tags LATEST_LTS=true # # Notes: # - Requires Bash 4+ for `BASH_SOURCE` handling. # - The script is safe to run from any directory; golden files are always relative to the script's own location. if [[ $# -lt 2 ]]; then echo "Usage: $0 " echo "Example:" echo " $0 expected_tags_latest_lts make tags LATEST_LTS=true" exit 1 fi name="$1" shift # Ensure golden folder is always relative to this script script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" golden_file="${name}.txt" golden_path="${script_dir}/golden/${golden_file}" tmp="$(mktemp)" echo echo "Golden file path:" echo " ${golden_path}" echo echo "Running command:" echo " $*" echo "$@" > "${tmp}" action="create" if [[ -f "${golden_path}" ]]; then if diff -u "${golden_path}" "${tmp}" > /dev/null; then echo "Golden file '${golden_file}' is already up-to-date." rm "${tmp}" exit 0 fi echo "Diff against existing golden file '${golden_file}':" diff -u "${golden_path}" "${tmp}" || true action="update" else echo "Golden file '${golden_file}' does not exist yet." fi echo echo "Golden file to ${action}: '${golden_file}'" read -rp "Proceed? [y/N] " answer if [[ "${answer}" =~ ^[Yy]$ ]]; then mkdir -p "$(dirname "${golden_path}")" mv "${tmp}" "${golden_path}" echo "Golden file '${golden_file}' ${action}d." else rm "${tmp}" echo "Aborted. Golden file '${golden_file}' unchanged." fi ================================================ FILE: tests/upgrade-plugins/Dockerfile ================================================ FROM bats-jenkins RUN jenkins-plugin-cli --plugins junit:1.28 ant:1.2 ================================================ FILE: tests/upgrade-plugins/Dockerfile-windows ================================================ FROM bats-jenkins # hadolint shell=powershell RUN & C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --plugins junit:1.28 ant:1.2 ================================================ FILE: tini_pub.gpg ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBFANDtsBEACpb69Ul0Ko7D4XxRIvPGnDMuGdocb8PxR+EGbnHe0uS2tCbsfj TOoWWUrjufrWYxGlKNqOxbEhzFA2wSQ6VD6xROPQT5dAdKaGnSCiaUg7XTzcb9u3 a5Qbx99EDZWaYDNMnLZnIElDX+YmkkEyrrmjiML63m+1P88Bz7ag18hLkqpCiIVM TMRfQluBJVvndX7Stzm35utugN+xeTQryjLx74CO6TUWyC7hAjvQhR5IdAk4H0oT RsOKZ9OQmpO0CJ1XXpKkDdDc60WVrLp1jwq2M7fx/Nz+z13nTHa3fDw8j10+1k0+ c2HafM+GLR5CHlXVMqveWJrimII1ZILxRj/86fFCEC8ZhVW1ym4j+mqEENrzP4I7 L3OnyKLxNKIY9CFDhfzLhNAuNeuIp6KgynzuyxWnJO4q7m/B0zcRIBcjXPrpblIx QlT3qQ/vFdcylDDSdbgtjD+9URG6bFR9PVlRTllBDPGQEK8vjV44pxLCenm/TzdB Y4RlEePf+3y7wVrkjg+l4rIDH57Vl188RODuWVGeLZ3IYWqvRUnYxHmta27UH6zY 7FNN5p7H2VqP6v9GFhiHOCTKdUbQhOoPLmUTyBas0WsC8sXdwpTy3mJthzfUwgVN 2SIXPnndz7RcHwZtW1x9ZtVMDr6ll99kT63+sdZJHmUdlnDr+EGEd/L61QARAQAB tCBUaG9tYXMgT3JvemNvIDx0aG9tYXNAb3JvemNvLmZyPokBHAQQAQIABgUCUA8L RQAKCRBJYaTFxD1O6EKGB/0Y6xVDkvKFdTCTeaGnqzn2IUSC+JBSuVOQD5bxVkUs 9OmtalVNU+vN0nkcL1Apxr4Hz0DBXH1PktIGTNRI3zdkZ37mwJDmUafy7uUpZr4T T7z4ppYn9V6zSCH3tp8GI0NI/1E7JtAjGTpIFwC8Cj1L07vjyGjH8C2kEexImsTB ScPzkGVxl0BqL5FOuF+uSx8IyDo6WnvZMifkDkomEqxtl+gasgjsB/Vohs1VokJ3 JcJZ46KIsbhgD1ma/J0jn1kycsq5k00BpwNTfLporn9sSjbVUf0onldKDmDFY3k4 tSgKC0sGa4YGiogWVT7jZt8A3UWRrIG3T4lpru55vkKyiQI4BBMBAgAiBQJQDQ7b AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCahBWdcAGk5WJgD/90NC3h /s0Ztk2dDQOA4aAFCMBpt5q6nodfVQFUwGZTc4QqRRCgDrYCLHyQ7PX0cly46XRj eShnuk6t//f2z62Vkq9W5fsXpEWImnHX7x/5/gh9AQ8lGK6vCQSnbPBNB8Q6Wjgn O/nuktS2bIlWfEwOLa8V5S576P0F4+nbRnVkRowEkOOYxlqjeJUJW9IdmdLby+QP n7LxVRvKEfh3JoO27yxvTq8K7a4Ptmwx8NO12QNZGo4uzwV1qZkt/w/wAPoBfZw+ GEBIxYXtPLaRG+HMeuK6mXMNQGqh3fyIvGgLaXnGgOM0xmxjSr105cFLlGwrCzar Gsktr8XrvaWnGUwD1eebQswI0xydet6V1SGXXzNU2h8gMZ7HnI/bR2M7GXtaxDrR UEDNtuIYtorJ+SjkK5Ka6XNMk05Q3EdOJeNKLpDPn7mWHTBiccW6WGJgTXoRfbn+ uG8PM3kGCxPcp3KT9dP+SUgHmTh4tcrw+AyxHOn4UNVQvscozUekcxjrmB1JHzJm 5fMpdULjcwU9gkyuN3GgQlcmOfOF/4o7qOTKudrQ5CgzVhMfr/lf1ehfNDPxMN7u km3hLbs9kK/U2G6o2/uc+FQA2vucUxHOtvHYM0fLFSvE++7I2ZjH8qSgDPDfJpN+ tsVdTpxzCxdZbrN1zIsRgjZmoYN8tLvOSOZGxrkBDQRTM1XNAQgArFZl/nlHrvoz chRGDHw6+3z4XczhOP6J949yljkkdKP2waQlG6Bb03J7lZ6G4mMVg4SEquWMJM/+ aOcTuJCpVPJgLUCv5xO01Clp1a9BHYxb9TOKZ1mMG1nF20YUJZHbTeO2cb4sZGxH sfJHoDw4EouXSF5NIfH3Z/50kIYRJkl5WBjj3E06UbgguXw2x5iFoPfpBBlF4ucJ rPLZo56nYf+PPu+fPJ7fxAyR0dyNrD9Z9AdYMrMzlaV/7lMsX/ZVM+Upya2fRAVH D+/DqSPCy/14JDdOEif8VX/Jx1SSE5/P1K7ZohvBgLgLpeLzZHEkpuepQY4TF4I8 baW65afJBwARAQABiQNEBBgBAgAPBQJTM1XNAhsCBQkDwmcAASkJEJqEFZ1wAaTl wF0gBBkBAgAGBQJTM1XNAAoJECqK4Nq/8uWmyHMIAKdDGd3BctlhbCJc6Ji+ZQhT 2Y8rKdRba99PBoikUEhieosCNWw+ynB/HvNgKgy1D5Qr134mHK2zy8Jo/Akc+oIK sFs//L0rbNF1wMDUNhb9y65vByy3KmY7oSHqILrFHFrkgdmiArpT5Oup/8QZ8j9j +xzcoFVhJ0uSeABJ1WvlNCW1Uf0uzENSjsFrfzYKLSD9WYqRSG39oVGQR+8yuuM4 0QZvILCTaFm7ArVoTpjB6re1vOsHI4XjCogh7p7WnA/PuVyGgZibtcnu5ySlsaKV miO+72u7FsaXy2Ktj8GPRRliMBIM0JxddEy+uLYVmEPJYwyh5EuPszDzGl5aOvs2 Gg//YADXocfBGN89Cjl8Nd7t4Zch5vBaFuvWcBhC0vY8cV4cb+KYRWjNx+0Q0221 XCr8lx+PKIhwoDSeIZmauhAI7QdHNGztPzkhNZ9Zyf8epqiTqCYuHpAnp1oLsT0y x7TvNgznZLb9xLiix/NmPgkQLafzZW6or1+rNHnwYKQ+f2uyHCWMSQyAR9h9O8CT +2byMAKlJ0fdsKlI/asvoUG2b5G4vzNsC5yDwEJpU+g6G2nffboxYKfEftB2IeXV 1PgOCarhSW5c6/IxljscjVyrhFdbl5cbmYbQBNCHUkiEglRoQ/N1oS0/ks5WooTj bbQxUJZZqT/bs72YAWb6UEDl3w7wxZbYRGqnDJgxaD8NW9A/+kH8PrA7gOnsIwMw uXUuzMdpv2hGjy9bZxY1VLqZD/nqOBLXSdF7yj9VBE593UN7qyTmT3KmTbLaILum nuyKZ7cyyfONHR8pzGMIMBfVKimL1DOTKkzrpYc7jpRqafQZ4vs6FzCNyhRHMlzL m57WN54qaZHsBN7GWATzusDie4xlqTW5XPSzoWxvV6RggoUQWrASmo2fIqAGu+0Y DzKf1mnjAX7qW5ooyrCm3HSrU+Yxlpw30TrZd07IYFpVc+rn30XdHrTj3OjTihZY xbuztXXRWf+mpGQHhuGFTNq2JQdZW5qaGlHxKWKBniRY8dm5Ag0EUA0O2wEQAMky 8GHJYQGguCL2c72GBtsvGIuMqei0rNGm7mbv2uGma2oQ3aTQA+ahF1RSsTZjyx1n nnWNPRMTY7WPvSotX1zmm4rqrJXcFlv06BDzCrIIUWOYh09AASCcPcr3ZI6Hwy6W olb74SzFKoMG3aJVsvLNC6SVsgk6DidUrxmCzAjJ7CIdV6HfGBwbEm1VvdCMpLPG GPQZgzpfuajmuPPEQVXKIld3xkfv3LgUdSv60lATMlu9YAYkKU/H1zOH0OvDav5O Cr7a/8SQCjJd2/fPu4KGH/2zfehC2ywnlNV8BsLTpCw+nnlEkKQZtOsFCBEHxdqJ 08mEb2whuFWCtF6OfmSYrkv/wAKeBtJTjM7U1Nqq4tkZkzMjslvH9B4bYNtWqNXo EsDdf5aJ4z0hRrW+L8JeIp53OU4y/v4Jf03vrLhaWzjOxPBdMjOY8IOjKwb7vkMC gVlbVcmUx4Q4Y+EEOVHpfjup0sbM4jXg0lkQueLXj4iKHpW/GG9RaYl9OdP7MT49 ZvTcLQQewXy5NioE9qD0tjDZwOq8/fVtRKJX5nlhH7iwNx+JVPjStcQJ3XvnYhrI A14vu4b+AG+X7osfNvdWEjkguSSdW6aZVjSvLxikADSvUZ8ET+YUsTePBOpiQzGe v9INrcciqXuoGH2T0CFMwh3y1dfqneU7qN3pN57BABEBAAGJAh8EGAECAAkFAlAN DtsCGwwACgkQmoQVnXABpOVN4g/9Hr/QQCorIhLGfH/r5ULLaPmaPi/xv4pq5NN1 5cmE/fuLYQyyiwapQPUsyjxa/0DPtc5ID/aVbi/xQdCiUbCywsWb0vgMNNkEfwyA 4XMH77Ac3GAigPa728LZ56GEx9WUJwf5ha3NNKz3j7mAj3FzLyt6OQ74AeFdgNHS evkwzTvoJYSNa05GBl0dtdaKWav9J0qch2+RcNe81x3u0LqLMh+cPE3v/ht5Y4We //WzQkvMgUi60W2qpgMLxj/gnGgxwAuGain4XJiT5FQdPm4Z4Nq4m6ePB0FLdVjk 2sxzDgvFqlXQ5ATHG7xEAxxhfHoVmagEyUUj4npHlkxLrLVMosoimrjoKw5LPZVf VIBpy106le405ct7Zy/SZizBYYBRF5tyZyY3j2f0kSfUg3yGFrAWAW5h1RaOljEr Yro1nS8WBEeRGd6L3Gt/aZbuhvW8rDN4Fga9mP/EgS78W/eSLNLq3Uh4wkpQqWmx 6YA5tb6wQ7SMs56Uv5l9Q758iYv7R8XFDLOR80qTl2nHO0+U2a/cNjopN11zUvW4 4YLOcCayDwmnS/Q6YIHZeXXlY/DCdiLcKQsZsRwr2y0Wa7alm4R5HYO5UaXPc2sG 7Vb/fhxd7K51RPG956G69+SNgExdV8QzfkEcRSqAK0Dj1vlkzxKliCpTeiZWw3Pt GMwAh7e5Ag0EVqgZAQEQALKlBucLl8yGkJ+3pq6BeedXOYSHRJ7bAHFS6bcMwWVT jwMlg2USwpBiyDMS9prJMjSADVnJ4ODcTgWeTrKxCbNb0En/dDalrQYoiS9c6Auf C9JdKdNOorfMIO4kjQ2Pi72Ajxtft3g8epDLIXM9aOIhuD21YduFPzDAJprLWIao JmxNrwjDK97RZpX7ti1ElB5cR9j1nqgQaqOKztAk15/UwrLWBLUqr7iyq93CCjd/ cAVI1HXiBqBi/abIDfR6F/gTPMiLx2H0MBNzhPzoIG4rdOzMgstJTAWetRVPTSPe yvZaKpavEir+lqTLzM0RXdDS9p/aSF4tJlFJmyhUfUB8KxUePg6hYfcE4DN2vnCx M+xXYdVHQ70r3w2gDR/jl6p8wUGIZWwW4lqYElq0+xNVVdnp9H1oMMrJJIHnjRZ6 AegAHBqL6zgWFS2ONlqss8mjjv+AWbEXscdTI0hhgqPmP32HTTNwztc60p7IfMe9 9m+a7XnS7YNxxj1dfnF3MOxa4F5AlDtV3JVVM+hkYgFnUzDcQh8XIDNi+CVMIqgw wcfr3XY+ewwtxtyYX/ukniut6GnxuR9LtHcEE0yCUta2hkhgFJagrw2XQ7IHNDmW w+9F1Qe4x7qvHADXWBGfHjIt33VtZHD/EgHDHPomr+WTTJKB26b+Ehw6as12Dvez ABEBAAGJBD4EGAECAAkFAlaoGQECGwICKQkQmoQVnXABpOXBXSAEGQECAAYFAlao GQEACgkQC1iN/wUnqbe49A/+OidDlCs4Q34Ht11iai0DCPrAv3CGzutyBv5Ml8is myTIFAb6QfKZYQGI4Oj8ByqQqCUbNM1xoMbXneXfz8hD8uj2bFmaQ0e0sPpKuRsa M2sEiSxFJH6FZuvxSMD9a6oCYNC8VZ5ubIXinp14QB/dGy/YyX9rSW8LpPJZF2x0 zJaUM+2Upif2raoeMJVfh7abi4HfxWrd+jz5ilqCzkpEL0rIaBkUQbLcXfun7hT+ sNP63/qgq6jn/xFYCLF5Y1YiWLsND0NsDjD2p3AvgobByc4JwIKVP0qooDcw52yv 51wAX4uQ2sbfK+en55nsscXo9UkCRedIs7O4RnNen3SkD7VLDtBYZanJX+/oJI+z Fkics+ME8mZ1WGMyuZraz1IYqzaTh6K8N4rgavqgXcZErdVDOxE8NiT2+vzBJsAF HhCHoXL9aIwTVLIR1yzZRZz54ZnTJrtASRCrPsK+OnpV7NS1dYWDLbylWSAQ5ESy qiDKmNtKfJt6KzpHO/F/SDrzV9aNZJNwwEZcpO+Bbtecuob1cxZ3HSfqlrqznIue SgznNhyMv5XrANVDCcX26hqWVw7XU8SOwQx4reDPRjzUSYu4c3rjXUgrVxZ/EAge 6fA6g97ufMY4fYRfX4iQK/g88moyWgRwgg+XA/QgvFNo+7LXvB53Skfd29FmCW8n upBT8A//c2GHkCNS0xEj6YOAmleli4zLu012AoC+AYgvVs/7Uucu0/b7u7LU+iTf 3fz94AnABP2w6GkbepE+zXnOPi1zbT8mzKluTjTfYSFEVYYvlMG1h/0tWX12gViE YdSADNERZzDyXuHT+nlU0+b0rSQSULDZ83fPvHCt5I9rVYZWyox0zedz8Hjihxkc Wqt32SDo3hqXojGRsvsNgBSN+GOSE5BdldT4ws9UD2V+UXjr3iVuuvsXgbCfN8Xz 9I7D0sdWH0rdqCejqMcHrg/9Wq2Rrhrn70ApwSSiNYQcOgtTwExRcNlm706QVIm3 OoHIDO2LR2wsT7wYZ9qzjtvf+uN/qrIq+Hb6I/D4jrMcfiTssqkiIr4yRDb5l8zM HeND0080BzrcAv9hucXlcNIXEYFGRwVmjnStklXlYcDen5QwhKyOg2LMTtqAuojY k+coHMHdpAUWkjdCKK9aEp+w7TZW3OX7VOOpF5a7ipLW7AltKwt5y0DL7grZYWdE 1nWb+v396fMSHIqjzM6ZQFTNUuFFH8YbtLUybEHwVI5mHtoeDXFtNhW+rrYBbprQ 8q8SmfWDTC88SgpD7jX91z+J5aCeRmIokkXEOyEoabF8POdadmqc4RIvP/eFsbJT jWeTRQ/MeWRRnCwuj1QF2v19RVi+VhTKUGnZ1XSMRzmovraDKkA= =YltV -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: tools/hadolint ================================================ #!/bin/sh -e exec docker run --rm \ -w "${PWD}" \ -v "${PWD}:${PWD}" \ ghcr.io/hadolint/hadolint:v2.14.0-debian hadolint "$@" ================================================ FILE: tools/shellcheck ================================================ #!/bin/sh -e exec docker run --rm \ -w "${PWD}" \ -v "${PWD}:${PWD}" \ koalaman/shellcheck:v0.11.0 "$@" ================================================ FILE: updatecli/scripts/rhel-latest-tag.sh ================================================ #!/bin/bash # This script fetches the latest tag from the Red Hat Container Catalog API for the images of the current RHEL release line. # It ensures that `jq` and `curl` are installed, fetches the most recent tags, and processes them to find the unique tag associated to `latest`s. # The Swagger API endpoints for the Red Hat Container Catalog API are documented at: # https://catalog.redhat.com/api/containers/v1/ui/#/Repositories/graphql.images.get_images_by_repo # The script uses the following parameters for the API request: # - registry: registry.access.redhat.com # - repository: # - page_size: 100 # - page: 0 # - sort_by: last_update_date[desc] # The curl command fetches the JSON data containing the tags for the images of the RHEL release line passed in parameter, # then parses it using `jq` to find the version associated with the "latest" tag. # It focuses on tags that contain a hyphen, as these represent the long-form tag names. # The script ensures that only one instance of each tag is kept, in case of duplicates. if [[ $# -lt 1 ]]; then echo "Usage: $0 " echo "Example:" echo " $0 ubi9" exit 1 fi release_line="$1" # Correct URL of the Red Hat Container Catalog API for the release line URL="https://catalog.redhat.com/api/containers/v1/repositories/registry/registry.access.redhat.com/repository/${release_line}/images?page_size=100&page=0&sort_by=last_update_date%5Bdesc%5D" # Check if jq and curl are installed # If they are not installed, exit the script with an error message if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then >&2 echo "jq and curl are required but not installed. Exiting with status 1." >&2 exit 1 fi # Fetch release line from registry.access.redhat.com sorted by most recent update date, and keeping only the first page. response=$(curl --silent --fail --location --connect-timeout 10 --retry 3 --retry-delay 2 --max-time 30 --header 'accept: application/json' "$URL") # Check if the response is empty or null if [ -z "$response" ] || [ "$response" == "null" ]; then >&2 echo "Error: Failed to fetch tags from the Red Hat Container Catalog API." exit 1 fi # Parse the JSON response using jq to find the version associated with the "latest" tag # - The response is expected to be a JSON object containing repository data. # - The script uses `jq` to: # 1. Iterate over all repositories in the `data` array. # 2. Select repositories where at least one tag has the name "latest". # 3. From those repositories, select tags that: # - Do not have the name "latest". # - Contain a hyphen in their name (indicating a long-form tag). # 4. Extract the `name` of the matching tags. # 5. Sort the tag names uniquely (`sort -u`). # 6. Take the last tag in the sorted list (`tail -n 1`), which is assumed to be the most recent valid tag. latest_tag=$(echo "$response" | jq -r '.data[].repositories[] | select(.tags[].name == "latest") | .tags[] | select(.name != "latest" and (.name | contains("-"))) | .name' | sort -u | tail -n 1) # Check if the latest_tag is empty if [ -z "$latest_tag" ]; then echo "Error: No valid tags found." exit 1 fi # Output the latest tag version echo "$latest_tag" exit 0 ================================================ FILE: updatecli/updatecli.d/alpine.yaml ================================================ --- name: Bump Alpine version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: latestVersion: kind: githubrelease name: "Get the latest Alpine Linux version" spec: owner: "alpinelinux" repository: "aports" # Its release process follows Alpine's token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" versionfilter: kind: semver pattern: "~3" transformers: - trimprefix: "v" conditions: testDockerfileArg: name: "Does the Dockerfile have an ARG instruction for the Alpine Linux version?" kind: dockerfile disablesourceinput: true spec: file: alpine/hotspot/Dockerfile instruction: keyword: "ARG" matcher: "ALPINE_TAG" testDockerImageExists: name: "Does the Docker Image exist on the Docker Hub?" kind: dockerimage sourceid: latestVersion spec: image: "alpine" # tag come from the source architecture: amd64 targets: updateDockerBake: name: "Update the value of the base image (ARG ALPINE_TAG) in the docker-bake.hcl" kind: hcl spec: file: docker-bake.hcl path: variable.ALPINE_FULL_TAG.default scmid: default updateDockerfile: name: "Update the value of the JDK base image (ARG ALPINE_TAG) in the Dockerfile" kind: dockerfile spec: file: alpine/hotspot/Dockerfile instruction: keyword: "ARG" matcher: "ALPINE_TAG" scmid: default actions: default: kind: github/pullrequest scmid: default title: Bump Alpine Linux Version to {{ source "latestVersion" }} spec: labels: - dependencies - alpine ================================================ FILE: updatecli/updatecli.d/debian.yaml ================================================ --- name: Bump Debian version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: releaseLine: kind: hcl name: "Get the current Debian release line" spec: file: docker-bake.hcl path: variable.DEBIAN_RELEASE_LINE.default transformers: - addsuffix: "-" latestVersion: kind: dockerimage name: "Get the latest Debian version" spec: image: "debian" tagfilter: >- {{ source "releaseLine" }} versionfilter: kind: regex pattern: >- {{ source "releaseLine" }}\d+$ transformers: - trimprefix: >- {{ source "releaseLine" }} conditions: testDockerfileArg: name: "Does the Dockerfile have an ARG instruction for the Debian version?" kind: dockerfile disablesourceinput: true spec: file: debian/Dockerfile instruction: keyword: "ARG" matcher: "DEBIAN_VERSION" testVersionInBakeFile: name: "Does the bake file have variable DEBIAN_VERSION" kind: file disablesourceinput: true spec: file: docker-bake.hcl matchpattern: "(.*DEBIAN_VERSION.*)" checkArchitecturesAvailability: kind: dockerimage name: Check if container image is available for all architectures sourceid: latestVersion spec: image: "debian" architectures: - linux/amd64 - linux/arm64 - linux/s390x - linux/ppc64le targets: updateDockerBake: name: "Update the value of the base image DEBIAN_VERSION in the docker-bake.hcl" kind: hcl sourceid: latestVersion spec: file: docker-bake.hcl path: variable.DEBIAN_VERSION.default scmid: default updateDockerfile: name: "Update the value of the base image (ARG DEBIAN_VERSION) in the Dockerfile" kind: dockerfile sourceid: latestVersion spec: file: debian/Dockerfile instruction: keyword: "ARG" matcher: "DEBIAN_VERSION" scmid: default actions: default: kind: github/pullrequest scmid: default title: Bump Debian version to {{ source "latestVersion" }} spec: labels: - dependencies - debian - debian-slim ================================================ FILE: updatecli/updatecli.d/git-lfs.yaml ================================================ --- name: Bump `git-lfs` version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: lastReleaseVersion: kind: githubrelease name: Get the latest `git-lfs` release version spec: owner: "git-lfs" repository: "git-lfs" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" versionfilter: kind: semver transformers: - trimprefix: v targets: updateVersion: name: Update `git-lfs` version in debian dockerfile sourceid: lastReleaseVersion kind: dockerfile spec: file: debian/Dockerfile instruction: keyword: "ARG" matcher: "GIT_LFS_VERSION" scmid: default actions: default: kind: github/pullrequest title: Bump `git-lfs` version to {{ source "lastReleaseVersion" }} scmid: default spec: labels: - dependencies - git-lfs ================================================ FILE: updatecli/updatecli.d/hadolint.yaml ================================================ --- name: Bump hadolint version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: lastReleaseVersion: kind: githubrelease name: Get the latest hadolint release version spec: owner: "hadolint" repository: "hadolint" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" versionfilter: kind: semver transformers: - trimprefix: v targets: updateVersion: name: "Update the `hadolint` version in the tools/hadolint script" sourceid: lastReleaseVersion kind: file spec: file: "tools/hadolint" matchpattern: "ghcr.io/hadolint/hadolint:v(.*)-debian" content: 'ghcr.io/hadolint/hadolint:v{{ source `lastReleaseVersion` }}-debian' scmid: default actions: default: kind: github/pullrequest title: Bump `hadolint` version to {{ source "lastReleaseVersion" }} scmid: default spec: labels: - dependencies - hadolint ================================================ FILE: updatecli/updatecli.d/jdk17.yaml ================================================ --- name: Bump JDK17 version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: lastVersion: kind: temurin name: Get the latest Adoptium JDK17 version spec: featureversion: 17 transformers: - trimprefix: "jdk-" conditions: checkTemurinAllReleases: name: Check if the "" is available for all platforms kind: temurin sourceid: lastVersion spec: featureversion: 17 platforms: - alpine-linux/x64 - linux/x64 - linux/aarch64 - linux/ppc64le - linux/s390x - windows/x64 targets: ## Global config file setJDK17VersionDockerBake: name: "Bump JDK17 version for Linux images in the docker-bake.hcl file" kind: hcl transformers: - replacer: from: "+" to: "_" spec: file: docker-bake.hcl path: variable.JAVA17_VERSION.default scmid: default ## Dockerfiles setJDK17Version: name: "Bump JDK17 version in Dockerfiles" kind: dockerfile transformers: - replacer: from: "+" to: "_" spec: files: - alpine/hotspot/Dockerfile - debian/Dockerfile - rhel/Dockerfile - windows/windowsservercore/hotspot/Dockerfile instruction: keyword: ARG matcher: JAVA_VERSION scmid: default actions: default: kind: github/pullrequest scmid: default title: Bump JDK17 version to {{ source "lastVersion" }} spec: labels: - dependencies - jdk17 ================================================ FILE: updatecli/updatecli.d/jdk21.yaml ================================================ name: Bump JDK21 version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: lastTemurin21Version: kind: temurin name: Get the latest Adoptium JDK21 version via the API spec: featureversion: 21 transformers: - trimprefix: "jdk-" conditions: checkTemurinAllReleases: name: Check if the "" is available for all platforms kind: temurin sourceid: lastTemurin21Version spec: featureversion: 21 platforms: - alpine-linux/x64 - alpine-linux/aarch64 - linux/x64 - linux/aarch64 - linux/ppc64le - linux/s390x - windows/x64 targets: setJDK21VersionDockerBake: name: "Bump JDK21 version for Linux images in the docker-bake.hcl file" kind: hcl transformers: - replacer: from: "+" to: "_" spec: file: docker-bake.hcl path: variable.JAVA21_VERSION.default scmid: default actions: default: kind: github/pullrequest scmid: default title: Bump JDK21 version to {{ source "lastTemurin21Version" }} spec: labels: - dependencies - jdk21 ================================================ FILE: updatecli/updatecli.d/jdk25.yaml ================================================ name: Bump JDK25 version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: lastTemurin25Version: kind: temurin name: Get the latest Adoptium JDK25 version via the API spec: featureversion: 25 transformers: - trimprefix: "jdk-" conditions: checkTemurinAllReleases: name: Check if the "" is available for all platforms kind: temurin sourceid: lastTemurin25Version spec: featureversion: 25 platforms: - alpine-linux/x64 - alpine-linux/aarch64 - linux/x64 - linux/aarch64 - linux/ppc64le - linux/s390x - windows/x64 targets: setJDK25VersionDockerBake: name: "Bump JDK25 version for Linux images in the docker-bake.hcl file" kind: hcl transformers: - replacer: from: "+" to: "_" spec: file: docker-bake.hcl path: variable.JAVA25_VERSION.default scmid: default actions: default: kind: github/pullrequest scmid: default title: Bump JDK25 version to {{ source "lastTemurin25Version" }} spec: labels: - dependencies - jdk25 ================================================ FILE: updatecli/updatecli.d/jenkins-version-simulated-lts.yaml ================================================ --- name: Bump simulated LTS `JENKINS_VERSION` version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: latestVersion: kind: githubrelease name: Get latest Jenkins Core LTS release version (.1 only) spec: owner: jenkinsci repository: jenkins token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" versionfilter: kind: regex pattern: >- \d+\.\d+\.1$ transformers: - trimprefix: "jenkins-" conditions: isDockerImagePublished: name: Check if the docker image has been published kind: dockerimage sourceid: latestVersion spec: image: jenkins/jenkins targets: updateJenkinsVersionInJenkinsfile: name: Update default value of simulated LTS JENKINS_VERSION in Jenkinsfile kind: file scmid: default sourceid: latestVersion spec: file: Jenkinsfile matchpattern: >- 'JENKINS_VERSION=(.*)' content: >- 'JENKINS_VERSION={{ source "latestVersion" }}' updateJenkinsVersionInTests: name: Update default value of LTS_JENKINS_VERSION in tests kind: file scmid: default sourceid: latestVersion spec: file: tests/bake.bats matchpattern: >- LTS_JENKINS_VERSION=(.*) content: >- LTS_JENKINS_VERSION="{{ source "latestVersion" }}" updateJenkinsVersionInGoldenFiles: kind: file scmid: default sourceid: latestVersion name: Update value of JENKINS_VERSION in LTS golden file spec: file: tests/golden/expected_tags_latest_lts.txt matchpattern: :(\d+\.\d+\.\d+) replacepattern: :{{ source "latestVersion" }} actions: default: kind: github/pullrequest title: Bump simulated LTS `JENKINS_VERSION` to {{ source "latestVersion" }} scmid: default spec: labels: - dependencies - jenkins-version - skip-changelog ================================================ FILE: updatecli/updatecli.d/jenkins-version.yaml ================================================ --- name: Bump default `JENKINS_VERSION` version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: latestVersion: kind: file name: Get latest Jenkins Core release version spec: file: https://updates.jenkins.io/latestCore.txt conditions: isDockerImagePublished: name: Check if the docker image has been published kind: dockerimage sourceid: latestVersion spec: image: jenkins/jenkins targets: updateJenkinsVersionInDockerBake: name: Update default value of JENKINS_VERSION in docker-bake.hcl kind: hcl scmid: default sourceid: latestVersion spec: file: docker-bake.hcl path: variable.JENKINS_VERSION.default updateJenkinsVersionInDockerfiles: name: Update value of JENKINS_VERSION in Dockerfile kind: dockerfile scmid: default sourceid: latestVersion spec: files: - alpine/hotspot/Dockerfile - debian/Dockerfile - rhel/Dockerfile - windows/windowsservercore/hotspot/Dockerfile instruction: keyword: "ARG" matcher: "JENKINS_VERSION" updateJenkinsVersionInGoldenFilesTags: kind: file scmid: default sourceid: latestVersion name: Update value of JENKINS_VERSION in (weekly) golden files for tags spec: files: - tests/golden/expected_tags.txt - tests/golden/expected_tags_latest_weekly.txt matchpattern: :(\d+\.\d+) replacepattern: :{{ source "latestVersion" }} updateJenkinsVersionInGoldenFilesEnvVars: kind: file scmid: default sourceid: latestVersion name: Update value of JENKINS_VERSION in golden file for env vars spec: file: tests/golden/expected_env_vars_except_hostname.txt matchpattern: JENKINS_VERSION=(\d+\.\d+) replacepattern: JENKINS_VERSION={{ source "latestVersion" }} updateJenkinsVersionInMakePs1: name: Update value of $JenkinsVersion in make.ps1 kind: file scmid: default sourceid: latestVersion spec: file: make.ps1 matchpattern: JenkinsVersion = '(\d+\.\d+)' replacepattern: JenkinsVersion = '{{ source "latestVersion" }}' actions: default: kind: github/pullrequest title: Bump default `JENKINS_VERSION` to Weekly {{ source "latestVersion" }} scmid: default spec: labels: - dependencies - jenkins-version - skip-changelog ================================================ FILE: updatecli/updatecli.d/plugin-installation-manager-tool.yaml ================================================ --- name: Bump `plugin-installation-manager-tool` version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: lastReleaseVersion: kind: githubrelease name: Get latest `plugin-installation-manager-tool` release version spec: owner: jenkinsci repository: plugin-installation-manager-tool token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" versionfilter: kind: semver transformers: - trimprefix: v targets: updateDockerBake: name: Bump `plugin-installation-manager-tool` version for Linux images in the docker-bake.hcl file kind: hcl spec: file: docker-bake.hcl path: variable.PLUGIN_CLI_VERSION.default scmid: default updateDockerfiles: name: Bump `plugin-installation-manager-tool` version in Dockerfiles kind: dockerfile spec: files: - alpine/hotspot/Dockerfile - debian/Dockerfile - rhel/Dockerfile - windows/windowsservercore/hotspot/Dockerfile instruction: keyword: ARG matcher: PLUGIN_CLI_VERSION scmid: default actions: default: kind: github/pullrequest title: Bump `plugin-installation-manager-tool` version to {{ source "lastReleaseVersion" }} scmid: default spec: labels: - dependencies - plugin-installation-manager-tool ================================================ FILE: updatecli/updatecli.d/rhel.yaml ================================================ --- name: Bump RHEL version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: releaseLine: kind: hcl name: "Get the current Debian release line" spec: file: docker-bake.hcl path: variable.RHEL_RELEASE_LINE.default latestVersion: name: "Get latest RHEL version" kind: shell spec: command: bash -x updatecli/scripts/rhel-latest-tag.sh "{{ source "releaseLine" }}" conditions: checkDockerImage: kind: dockerimage name: Check if container image of the current RHEL release line is available sourceid: latestVersion spec: architectures: - linux/amd64 - linux/arm64 - linux/ppc64le image: registry.access.redhat.com/{{ source "releaseLine" }} targets: updateDockerfile: name: "Update value of base image (ARG RHEL_TAG) in Dockerfile" kind: dockerfile sourceid: latestVersion spec: file: rhel/Dockerfile instruction: keyword: ARG matcher: RHEL_TAG scmid: default updateDockerBake: name: "Update default value of variable RHEL_TAG in docker-bake.hcl" kind: hcl sourceid: latestVersion spec: file: docker-bake.hcl path: variable.RHEL_TAG.default scmid: default actions: default: kind: github/pullrequest scmid: default title: Bump RHEL version to {{ source "latestVersion" }} spec: labels: - dependencies - rhel ================================================ FILE: updatecli/updatecli.d/shellcheck.yaml ================================================ --- name: Bump shellcheck version scms: default: kind: github spec: user: "{{ .github.user }}" email: "{{ .github.email }}" owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" branch: "{{ .github.branch }}" sources: lastReleaseVersion: kind: githubrelease name: Get the latest shellcheck release version spec: owner: "koalaman" repository: "shellcheck" token: "{{ requiredEnv .github.token }}" username: "{{ .github.username }}" versionfilter: kind: semver transformers: - trimprefix: v targets: updateVersion: name: "Update the `shellcheck` version in the tools/shellcheck script" sourceid: lastReleaseVersion kind: file spec: file: "tools/shellcheck" matchpattern: "koalaman/shellcheck:v(.*) " content: 'koalaman/shellcheck:v{{ source `lastReleaseVersion` }} ' scmid: default actions: default: kind: github/pullrequest title: Bump `shellcheck` version to {{ source "lastReleaseVersion" }} scmid: default spec: labels: - dependencies - shellcheck ================================================ FILE: updatecli/values.github-action.yaml ================================================ github: user: "GitHub Actions" email: "41898282+github-actions[bot]@users.noreply.github.com" username: "github-actions" token: "UPDATECLI_GITHUB_TOKEN" branch: "master" owner: "jenkinsci" repository: "docker" ================================================ FILE: windows/windowsservercore/hotspot/Dockerfile ================================================ # escape=` # hadolint shell=powershell ARG JAVA_VERSION=17.0.18_8 ARG WINDOWS_VERSION=ltsc2022 FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION}" AS jre-and-war # $ProgressPreference: https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251261324 SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] ARG JAVA_VERSION=17.0.18_8 RUN New-Item -ItemType Directory -Path C:\temp | Out-Null ; ` $javaMajorVersion = $env:JAVA_VERSION.substring(0,2) ; ` $msiUrl = 'https://api.adoptium.net/v3/installer/version/jdk-{0}/windows/x64/jdk/hotspot/normal/eclipse?project=jdk' -f $env:JAVA_VERSION.Replace('_', '%2B') ; ` Invoke-WebRequest $msiUrl -OutFile 'C:\temp\jdk.msi' ; ` $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList '/i', 'C:\temp\jdk.msi', '/L*V', 'C:\temp\OpenJDK.log', '/quiet', 'ADDLOCAL=FeatureEnvironment,FeatureJarFileRunWith,FeatureJavaHome', "INSTALLDIR=C:\openjdk-${javaMajorVersion}" -Wait -Passthru ; ` $proc.WaitForExit() ; ` Remove-Item -Path C:\temp -Recurse | Out-Null RUN Write-Host 'javac --version' ; javac --version ; ` Write-Host 'java --version' ; java --version RUN $version = (jlink --version) ; ` $javaMajorVersion = $version.Substring(0,2) ; ` if ($javaMajorVersion -eq '25') { ` $openjdkFolder = 'openjdk-{0}' -f $javaMajorVersion ; ` Copy-Item -Path $openjdkFolder -Destination C:\javaruntime -Recurse ; ` } else { ` $options = '--compress=2' ; ` switch ($version.Substring(0,3)) { ` '17.' { $options = '--compress=2' } ` '21.' { $options = '--compress=zip-6' } ` Default { ` Write-Error 'ERROR: unmanaged jlink version pattern' ; ` exit 1 ; ` } ` } ` & jlink ` --strip-java-debug-attributes ` $options ` --add-modules ALL-MODULE-PATH ` --no-man-pages ` --no-header-files ` --output /javaruntime ; ` } # GnuGPG ARG GNUGPG_VERSION=2.5.16_20251230 RUN New-Item -ItemType Directory -Path C:/temp | Out-Null ; ` Invoke-WebRequest -Uri https://www.gnupg.org/ftp/gcrypt/binary/gnupg-w32-${env:GNUGPG_VERSION}.exe -OutFile C:/temp/gnupg.exe ; ` Start-Process -FilePath C:/temp/gnupg.exe -ArgumentList '/S' -Wait ; ` Remove-Item -Path C:\temp -Recurse | Out-Null # Jenkins version being bundled in this docker image ARG JENKINS_VERSION=2.555 # Can be used to customize where jenkins.war get downloaded from ARG WAR_URL=https://get.jenkins.io/war/${JENKINS_VERSION}/jenkins.war ENV WAR_URL=${WAR_URL} ENV WAR_ASC_URL=${WAR_URL}.asc # Not using ADD as it does not check Last-Modified header # # see https://github.com/docker/docker/issues/8331 RUN New-Item -ItemType Directory -Path C:/war | Out-Null ; ` Write-Host $env:WAR_URL; Invoke-WebRequest -Uri "$env:WAR_URL" -OutFile C:/war/jenkins.war ; ` Write-Host $env:WAR_ASC_URL; Invoke-WebRequest -Uri "$env:WAR_ASC_URL" -OutFile C:/war/jenkins.war.asc COPY jenkins.io-2026.key /war/jenkins-key.pub RUN & 'C:/Program Files/GnuPG/bin/gpg.exe' --version ; ` & 'C:/Program Files/GnuPG/bin/gpg.exe' --import C:/war/jenkins-key.pub ; ` & 'C:/Program Files/GnuPG/bin/gpg.exe' --verify --trust-model direct C:/war/jenkins.war.asc C:/war/jenkins.war FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION}" AS controller ARG JAVA_HOME="C:/openjdk-17" ENV JAVA_HOME=${JAVA_HOME} COPY --from=jre-and-war /javaruntime $JAVA_HOME COPY --from=jre-and-war /war/jenkins.war C:/ProgramData/Jenkins/jenkins.war SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] # Add java in PATH RUN $CurrentPath = (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path ; ` $NewPath = $CurrentPath + $(';{0}/bin' -f $env:JAVA_HOME) ; ` Set-ItemProperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path -Value $NewPath ARG user=jenkins ARG http_port=8080 ARG agent_port=50000 ARG JENKINS_HOME=C:/ProgramData/Jenkins/JenkinsHome ARG COMMIT_SHA ENV JENKINS_HOME=$JENKINS_HOME ENV JENKINS_AGENT_PORT=${agent_port} # Jenkins home directory is a volume, so configuration and build history # can be persisted and survive image upgrades VOLUME $JENKINS_HOME # Jenkins is run with user `jenkins` # If you bind mount a volume from the host or a data container, # ensure you use the same uid # hadolint ignore=DL4006 RUN New-LocalUser -Name $env:user -AccountNeverExpires -Description 'Jenkins User' -NoPassword -UserMayNotChangePassword | Out-Null ; ` Set-Localuser -Name $env:user -PasswordNeverExpires $true | Out-Null ; ` Add-LocalGroupMember -Group "Administrators" -Member "${env:user}" ; ` New-Item -Type Directory -Force -Path "C:/ProgramData/Jenkins" | Out-Null ; ` icacls.exe "C:/ProgramData/Jenkins" /setowner ${env:user} | Out-Null ; ` icacls.exe "C:/ProgramData/Jenkins" /inheritance:r | Out-Null ; ` icacls.exe "C:/ProgramData/Jenkins" /grant:r $('{0}:(CI)(OI)(F)' -f $env:user) /grant 'Administrators:(CI)(OI)(F)' | Out-Null ; ` icacls.exe "$env:JENKINS_HOME" /setowner ${env:user} | Out-Null ; ` icacls.exe "$env:JENKINS_HOME" /grant:r $('{0}:(CI)(OI)(F)' -f $env:user) /grant 'Administrators:(CI)(OI)(F)' | Out-Null USER ${user} # `C:/ProgramData/Jenkins/Reference/` contains all reference configuration we want # to set on a fresh new installation. Use it to bundle additional plugins # or config file with your custom jenkins Docker image. # hadolint ignore=DL4006 RUN New-Item -ItemType Directory -Force -Path C:/ProgramData/Jenkins/Reference/init.groovy.d | Out-Null ENV JENKINS_UC=https://updates.jenkins.io ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals ARG PLUGIN_CLI_VERSION=2.14.0 ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar RUN $sha256sum = [System.Text.Encoding]::UTF8.GetString((Invoke-WebRequest -Uri ($env:PLUGIN_CLI_URL + '.sha256') -UseBasicParsing).Content); ` Invoke-WebRequest -Uri "$env:PLUGIN_CLI_URL" -OutFile C:/ProgramData/Jenkins/jenkins-plugin-manager.jar; ` if ((Get-FileHash -Path C:/ProgramData/Jenkins/jenkins-plugin-manager.jar -Algorithm SHA256).Hash -ne $sha256sum) {exit 1} # for main web interface: EXPOSE ${http_port} # will be used by attached agents: EXPOSE ${agent_port} ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log COPY jenkins-support.psm1 C:/ProgramData/Jenkins COPY jenkins.ps1 C:/ProgramData/Jenkins # See https://github.com/jenkinsci/plugin-installation-manager-tool#cli-options for information on parameters for jenkins-plugin-cli.ps1 for installing plugins into the docker image COPY jenkins-plugin-cli.ps1 C:/ProgramData/Jenkins ARG JENKINS_VERSION=2.555 ENV JENKINS_VERSION=${JENKINS_VERSION} ENTRYPOINT ["powershell.exe", "-f", "C:/ProgramData/Jenkins/jenkins.ps1"] # metadata labels LABEL ` org.opencontainers.image.vendor="Jenkins project" ` org.opencontainers.image.title="Official Jenkins Docker image" ` org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" ` org.opencontainers.image.version="${JENKINS_VERSION}" ` org.opencontainers.image.url="https://www.jenkins.io/" ` org.opencontainers.image.source="https://github.com/jenkinsci/docker" ` org.opencontainers.image.revision="${COMMIT_SHA}" ` org.opencontainers.image.licenses="MIT"