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