Showing preview only (250K chars total). Download the full file or copy to clipboard to get everything.
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 '<version>.*</version>')"
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 <<EOF
Using the following settings:
* JENKINS_REPO: ${JENKINS_REPO}
* JENKINS_VERSION: ${JENKINS_VERSION}
* COMMIT_SHA: ${COMMIT_SHA}
* LATEST_WEEKLY: ${LATEST_WEEKLY}
* LATEST_LTS: ${LATEST_LTS}
* BUILD_METADATA_PATH: ${BUILD_METADATA_PATH}
* BAKE_TARGET: ${BAKE_TARGET}
* BAKE OPTIONS:
$(printf ' %s\n' "${build_opts[@]}")
EOF
echo '* RESOLVED BAKE CONFIG:'
docker buildx bake --file docker-bake.hcl --progress=quiet --print "${BAKE_TARGET}"
if [[ "${CI:-false}" == "false" ]]; then
read -rp "Confirm? [y/N] " answer
if [[ ! "${answer}" =~ ^[Yy]$ ]]; then
exit 0
fi
fi
docker buildx bake --file docker-bake.hcl "${build_opts[@]}" "${BAKE_TARGET}"
================================================
FILE: .git-blame-ignore-revs
================================================
# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-and-understanding-files#ignore-commits-in-the-blame-view
# Format Jenkinsfile: https://github.com/jenkinsci/docker/pull/2003
9ade9569a658c0ed27107915d7597fcc98d7a577
================================================
FILE: .github/CODEOWNERS
================================================
* @jenkinsci/team-docker-packaging
*/debian @ksalerno99
*/rhel @ksalerno99
================================================
FILE: .github/FUNDING.yml
================================================
community_bridge: jenkins
custom: ["https://jenkins.io/donate/#why-donate"]
================================================
FILE: .github/dependabot.yml
================================================
# Per https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# GitHub actions
- package-ecosystem: "github-actions"
labels:
- "skip-changelog"
target-branch: master
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
================================================
FILE: .github/release-drafter.yml
================================================
# https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc
_extends: github:jenkinsci/.github:/.github/release-drafter.yml
# We are using 2-digit weekly versioning here
version-template: $MAJOR.$MINOR
tag-template: $NEXT_MINOR_VERSION
name-template: $NEXT_MINOR_VERSION
template: |
<!-- Optional: add a release summary here -->
## 📦 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 <package>-<version>
- DL3041 # Specify version with dnf install -y <package>-<version>
# 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=<rule>" 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
[](https://hub.docker.com/r/jenkins/jenkins/)
[](https://hub.docker.com/r/jenkins/jenkins/)
[](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/).
<img src="https://jenkins.io/sites/default/files/jenkins_logo.png"/>
# 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 <jenkins_container_id_or_name> cat /var/jenkins_home/secrets/initialAdminPassword
```
Replace <jenkins_container_id_or_name> 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 <<EOF
handlers=java.util.logging.ConsoleHandler
jenkins.level=FINEST
java.util.logging.ConsoleHandler.level=FINEST
EOF
docker run --name myjenkins -p 8080:8080 -p 50000:50000 --restart=on-failure --env JAVA_OPTS="-Djava.util.logging.config.file=/var/jenkins_home/log.properties" -v `pwd`/data:/var/jenkins_home jenkins/jenkins:lts-jdk21
```
## Configuring reverse proxy
If you want to install Jenkins behind a reverse proxy with a prefix, example: mysite.com/jenkins, you need to add environment variable `JENKINS_OPTS="--prefix=/jenkins"` and then follow the below procedures to configure your reverse proxy, which will depend if you have Apache or Nginx:
- [Apache](https://www.jenkins.io/doc/book/system-administration/reverse-proxy-configuration-apache/)
- [Nginx](https://www.jenkins.io/doc/book/system-administration/reverse-proxy-configuration-nginx/)
## DNS configuration
If the message "This Jenkins instance appears to be offline." appears on first startup, and the container logs show lines such as `java.net.UnknownHostException: updates.jenkins.io`, your container may be having issues with resolving DNS names.
To potentially solve the issue, start the container specifying a dns server (for example Cloudflare's 1.1.1.1 or Google's 8.8.8.8, or any other DNS server):
```
docker run -p 8080:8080 -p 50000:50000 --restart=on-failure --dns 1.1.1.1 --dns 8.8.8.8 jenkins/jenkins:lts-jdk21
```
## Passing Jenkins launcher parameters
Arguments you pass to docker running the Jenkins image are passed to jenkins launcher, so for example you can run:
```
docker run jenkins/jenkins:lts-jdk21 --version
```
This will show the Jenkins version, the same as when you run Jenkins from an executable war.
You can also define Jenkins arguments via `JENKINS_OPTS`. This is useful for customizing arguments to the jenkins
launcher in a derived Jenkins image. The following sample Dockerfile uses this option
to force use of HTTPS with a certificate included in the image.
```
FROM jenkins/jenkins:lts-jdk21
COPY --chown=jenkins:jenkins certificate.pfx /var/lib/jenkins/certificate.pfx
COPY --chown=jenkins:jenkins https.key /var/lib/jenkins/pk
ENV JENKINS_OPTS="--httpPort=-1 --httpsPort=8083 --httpsKeyStore=/var/lib/jenkins/certificate.pfx --httpsKeyStorePassword=Password12"
EXPOSE 8083
```
You can also change the default agent port for Jenkins by defining `JENKINS_SLAVE_AGENT_PORT` in a sample Dockerfile.
```
FROM jenkins/jenkins:lts-jdk21
ENV JENKINS_SLAVE_AGENT_PORT=50001
```
or as a parameter to docker,
```
docker run --name myjenkins -p 8080:8080 -p 50001:50001 --restart=on-failure --env JENKINS_SLAVE_AGENT_PORT=50001 jenkins/jenkins:lts-jdk21
```
**Note**: This environment variable will be used to set the
[system property](https://www.jenkins.io/doc/book/managing/system-properties/) `jenkins.model.Jenkins.slaveAgentPort`.
> 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/.*?<shortName>([\w-]+).*?<version>([^<]+)()(<\/\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 `<repository_name>:<tag>`, 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 "<windowsFlavor>" 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 '^(?<os>.+)[\\/](?<flavor>.+)[\\/](?<jvm>.+)[\\/]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" = $c
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
Condensed preview — 88 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (250K chars).
[
{
"path": ".ci/publish.sh",
"chars": 2947,
"preview": "#!/bin/bash -eu\n\n# Publish any versions of the docker image not yet pushed to ${JENKINS_REPO}\n# Arguments:\n# -n dry ru"
},
{
"path": ".git-blame-ignore-revs",
"chars": 247,
"preview": "# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-and-understanding-files#ignore-commits-"
},
{
"path": ".github/CODEOWNERS",
"chars": 75,
"preview": "* @jenkinsci/team-docker-packaging\n*/debian @ksalerno99\n*/rhel @ksalerno99\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 76,
"preview": "community_bridge: jenkins\ncustom: [\"https://jenkins.io/donate/#why-donate\"]\n"
},
{
"path": ".github/dependabot.yml",
"chars": 350,
"preview": "# Per https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates\nversion:"
},
{
"path": ".github/release-drafter.yml",
"chars": 503,
"preview": "# https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc\n\n_extends: github:jenkinsci/.github:/.gi"
},
{
"path": ".github/workflows/release-drafter.yml",
"chars": 791,
"preview": "# Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc\n"
},
{
"path": ".github/workflows/updatecli.yaml",
"chars": 1022,
"preview": "name: updatecli\non:\n # Allow to be run manually\n workflow_dispatch:\n schedule:\n # Run once per week (to avoid aler"
},
{
"path": ".gitignore",
"chars": 384,
"preview": "*.tmp\nbats/\ntarget/\ntests/functions/init.groovy.d/\ntests/functions/java_cp/\ntests/functions/copy_reference_file.log\ntest"
},
{
"path": ".gitmodules",
"chars": 259,
"preview": "[submodule \"tests/test_helper/bats-support\"]\n\tpath = tests/test_helper/bats-support\n\turl = https://github.com/ztombol/ba"
},
{
"path": ".hadolint.yml",
"chars": 912,
"preview": "# Hadolint configuration file\n---\n# configure ignore rules\n# see https://github.com/hadolint/hadolint#rules for a list o"
},
{
"path": "CHANGELOG.md",
"chars": 572,
"preview": "Changelog\n=========\n\n| See [GitHub releases](https://github.com/jenkinsci/docker/releases) |\n| --- |\n\nThese release note"
},
{
"path": "CONTRIBUTING.md",
"chars": 955,
"preview": "# Issues and Contributing\n\nPlease note that only issues related to this Docker image will be addressed here.\n\n* If you h"
},
{
"path": "HACKING.adoc",
"chars": 8244,
"preview": "= Hacking documentation\n\nThis document explains how to develop on this repository.\n\n== Requirements\n\n* A Bourne-Again-Sh"
},
{
"path": "Jenkinsfile",
"chars": 10242,
"preview": "#!/usr/bin/env groovy\n\ndef listOfProperties = []\nlistOfProperties << buildDiscarder(logRotator(numToKeepStr: '50', artif"
},
{
"path": "LICENSE.txt",
"chars": 1149,
"preview": "The MIT License\n\nCopyright (c) 2014-, Michael Neale, Nicolas de Loof, Carlos Sanchez, and a number of other of contribut"
},
{
"path": "Makefile",
"chars": 7287,
"preview": "ROOT_DIR=\"$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/\"\n\n## For Docker <=20.04\nexport DOCKER_BUILDKIT=1\n##"
},
{
"path": "README.md",
"chars": 18395,
"preview": "# Official Jenkins Docker image\n\n[](https://hub."
},
{
"path": "SECURITY.md",
"chars": 2981,
"preview": "# Security Policy\n\nThe Jenkins project takes security seriously.\nWe make every possible effort to ensure users can adequ"
},
{
"path": "alpine/hotspot/Dockerfile",
"chars": 5023,
"preview": "ARG ALPINE_TAG=3.23.3\n\nFROM alpine:\"${ALPINE_TAG}\" AS jre-and-war\n\nARG JAVA_VERSION=17.0.18_8\n\nSHELL [\"/bin/ash\", \"-o\", "
},
{
"path": "debian/Dockerfile",
"chars": 5763,
"preview": "ARG TRIXIE_TAG=20251103\n\nARG DEBIAN_RELEASE_LINE=trixie\nARG DEBIAN_VERSION=20251117\nARG DEBIAN_VARIANT=\"-slim\"\nFROM debi"
},
{
"path": "docker-bake.hcl",
"chars": 11384,
"preview": "## Variables\nvariable \"jdks_to_build\" {\n default = [21, 25]\n}\n\nvariable \"windows_version_to_build\" {\n default = [\"ltsc"
},
{
"path": "jdk-download-url.sh",
"chars": 3761,
"preview": "#!/bin/sh\n\n# Check if at least one argument was passed to the script\n# If one argument was passed and JAVA_VERSION is se"
},
{
"path": "jdk-download.sh",
"chars": 1738,
"preview": "#!/bin/sh\nset -x\n# Check if curl and tar are installed\nif ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/nu"
},
{
"path": "jenkins-plugin-cli.ps1",
"chars": 84,
"preview": "& java \"$env:JAVA_OPTS\" -jar C:/ProgramData/Jenkins/jenkins-plugin-manager.jar $args"
},
{
"path": "jenkins-plugin-cli.sh",
"chars": 323,
"preview": "#!/bin/bash\n\n# read JAVA_OPTS into array to avoid need for eval (and associated vulnerabilities)\njava_opts_array=()\nwhil"
},
{
"path": "jenkins-support",
"chars": 6149,
"preview": "#!/bin/bash -eu\n\n: \"${REF:=\"/usr/share/jenkins/ref\"}\"\n\n# compare if version1 < version2\nversionLT() {\n local normaliz"
},
{
"path": "jenkins-support.psm1",
"chars": 9146,
"preview": "\n# compare if version1 < version2\nfunction Compare-VersionLessThan([string] $version1 = '', [string] $version2 = '') {\n "
},
{
"path": "jenkins.io-2026.key",
"chars": 1680,
"preview": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGlJRoMBEADGTw4Jms5rD1Wd0evqpTkNBgAIvCzvsjgGXHevmNIsDmm/niiE\ngKJlrl73T9d8GZeoa"
},
{
"path": "jenkins.ps1",
"chars": 2101,
"preview": "Import-Module -Force -DisableNameChecking C:/ProgramData/Jenkins/jenkins-support.psm1\n\n$JENKINS_WAR = Get-EnvOrDefault '"
},
{
"path": "jenkins.sh",
"chars": 2490,
"preview": "#! /bin/bash -e\n\n: \"${JENKINS_WAR:=\"/usr/share/jenkins/jenkins.war\"}\"\n: \"${JENKINS_HOME:=\"/var/jenkins_home\"}\"\n\nif [[ -n"
},
{
"path": "make.ps1",
"chars": 10910,
"preview": "[CmdletBinding()]\r\nParam(\r\n [Parameter(Position = 1)]\r\n # Default script target\r\n [String] $Target = 'build',\r\n"
},
{
"path": "rhel/Dockerfile",
"chars": 5882,
"preview": "ARG RHEL_TAG=9.7-1773204657\nARG RHEL_RELEASE_LINE=ubi9\nFROM registry.access.redhat.com/${RHEL_RELEASE_LINE}/ubi:${RHEL_T"
},
{
"path": "tests/bake.bats",
"chars": 784,
"preview": "#!/usr/bin/env bats\n\n# bats file_tags=test-suite:bake\n# bats file_tags=test-type:golden-file\n\nload test_helpers\n\nSUT_DES"
},
{
"path": "tests/functions/.ssh/config",
"chars": 0,
"preview": ""
},
{
"path": "tests/functions/Dockerfile",
"chars": 171,
"preview": "FROM bats-jenkins\n\nRUN mkdir -p /usr/share/jenkins/ref/.ssh && touch /usr/share/jenkins/ref/.ssh/config.override\nRUN chm"
},
{
"path": "tests/functions/Dockerfile-windows",
"chars": 169,
"preview": "FROM bats-jenkins\r\n# hadolint shell=powershell\r\n\r\nRUN mkdir C:/ProgramData/Jenkins/Reference/pester ; echo $null >> C:/P"
},
{
"path": "tests/functions.Tests.ps1",
"chars": 5200,
"preview": "Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1\r\nImport-Module -DisableNameChecking -For"
},
{
"path": "tests/functions.bats",
"chars": 2715,
"preview": "#!/usr/bin/env bats\n\n# bats file_tags=test-suite:functions\n\nload 'test_helper/bats-support/load'\nload 'test_helper/bats-"
},
{
"path": "tests/golden/expected_env_vars_except_hostname.txt",
"chars": 534,
"preview": "COPY_REFERENCE_FILE_LOG=/var/jenkins_home/copy_reference_file.log\nHOME=/var/jenkins_home\nJAVA_HOME=/opt/java/openjdk\nJEN"
},
{
"path": "tests/golden/expected_platforms.txt",
"chars": 872,
"preview": "alpine_jdk21:linux/amd64\nalpine_jdk21:linux/arm64\nalpine_jdk25:linux/amd64\nalpine_jdk25:linux/arm64\ndebian-slim_jdk21:li"
},
{
"path": "tests/golden/expected_tags.txt",
"chars": 1268,
"preview": "docker.io/jenkins/jenkins:2.555 (debian_jdk21)\ndocker.io/jenkins/jenkins:2.555-alpine (alpine_jdk21)\ndocker.io/jenkins/j"
},
{
"path": "tests/golden/expected_tags_latest_lts.txt",
"chars": 3140,
"preview": "docker.io/jenkins/jenkins:2.541.1 (debian_jdk21)\ndocker.io/jenkins/jenkins:2.541.1-alpine (alpine_jdk21)\ndocker.io/jenki"
},
{
"path": "tests/golden/expected_tags_latest_weekly.txt",
"chars": 2837,
"preview": "docker.io/jenkins/jenkins:2.555 (debian_jdk21)\ndocker.io/jenkins/jenkins:2.555-alpine (alpine_jdk21)\ndocker.io/jenkins/j"
},
{
"path": "tests/jenkinsfile.bats",
"chars": 315,
"preview": "#!/usr/bin/env bats\n\n# bats file_tags=test-suite:jenkinsfile\n\nload test_helpers\n\nSUT_DESCRIPTION=\"Jenkinsfile\"\n\n@test \"["
},
{
"path": "tests/plugins-cli/Dockerfile",
"chars": 142,
"preview": "FROM bats-jenkins\n\nRUN jenkins-plugin-cli --plugins junit:1.6 ant:1.3 mesos:0.13.0 git:latest filesystem_scm:experimenta"
},
{
"path": "tests/plugins-cli/Dockerfile-windows",
"chars": 208,
"preview": "FROM bats-jenkins\n# hadolint shell=powershell\n\nRUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose --plugins jun"
},
{
"path": "tests/plugins-cli/custom-war/Dockerfile",
"chars": 661,
"preview": "FROM bats-jenkins\n\n# Define a custom location for the war\nENV JENKINS_WAR=/test-custom-dockerfile/my-custom-jenkins.war\n"
},
{
"path": "tests/plugins-cli/custom-war/Dockerfile-windows",
"chars": 663,
"preview": "FROM bats-jenkins\n# hadolint shell=powershell\n\n# Define a custom location for the war\nENV JENKINS_WAR=C:/ProgramData/Tes"
},
{
"path": "tests/plugins-cli/java-opts/Dockerfile",
"chars": 128,
"preview": "FROM bats-jenkins-plugins-cli\n\nENV JAVA_OPTS=\"-Djava.opts.test=true -XshowSettings:properties\"\nRUN jenkins-plugin-cli --"
},
{
"path": "tests/plugins-cli/no-war/Dockerfile",
"chars": 197,
"preview": "FROM bats-jenkins\n\nCOPY plugins.txt /usr/share/jenkins/ref/plugins.txt\nUSER root\nRUN rm -rf /usr/share/jenkins/jenkins.w"
},
{
"path": "tests/plugins-cli/no-war/Dockerfile-windows",
"chars": 290,
"preview": "FROM bats-jenkins\n# hadolint shell=powershell\n\nCOPY plugins.txt C:/ProgramData/Jenkins/Reference/plugins.txt\nRUN Remove-"
},
{
"path": "tests/plugins-cli/no-war/plugins.txt",
"chars": 295,
"preview": "# comment line should be skipped\n\n# simple case\nant:1.3\n\n# trailing spaces\njunit:1.6 \n\n# leading spaces\n mesos:0.13.0"
},
{
"path": "tests/plugins-cli/pluginsfile/Dockerfile",
"chars": 132,
"preview": "FROM bats-jenkins\n\nCOPY plugins.txt /usr/share/jenkins/ref/plugins.txt\nRUN jenkins-plugin-cli -f /usr/share/jenkins/ref/"
},
{
"path": "tests/plugins-cli/pluginsfile/Dockerfile-windows",
"chars": 217,
"preview": "FROM bats-jenkins\n# hadolint shell=powershell\n\nCOPY plugins.txt C:/ProgramData/Jenkins/Reference/plugins.txt\nRUN C:/Prog"
},
{
"path": "tests/plugins-cli/pluginsfile/plugins.txt",
"chars": 397,
"preview": "# comment line should be skipped\n\n# simple case\nant:1.3\n\n# trailing spaces\njunit:1.6 \n\n# leading spaces\n mesos:0.13.0"
},
{
"path": "tests/plugins-cli/ref/Dockerfile",
"chars": 115,
"preview": "FROM bats-jenkins-plugins-cli\n\nRUN rm -rf /usr/share/jenkins/ref ; jenkins-plugin-cli --plugins junit:1.28 ant:1.3\n"
},
{
"path": "tests/plugins-cli/ref/Dockerfile-windows",
"chars": 199,
"preview": "FROM bats-jenkins\n# hadolint shell=powershell\n\nRUN Remove-Item -Recurse -Force C:/ProgramData/Jenkins/Reference ; C:/Pro"
},
{
"path": "tests/plugins-cli/update/Dockerfile",
"chars": 93,
"preview": "FROM bats-jenkins-plugins-cli\n\nRUN jenkins-plugin-cli --verbose --plugins junit:1.28 ant:1.3\n"
},
{
"path": "tests/plugins-cli/update/Dockerfile-windows",
"chars": 148,
"preview": "FROM bats-jenkins-plugins-cli\n# hadolint shell=powershell\n\nRUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose -"
},
{
"path": "tests/plugins-cli.Tests.ps1",
"chars": 14555,
"preview": "Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1\r\nImport-Module -DisableNameChecking -For"
},
{
"path": "tests/plugins-cli.bats",
"chars": 9395,
"preview": "#!/usr/bin/env bats\n\n# bats file_tags=test-suite:plugin-cli\n\nload 'test_helper/bats-support/load'\nload 'test_helper/bats"
},
{
"path": "tests/runtime.Tests.ps1",
"chars": 5046,
"preview": "Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1\nImport-Module -DisableNameChecking -Forc"
},
{
"path": "tests/runtime.bats",
"chars": 6123,
"preview": "#!/usr/bin/env bats\n\n# bats file_tags=test-suite:runtime\n\nload 'test_helper/bats-support/load'\nload 'test_helper/bats-as"
},
{
"path": "tests/test_helpers.bash",
"chars": 4850,
"preview": "#!/bin/bash\nset -euo pipefail\n\n# Assert that $1 is the outputof a command $2\nfunction assert {\n local expected_output"
},
{
"path": "tests/test_helpers.psm1",
"chars": 7419,
"preview": "Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1\n\nfunction Test-CommandExists($command) {"
},
{
"path": "tests/update-golden-file.sh",
"chars": 2292,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\n# This script runs a specified command, captures its output,\n# and compares it ag"
},
{
"path": "tests/upgrade-plugins/Dockerfile",
"chars": 71,
"preview": "FROM bats-jenkins\n\nRUN jenkins-plugin-cli --plugins junit:1.28 ant:1.2\n"
},
{
"path": "tests/upgrade-plugins/Dockerfile-windows",
"chars": 128,
"preview": "FROM bats-jenkins\n# hadolint shell=powershell\n\nRUN & C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --plugins junit:1.28 "
},
{
"path": "tini_pub.gpg",
"chars": 7152,
"preview": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFANDtsBEACpb69Ul0Ko7D4XxRIvPGnDMuGdocb8PxR+EGbnHe0uS2tCbsfj\nTOoWWUrjufrWYxGlK"
},
{
"path": "tools/hadolint",
"chars": 130,
"preview": "#!/bin/sh -e\n\nexec docker run --rm \\\n\t-w \"${PWD}\" \\\n\t-v \"${PWD}:${PWD}\" \\\n\tghcr.io/hadolint/hadolint:v2.14.0-debian hado"
},
{
"path": "tools/shellcheck",
"chars": 117,
"preview": "#!/bin/sh -e\n\nexec docker run --rm \\\n -w \"${PWD}\" \\\n -v \"${PWD}:${PWD}\" \\\n koalaman/shellcheck:v0.11.0 \"$@\"\n"
},
{
"path": "updatecli/scripts/rhel-latest-tag.sh",
"chars": 3260,
"preview": "#!/bin/bash\n\n# This script fetches the latest tag from the Red Hat Container Catalog API for the images of the current R"
},
{
"path": "updatecli/updatecli.d/alpine.yaml",
"chars": 1959,
"preview": "---\nname: Bump Alpine version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n email:"
},
{
"path": "updatecli/updatecli.d/debian.yaml",
"chars": 2463,
"preview": "---\nname: Bump Debian version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n email:"
},
{
"path": "updatecli/updatecli.d/git-lfs.yaml",
"chars": 1167,
"preview": "---\nname: Bump `git-lfs` version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n ema"
},
{
"path": "updatecli/updatecli.d/hadolint.yaml",
"chars": 1246,
"preview": "---\nname: Bump hadolint version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n emai"
},
{
"path": "updatecli/updatecli.d/jdk17.yaml",
"chars": 1817,
"preview": "---\nname: Bump JDK17 version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n email: "
},
{
"path": "updatecli/updatecli.d/jdk21.yaml",
"chars": 1434,
"preview": "name: Bump JDK21 version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n email: \"{{ "
},
{
"path": "updatecli/updatecli.d/jdk25.yaml",
"chars": 1434,
"preview": "name: Bump JDK25 version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n email: \"{{ "
},
{
"path": "updatecli/updatecli.d/jenkins-version-simulated-lts.yaml",
"chars": 2187,
"preview": "---\nname: Bump simulated LTS `JENKINS_VERSION` version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .git"
},
{
"path": "updatecli/updatecli.d/jenkins-version.yaml",
"chars": 2664,
"preview": "---\nname: Bump default `JENKINS_VERSION` version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.us"
},
{
"path": "updatecli/updatecli.d/plugin-installation-manager-tool.yaml",
"chars": 1643,
"preview": "---\nname: Bump `plugin-installation-manager-tool` version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ ."
},
{
"path": "updatecli/updatecli.d/rhel.yaml",
"chars": 1708,
"preview": "---\nname: Bump RHEL version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n email: \""
},
{
"path": "updatecli/updatecli.d/shellcheck.yaml",
"chars": 1238,
"preview": "---\nname: Bump shellcheck version\n\nscms:\n default:\n kind: github\n spec:\n user: \"{{ .github.user }}\"\n em"
},
{
"path": "updatecli/values.github-action.yaml",
"chars": 224,
"preview": "github:\n user: \"GitHub Actions\"\n email: \"41898282+github-actions[bot]@users.noreply.github.com\"\n username: \"github-ac"
},
{
"path": "windows/windowsservercore/hotspot/Dockerfile",
"chars": 7732,
"preview": "# escape=`\n# hadolint shell=powershell\n\nARG JAVA_VERSION=17.0.18_8\nARG WINDOWS_VERSION=ltsc2022\n\nFROM mcr.microsoft.com/"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the jenkinsci/docker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 88 files (229.7 KB), approximately 70.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.