├── .ci
└── publish.sh
├── .git-blame-ignore-revs
├── .github
├── CODEOWNERS
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── dependabot.yml
├── release-drafter.yml
└── workflows
│ ├── release-drafter.yml
│ ├── sync-plugin-manager.yml
│ └── updatecli.yaml
├── .gitignore
├── .gitmodules
├── .hadolint.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── HACKING.adoc
├── Jenkinsfile
├── LICENSE.txt
├── Makefile
├── README.md
├── SECURITY.md
├── alpine
└── hotspot
│ └── Dockerfile
├── build-windows.yaml
├── debian
├── bookworm-slim
│ └── hotspot
│ │ └── Dockerfile
└── bookworm
│ └── hotspot
│ └── 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.ps1
├── jenkins.sh
├── make.ps1
├── rhel
└── ubi9
│ └── hotspot
│ └── Dockerfile
├── tests
├── functions.Tests.ps1
├── functions.bats
├── functions
│ ├── .ssh
│ │ └── config
│ ├── Dockerfile
│ └── Dockerfile-windows
├── plugins-cli.Tests.ps1
├── plugins-cli.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
├── runtime.Tests.ps1
├── runtime.bats
├── test_helpers.bash
├── test_helpers.psm1
└── upgrade-plugins
│ ├── Dockerfile
│ └── Dockerfile-windows
├── tini_pub.gpg
├── tools
├── hadolint
└── shellcheck
├── updatecli
├── updatecli.d
│ ├── alpine.yaml
│ ├── debian.yaml
│ ├── git-lfs.yaml
│ ├── hadolint.yaml
│ ├── jdk17.yaml
│ ├── jdk21.yaml
│ └── shellcheck.yaml
└── values.github-action.yaml
└── windows
└── windowsservercore
└── hotspot
└── Dockerfile
/.ci/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eu
2 |
3 | # Publish any versions of the docker image not yet pushed to ${JENKINS_REPO}
4 | # Arguments:
5 | # -n dry run, do not build or publish images
6 | # -d debug
7 |
8 | : "${JENKINS_VERSION:?Variable \$JENKINS_VERSION not set or empty.}"
9 |
10 | set -eu -o pipefail
11 |
12 | : "${DOCKERHUB_ORGANISATION:=jenkins}"
13 | : "${DOCKERHUB_REPO:=jenkins}"
14 |
15 | export JENKINS_REPO="${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}"
16 |
17 | function sort-versions() {
18 | if [ "$(uname)" == 'Darwin' ]; then
19 | gsort --version-sort
20 | else
21 | sort --version-sort
22 | fi
23 | }
24 |
25 | # Process arguments
26 | dry_run=false
27 | debug=false
28 |
29 | while [[ $# -gt 0 ]]; do
30 | key="$1"
31 | case $key in
32 | -n)
33 | dry_run=true
34 | ;;
35 | -d)
36 | debug=true
37 | ;;
38 | *)
39 | echo "ERROR: Unknown option: $key"
40 | exit 1
41 | ;;
42 | esac
43 | shift
44 | done
45 |
46 |
47 | if [ "$debug" = true ]; then
48 | echo "Debug mode enabled"
49 | set -x
50 | fi
51 |
52 | if [ "$dry_run" = true ]; then
53 | echo "Dry run, will not publish images"
54 | fi
55 |
56 | # Retrieve all the Jenkins versions from Artifactory
57 | all_jenkins_versions="$(curl --disable --fail --silent --show-error --location \
58 | https://repo.jenkins-ci.org/releases/org/jenkins-ci/main/jenkins-war/maven-metadata.xml \
59 | | grep '.*')"
60 |
61 | latest_lts_version="$(echo "${all_jenkins_versions}" | grep -E -o '[0-9]\.[0-9]+\.[0-9]' | sort-versions | tail -n1)"
62 | latest_weekly_version="$(echo "${all_jenkins_versions}" | grep -E -o '[0-9]\.[0-9]+' | sort-versions | tail -n 1)"
63 |
64 | if [[ "${JENKINS_VERSION}" == "${latest_weekly_version}" ]]
65 | then
66 | LATEST_WEEKLY="true"
67 | else
68 | LATEST_WEEKLY="false"
69 | fi
70 |
71 | if [[ "${JENKINS_VERSION}" == "${latest_lts_version}" ]]
72 | then
73 | LATEST_LTS="true"
74 | else
75 | LATEST_LTS="false"
76 | fi
77 |
78 | build_opts=("--pull")
79 | if test "${dry_run}" == "true"; then
80 | build_opts+=("--load")
81 | else
82 | build_opts+=("--push")
83 | fi
84 |
85 | JENKINS_SHA="$(curl --disable --fail --silent --show-error --location "https://repo.jenkins-ci.org/releases/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war.sha256")"
86 | COMMIT_SHA=$(git rev-parse HEAD)
87 | export COMMIT_SHA JENKINS_VERSION JENKINS_SHA LATEST_WEEKLY LATEST_LTS
88 |
89 | cat <
9 | ## 📦 Jenkins Core updates
10 |
11 | * Update to Jenkins $NEXT_MINOR_VERSION ([changelog](https://www.jenkins.io/changelog/$NEXT_MINOR_VERSION))
12 |
13 | $CHANGES
14 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc
2 |
3 | name: Release Drafter
4 |
5 | on:
6 | push:
7 | branches:
8 | - "master"
9 | workflow_dispatch:
10 |
11 | jobs:
12 | update_release_draft:
13 | runs-on: ubuntu-latest
14 | if: github.repository_owner == 'jenkinsci'
15 | steps:
16 | # Drafts your next Release notes as Pull Requests are merged into the default branch
17 | - uses: release-drafter/release-drafter@v6
18 | env:
19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20 |
--------------------------------------------------------------------------------
/.github/workflows/sync-plugin-manager.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Sync Plugin manager Version
3 |
4 | on:
5 | schedule:
6 | - cron: "* 23 * * *"
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | if: github.repository_owner == 'jenkinsci'
13 | steps:
14 | - name: Check out source code
15 | uses: actions/checkout@v4
16 |
17 | - name: PLUGIN_CLI_VERSION
18 | id: lts
19 | run: |
20 | NEXT_VERSION=$(curl -sL https://api.github.com/repos/jenkinsci/plugin-installation-manager-tool/releases/latest | jq -r ".tag_name")
21 | if [ -z "$NEXT_VERSION" ] || [ $NEXT_VERSION = "null" ]; then
22 | echo "ERROR: Empty or 'null' latest plugin-installation-manager-tool version returned by GitHub API."
23 | exit 1
24 | fi
25 | echo "next-version=$NEXT_VERSION" >> $GITHUB_OUTPUT
26 |
27 | - name: Check if update is available
28 | id: update
29 | run: |
30 | CURRENT_VERSION=$(docker buildx bake -f docker-bake.hcl --print alpine_jdk17 | jq -r ".target.alpine_jdk17.args.PLUGIN_CLI_VERSION")
31 | if [ "$CURRENT_VERSION" = "${{ steps.lts.outputs.next-version }}" ]; then
32 | echo "available=false" >> $GITHUB_OUTPUT
33 | else
34 | echo "available=true" >> $GITHUB_OUTPUT
35 | echo "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
36 | fi
37 |
38 | - name: Update LTS version in files
39 | if: ${{ steps.update.outputs.available == 'true' }}
40 | run: |
41 | grep -ilrF ${{ steps.update.outputs.current-version }} ./ | grep -v CHANGELOG.md | xargs sed -i 's/${{ steps.update.outputs.current-version }}/${{ steps.lts.outputs.next-version }}/g'
42 |
43 | - name: Changelog
44 | if: ${{ steps.update.outputs.available == 'true' }}
45 | run: |
46 | sed -i '/git commit to be able to get more details./a \\n## ${{ steps.nextversion.outputs.version }}\n\nUpdate Jenkins plugin cli verions to jenkins plugin cli lts release version ${{ steps.lts.outputs.next-version }}\n' CHANGELOG.md
47 |
48 | - name: Git Diff
49 | if: ${{ steps.update.outputs.available == 'true' }}
50 | run: |
51 | git diff
52 | # update the changelog
53 |
54 | - uses: tibdex/github-app-token@v2
55 | id: generate-token
56 | with:
57 | app_id: ${{ secrets.JENKINS_DEPENDENCY_UPDATER_APP_ID }}
58 | private_key: ${{ secrets.JENKINS_DEPENDENCY_UPDATER_PRIVATE_KEY }}
59 |
60 | - name: Create Pull Request
61 | id: cpr
62 | uses: peter-evans/create-pull-request@v7
63 | if: ${{ steps.update.outputs.available == 'true' }}
64 | with:
65 | token: ${{ steps.generate-token.outputs.token }}
66 | commit-message: 'chore(deps): bump plugin manager to ${{ steps.lts.outputs.next-version }}'
67 | author: jenkins-dependency-updater <81680575+jenkins-dependency-updater[bot]@users.noreply.github.com>
68 | committer: jenkins-dependency-updater <81680575+jenkins-dependency-updater[bot]@users.noreply.github.com>
69 | signoff: true
70 | labels: 'dependencies'
71 | title: 'chore(deps): bump plugin manager to ${{ steps.lts.outputs.next-version }}'
72 |
--------------------------------------------------------------------------------
/.github/workflows/updatecli.yaml:
--------------------------------------------------------------------------------
1 | name: updatecli
2 | on:
3 | # Allow to be run manually
4 | workflow_dispatch:
5 | schedule:
6 | # Run once per week (to avoid alert fatigue)
7 | - cron: '0 2 * * 1' # Every Monday at 2am UTC
8 | push:
9 | pull_request:
10 | jobs:
11 | updatecli:
12 | runs-on: ubuntu-latest
13 | if: github.repository_owner == 'jenkinsci'
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 |
18 | - name: Install Updatecli in the runner
19 | uses: updatecli/updatecli-action@v2.84.0
20 |
21 | - name: Run Updatecli in Dry Run mode
22 | run: updatecli diff --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml
23 | env:
24 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 |
26 | - name: Run Updatecli in Apply mode
27 | if: github.ref == 'refs/heads/master'
28 | run: updatecli apply --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml
29 | env:
30 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.tmp
2 | bats/
3 | target/
4 | tests/functions/init.groovy.d/
5 | tests/functions/java_cp/
6 | tests/functions/copy_reference_file.log
7 | tests/**/work-*/
8 | manifest-tool
9 | multiarch/qemu-*
10 | multiarch/Dockerfile-*
11 | /docker.iml
12 | work-pester-jenkins-windows/
13 | /.idea/
14 |
15 | /**/windows/**/jenkins.ps1
16 | /**/windows/**/jenkins-plugin-cli.ps1
17 | /**/windows/**/jenkins-support.psm1
18 |
19 | tests/**/Dockerfile\.*
20 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "tests/test_helper/bats-support"]
2 | path = tests/test_helper/bats-support
3 | url = https://github.com/ztombol/bats-support
4 | [submodule "tests/test_helper/bats-assert"]
5 | path = tests/test_helper/bats-assert
6 | url = https://github.com/ztombol/bats-assert
7 |
--------------------------------------------------------------------------------
/.hadolint.yml:
--------------------------------------------------------------------------------
1 | # Hadolint configuration file
2 | ---
3 | # configure ignore rules
4 | # see https://github.com/hadolint/hadolint#rules for a list of available rules.
5 |
6 | ignored:
7 | # Exclusions in this section have been triaged and determined to be false
8 | # positives.
9 | - DL3008 # Pin versions in apt-get install
10 | - DL3018 # Pin versions in apk add
11 | - DL3033 # Specify version with yum install -y -
12 | - DL3041 # Specify version with dnf install -y -
13 |
14 | # Here lies technical debt. Exclusions in this section have not yet been
15 | # triaged. When working on on this section, pick an exclusion to triage, then:
16 | # - If it is a false positive, add a "# hadolint ignore=" suppression,
17 | # then remove the exclusion from this section.
18 | # - If it is not a false positive, fix the bug, then remove the exclusion from
19 | # this section.
20 | - DL3006 # Always tag the version of an image explicitly
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 | =========
3 |
4 | | See [GitHub releases](https://github.com/jenkinsci/docker/releases) |
5 | | --- |
6 |
7 | ## Status
8 |
9 | We are doing experimental changelogs for Jenkins controller Docker packaging
10 | ([discussion in the developer list](https://groups.google.com/forum/#!topic/jenkinsci-dev/KvV_UjU02gE)).
11 | These release notes represent changes in in the packaging, but not in the bundled WAR files.
12 | Please refer to the [weekly changelog](https://jenkins.io/changelog/) and [LTS changelog](https://jenkins.io/changelog-stable/) for WAR file changelogs.
13 |
14 | ## Version scheme
15 |
16 | The repository follows the 3-digit scheme of [Jenkins LTS releases](https://jenkins.io/download/lts/).
17 |
18 | ## Mapping of Docker packaging to Jenkins releases
19 |
20 | Both Weekly and LTS distributions follow the Continuous Delivery approach and pick up the most recent versions available by the time of the release Pipeline execution.
21 | In this repository we follow the Jenkins LTS releases and release packaging changelogs for them.
22 | There is no version mapping for Weekly releases, users should be using changelogs to track down the changes
23 | (see also [Issue #865](https://github.com/jenkinsci/docker/issues/865)).
24 |
25 | ## Notable changes in Jenkins versions before 2.164.1
26 |
27 | Below you can find incomplete list of changes in Docker packaging for Jenkins releases
28 |
29 | ### 2.99
30 |
31 | * `/bin/tini` has been relocated to `/sbin/tini`, location defined by alpine
32 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Issues and Contributing
2 |
3 | Please note that only issues related to this Docker image will be addressed here.
4 |
5 | * If you have Docker related issues, please ask in the [Docker user mailing list](https://groups.google.com/forum/#!forum/docker-user).
6 | * If you have Jenkins related issues, please ask in the [Jenkins mailing lists](https://jenkins-ci.org/content/mailing-lists).
7 | * 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.
8 |
9 | If after going through the previous checklist you still think you should create an issue here please provide:
10 |
11 | * Docker commands that you execute
12 | * Actual result
13 | * Expected outcome
14 | * Have you tried a non-dockerized Jenkins and get the expected outcome?
15 | * Output of `docker version`
16 | * Other relevant information
17 |
18 | If you are interested to provide fixes by yourself, you might want to check out the [dedicated documentation](HACKING.adoc).
19 |
--------------------------------------------------------------------------------
/HACKING.adoc:
--------------------------------------------------------------------------------
1 | = Hacking documentation
2 |
3 | This document explains how to develop on this repository.
4 |
5 | == Requirements
6 |
7 | * A Bourne-Again-Shell compatible prompt (bash)
8 | * GNU `make` 3.80+
9 | * Docker with https://github.com/docker/buildx[BuildX] capability
10 | ** Docker 20.10+ is recommended as it is usually packaged with Buildx
11 | ** Docker 19.03+ is required
12 | ** 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)
13 | * https://git-scm.com/[git] 1.6+ (git 2+ is recommended)
14 | * https://stedolan.github.io/jq/[jq] 1.6+
15 | * https://curl.se/[curl] 7+
16 |
17 | We recommend https://www.gnu.org/software/parallel/[GNU Parallel] for parallel test execution, but it is not required.
18 |
19 | // In case the link breaks, and the bug hasn't been fixed yet:
20 | // On Apple Silicon in native arm64 containers, older versions of libssl in
21 | // debian:buster and ubuntu:20.04 will segfault when connected to some TLS
22 | // servers, for example curl https://dl.yarnpkg.com. The bug is fixed in newer versions
23 | // of libssl in debian:bookworm, ubuntu:21.04 and fedora:35.
24 |
25 | 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'.
26 |
27 | == Building
28 |
29 | === Linux
30 |
31 | [source,bash]
32 | --
33 | ## build all linux platforms
34 | make build
35 |
36 | ## only build a specific linux image
37 | make build-debian_jdk17 # or build-alpine_jdk17 build-debian_slim_jdk17 build-debian_jdk17 ...
38 |
39 | --
40 |
41 | == Testing
42 |
43 | === Linux
44 |
45 | Tests for Linux images are written using https://github.com/bats-core/bats-core[bats] under the `tests/` directory.
46 |
47 | Tests pre-requisites are automatically managed by the `make prepare-test` target (dependency of any `make test*` target) which:
48 |
49 | - Ensures that the `bats` command is installed in the `bats/bin/` directory (along with all the bats project in `./bats/`)
50 | - Ensures that the additional bats helper are installed as git sub-modules in `./tests/test_helper/`
51 |
52 | For efficiency, the tests are executed in parallel.
53 |
54 | [IMPORTANT]
55 | Due to the parallel execution, each test should be self-contained
56 | and not depend on another test, even inside a given test harness.
57 |
58 | Please note that:
59 |
60 | - You can disable the parallel execution by setting the environment variable `DISABLE_PARALLEL_TESTS` to the value `true`
61 | - Parallel execution is disabled if the commands `docker` or (GNU) `parallel` are not installed.
62 |
63 |
64 | You can restrict the execution to only a subset of test harness files. By setting the environment variable `TEST_SUITES`
65 | to the path of the bats test harness file to execute alone.
66 |
67 | [source,bash]
68 | --
69 | ## Run tests for all linux platforms
70 | make test
71 |
72 | ## Run tests for a specific linux platform
73 | make test-debian_jdk17 # or test-alpine_jdk17 test-debian_slim_jdk17 test-debian_jdk17 ...
74 |
75 | ## Run tests for Alpine Linux JDK17 platform in sequential mode
76 | DISABLE_PARALLEL_TESTS=true make test-alpine_jdk17
77 |
78 | ## Only run the test suite `functions.bats` for the Debian JDK21 platform
79 | TEST_SUITES=./tests/functions.bats make test-debian_jdk21
80 | --
81 |
82 | == Multiarch support
83 |
84 | 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).
85 |
86 | Planned supported architectures:
87 |
88 | * amd64
89 | * arm64
90 | * s390x
91 |
92 | == Debugging
93 |
94 | In order to debug the controller, use the `-e DEBUG=true -p 5005:5005` when starting the container.
95 | Jenkins will be suspended on the startup in such case,
96 | and then it will be possible to attach a debugger from IDE to it.
97 |
98 | == Test the publishing using an overridden target repository on Docker Hub
99 |
100 | Create a new dedicated target repository in your Docker Hub account, and use it like follows:
101 |
102 | [source,bash]
103 | --
104 | export DOCKERHUB_ORGANISATION=batmat
105 | export DOCKERHUB_REPO=test-jenkins
106 | # The log below will help confirm this override was taken in account:
107 | ./publish.sh
108 | Docker repository in Use:
109 | * JENKINS_REPO: batmat/test-jenkins
110 | ...
111 | --
112 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env groovy
2 |
3 | def listOfProperties = []
4 | listOfProperties << buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '5'))
5 |
6 | // Only master branch will run on a timer basis
7 | if (env.BRANCH_NAME.trim() == 'master') {
8 | listOfProperties << pipelineTriggers([cron('''H H/6 * * 0-2,4-6
9 | H 6,21 * * 3''')])
10 | }
11 |
12 | properties(listOfProperties)
13 |
14 | // Default environment variable set to allow images publication
15 | def envVars = ['PUBLISH=true']
16 |
17 | // Set to true in a replay to simulate a LTS build on ci.jenkins.io
18 | // It will set the environment variables needed for a LTS
19 | // and disable images publication out of caution
20 | def SIMULATE_LTS_BUILD = false
21 |
22 | if (SIMULATE_LTS_BUILD) {
23 | envVars = [
24 | 'PUBLISH=false',
25 | 'TAG_NAME=2.462.3',
26 | 'JENKINS_VERSION=2.462.3',
27 | 'JENKINS_SHA=3e53b52a816405e3b10ad07f1c48cd0cb5cb3f893207ef7f9de28415806b93c1'
28 | ]
29 | }
30 |
31 | stage('Build') {
32 | def builds = [:]
33 |
34 | withEnv(envVars) {
35 | echo '= bake target: linux'
36 |
37 | def windowsImageTypes = ['windowsservercore-ltsc2019']
38 | for (anImageType in windowsImageTypes) {
39 | def imageType = anImageType
40 | builds[imageType] = {
41 | nodeWithTimeout('windows-2019') {
42 | stage('Checkout') {
43 | checkout scm
44 | }
45 |
46 | withEnv(["IMAGE_TYPE=${imageType}"]) {
47 | if (!infra.isTrusted()) {
48 | /* Outside of the trusted.ci environment, we're building and testing
49 | * the Dockerfile in this repository, but not publishing to docker hub
50 | */
51 | stage("Build ${imageType}") {
52 | infra.withDockerCredentials {
53 | powershell './make.ps1'
54 | }
55 | }
56 |
57 | stage("Test ${imageType}") {
58 | infra.withDockerCredentials {
59 | def windowsTestStatus = powershell(script: './make.ps1 test', returnStatus: true)
60 | junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results.xml')
61 | if (windowsTestStatus > 0) {
62 | // If something bad happened let's clean up the docker images
63 | error('Windows test stage failed.')
64 | }
65 | }
66 | }
67 |
68 | // disable until we get the parallel changes merged in
69 | // def branchName = "${env.BRANCH_NAME}"
70 | // if (branchName ==~ 'master'){
71 | // stage('Publish Experimental') {
72 | // infra.withDockerCredentials {
73 | // withEnv(['DOCKERHUB_ORGANISATION=jenkins4eval','DOCKERHUB_REPO=jenkins']) {
74 | // powershell './make.ps1 publish'
75 | // }
76 | // }
77 | // }
78 | // }
79 | } else {
80 | // Only publish when a tag triggered the build & the publication is enabled (ie not simulating a LTS)
81 | if (env.TAG_NAME && (env.PUBLISH == 'true')) {
82 | // Split to ensure any suffix is not taken in account (but allow suffix tags to trigger rebuilds)
83 | jenkins_version = env.TAG_NAME.split('-')[0]
84 | withEnv(["JENKINS_VERSION=${jenkins_version}"]) {
85 | stage('Publish') {
86 | infra.withDockerCredentials {
87 | withEnv(['DOCKERHUB_ORGANISATION=jenkins', 'DOCKERHUB_REPO=jenkins']) {
88 | powershell './make.ps1 publish'
89 | }
90 | }
91 | }
92 | }
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | if (!infra.isTrusted()) {
101 | def images = [
102 | 'alpine_jdk17',
103 | 'alpine_jdk21',
104 | 'debian_jdk17',
105 | 'debian_jdk21',
106 | 'debian_slim_jdk17',
107 | 'debian_slim_jdk21',
108 | 'rhel_ubi9_jdk17',
109 | 'rhel_ubi9_jdk21',
110 | ]
111 | for (i in images) {
112 | def imageToBuild = i
113 |
114 | builds[imageToBuild] = {
115 | nodeWithTimeout('docker') {
116 | deleteDir()
117 |
118 | stage('Checkout') {
119 | checkout scm
120 | }
121 |
122 | stage('Static analysis') {
123 | sh 'make hadolint shellcheck'
124 | }
125 |
126 | /* Outside of the trusted.ci environment, we're building and testing
127 | * the Dockerfile in this repository, but not publishing to docker hub
128 | */
129 | stage("Build linux-${imageToBuild}") {
130 | infra.withDockerCredentials {
131 | sh "make build-${imageToBuild}"
132 | }
133 | }
134 |
135 | stage("Test linux-${imageToBuild}") {
136 | sh 'make prepare-test'
137 | try {
138 | infra.withDockerCredentials {
139 | sh "make test-${imageToBuild}"
140 | }
141 | } catch (err) {
142 | error("${err.toString()}")
143 | } finally {
144 | junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/*.xml')
145 | }
146 | }
147 | }
148 | }
149 | }
150 | builds['multiarch-build'] = {
151 | nodeWithTimeout('docker') {
152 | stage('Checkout') {
153 | deleteDir()
154 | checkout scm
155 | }
156 |
157 | // sanity check that proves all images build on declared platforms
158 | stage('Multi arch build') {
159 | infra.withDockerCredentials {
160 | sh '''
161 | make docker-init
162 | docker buildx bake --file docker-bake.hcl linux
163 | '''
164 | }
165 | }
166 | }
167 | }
168 | } else {
169 | // Only publish when a tag triggered the build
170 | if (env.TAG_NAME) {
171 | // Split to ensure any suffix is not taken in account (but allow suffix tags to trigger rebuilds)
172 | jenkins_version = env.TAG_NAME.split('-')[0]
173 | builds['linux'] = {
174 | withEnv(["JENKINS_VERSION=${jenkins_version}"]) {
175 | nodeWithTimeout('docker') {
176 | stage('Checkout') {
177 | checkout scm
178 | }
179 |
180 | stage('Publish') {
181 | // Publication is enabled by default, disabled when simulating a LTS
182 | if (env.PUBLISH == 'true') {
183 | infra.withDockerCredentials {
184 | sh 'make docker-init'
185 | sh 'make publish'
186 | }
187 | }
188 | }
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
195 | parallel builds
196 | }
197 | }
198 |
199 | void nodeWithTimeout(String label, def body) {
200 | node(label) {
201 | timeout(time: 60, unit: 'MINUTES') {
202 | body.call()
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2014-, Michael Neale, Nicolas de Loof, Carlos Sanchez, and a number of other of contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ROOT_DIR="$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/"
2 |
3 | ## For Docker <=20.04
4 | export DOCKER_BUILDKIT=1
5 | ## For Docker <=20.04
6 | export DOCKER_CLI_EXPERIMENTAL=enabled
7 | ## Required to have docker build output always printed on stdout
8 | export BUILDKIT_PROGRESS=plain
9 | ## Required to have the commit SHA added as a Docker image label
10 | export COMMIT_SHA=$(shell git rev-parse HEAD)
11 |
12 | current_arch := $(shell uname -m)
13 | 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)
14 |
15 | all: hadolint shellcheck build test
16 |
17 | # Set to 'true' to disable parellel tests
18 | DISABLE_PARALLEL_TESTS ?= false
19 |
20 | # Set to the path of a specific test suite to restrict execution only to this
21 | # default is "all test suites in the "tests/" directory
22 | TEST_SUITES ?= $(CURDIR)/tests
23 |
24 | ##### Macros
25 | ## Check the presence of a CLI in the current PATH
26 | check_cli = type "$(1)" >/dev/null 2>&1 || { echo "Error: command '$(1)' required but not found. Exiting." ; exit 1 ; }
27 | ## Check if a given image exists in the current manifest docker-bake.hcl
28 | check_image = make --silent list | grep -w '$(1)' >/dev/null 2>&1 || { echo "Error: the image '$(1)' does not exist in manifest for the platform 'linux/$(ARCH)'. Please check the output of 'make list'. Exiting." ; exit 1 ; }
29 | ## Base "docker buildx base" command to be reused everywhere
30 | bake_base_cli := docker buildx bake -f docker-bake.hcl --load
31 |
32 | check-reqs:
33 | ## Build requirements
34 | @$(call check_cli,bash)
35 | @$(call check_cli,git)
36 | @$(call check_cli,docker)
37 | @docker info | grep 'buildx:' >/dev/null 2>&1 || { echo "Error: Docker BuildX plugin required but not found. Exiting." ; exit 1 ; }
38 | ## Test requirements
39 | @$(call check_cli,curl)
40 | @$(call check_cli,jq)
41 |
42 | ## This function is specific to Jenkins infrastructure and isn't required in other contexts
43 | docker-init: check-reqs
44 | ifeq ($(CI),true)
45 | ifeq ($(wildcard /etc/buildkitd.toml),)
46 | echo 'WARNING: /etc/buildkitd.toml not found, using default configuration.'
47 | docker buildx create --use --bootstrap --driver docker-container
48 | else
49 | docker buildx create --use --bootstrap --driver docker-container --config /etc/buildkitd.toml
50 | endif
51 | else
52 | docker buildx create --use --bootstrap --driver docker-container
53 | endif
54 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
55 |
56 | hadolint:
57 | find . -type f -name 'Dockerfile*' -not -path "./bats/*" -print0 | xargs -0 $(ROOT_DIR)/tools/hadolint
58 |
59 | shellcheck:
60 | $(ROOT_DIR)/tools/shellcheck -e SC1091 jenkins-support *.sh tests/test_helpers.bash tools/hadolint tools/shellcheck .ci/publish.sh
61 |
62 | build: check-reqs
63 | @set -x; $(bake_base_cli) --set '*.platform=linux/$(ARCH)' $(shell make --silent list)
64 |
65 | build-%: check-reqs
66 | @$(call check_image,$*)
67 | @set -x; $(bake_base_cli) --set '*.platform=linux/$(ARCH)' '$*'
68 |
69 | show:
70 | @$(bake_base_cli) linux --print
71 |
72 | list: check-reqs
73 | @set -x; make --silent show | jq -r '.target | path(.. | select(.platforms[] | contains("linux/$(ARCH)"))?) | add'
74 |
75 | bats:
76 | git clone https://github.com/bats-core/bats-core bats ;\
77 | cd bats ;\
78 | git checkout 410dd229a5ed005c68167cc90ed0712ad2a1c909; # v1.7.0
79 |
80 | prepare-test: bats check-reqs
81 | git submodule update --init --recursive
82 | mkdir -p target
83 |
84 | ## Define bats options based on environment
85 | # common flags for all tests
86 | bats_flags := $(TEST_SUITES)
87 | # if DISABLE_PARALLEL_TESTS true, then disable parallel execution
88 | ifneq (true,$(DISABLE_PARALLEL_TESTS))
89 | # If the GNU 'parallel' command line is absent, then disable parallel execution
90 | parallel_cli := $(shell command -v parallel 2>/dev/null)
91 | ifneq (,$(parallel_cli))
92 | # If parallel execution is enabled, then set 2 tests per core available for the Docker Engine
93 | test-%: PARALLEL_JOBS ?= $(shell echo $$(( $(shell docker run --rm alpine grep -c processor /proc/cpuinfo) * 2)))
94 | test-%: bats_flags += --jobs $(PARALLEL_JOBS)
95 | endif
96 | endif
97 | test-%: prepare-test
98 | # Check that the image exists in the manifest
99 | @$(call check_image,$*)
100 | # Ensure that the image is built
101 | @make --silent build-$*
102 | # Execute the test harness and write result to a TAP file
103 | IMAGE=$* bats/bin/bats $(bats_flags) --formatter junit | tee target/junit-results-$*.xml
104 |
105 | test: prepare-test
106 | @make --silent list | while read image; do make --silent "test-$${image}"; done
107 |
108 | publish:
109 | ./.ci/publish.sh
110 |
111 | clean:
112 | rm -rf tests/test_helper/bats-*; \
113 | rm -rf bats
114 |
115 | .PHONY: hadolint shellcheck check-reqs build clean test list show
116 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Official Jenkins Docker image
2 |
3 | [](https://hub.docker.com/r/jenkins/jenkins/)
4 | [](https://hub.docker.com/r/jenkins/jenkins/)
5 | [](https://gitter.im/jenkinsci/docker?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6 |
7 | The Jenkins Continuous Integration and Delivery server [available on Docker Hub](https://hub.docker.com/r/jenkins/jenkins).
8 |
9 | This is a fully functional Jenkins server.
10 | [https://jenkins.io/](https://jenkins.io/).
11 |
12 |
13 |
14 | # Usage
15 |
16 | ```
17 | docker run -p 8080:8080 -p 50000:50000 --restart=on-failure jenkins/jenkins:lts-jdk17
18 | ```
19 |
20 | NOTE: read the section [_Connecting agents_](#connecting-agents) below for the role of the `50000` port mapping.
21 | NOTE: read the section [_DNS Configuration_](#dns-configuration) in case you see the message "This Jenkins instance appears to be offline."
22 |
23 | This will store the workspace in `/var/jenkins_home`.
24 | All Jenkins data lives in there - including plugins and configuration.
25 | You will probably want to make that an explicit volume so you can manage it and attach to another container for upgrades :
26 |
27 | ```
28 | docker run -p 8080:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk17
29 | ```
30 |
31 | This will automatically create a 'jenkins_home' [docker volume](https://docs.docker.com/storage/volumes/) on the host machine.
32 | Docker volumes retain their content even when the container is stopped, started, or deleted.
33 |
34 | 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).
35 | 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`.
36 |
37 | ```
38 | docker run -d -v jenkins_home:/var/jenkins_home -p 8080:8080 -p 50000:50000 --restart=on-failure jenkins/jenkins:lts-jdk17
39 | ```
40 |
41 | 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.
42 |
43 | ## Backing up data
44 |
45 | If you bind mount in a volume - you can simply back up that directory
46 | (which is jenkins_home) at any time.
47 |
48 | 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.
49 |
50 | 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.
51 | Note that some symlinks on some OSes may be converted to copies (this can confuse jenkins with lastStableBuild links, etc)
52 |
53 | For more info check Docker docs section on [Use volumes](https://docs.docker.com/storage/volumes/)
54 |
55 | ## Setting the number of executors
56 |
57 | You can define the number of executors on the Jenkins built-in node using a groovy script.
58 | 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) :
59 |
60 | `executors.groovy`
61 |
62 | ```
63 | import jenkins.model.*
64 | Jenkins.instance.setNumExecutors(0) // Recommended to not run builds on the built-in node
65 | ```
66 |
67 | and `Dockerfile`
68 |
69 | ```
70 | FROM jenkins/jenkins:lts
71 | COPY --chown=jenkins:jenkins executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy
72 | ```
73 |
74 | ## Connecting agents
75 |
76 | You can run builds on the controller out of the box.
77 | The Jenkins project recommends that no executors be enabled on the controller.
78 |
79 | In order to connect agents **through an inbound TCP connection**, map the port: `-p 50000:50000`.
80 | That port will be used when you connect agents to the controller.
81 |
82 | 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.
83 | If you connect agents using web sockets (since Jenkins 2.217), the TCP agent port is not used either.
84 |
85 | ## Passing JVM parameters
86 |
87 | 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.
88 | Use the `JAVA_OPTS` or `JENKINS_JAVA_OPTS` environment variables for this purpose :
89 |
90 | ```
91 | docker run --name myjenkins -p 8080:8080 -p 50000:50000 --restart=on-failure --env JAVA_OPTS=-Dhudson.footerURL=http://mycompany.com jenkins/jenkins:lts-jdk17
92 | ```
93 |
94 | 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.
95 |
96 | ## Configuring logging
97 |
98 | Jenkins logging can be configured through a properties file and `java.util.logging.config.file` Java property.
99 | For example:
100 |
101 | ```
102 | mkdir data
103 | cat > data/log.properties < If this property is already set in **JAVA_OPTS** or **JENKINS_JAVA_OPTS**, then the value of
167 | > `JENKINS_SLAVE_AGENT_PORT` will be ignored.
168 |
169 | # Installing more tools
170 |
171 | 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:
172 |
173 | ```
174 | FROM jenkins/jenkins:lts-jdk17
175 | # if we want to install via apt
176 | USER root
177 | RUN apt-get update && apt-get install -y ruby make more-thing-here
178 | # drop back to the regular jenkins user - good practice
179 | USER jenkins
180 | ```
181 |
182 | In such a derived image, you can customize your jenkins instance with hook scripts or additional plugins.
183 | For this purpose, use `/usr/share/jenkins/ref` as a place to define the default JENKINS_HOME content you
184 | wish the target installation to look like :
185 |
186 | ```
187 | FROM jenkins/jenkins:lts-jdk17
188 | COPY --chown=jenkins:jenkins custom.groovy /usr/share/jenkins/ref/init.groovy.d/custom.groovy
189 | ```
190 |
191 | 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`
192 |
193 | ## Preinstalling plugins
194 |
195 | ### Install plugins
196 |
197 | 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.
198 |
199 | ### Setting update centers
200 |
201 | During the download, the CLI will use update centers defined by the following environment variables:
202 |
203 | - `JENKINS_UC` - Main update center.
204 | This update center may offer plugin versions depending on the Jenkins LTS Core versions.
205 | Default value: https://updates.jenkins.io
206 | - `JENKINS_UC_EXPERIMENTAL` - [Experimental Update Center](https://jenkins.io/blog/2013/09/23/experimental-plugins-update-center/).
207 | This center offers Alpha and Beta versions of plugins.
208 | Default value: https://updates.jenkins.io/experimental
209 | - `JENKINS_INCREMENTALS_REPO_MIRROR` -
210 | Defines Maven mirror to be used to download plugins from the
211 | [Incrementals repo](https://jenkins.io/blog/2018/05/15/incremental-deployment/).
212 | Default value: https://repo.jenkins-ci.org/incrementals
213 | - `JENKINS_UC_DOWNLOAD` - Download url of the Update Center.
214 | Default value: `$JENKINS_UC/download`
215 | - `JENKINS_PLUGIN_INFO` - Location of plugin information.
216 | Default value: https://updates.jenkins.io/current/plugin-versions.json
217 |
218 | It is possible to override the environment variables in images.
219 |
220 | :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.
221 |
222 | ### Installing Custom Plugins
223 |
224 | Installing prebuilt, custom plugins can be accomplished by copying the plugin HPI file into `/usr/share/jenkins/ref/plugins/` within the `Dockerfile`:
225 |
226 | ```
227 | COPY --chown=jenkins:jenkins path/to/custom.hpi /usr/share/jenkins/ref/plugins/
228 | ```
229 |
230 | ### Usage
231 |
232 | You can run the CLI manually in Dockerfile:
233 |
234 | ```Dockerfile
235 | FROM jenkins/jenkins:lts-jdk17
236 | RUN jenkins-plugin-cli --plugins pipeline-model-definition github-branch-source:1.8
237 | ```
238 |
239 | Furthermore it is possible to pass a file that contains this set of plugins (with or without line breaks).
240 |
241 | ```Dockerfile
242 | FROM jenkins/jenkins:lts-jdk17
243 | COPY --chown=jenkins:jenkins plugins.txt /usr/share/jenkins/ref/plugins.txt
244 | RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt
245 | ```
246 |
247 | When jenkins container starts, it will check `JENKINS_HOME` has this reference content, and copy them
248 | there if required. It will not override such files, so if you upgraded some plugins from UI they won't
249 | be reverted on next start.
250 |
251 | In case you _do_ want to override, append '.override' to the name of the reference file. E.g. a file named
252 | `/usr/share/jenkins/ref/config.xml.override` will overwrite an existing `config.xml` file in JENKINS_HOME.
253 |
254 | Also see [JENKINS-24986](https://issues.jenkins.io/browse/JENKINS-24986)
255 |
256 | Here is an example to get the list of plugins from an existing server:
257 |
258 | ```
259 | JENKINS_HOST=username:password@myhost.com:port
260 | 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/ /:/'
261 | ```
262 |
263 | Example Output:
264 |
265 | ```
266 | cucumber-testresult-plugin:0.8.2
267 | pam-auth:1.1
268 | matrix-project:1.4.1
269 | script-security:1.13
270 | ...
271 | ```
272 |
273 | For 2.x-derived images, you may also want to
274 |
275 | RUN echo 2.0 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state
276 |
277 | to indicate that this Jenkins installation is fully configured.
278 | Otherwise a banner will appear prompting the user to install additional plugins,
279 | which may be inappropriate.
280 |
281 | ### Access logs
282 |
283 | 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`
284 |
285 | ### Naming convention in tags
286 |
287 | 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.
288 | In the case of the LTS and latest versions, the tags are `lts` and `latest`, respectively.
289 |
290 | You can use these tags to pull the corresponding Jenkins images from Docker Hub and run them on your system.
291 | For example, to pull the LTS version of the Jenkins image use this command: `docker pull jenkins/jenkins:lts`
292 |
293 | ### Docker Compose with Jenkins
294 |
295 | 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.
296 | For example, the following docker-compose.yml file defines a Jenkins controller and a Jenkins SSH agent:
297 |
298 | ```yaml
299 | services:
300 | jenkins:
301 | image: jenkins/jenkins:lts
302 | ports:
303 | - "8080:8080"
304 | volumes:
305 | - jenkins_home:/var/jenkins_home
306 | ssh-agent:
307 | image: jenkins/ssh-agent
308 | volumes:
309 | jenkins_home:
310 | ```
311 |
312 | This `docker-compose.yml` file creates two containers: one for Jenkins and one for the Jenkins SSH agent.
313 |
314 | The Jenkins container is based on the `jenkins/jenkins:lts` image and exposes the Jenkins web interface on port 8080.
315 | The `jenkins_home` volume is a [named volume](https://docs.docker.com/storage/volumes/) that is created and managed by Docker.
316 |
317 | It is mounted at `/var/jenkins_home` in the Jenkins container, and it will persist the Jenkins configuration and data.
318 |
319 | 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/).
320 |
321 | To start the Jenkins instance and the other services defined in the `docker-compose.yml` file, run the `docker compose up -d`.
322 |
323 | 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.
324 |
325 | 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).
326 |
327 | 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:
328 | ```yaml
329 | services:
330 | jenkins:
331 | # ... other config
332 | dns:
333 | - 1.1.1.1
334 | - 8.8.8.8
335 | # ... other config
336 | ```
337 |
338 | ### Updating plugins file
339 |
340 | The [plugin-installation-manager-tool](https://github.com/jenkinsci/plugin-installation-manager-tool) supports updating the plugin file for you.
341 |
342 | Example command:
343 |
344 | ```command
345 | JENKINS_IMAGE=jenkins/jenkins:lts-jdk17
346 | 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
347 | mv plugins2.txt plugins.txt
348 | ```
349 |
350 | ## Upgrading
351 |
352 | All the data needed is in the /var/jenkins_home directory - so depending on how you manage that - depends on how you upgrade.
353 | 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.
354 |
355 | As always - please ensure that you know how to drive docker - especially volume handling!
356 |
357 | 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.
358 |
359 | 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.
360 |
361 | ### Upgrading plugins
362 |
363 | 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.
364 | Versions installed by the docker image are tracked through a marker file.
365 |
366 | To force upgrades of plugins that have been manually upgraded, run the docker image with `-e PLUGINS_FORCE_UPGRADE=true`.
367 |
368 | The default behaviour when upgrading from a docker image that didn't write marker files is to leave existing plugins in place.
369 | If you want to upgrade existing plugins without marker you may run the docker image with `-e TRY_UPGRADE_IF_NO_MARKER=true`.
370 | Then plugins will be upgraded if the version provided by the docker image is newer.
371 |
372 | # Hacking
373 |
374 | If you wish to contribute fixes to this repository, please refer to the [dedicated documentation](HACKING.adoc).
375 |
376 | # Security
377 |
378 | For information related to the security of this Docker image, please refer to the [dedicated documentation](SECURITY.md).
379 |
380 | # Questions?
381 |
382 | We're on Gitter, https://gitter.im/jenkinsci/docker
383 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | The Jenkins project takes security seriously.
4 | We make every possible effort to ensure users can adequately secure their automation infrastructure.
5 |
6 | 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.
7 |
8 | ## Docker Image Publication
9 |
10 | When an image is published, the latest image and the latest available packages are used.
11 |
12 | We rely on the base image provider for the security of the system libraries.
13 | The default base image is Debian but multiple other variants are proposed, that could potentially better fit your needs.
14 |
15 | ## Reporting Security Vulnerabilities
16 |
17 | If you have identified a security vulnerability and would like to report it, please be aware of those requirements.
18 |
19 | For findings from a **Software Composition Analysis (SCA) scanner report**, all of the following points must be satisfied:
20 | - If the finding is coming from the system (Docker layer):
21 | - The scan must have been done on the latest version of the image.
22 | Vulnerabilities are discovered in a continuous way, so it is expected that past releases could contain some.
23 | - The package should have a fixed version provided in the base image that is not yet included in our image.
24 | We rely on the base image provider to propose the corrections.
25 | - The correction should have existed at the time the image was created.
26 | Normally our update workflow ensures that the latest available versions are used.
27 | - If the finding is coming from the application dependencies:
28 | - Proof of exploitation or sufficiently good explanation about why you think it's impacting the application.
29 |
30 | For all "valid" findings from SCA, your report must contain:
31 | - The path to the library (there are ~2000 components in the ecosystem, we don't want to have to guess)
32 | - The version and variant of the Docker image you scanned.
33 | - The scanner name and version as well.
34 | - The publicly accessible information about the vulnerability (ideally CVE). For private vulnerability database, please provide all the information at your disposal.
35 |
36 | The objective is to reduce the number of reports we receive that are not relevant to the security of the project.
37 |
38 | For findings from a **manual audit**, the report must contain either reproduction steps or a sufficiently well described proof to demonstrate the impact.
39 |
40 | Once the report is ready, please follow the process about [Reporting Security Vulnerabilities](https://jenkins.io/security/reporting/).
41 |
42 | We will reject reports that are not satisfying those requirements.
43 |
44 | ## Vulnerability Management
45 |
46 | Once the report is considered legitimate, a new image is published with the latest packages.
47 | 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.
48 |
49 | By default we do not plan to publish advisories for vulnerabilities at the Docker level.
50 | There may be exceptions.
51 |
--------------------------------------------------------------------------------
/alpine/hotspot/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG ALPINE_TAG=3.22.0
2 |
3 | FROM alpine:"${ALPINE_TAG}" AS jre-build
4 |
5 | ARG JAVA_VERSION=17.0.15_6
6 |
7 | SHELL ["/bin/ash", "-o", "pipefail", "-c"]
8 |
9 | COPY jdk-download-url.sh /usr/bin/jdk-download-url.sh
10 | COPY jdk-download.sh /usr/bin/jdk-download.sh
11 |
12 | RUN apk add --no-cache \
13 | ca-certificates \
14 | jq \
15 | curl \
16 | && rm -fr /var/cache/apk/* \
17 | && /usr/bin/jdk-download.sh alpine
18 |
19 | ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}"
20 |
21 | # Generate smaller java runtime without unneeded files
22 | # for now we include the full module path to maintain compatibility
23 | # while still saving space (approx 200mb from the full distribution)
24 | RUN case "$(jlink --version 2>&1)" in \
25 | "17."*) set -- "--compress=2" ;; \
26 | # the compression argument is different for JDK21
27 | "21."*) set -- "--compress=zip-6" ;; \
28 | *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \
29 | esac; \
30 | jlink \
31 | --strip-java-debug-attributes \
32 | "$1" \
33 | --add-modules ALL-MODULE-PATH \
34 | --no-man-pages \
35 | --no-header-files \
36 | --output /javaruntime
37 |
38 | FROM alpine:"${ALPINE_TAG}" AS controller
39 |
40 | RUN apk add --no-cache \
41 | bash \
42 | coreutils \
43 | curl \
44 | git \
45 | git-lfs \
46 | gnupg \
47 | musl-locales \
48 | musl-locales-lang \
49 | openssh-client \
50 | tini \
51 | ttf-dejavu \
52 | tzdata \
53 | unzip \
54 | && git lfs install
55 |
56 | ENV LANG=C.UTF-8
57 |
58 | ARG TARGETARCH
59 | ARG COMMIT_SHA
60 |
61 | ARG user=jenkins
62 | ARG group=jenkins
63 | ARG uid=1000
64 | ARG gid=1000
65 | ARG http_port=8080
66 | ARG agent_port=50000
67 | ARG JENKINS_HOME=/var/jenkins_home
68 | ARG REF=/usr/share/jenkins/ref
69 |
70 | ENV JENKINS_HOME=$JENKINS_HOME
71 | ENV JENKINS_SLAVE_AGENT_PORT=${agent_port}
72 | ENV REF=$REF
73 |
74 | # Jenkins is run with user `jenkins`, uid = 1000
75 | # If you bind mount a volume from the host or a data container,
76 | # ensure you use the same uid
77 | RUN mkdir -p $JENKINS_HOME \
78 | && chown ${uid}:${gid} $JENKINS_HOME \
79 | && addgroup -g ${gid} ${group} \
80 | && adduser -h "$JENKINS_HOME" -u ${uid} -G ${group} -s /bin/bash -D ${user}
81 |
82 | # Jenkins home directory is a volume, so configuration and build history
83 | # can be persisted and survive image upgrades
84 | VOLUME $JENKINS_HOME
85 |
86 | # $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want
87 | # to set on a fresh new installation. Use it to bundle additional plugins
88 | # or config file with your custom jenkins Docker image.
89 | RUN mkdir -p ${REF}/init.groovy.d
90 |
91 | # jenkins version being bundled in this docker image
92 | ARG JENKINS_VERSION
93 | ENV JENKINS_VERSION=${JENKINS_VERSION:-2.504}
94 |
95 | # jenkins.war checksum, download will be validated using it
96 | ARG JENKINS_SHA=efc91d6be8d79dd078e7f930fc4a5f135602d0822a5efe9091808fdd74607d32
97 |
98 | # Can be used to customize where jenkins.war get downloaded from
99 | ARG JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war
100 |
101 | # could use ADD but this one does not check Last-Modified header neither does it allow to control checksum
102 | # see https://github.com/docker/docker/issues/8331
103 | RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \
104 | && echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" >/tmp/jenkins_sha \
105 | && sha256sum -c --strict /tmp/jenkins_sha \
106 | && rm -f /tmp/jenkins_sha
107 |
108 | ENV JENKINS_UC=https://updates.jenkins.io
109 | ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
110 | ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
111 | RUN chown -R ${user} "$JENKINS_HOME" "$REF"
112 |
113 | ARG PLUGIN_CLI_VERSION=2.13.2
114 | ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar
115 | RUN curl -fsSL ${PLUGIN_CLI_URL} -o /opt/jenkins-plugin-manager.jar \
116 | && echo "$(curl -fsSL "${PLUGIN_CLI_URL}.sha256") /opt/jenkins-plugin-manager.jar" >/tmp/jenkins_sha \
117 | && sha256sum -c --strict /tmp/jenkins_sha \
118 | && rm -f /tmp/jenkins_sha
119 |
120 | # for main web interface:
121 | EXPOSE ${http_port}
122 |
123 | # will be used by attached agents:
124 | EXPOSE ${agent_port}
125 |
126 | ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log
127 |
128 | ENV JAVA_HOME=/opt/java/openjdk
129 | ENV PATH="${JAVA_HOME}/bin:${PATH}"
130 | COPY --from=jre-build /javaruntime $JAVA_HOME
131 |
132 | USER ${user}
133 |
134 | COPY jenkins-support /usr/local/bin/jenkins-support
135 | COPY jenkins.sh /usr/local/bin/jenkins.sh
136 | COPY jenkins-plugin-cli.sh /bin/jenkins-plugin-cli
137 |
138 | ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"]
139 |
140 | # metadata labels
141 | LABEL \
142 | org.opencontainers.image.vendor="Jenkins project" \
143 | org.opencontainers.image.title="Official Jenkins Docker image" \
144 | org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" \
145 | org.opencontainers.image.version="${JENKINS_VERSION}" \
146 | org.opencontainers.image.url="https://www.jenkins.io/" \
147 | org.opencontainers.image.source="https://github.com/jenkinsci/docker" \
148 | org.opencontainers.image.revision="${COMMIT_SHA}" \
149 | org.opencontainers.image.licenses="MIT"
150 |
--------------------------------------------------------------------------------
/build-windows.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | jdk17:
3 | image: ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}${SEPARATOR_LTS_PREFIX}jdk17-hotspot-${WINDOWS_FLAVOR}-${WINDOWS_VERSION}
4 | build:
5 | context: ./
6 | dockerfile: ./windows/${WINDOWS_FLAVOR}/hotspot/Dockerfile
7 | args:
8 | COMMIT_SHA: ${COMMIT_SHA}
9 | JAVA_HOME: "C:/openjdk-17"
10 | JAVA_VERSION: 17.0.15_6
11 | JENKINS_SHA: ${JENKINS_SHA}
12 | JENKINS_VERSION: ${JENKINS_VERSION}
13 | TOOLS_WINDOWS_VERSION: ${TOOLS_WINDOWS_VERSION}
14 | WINDOWS_VERSION: ${WINDOWS_VERSION}
15 | tags:
16 | - ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${JENKINS_VERSION}-jdk17-hotspot-${WINDOWS_FLAVOR}-${WINDOWS_VERSION}
17 | jdk21:
18 | image: ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}${SEPARATOR_LTS_PREFIX}jdk21-hotspot-${WINDOWS_FLAVOR}-${WINDOWS_VERSION}
19 | build:
20 | context: ./
21 | dockerfile: ./windows/${WINDOWS_FLAVOR}/hotspot/Dockerfile
22 | args:
23 | COMMIT_SHA: ${COMMIT_SHA}
24 | JAVA_HOME: "C:/openjdk-21"
25 | JAVA_VERSION: 21.0.7_6
26 | JENKINS_SHA: ${JENKINS_SHA}
27 | JENKINS_VERSION: ${JENKINS_VERSION}
28 | TOOLS_WINDOWS_VERSION: ${TOOLS_WINDOWS_VERSION}
29 | WINDOWS_VERSION: ${WINDOWS_VERSION}
30 | tags:
31 | - ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${JENKINS_VERSION}-jdk21-hotspot-${WINDOWS_FLAVOR}-${WINDOWS_VERSION}
32 | - ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${JENKINS_VERSION}-${WINDOWS_FLAVOR}-${WINDOWS_VERSION}
33 | - ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}${SEPARATOR_LTS_PREFIX}${WINDOWS_FLAVOR}-${WINDOWS_VERSION}
34 |
--------------------------------------------------------------------------------
/debian/bookworm-slim/hotspot/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG BOOKWORM_TAG=20250520
2 |
3 | FROM debian:bookworm-"${BOOKWORM_TAG}"-slim AS jre-build
4 |
5 | ARG JAVA_VERSION=17.0.15_6
6 |
7 | SHELL ["/bin/bash", "-o", "pipefail", "-c"]
8 |
9 | COPY jdk-download-url.sh /usr/bin/jdk-download-url.sh
10 | COPY jdk-download.sh /usr/bin/jdk-download.sh
11 |
12 | RUN apt-get update \
13 | && apt-get install --no-install-recommends -y \
14 | ca-certificates \
15 | curl \
16 | jq \
17 | && rm -rf /var/lib/apt/lists/* \
18 | && /usr/bin/jdk-download.sh
19 |
20 | ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}"
21 |
22 | # Generate smaller java runtime without unneeded files
23 | # for now we include the full module path to maintain compatibility
24 | # while still saving space (approx 200mb from the full distribution)
25 | RUN case "$(jlink --version 2>&1)" in \
26 | "17."*) set -- "--compress=2" ;; \
27 | # the compression argument is different for JDK21
28 | "21."*) set -- "--compress=zip-6" ;; \
29 | *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \
30 | esac; \
31 | jlink \
32 | --strip-java-debug-attributes \
33 | "$1" \
34 | --add-modules ALL-MODULE-PATH \
35 | --no-man-pages \
36 | --no-header-files \
37 | --output /javaruntime
38 |
39 | FROM debian:bookworm-"${BOOKWORM_TAG}"-slim AS controller
40 |
41 | RUN apt-get update \
42 | && apt-get install -y --no-install-recommends \
43 | ca-certificates \
44 | curl \
45 | git \
46 | gnupg \
47 | gpg \
48 | libfontconfig1 \
49 | libfreetype6 \
50 | procps \
51 | ssh-client \
52 | tini \
53 | unzip \
54 | tzdata \
55 | && rm -rf /var/lib/apt/lists/*
56 |
57 | # Git LFS is not available from a package manager on all the platforms we support
58 | # Download and unpack the tar.gz distribution
59 | ARG GIT_LFS_VERSION=3.6.1
60 | # hadolint ignore=DL4006
61 | RUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g') \
62 | && 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" \
63 | && tar xzf git-lfs.tgz \
64 | && bash git-lfs-*/install.sh \
65 | && rm -rf git-lfs*
66 |
67 | ENV LANG=C.UTF-8
68 |
69 | ARG TARGETARCH
70 | ARG COMMIT_SHA
71 |
72 | ARG user=jenkins
73 | ARG group=jenkins
74 | ARG uid=1000
75 | ARG gid=1000
76 | ARG http_port=8080
77 | ARG agent_port=50000
78 | ARG JENKINS_HOME=/var/jenkins_home
79 | ARG REF=/usr/share/jenkins/ref
80 |
81 | ENV JENKINS_HOME=$JENKINS_HOME
82 | ENV JENKINS_SLAVE_AGENT_PORT=${agent_port}
83 | ENV REF=$REF
84 |
85 | # Jenkins is run with user `jenkins`, uid = 1000
86 | # If you bind mount a volume from the host or a data container,
87 | # ensure you use the same uid
88 | RUN mkdir -p $JENKINS_HOME \
89 | && chown ${uid}:${gid} $JENKINS_HOME \
90 | && groupadd -g ${gid} ${group} \
91 | && useradd -d "$JENKINS_HOME" -u ${uid} -g ${gid} -l -m -s /bin/bash ${user}
92 |
93 | # Jenkins home directory is a volume, so configuration and build history
94 | # can be persisted and survive image upgrades
95 | VOLUME $JENKINS_HOME
96 |
97 | # $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want
98 | # to set on a fresh new installation. Use it to bundle additional plugins
99 | # or config file with your custom jenkins Docker image.
100 | RUN mkdir -p ${REF}/init.groovy.d
101 |
102 | # jenkins version being bundled in this docker image
103 | ARG JENKINS_VERSION
104 | ENV JENKINS_VERSION=${JENKINS_VERSION:-2.504}
105 |
106 | # jenkins.war checksum, download will be validated using it
107 | ARG JENKINS_SHA=efc91d6be8d79dd078e7f930fc4a5f135602d0822a5efe9091808fdd74607d32
108 |
109 | # Can be used to customize where jenkins.war get downloaded from
110 | ARG JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war
111 |
112 | # could use ADD but this one does not check Last-Modified header neither does it allow to control checksum
113 | # see https://github.com/docker/docker/issues/8331
114 | RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \
115 | && echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" >/tmp/jenkins_sha \
116 | && sha256sum -c --strict /tmp/jenkins_sha \
117 | && rm -f /tmp/jenkins_sha
118 |
119 | ENV JENKINS_UC=https://updates.jenkins.io
120 | ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
121 | ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
122 | RUN chown -R ${user} "$JENKINS_HOME" "$REF"
123 |
124 | ARG PLUGIN_CLI_VERSION=2.13.2
125 | ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar
126 | RUN curl -fsSL ${PLUGIN_CLI_URL} -o /opt/jenkins-plugin-manager.jar \
127 | && echo "$(curl -fsSL "${PLUGIN_CLI_URL}.sha256") /opt/jenkins-plugin-manager.jar" >/tmp/jenkins_sha \
128 | && sha256sum -c --strict /tmp/jenkins_sha \
129 | && rm -f /tmp/jenkins_sha
130 |
131 | # for main web interface:
132 | EXPOSE ${http_port}
133 |
134 | # will be used by attached agents:
135 | EXPOSE ${agent_port}
136 |
137 | ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log
138 |
139 | ENV JAVA_HOME=/opt/java/openjdk
140 | ENV PATH="${JAVA_HOME}/bin:${PATH}"
141 | COPY --from=jre-build /javaruntime $JAVA_HOME
142 |
143 | USER ${user}
144 |
145 | COPY jenkins-support /usr/local/bin/jenkins-support
146 | COPY jenkins.sh /usr/local/bin/jenkins.sh
147 | COPY jenkins-plugin-cli.sh /bin/jenkins-plugin-cli
148 |
149 | ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/jenkins.sh"]
150 |
151 | # metadata labels
152 | LABEL \
153 | org.opencontainers.image.vendor="Jenkins project" \
154 | org.opencontainers.image.title="Official Jenkins Docker image" \
155 | org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" \
156 | org.opencontainers.image.version="${JENKINS_VERSION}" \
157 | org.opencontainers.image.url="https://www.jenkins.io/" \
158 | org.opencontainers.image.source="https://github.com/jenkinsci/docker" \
159 | org.opencontainers.image.revision="${COMMIT_SHA}" \
160 | org.opencontainers.image.licenses="MIT"
161 |
--------------------------------------------------------------------------------
/debian/bookworm/hotspot/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG BOOKWORM_TAG=20250520
2 |
3 | FROM debian:bookworm-"${BOOKWORM_TAG}" AS jre-build
4 |
5 | ARG JAVA_VERSION=17.0.15_6
6 |
7 | SHELL ["/bin/bash", "-o", "pipefail", "-c"]
8 |
9 | COPY jdk-download-url.sh /usr/bin/jdk-download-url.sh
10 | COPY jdk-download.sh /usr/bin/jdk-download.sh
11 |
12 | RUN apt-get update \
13 | && apt-get install --no-install-recommends -y \
14 | ca-certificates \
15 | curl \
16 | jq \
17 | && rm -rf /var/lib/apt/lists/* \
18 | && /usr/bin/jdk-download.sh
19 |
20 | ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}"
21 |
22 | # Generate smaller java runtime without unneeded files
23 | # for now we include the full module path to maintain compatibility
24 | # while still saving space (approx 200mb from the full distribution)
25 | RUN case "$(jlink --version 2>&1)" in \
26 | "17."*) set -- "--compress=2" ;; \
27 | # the compression argument is different for JDK21
28 | "21."*) set -- "--compress=zip-6" ;; \
29 | *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \
30 | esac; \
31 | jlink \
32 | --strip-java-debug-attributes \
33 | "$1" \
34 | --add-modules ALL-MODULE-PATH \
35 | --no-man-pages \
36 | --no-header-files \
37 | --output /javaruntime
38 |
39 | FROM debian:bookworm-"${BOOKWORM_TAG}" AS controller
40 |
41 | RUN apt-get update \
42 | && apt-get install -y --no-install-recommends \
43 | ca-certificates \
44 | curl \
45 | git \
46 | gnupg \
47 | gpg \
48 | libfontconfig1 \
49 | libfreetype6 \
50 | procps \
51 | ssh-client \
52 | tini \
53 | unzip \
54 | tzdata \
55 | && rm -rf /var/lib/apt/lists/*
56 |
57 | # Git LFS is not available from a package manager on all the platforms we support
58 | # Download and unpack the tar.gz distribution
59 | ARG GIT_LFS_VERSION=3.6.1
60 | # hadolint ignore=DL4006
61 | RUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g') \
62 | && 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" \
63 | && tar xzf git-lfs.tgz \
64 | && bash git-lfs-*/install.sh \
65 | && rm -rf git-lfs*
66 |
67 | ENV LANG=C.UTF-8
68 |
69 | ARG TARGETARCH
70 | ARG COMMIT_SHA
71 |
72 | ARG user=jenkins
73 | ARG group=jenkins
74 | ARG uid=1000
75 | ARG gid=1000
76 | ARG http_port=8080
77 | ARG agent_port=50000
78 | ARG JENKINS_HOME=/var/jenkins_home
79 | ARG REF=/usr/share/jenkins/ref
80 |
81 | ENV JENKINS_HOME=$JENKINS_HOME
82 | ENV JENKINS_SLAVE_AGENT_PORT=${agent_port}
83 | ENV REF=$REF
84 |
85 | # Jenkins is run with user `jenkins`, uid = 1000
86 | # If you bind mount a volume from the host or a data container,
87 | # ensure you use the same uid
88 | RUN mkdir -p $JENKINS_HOME \
89 | && chown ${uid}:${gid} $JENKINS_HOME \
90 | && groupadd -g ${gid} ${group} \
91 | && useradd -d "$JENKINS_HOME" -u ${uid} -g ${gid} -l -m -s /bin/bash ${user}
92 |
93 | # Jenkins home directory is a volume, so configuration and build history
94 | # can be persisted and survive image upgrades
95 | VOLUME $JENKINS_HOME
96 |
97 | # $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want
98 | # to set on a fresh new installation. Use it to bundle additional plugins
99 | # or config file with your custom jenkins Docker image.
100 | RUN mkdir -p ${REF}/init.groovy.d
101 |
102 | # jenkins version being bundled in this docker image
103 | ARG JENKINS_VERSION
104 | ENV JENKINS_VERSION=${JENKINS_VERSION:-2.504}
105 |
106 | # jenkins.war checksum, download will be validated using it
107 | ARG JENKINS_SHA=efc91d6be8d79dd078e7f930fc4a5f135602d0822a5efe9091808fdd74607d32
108 |
109 | # Can be used to customize where jenkins.war get downloaded from
110 | ARG JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war
111 |
112 | # could use ADD but this one does not check Last-Modified header neither does it allow to control checksum
113 | # see https://github.com/docker/docker/issues/8331
114 | RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \
115 | && echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" >/tmp/jenkins_sha \
116 | && sha256sum -c --strict /tmp/jenkins_sha \
117 | && rm -f /tmp/jenkins_sha
118 |
119 | ENV JENKINS_UC=https://updates.jenkins.io
120 | ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
121 | ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
122 | RUN chown -R ${user} "$JENKINS_HOME" "$REF"
123 |
124 | ARG PLUGIN_CLI_VERSION=2.13.2
125 | ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar
126 | RUN curl -fsSL ${PLUGIN_CLI_URL} -o /opt/jenkins-plugin-manager.jar \
127 | && echo "$(curl -fsSL "${PLUGIN_CLI_URL}.sha256") /opt/jenkins-plugin-manager.jar" >/tmp/jenkins_sha \
128 | && sha256sum -c --strict /tmp/jenkins_sha \
129 | && rm -f /tmp/jenkins_sha
130 |
131 | # for main web interface:
132 | EXPOSE ${http_port}
133 |
134 | # will be used by attached agents:
135 | EXPOSE ${agent_port}
136 |
137 | ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log
138 |
139 | ENV JAVA_HOME=/opt/java/openjdk
140 | ENV PATH="${JAVA_HOME}/bin:${PATH}"
141 | COPY --from=jre-build /javaruntime $JAVA_HOME
142 |
143 | USER ${user}
144 |
145 | COPY jenkins-support /usr/local/bin/jenkins-support
146 | COPY jenkins.sh /usr/local/bin/jenkins.sh
147 | COPY jenkins-plugin-cli.sh /bin/jenkins-plugin-cli
148 |
149 | ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/jenkins.sh"]
150 |
151 | # metadata labels
152 | LABEL \
153 | org.opencontainers.image.vendor="Jenkins project" \
154 | org.opencontainers.image.title="Official Jenkins Docker image" \
155 | org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" \
156 | org.opencontainers.image.version="${JENKINS_VERSION}" \
157 | org.opencontainers.image.url="https://www.jenkins.io/" \
158 | org.opencontainers.image.source="https://github.com/jenkinsci/docker" \
159 | org.opencontainers.image.revision="${COMMIT_SHA}" \
160 | org.opencontainers.image.licenses="MIT"
161 |
--------------------------------------------------------------------------------
/docker-bake.hcl:
--------------------------------------------------------------------------------
1 | # ---- groups ----
2 |
3 | group "linux" {
4 | targets = [
5 | "alpine_jdk17",
6 | "alpine_jdk21",
7 | "debian_jdk17",
8 | "debian_jdk21",
9 | "debian_slim_jdk17",
10 | "debian_slim_jdk21",
11 | "rhel_ubi9_jdk17",
12 | "rhel_ubi9_jdk21",
13 | ]
14 | }
15 |
16 | group "linux-arm64" {
17 | targets = [
18 | "alpine_jdk21",
19 | "debian_jdk17",
20 | "debian_jdk21",
21 | "debian_slim_jdk21",
22 | "rhel_ubi9_jdk17",
23 | "rhel_ubi9_jdk21",
24 | ]
25 | }
26 |
27 | group "linux-s390x" {
28 | targets = [
29 | "debian_jdk17",
30 | "debian_jdk21",
31 | ]
32 | }
33 |
34 | group "linux-ppc64le" {
35 | targets = [
36 | "debian_jdk17",
37 | "debian_jdk21",
38 | "rhel_ubi9_jdk17",
39 | "rhel_ubi9_jdk21",
40 | ]
41 | }
42 |
43 | # ---- variables ----
44 |
45 | variable "JENKINS_VERSION" {
46 | default = "2.504"
47 | }
48 |
49 | variable "JENKINS_SHA" {
50 | default = "efc91d6be8d79dd078e7f930fc4a5f135602d0822a5efe9091808fdd74607d32"
51 | }
52 |
53 | variable "REGISTRY" {
54 | default = "docker.io"
55 | }
56 |
57 | variable "JENKINS_REPO" {
58 | default = "jenkins/jenkins"
59 | }
60 |
61 | variable "LATEST_WEEKLY" {
62 | default = "false"
63 | }
64 |
65 | variable "LATEST_LTS" {
66 | default = "false"
67 | }
68 |
69 | variable "PLUGIN_CLI_VERSION" {
70 | default = "2.13.2"
71 | }
72 |
73 | variable "COMMIT_SHA" {
74 | default = ""
75 | }
76 |
77 | variable "ALPINE_FULL_TAG" {
78 | default = "3.22.0"
79 | }
80 |
81 | variable "ALPINE_SHORT_TAG" {
82 | default = regex_replace(ALPINE_FULL_TAG, "\\.\\d+$", "")
83 | }
84 |
85 | variable "JAVA17_VERSION" {
86 | default = "17.0.15_6"
87 | }
88 |
89 | variable "JAVA21_VERSION" {
90 | default = "21.0.7_6"
91 | }
92 |
93 | variable "BOOKWORM_TAG" {
94 | default = "20250520"
95 | }
96 |
97 | # ---- user-defined functions ----
98 |
99 | # return a tag prefixed by the Jenkins version
100 | function "_tag_jenkins_version" {
101 | params = [tag]
102 | result = notequal(tag, "") ? "${REGISTRY}/${JENKINS_REPO}:${JENKINS_VERSION}-${tag}" : "${REGISTRY}/${JENKINS_REPO}:${JENKINS_VERSION}"
103 | }
104 |
105 | # return a tag optionaly prefixed by the Jenkins version
106 | function "tag" {
107 | params = [prepend_jenkins_version, tag]
108 | result = equal(prepend_jenkins_version, true) ? _tag_jenkins_version(tag) : "${REGISTRY}/${JENKINS_REPO}:${tag}"
109 | }
110 |
111 | # return a weekly optionaly prefixed by the Jenkins version
112 | function "tag_weekly" {
113 | params = [prepend_jenkins_version, tag]
114 | result = equal(LATEST_WEEKLY, "true") ? tag(prepend_jenkins_version, tag) : ""
115 | }
116 |
117 | # return a LTS optionaly prefixed by the Jenkins version
118 | function "tag_lts" {
119 | params = [prepend_jenkins_version, tag]
120 | result = equal(LATEST_LTS, "true") ? tag(prepend_jenkins_version, tag) : ""
121 | }
122 |
123 | # ---- targets ----
124 |
125 | target "alpine_jdk17" {
126 | dockerfile = "alpine/hotspot/Dockerfile"
127 | context = "."
128 | args = {
129 | JENKINS_VERSION = JENKINS_VERSION
130 | JENKINS_SHA = JENKINS_SHA
131 | COMMIT_SHA = COMMIT_SHA
132 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
133 | ALPINE_TAG = ALPINE_FULL_TAG
134 | JAVA_VERSION = JAVA17_VERSION
135 | }
136 | tags = [
137 | tag(true, "alpine-jdk17"),
138 | tag_weekly(false, "alpine-jdk17"),
139 | tag_weekly(false, "alpine${ALPINE_SHORT_TAG}-jdk17"),
140 | tag_lts(false, "lts-alpine-jdk17"),
141 | ]
142 | platforms = ["linux/amd64"]
143 | }
144 |
145 | target "alpine_jdk21" {
146 | dockerfile = "alpine/hotspot/Dockerfile"
147 | context = "."
148 | args = {
149 | JENKINS_VERSION = JENKINS_VERSION
150 | JENKINS_SHA = JENKINS_SHA
151 | COMMIT_SHA = COMMIT_SHA
152 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
153 | ALPINE_TAG = ALPINE_FULL_TAG
154 | JAVA_VERSION = JAVA21_VERSION
155 | }
156 | tags = [
157 | tag(true, "alpine"),
158 | tag(true, "alpine-jdk21"),
159 | tag_weekly(false, "alpine"),
160 | tag_weekly(false, "alpine-jdk21"),
161 | tag_weekly(false, "alpine${ALPINE_SHORT_TAG}-jdk21"),
162 | tag_lts(false, "lts-alpine"),
163 | tag_lts(false, "lts-alpine-jdk21"),
164 | tag_lts(true, "lts-alpine"),
165 | ]
166 | platforms = ["linux/amd64", "linux/arm64"]
167 | }
168 |
169 | target "debian_jdk17" {
170 | dockerfile = "debian/bookworm/hotspot/Dockerfile"
171 | context = "."
172 | args = {
173 | JENKINS_VERSION = JENKINS_VERSION
174 | JENKINS_SHA = JENKINS_SHA
175 | COMMIT_SHA = COMMIT_SHA
176 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
177 | BOOKWORM_TAG = BOOKWORM_TAG
178 | JAVA_VERSION = JAVA17_VERSION
179 | }
180 | tags = [
181 | tag(true, "jdk17"),
182 | tag_weekly(false, "latest-jdk17"),
183 | tag_weekly(false, "jdk17"),
184 | tag_lts(false, "lts-jdk17"),
185 | tag_lts(true, "lts-jdk17")
186 | ]
187 | platforms = ["linux/amd64", "linux/arm64", "linux/s390x", "linux/ppc64le"]
188 | }
189 |
190 | target "debian_jdk21" {
191 | dockerfile = "debian/bookworm/hotspot/Dockerfile"
192 | context = "."
193 | args = {
194 | JENKINS_VERSION = JENKINS_VERSION
195 | JENKINS_SHA = JENKINS_SHA
196 | COMMIT_SHA = COMMIT_SHA
197 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
198 | BOOKWORM_TAG = BOOKWORM_TAG
199 | JAVA_VERSION = JAVA21_VERSION
200 | }
201 | tags = [
202 | tag(true, ""),
203 | tag(true, "jdk21"),
204 | tag_weekly(false, "latest"),
205 | tag_weekly(false, "latest-jdk21"),
206 | tag_weekly(false, "jdk21"),
207 | tag_lts(false, "lts"),
208 | tag_lts(false, "lts-jdk21"),
209 | tag_lts(true, "lts"),
210 | tag_lts(true, "lts-jdk21")
211 | ]
212 | platforms = ["linux/amd64", "linux/arm64", "linux/s390x", "linux/ppc64le"]
213 | }
214 |
215 | target "debian_slim_jdk17" {
216 | dockerfile = "debian/bookworm-slim/hotspot/Dockerfile"
217 | context = "."
218 | args = {
219 | JENKINS_VERSION = JENKINS_VERSION
220 | JENKINS_SHA = JENKINS_SHA
221 | COMMIT_SHA = COMMIT_SHA
222 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
223 | BOOKWORM_TAG = BOOKWORM_TAG
224 | JAVA_VERSION = JAVA17_VERSION
225 | }
226 | tags = [
227 | tag(true, "slim-jdk17"),
228 | tag_weekly(false, "slim-jdk17"),
229 | tag_lts(false, "lts-slim-jdk17"),
230 | ]
231 | platforms = ["linux/amd64"]
232 | }
233 |
234 | target "debian_slim_jdk21" {
235 | dockerfile = "debian/bookworm-slim/hotspot/Dockerfile"
236 | context = "."
237 | args = {
238 | JENKINS_VERSION = JENKINS_VERSION
239 | JENKINS_SHA = JENKINS_SHA
240 | COMMIT_SHA = COMMIT_SHA
241 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
242 | BOOKWORM_TAG = BOOKWORM_TAG
243 | JAVA_VERSION = JAVA21_VERSION
244 | }
245 | tags = [
246 | tag(true, "slim"),
247 | tag(true, "slim-jdk21"),
248 | tag_weekly(false, "slim"),
249 | tag_weekly(false, "slim-jdk21"),
250 | tag_lts(false, "lts-slim"),
251 | tag_lts(false, "lts-slim-jdk21"),
252 | tag_lts(true, "lts-slim"),
253 | ]
254 | platforms = ["linux/amd64", "linux/arm64"]
255 | }
256 |
257 | target "rhel_ubi9_jdk17" {
258 | dockerfile = "rhel/ubi9/hotspot/Dockerfile"
259 | context = "."
260 | args = {
261 | JENKINS_VERSION = JENKINS_VERSION
262 | JENKINS_SHA = JENKINS_SHA
263 | COMMIT_SHA = COMMIT_SHA
264 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
265 | JAVA_VERSION = JAVA17_VERSION
266 | }
267 | tags = [
268 | tag(true, "rhel-ubi9-jdk17"),
269 | tag_weekly(false, "rhel-ubi9-jdk17"),
270 | tag_lts(false, "lts-rhel-ubi9-jdk17"),
271 | tag_lts(true, "lts-rhel-ubi9-jdk17")
272 | ]
273 | platforms = ["linux/amd64", "linux/arm64", "linux/ppc64le"]
274 | }
275 |
276 | target "rhel_ubi9_jdk21" {
277 | dockerfile = "rhel/ubi9/hotspot/Dockerfile"
278 | context = "."
279 | args = {
280 | JENKINS_VERSION = JENKINS_VERSION
281 | JENKINS_SHA = JENKINS_SHA
282 | COMMIT_SHA = COMMIT_SHA
283 | PLUGIN_CLI_VERSION = PLUGIN_CLI_VERSION
284 | JAVA_VERSION = JAVA21_VERSION
285 | }
286 | tags = [
287 | tag(true, "rhel-ubi9-jdk21"),
288 | tag_weekly(false, "rhel-ubi9-jdk21"),
289 | tag_lts(false, "lts-rhel-ubi9-jdk21"),
290 | tag_lts(true, "lts-rhel-ubi9-jdk21")
291 | ]
292 | platforms = ["linux/amd64", "linux/arm64", "linux/ppc64le"]
293 | }
294 |
--------------------------------------------------------------------------------
/jdk-download-url.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Check if at least one argument was passed to the script
4 | # If one argument was passed and JAVA_VERSION is set, assign the argument to OS
5 | # If two arguments were passed, assign them to JAVA_VERSION and OS respectively
6 | # If three arguments were passed, assign them to JAVA_VERSION, OS and ARCHS respectively
7 | # If not, check if JAVA_VERSION and OS are already set. If they're not set, exit the script with an error message
8 | if [ $# -eq 1 ] && [ -n "$JAVA_VERSION" ]; then
9 | OS=$1
10 | elif [ $# -eq 2 ]; then
11 | JAVA_VERSION=$1
12 | OS=$2
13 | elif [ $# -eq 3 ]; then
14 | JAVA_VERSION=$1
15 | OS=$2
16 | ARCHS=$3
17 | elif [ -z "$JAVA_VERSION" ] && [ -z "$OS" ]; then
18 | echo "Error: No Java version and OS specified. Please set the JAVA_VERSION and OS environment variables or pass them as arguments." >&2
19 | exit 1
20 | elif [ -z "$JAVA_VERSION" ]; then
21 | echo "Error: No Java version specified. Please set the JAVA_VERSION environment variable or pass it as an argument." >&2
22 | exit 1
23 | elif [ -z "$OS" ]; then
24 | OS=$1
25 | if [ -z "$OS" ]; then
26 | echo "Error: No OS specified. Please set the OS environment variable or pass it as an argument." >&2
27 | exit 1
28 | fi
29 | fi
30 |
31 | # Check if ARCHS is set. If it's not set, assign the current architecture to it
32 | if [ -z "$ARCHS" ]; then
33 | ARCHS=$(uname -m | sed -e 's/x86_64/x64/' -e 's/armv7l/arm/')
34 | else
35 | # Convert ARCHS to an array
36 | OLD_IFS=$IFS
37 | IFS=','
38 | set -- "$ARCHS"
39 | ARCHS=""
40 | for arch in "$@"; do
41 | ARCHS="$ARCHS $arch"
42 | done
43 | IFS=$OLD_IFS
44 | fi
45 |
46 | # Check if jq and curl are installed
47 | # If they are not installed, exit the script with an error message
48 | if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then
49 | echo "jq and curl are required but not installed. Exiting with status 1." >&2
50 | exit 1
51 | fi
52 |
53 | # Replace underscores with plus signs in JAVA_VERSION
54 | ARCHIVE_DIRECTORY=$(echo "$JAVA_VERSION" | tr '_' '+')
55 |
56 | # URL encode ARCHIVE_DIRECTORY
57 | ENCODED_ARCHIVE_DIRECTORY=$(echo "$ARCHIVE_DIRECTORY" | xargs -I {} printf %s {} | jq "@uri" -jRr)
58 |
59 | # Determine the OS type for the URL
60 | OS_TYPE="linux"
61 | if [ "$OS" = "alpine" ]; then
62 | OS_TYPE="alpine-linux"
63 | fi
64 | if [ "$OS" = "windows" ]; then
65 | OS_TYPE="windows"
66 | fi
67 |
68 | # Initialize a variable to store the URL for the first architecture
69 | FIRST_ARCH_URL=""
70 |
71 | # Loop over the array of architectures
72 | for ARCH in $ARCHS; do
73 | # Fetch the download URL from the Adoptium API
74 | URL="https://api.adoptium.net/v3/binary/version/jdk-${ENCODED_ARCHIVE_DIRECTORY}/${OS_TYPE}/${ARCH}/jdk/hotspot/normal/eclipse?project=jdk"
75 |
76 | if ! RESPONSE=$(curl -fsI "$URL"); then
77 | echo "Error: Failed to fetch the URL for architecture ${ARCH} from ${URL}. Exiting with status 1." >&2
78 | echo "Response: $RESPONSE" >&2
79 | exit 1
80 | fi
81 |
82 | # Extract the redirect URL from the HTTP response
83 | REDIRECTED_URL=$(echo "$RESPONSE" | grep -i location | awk '{print $2}' | tr -d '\r')
84 |
85 | # If no redirect URL was found, exit the script with an error message
86 | if [ -z "$REDIRECTED_URL" ]; then
87 | echo "Error: No redirect URL found for architecture ${ARCH} from ${URL}. Exiting with status 1." >&2
88 | echo "Response: $RESPONSE" >&2
89 | exit 1
90 | fi
91 |
92 | # Use curl to check if the URL is reachable
93 | # If the URL is not reachable, print an error message and exit the script with status 1
94 | if ! curl -v -fs "$REDIRECTED_URL" >/dev/null 2>&1; then
95 | echo "${REDIRECTED_URL}" is not reachable for architecture "${ARCH}". >&2
96 | exit 1
97 | fi
98 |
99 | # If FIRST_ARCH_URL is empty, store the current URL
100 | if [ -z "$FIRST_ARCH_URL" ]; then
101 | FIRST_ARCH_URL=$REDIRECTED_URL
102 | fi
103 | done
104 |
105 | # If all downloads are successful, print the URL for the first architecture
106 | echo "$FIRST_ARCH_URL"
107 |
--------------------------------------------------------------------------------
/jdk-download.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -x
3 | # Check if curl and tar are installed
4 | if ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1 ; then
5 | echo "curl and tar are required but not installed. Exiting with status 1." >&2
6 | exit 1
7 | fi
8 |
9 | # Set the OS to "standard" by default
10 | OS="standard"
11 |
12 | # If a second argument is provided, use it as the OS
13 | if [ $# -eq 1 ]; then
14 | OS=$1
15 | fi
16 |
17 | # Call jdk-download-url.sh with JAVA_VERSION and OS as arguments
18 | # The two scripts should be in the same directory.
19 | # That's why we're trying to find the directory of the current script and use it to call the other script.
20 | SCRIPT_DIR=$(cd "$(dirname "$0")" || exit; pwd)
21 | if ! DOWNLOAD_URL=$("${SCRIPT_DIR}"/jdk-download-url.sh "${JAVA_VERSION}" "${OS}"); then
22 | echo "Error: Failed to fetch the URL. Exiting with status 1." >&2
23 | exit 1
24 | fi
25 |
26 | # Use curl to download the JDK archive from the URL
27 | if ! curl --silent --location --output /tmp/jdk.tar.gz "${DOWNLOAD_URL}"; then
28 | echo "Error: Failed to download the JDK archive. Exiting with status 1." >&2
29 | exit 1
30 | fi
31 |
32 | # Extract the archive to the /opt/ directory
33 | if ! tar -xzf /tmp/jdk.tar.gz -C /opt/; then
34 | echo "Error: Failed to extract the JDK archive. Exiting with status 1." >&2
35 | exit 1
36 | fi
37 |
38 | # Get the name of the extracted directory
39 | EXTRACTED_DIR=$(tar -tzf /tmp/jdk.tar.gz | head -n 1 | cut -f1 -d"/")
40 |
41 | # Rename the extracted directory to /opt/jdk-${JAVA_VERSION}
42 | if ! mv "/opt/${EXTRACTED_DIR}" "/opt/jdk-${JAVA_VERSION}"; then
43 | echo "Error: Failed to rename the extracted directory. Exiting with status 1." >&2
44 | exit 1
45 | fi
46 |
47 | # Remove the downloaded archive
48 | if ! rm -f /tmp/jdk.tar.gz; then
49 | echo "Error: Failed to remove the downloaded archive. Exiting with status 1." >&2
50 | exit 1
51 | fi
52 |
--------------------------------------------------------------------------------
/jenkins-plugin-cli.ps1:
--------------------------------------------------------------------------------
1 | & java "$env:JAVA_OPTS" -jar C:/ProgramData/Jenkins/jenkins-plugin-manager.jar $args
--------------------------------------------------------------------------------
/jenkins-plugin-cli.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # read JAVA_OPTS into array to avoid need for eval (and associated vulnerabilities)
4 | java_opts_array=()
5 | while IFS= read -r -d '' item; do
6 | java_opts_array+=( "$item" )
7 | done < <([[ $JAVA_OPTS ]] && xargs printf '%s\0' <<<"$JAVA_OPTS")
8 |
9 | exec java "${java_opts_array[@]}" -jar /opt/jenkins-plugin-manager.jar "$@"
10 |
--------------------------------------------------------------------------------
/jenkins-support:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eu
2 |
3 | : "${REF:="/usr/share/jenkins/ref"}"
4 |
5 | # compare if version1 < version2
6 | versionLT() {
7 | local v1; v1=$(echo "$1" | cut -d '-' -f 1 )
8 | local q1; q1=$(echo "$1" | cut -s -d '-' -f 2- )
9 | local v2; v2=$(echo "$2" | cut -d '-' -f 1 )
10 | local q2; q2=$(echo "$2" | cut -s -d '-' -f 2- )
11 | if [ "$v1" = "$v2" ]; then
12 | if [ "$q1" = "$q2" ]; then
13 | return 1
14 | else
15 | if [ -z "$q1" ]; then
16 | return 1
17 | else
18 | if [ -z "$q2" ]; then
19 | return 0
20 | else
21 | [ "$q1" = "$(echo -e "$q1\n$q2" | sort -V | head -n1)" ]
22 | fi
23 | fi
24 | fi
25 | else
26 | [ "$v1" = "$(echo -e "$v1\n$v2" | sort -V | head -n1)" ]
27 | fi
28 | }
29 |
30 | # returns a plugin version from a plugin archive
31 | get_plugin_version() {
32 | local archive; archive=$1
33 | local version; version=$(unzip -p "$archive" META-INF/MANIFEST.MF | grep "^Plugin-Version: " | sed -e 's#^Plugin-Version: ##')
34 | version=${version%%[[:space:]]}
35 | echo "$version"
36 | }
37 |
38 | # Copy files from /usr/share/jenkins/ref into $JENKINS_HOME
39 | # So the initial JENKINS-HOME is set with expected content.
40 | # Don't override, as this is just a reference setup, and use from UI
41 | # can then change this, upgrade plugins, etc.
42 | copy_reference_file() {
43 | f="${1%/}"
44 | b="${f%.override}"
45 | rel="${b#"$REF/"}"
46 | version_marker="${rel}.version_from_image"
47 | dir=$(dirname "${rel}")
48 | local action;
49 | local reason;
50 | local container_version;
51 | local image_version;
52 | local marker_version;
53 | local log; log=false
54 | if [[ ${rel} == plugins/*.jpi ]]; then
55 | container_version=$(get_plugin_version "$JENKINS_HOME/${rel}")
56 | image_version=$(get_plugin_version "${f}")
57 | if [[ -e $JENKINS_HOME/${version_marker} ]]; then
58 | marker_version=$(cat "$JENKINS_HOME/${version_marker}")
59 | if versionLT "$marker_version" "$container_version"; then
60 | if ( versionLT "$container_version" "$image_version" && [[ -n $PLUGINS_FORCE_UPGRADE ]]); then
61 | action="UPGRADED"
62 | reason="Manually upgraded version ($container_version) is older than image version $image_version"
63 | log=true
64 | else
65 | action="SKIPPED"
66 | reason="Installed version ($container_version) has been manually upgraded from initial version ($marker_version)"
67 | log=true
68 | fi
69 | else
70 | if [[ "$image_version" == "$container_version" ]]; then
71 | action="SKIPPED"
72 | reason="Version from image is the same as the installed version $image_version"
73 | else
74 | if versionLT "$image_version" "$container_version"; then
75 | action="SKIPPED"
76 | log=true
77 | reason="Image version ($image_version) is older than installed version ($container_version)"
78 | else
79 | action="UPGRADED"
80 | log=true
81 | reason="Image version ($image_version) is newer than installed version ($container_version)"
82 | fi
83 | fi
84 | fi
85 | else
86 | if [[ -n "$TRY_UPGRADE_IF_NO_MARKER" ]]; then
87 | if [[ "$image_version" == "$container_version" ]]; then
88 | action="SKIPPED"
89 | reason="Version from image is the same as the installed version $image_version (no marker found)"
90 | # Add marker for next time
91 | echo "$image_version" > "$JENKINS_HOME/${version_marker}"
92 | else
93 | if versionLT "$image_version" "$container_version"; then
94 | action="SKIPPED"
95 | log=true
96 | reason="Image version ($image_version) is older than installed version ($container_version) (no marker found)"
97 | else
98 | action="UPGRADED"
99 | log=true
100 | reason="Image version ($image_version) is newer than installed version ($container_version) (no marker found)"
101 | fi
102 | fi
103 | fi
104 | fi
105 | if [[ ! -e $JENKINS_HOME/${rel} || "$action" == "UPGRADED" || $f = *.override ]]; then
106 | action=${action:-"INSTALLED"}
107 | log=true
108 | mkdir -p "$JENKINS_HOME/${dir}"
109 | cp -pr "${f}" "$JENKINS_HOME/${rel}";
110 | # pin plugins on initial copy
111 | touch "$JENKINS_HOME/${rel}.pinned"
112 | echo "$image_version" > "$JENKINS_HOME/${version_marker}"
113 | reason=${reason:-$image_version}
114 | else
115 | action=${action:-"SKIPPED"}
116 | fi
117 | else
118 | if [[ ! -e $JENKINS_HOME/${rel} || $f = *.override ]]
119 | then
120 | action="INSTALLED"
121 | log=true
122 | mkdir -p "$JENKINS_HOME/${dir}"
123 | cp -pr "$(realpath "${f}")" "$JENKINS_HOME/${rel}";
124 | else
125 | action="SKIPPED"
126 | fi
127 | fi
128 | if [[ -n "$VERBOSE" || "$log" == "true" ]]; then
129 | if [ -z "$reason" ]; then
130 | echo "$action $rel" >> "$COPY_REFERENCE_FILE_LOG"
131 | else
132 | echo "$action $rel : $reason" >> "$COPY_REFERENCE_FILE_LOG"
133 | fi
134 | fi
135 | }
136 |
137 | # Retries a command a configurable number of times with backoff.
138 | #
139 | # The retry count is given by ATTEMPTS (default 60), the initial backoff
140 | # timeout is given by TIMEOUT in seconds (default 1.)
141 | #
142 | function retry_command() {
143 | local max_attempts=${ATTEMPTS-3}
144 | local timeout=${TIMEOUT-1}
145 | local success_timeout=${SUCCESS_TIMEOUT-1}
146 | local max_success_attempt=${SUCCESS_ATTEMPTS-1}
147 | local attempt=0
148 | local success_attempt=0
149 | local exitCode=0
150 |
151 | while (( attempt < max_attempts ))
152 | do
153 | set +e
154 | "$@"
155 | exitCode=$?
156 | set -e
157 |
158 | if [[ $exitCode == 0 ]]
159 | then
160 | success_attempt=$(( success_attempt + 1 ))
161 | if (( success_attempt >= max_success_attempt))
162 | then
163 | break
164 | else
165 | sleep "$success_timeout"
166 | continue
167 | fi
168 | fi
169 |
170 | echo "$(date -u '+%T') Failure ($exitCode) Retrying in $timeout seconds..." 1>&2
171 | sleep "$timeout"
172 | success_attempt=0
173 | attempt=$(( attempt + 1 ))
174 | timeout=$(( timeout ))
175 | done
176 |
177 | if [[ $exitCode != 0 ]]
178 | then
179 | echo "$(date -u '+%T') Failed in the last attempt ($*)" 1>&2
180 | fi
181 |
182 | return $exitCode
183 | }
184 |
--------------------------------------------------------------------------------
/jenkins-support.psm1:
--------------------------------------------------------------------------------
1 |
2 | # compare if version1 < version2
3 | function Compare-VersionLessThan($version1, $version2) {
4 | $temp = $version1.Split('-')
5 | $v1 = $temp[0].Trim()
6 | $q1 = ''
7 | if($temp.Length -gt 1) {
8 | $q1 = $temp[1].Trim()
9 | }
10 |
11 | $temp = $version2.Split('-')
12 | $v2 = $temp[0].Trim()
13 | $q2 = ''
14 | if($temp.Length -gt 1) {
15 | $q2 = $temp[1].Trim()
16 | }
17 |
18 | if($v1 -eq $v2) {
19 | # this works even if both sides are "latest"
20 | if($q1 -eq $q2) {
21 | return $false
22 | } else {
23 | if([System.String]::IsNullOrWhiteSpace($q1)) {
24 | return $false
25 | } else {
26 | if([System.String]::IsNullOrWhiteSpace($q2)) {
27 | return $true
28 | } else {
29 | return ($q1 -eq $("$q1","$q2" | Sort-Object | Select-Object -First 1))
30 | }
31 | }
32 | }
33 | }
34 |
35 | if($v1 -eq "latest") {
36 | return $false
37 | } elseif($v2 -eq "latest") {
38 | return $true
39 | }
40 |
41 | return ($v1 -eq $("$v1","$v2" | Sort-Object {[version] $_} | Select-Object -first 1))
42 | }
43 |
44 | function Get-EnvOrDefault($name, $def) {
45 | $entry = Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Select-Object -First 1
46 | if(($null -ne $entry) -and ![System.String]::IsNullOrWhiteSpace($entry.Value)) {
47 | return $entry.Value
48 | }
49 | return $def
50 | }
51 |
52 | function Expand-Zip($archive, $file) {
53 | # load ZIP methods
54 | Add-Type -AssemblyName System.IO.Compression.FileSystem
55 |
56 | Write-Verbose "Unzipping $file from $archive"
57 |
58 | $contents = ""
59 |
60 | if(Test-Path $archive) {
61 | # open ZIP archive for reading
62 | $zip = [System.IO.Compression.ZipFile]::OpenRead($archive)
63 |
64 | if($null -ne $zip) {
65 | $entry = $zip.GetEntry($file)
66 | if($null -ne $entry) {
67 | $reader = New-Object -TypeName System.IO.StreamReader -ArgumentList $entry.Open()
68 | $contents = $reader.ReadToEnd()
69 | $reader.Dispose()
70 | }
71 |
72 | # close ZIP file
73 | $zip.Dispose()
74 | }
75 | }
76 |
77 | return $contents
78 | }
79 |
80 | # returns a plugin version from a plugin archive
81 | function Get-PluginVersion($archive) {
82 | $archive = $archive.Trim()
83 | Write-Verbose "Getting plugin version for $archive"
84 | if(-not (Test-Path $archive)) {
85 | return ""
86 | }
87 |
88 | $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
89 | return $version.Trim()
90 | }
91 |
92 | # Copy files from C:/ProgramData/Jenkins/Reference/ into $JENKINS_HOME
93 | # So the initial JENKINS-HOME is set with expected content.
94 | # Don't override, as this is just a reference setup, and use from UI
95 | # can then change this, upgrade plugins, etc.
96 | function Copy-ReferenceFile($file) {
97 | $action = ""
98 | $reason = ""
99 | $log = $false
100 | $refDir = Get-EnvOrDefault 'REF' 'C:/ProgramData/Jenkins/Reference'
101 |
102 | if(-not (Test-Path $refDir)) {
103 | return
104 | }
105 |
106 | Push-Location $refDir
107 | $rel = Resolve-Path -Relative -Path $file
108 | Pop-Location
109 | $dir = Split-Path -Parent $rel
110 |
111 | if($file -match "plugins[\\/].*\.jpi") {
112 | $fileName = Split-Path -Leaf $file
113 | $versionMarker = (Join-Path $env:JENKINS_HOME (Join-Path "plugins" "${fileName}.version_from_image"))
114 | $containerVersion = Get-PluginVersion (Join-Path $env:JENKINS_HOME $rel)
115 | $imageVersion = Get-PluginVersion $file
116 | if(Test-Path $versionMarker) {
117 | $markerVersion = (Get-Content -Raw $versionMarker).Trim()
118 | if(Compare-VersionLessThan $markerVersion $containerVersion) {
119 | if((Compare-VersionLessThan $containerVersion $imageVersion) -and ![System.String]::IsNullOrWhiteSpace($env:PLUGINS_FORCE_UPGRADE)) {
120 | $action = "UPGRADED"
121 | $reason="Manually upgraded version ($containerVersion) is older than image version $imageVersion"
122 | $log=$true
123 | } else {
124 | $action="SKIPPED"
125 | $reason="Installed version ($containerVersion) has been manually upgraded from initial version ($markerVersion)"
126 | $log=$true
127 | }
128 | } else {
129 | if($imageVersion -eq $containerVersion) {
130 | $action = "SKIPPED"
131 | $reason = "Version from image is the same as the installed version $imageVersion"
132 | } else {
133 | if(Compare-VersionLessThan $imageVersion $containerVersion) {
134 | $action = "SKIPPED"
135 | $log = $true
136 | $reason = "Image version ($imageVersion) is older than installed version ($containerVersion)"
137 | } else {
138 | $action="UPGRADED"
139 | $log=$true
140 | $reason="Image version ($imageVersion) is newer than installed version ($containerVersion)"
141 | }
142 | }
143 | }
144 | } else {
145 | if(![System.String]::IsNullOrWhiteSpace($env:TRY_UPGRADE_IF_NO_MARKER)) {
146 | if($imageVersion -eq $containerVersion) {
147 | $action = "SKIPPED"
148 | $reason = "Version from image is the same as the installed version $imageVersion (no marker found)"
149 | # Add marker for next time
150 | Add-Content -Path $versionMarker -Value $imageVersion
151 | } else {
152 | if(Compare-VersionLessThan $imageVersion $containerVersion) {
153 | $action = "SKIPPED"
154 | $log = $true
155 | $reason = "Image version ($imageVersion) is older than installed version ($containerVersion) (no marker found)"
156 | } else {
157 | $action = "UPGRADED"
158 | $log = $true
159 | $reason = "Image version ($imageVersion) is newer than installed version ($containerVersion) (no marker found)"
160 | }
161 | }
162 | }
163 | }
164 |
165 | if((-not (Test-Path (Join-Path $env:JENKINS_HOME $rel))) -or ($action -eq "UPGRADED") -or ($file -match "\.override")) {
166 | if([System.String]::IsNullOrWhiteSpace($action)) {
167 | $action = "INSTALLED"
168 | }
169 | $log=$true
170 |
171 | if(-not (Test-Path (Join-Path $env:JENKINS_HOME $dir))) {
172 | New-Item -ItemType Directory -Path (Join-Path $env:JENKINS_HOME $dir)
173 | }
174 | Copy-Item $file (Join-Path $env:JENKINS_HOME $rel)
175 | # pin plugins on initial copy
176 | Write-Output $null >> (Join-Path $env:JENKINS_HOME "${rel}.pinned")
177 | Add-Content -Path $versionMarker -Value $imageVersion
178 | if([System.String]::IsNullOrWhiteSpace($reason)) {
179 | $reason = $imageVersion
180 | }
181 | } else {
182 | if([System.String]::IsNullOrWhiteSpace($action)) {
183 | $action = "SKIPPED"
184 | }
185 | }
186 | } else {
187 | if((-not (Test-Path (Join-Path $env:JENKINS_HOME $rel))) -or ($file -match "\.override")) {
188 | $action = "INSTALLED"
189 | $log = $true
190 | if(-not (Test-Path (Join-Path $env:JENKINS_HOME (Split-Path -Parent $rel)))) {
191 | New-Item -ItemType Directory (Join-Path $env:JENKINS_HOME (Split-Path -Parent $rel))
192 | }
193 | Copy-Item $file (Join-Path $env:JENKINS_HOME $rel)
194 | } else {
195 | $action="SKIPPED"
196 | }
197 | }
198 |
199 | if(![System.String]::IsNullOrWhiteSpace($env:VERBOSE) -or $log) {
200 | if([System.String]::IsNullOrWhiteSpace($reason)) {
201 | Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "$action $rel"
202 | } else {
203 | Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "$action $rel : $reason"
204 | }
205 | }
206 | }
--------------------------------------------------------------------------------
/jenkins.ps1:
--------------------------------------------------------------------------------
1 | Import-Module -Force -DisableNameChecking C:/ProgramData/Jenkins/jenkins-support.psm1
2 |
3 | $JENKINS_WAR = Get-EnvOrDefault 'JENKINS_WAR' 'C:/ProgramData/Jenkins/jenkins.war'
4 | $JENKINS_HOME = Get-EnvOrDefault 'JENKINS_HOME' 'C:/ProgramData/Jenkins/JenkinsHome'
5 | $COPY_REFERENCE_FILE_LOG = Get-EnvOrDefault 'COPY_REFERENCE_FILE_LOG' "$($JENKINS_HOME)/copy_reference_file.log"
6 |
7 | try {
8 | [System.IO.File]::OpenWrite($COPY_REFERENCE_FILE_LOG).Close()
9 | } catch {
10 | Write-Error "Can not write to $COPY_REFERENCE_FILE_LOG. Wrong volume permissions?`n`n$_"
11 | exit 1
12 | }
13 |
14 | Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "--- Copying files at $(Get-Date)"
15 | Get-ChildItem -Recurse -File -Path 'C:/ProgramData/Jenkins/Reference' | ForEach-Object { Copy-ReferenceFile $_.FullName }
16 | Add-Content -Path $COPY_REFERENCE_FILE_LOG -Value "--- Copied files finished at $(Get-Date)"
17 |
18 | # if `docker run` first argument starts with `--` the user is passing jenkins launcher arguments
19 | if(($args.Count -eq 0) -or ($args[0] -match "^--.*")) {
20 |
21 | # read JAVA_OPTS and JENKINS_OPTS into arrays to avoid need for eval (and associated vulnerabilities)
22 | $java_opts_array = ($env:JAVA_OPTS -split ' ') + ($env:JENKINS_JAVA_OPTS -split ' ')
23 |
24 | $agent_port_property='jenkins.model.Jenkins.slaveAgentPort'
25 | if(![System.String]::IsNullOrWhiteSpace($env:JENKINS_AGENT_PORT) -and !($java_opts_array -match "$agent_port_property")) {
26 | $java_opts_array += "-D`"$agent_port_property=$env:JENKINS_AGENT_PORT`""
27 | }
28 |
29 | if($null -ne $env:DEBUG) {
30 | $java_opts_array += '-Xdebug'
31 | $java_opts_array += '-Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=y'
32 | }
33 |
34 | $jenkins_opts_array = $env:JENKINS_OPTS -split ' '
35 | $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"
36 | if($null -ne $proc) {
37 | $proc.WaitForExit()
38 | }
39 | } else {
40 | # As argument is not jenkins, assume user wants to run their own process, for example a `powershell` shell to explore this image
41 | Invoke-Expression "$args"
42 | exit $lastExitCode
43 | }
44 |
--------------------------------------------------------------------------------
/jenkins.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | : "${JENKINS_WAR:="/usr/share/jenkins/jenkins.war"}"
4 | : "${JENKINS_HOME:="/var/jenkins_home"}"
5 |
6 | if [[ -n "${PRE_CLEAR_INIT_GROOVY_D}" ]]; then
7 | rm -rf "${JENKINS_HOME}/init.groovy.d"
8 | fi
9 |
10 | : "${COPY_REFERENCE_FILE_LOG:="${JENKINS_HOME}/copy_reference_file.log"}"
11 | : "${REF:="/usr/share/jenkins/ref"}"
12 |
13 | if ! [ -r "${JENKINS_HOME}" ] || ! [ -w "${JENKINS_HOME}" ]; then
14 | echo "INSTALL WARNING: User: ${USER} missing rw permissions on JENKINS_HOME: ${JENKINS_HOME}"
15 | fi
16 | touch "${COPY_REFERENCE_FILE_LOG}" || { echo "Can not write to ${COPY_REFERENCE_FILE_LOG}. Wrong volume permissions?"; exit 1; }
17 | echo "--- Copying files at $(date)" >> "$COPY_REFERENCE_FILE_LOG"
18 | find "${REF}" \( -type f -o -type l \) -exec bash -c '. /usr/local/bin/jenkins-support; for arg; do copy_reference_file "$arg"; done' _ {} +
19 | echo "--- Copied files finished at $(date)" >> "$COPY_REFERENCE_FILE_LOG"
20 |
21 | # if `docker run` first argument start with `--` the user is passing jenkins launcher arguments
22 | if [[ $# -lt 1 ]] || [[ "$1" == "--"* ]]; then
23 |
24 | # shellcheck disable=SC2001
25 | effective_java_opts=$(sed -e 's/^ $//' <<<"$JAVA_OPTS $JENKINS_JAVA_OPTS")
26 |
27 | # read JAVA_OPTS and JENKINS_OPTS into arrays to avoid need for eval (and associated vulnerabilities)
28 | java_opts_array=()
29 | while IFS= read -r -d '' item; do
30 | java_opts_array+=( "$item" )
31 | done < <([[ $effective_java_opts ]] && xargs printf '%s\0' <<<"$effective_java_opts")
32 |
33 | readonly agent_port_property='jenkins.model.Jenkins.slaveAgentPort'
34 | if [ -n "${JENKINS_SLAVE_AGENT_PORT:-}" ] && [[ "${effective_java_opts:-}" != *"${agent_port_property}"* ]]; then
35 | java_opts_array+=( "-D${agent_port_property}=${JENKINS_SLAVE_AGENT_PORT}" )
36 | fi
37 |
38 | readonly lifecycle_property='hudson.lifecycle'
39 | if [[ "${JAVA_OPTS:-}" != *"${lifecycle_property}"* ]]; then
40 | java_opts_array+=( "-D${lifecycle_property}=hudson.lifecycle.ExitLifecycle" )
41 | fi
42 |
43 | if [[ "$DEBUG" ]] ; then
44 | java_opts_array+=( \
45 | '-Xdebug' \
46 | '-Xrunjdwp:server=y,transport=dt_socket,address=*:5005,suspend=y' \
47 | )
48 | fi
49 |
50 | jenkins_opts_array=( )
51 | while IFS= read -r -d '' item; do
52 | jenkins_opts_array+=( "$item" )
53 | done < <([[ $JENKINS_OPTS ]] && xargs printf '%s\0' <<<"$JENKINS_OPTS")
54 |
55 | exec java -Duser.home="$JENKINS_HOME" "${java_opts_array[@]}" -jar "${JENKINS_WAR}" "${jenkins_opts_array[@]}" "$@"
56 | fi
57 |
58 | # As argument is not jenkins, assume user wants to run a different process, for example a `bash` shell to explore this image
59 | exec "$@"
60 |
--------------------------------------------------------------------------------
/make.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param(
3 | [Parameter(Position=1)]
4 | [String] $Target = 'build',
5 | [String] $JenkinsVersion = '2.504',
6 | [switch] $DryRun = $false
7 | )
8 |
9 | $ErrorActionPreference = 'Stop'
10 | $ProgressPreference = 'SilentlyContinue' # Disable Progress bar for faster downloads
11 |
12 | $Repository = 'jenkins'
13 | $Organisation = 'jenkins4eval'
14 | $ImageType = 'windowsservercore-ltsc2019' # -
15 |
16 | if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO)) {
17 | $Repository = $env:DOCKERHUB_REPO
18 | }
19 |
20 | if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_ORGANISATION)) {
21 | $Organisation = $env:DOCKERHUB_ORGANISATION
22 | }
23 |
24 | if(![String]::IsNullOrWhiteSpace($env:JENKINS_VERSION)) {
25 | $JenkinsVersion = $env:JENKINS_VERSION
26 | }
27 |
28 | if(![String]::IsNullOrWhiteSpace($env:IMAGE_TYPE)) {
29 | $ImageType = $env:IMAGE_TYPE
30 | }
31 |
32 | $env:DOCKERHUB_ORGANISATION = "$Organisation"
33 | $env:DOCKERHUB_REPO = "$Repository"
34 | $env:JENKINS_VERSION = "$JenkinsVersion"
35 |
36 | # Add 'lts-' prefix to LTS tags not including Jenkins version
37 | # Compared to weekly releases, LTS releases include an additional build number in their version
38 | # Note: the ':' separator is included as trying to set an environment variable to empty on Windows unset it.
39 | $env:SEPARATOR_LTS_PREFIX = ':'
40 | if ($JenkinsVersion.Split('.').Count -eq 3) {
41 | $env:SEPARATOR_LTS_PREFIX = ':lts-'
42 | }
43 |
44 | $items = $ImageType.Split('-')
45 | $env:WINDOWS_FLAVOR = $items[0]
46 | $env:WINDOWS_VERSION = $items[1]
47 | $env:TOOLS_WINDOWS_VERSION = $items[1]
48 | if ($items[1] -eq 'ltsc2019') {
49 | # There are no eclipse-temurin:*-ltsc2019 or mcr.microsoft.com/powershell:*-ltsc2019 docker images unfortunately, only "1809" ones
50 | $env:TOOLS_WINDOWS_VERSION = '1809'
51 | }
52 |
53 | # Retrieve the sha256 corresponding to the JENKINS_VERSION
54 | $jenkinsShaURL = 'https://repo.jenkins-ci.org/releases/org/jenkins-ci/main/jenkins-war/{0}/jenkins-war-{0}.war.sha256' -f $env:JENKINS_VERSION
55 | $webClient = New-Object System.Net.WebClient
56 | $env:JENKINS_SHA = $webClient.DownloadString($jenkinsShaURL).ToUpper()
57 |
58 | $env:COMMIT_SHA=$(git rev-parse HEAD)
59 |
60 | $baseDockerCmd = 'docker-compose --file=build-windows.yaml'
61 | $baseDockerBuildCmd = '{0} build --parallel --pull' -f $baseDockerCmd
62 |
63 | Write-Host "= PREPARE: List of $Organisation/$Repository images and tags to be processed:"
64 | Invoke-Expression "$baseDockerCmd config"
65 |
66 | Write-Host '= BUILD: Building all images...'
67 | switch ($DryRun) {
68 | $true { Write-Host "(dry-run) $baseDockerBuildCmd" }
69 | $false { Invoke-Expression $baseDockerBuildCmd }
70 | }
71 | Write-Host '= BUILD: Finished building all images.'
72 |
73 | if($lastExitCode -ne 0 -and !$DryRun) {
74 | exit $lastExitCode
75 | }
76 |
77 | function Test-Image {
78 | param (
79 | $ImageName
80 | )
81 |
82 | Write-Host "= TEST: Testing image ${ImageName}:"
83 |
84 | $env:CONTROLLER_IMAGE = $ImageName
85 | $env:DOCKERFILE = 'windows/{0}/hotspot/Dockerfile' -f $env:WINDOWS_FLAVOR
86 |
87 | if (Test-Path ".\target\$ImageName") {
88 | Remove-Item -Recurse -Force ".\target\$ImageName"
89 | }
90 | New-Item -Path ".\target\$ImageName" -Type Directory | Out-Null
91 | $configuration.TestResult.OutputPath = ".\target\$ImageName\junit-results.xml"
92 |
93 | $TestResults = Invoke-Pester -Configuration $configuration
94 | $failed = $false
95 | if ($TestResults.FailedCount -gt 0) {
96 | Write-Host "There were $($TestResults.FailedCount) failed tests in $ImageName"
97 | $failed = $true
98 | } else {
99 | Write-Host "There were $($TestResults.PassedCount) passed tests out of $($TestResults.TotalCount) in $ImageName"
100 | }
101 |
102 | Remove-Item env:\CONTROLLER_IMAGE
103 | Remove-Item env:\DOCKERFILE
104 |
105 | return $failed
106 | }
107 |
108 | if($target -eq 'test') {
109 | if ($DryRun) {
110 | Write-Host '(dry-run) test'
111 | } else {
112 | # Only fail the run afterwards in case of any test failures
113 | $testFailed = $false
114 | $mod = Get-InstalledModule -Name Pester -MinimumVersion 5.3.0 -MaximumVersion 5.3.3 -ErrorAction SilentlyContinue
115 | if($null -eq $mod) {
116 | $module = 'c:\Program Files\WindowsPowerShell\Modules\Pester'
117 | if(Test-Path $module) {
118 | takeown /F $module /A /R
119 | icacls $module /reset
120 | icacls $module /grant Administrators:'F' /inheritance:d /T
121 | Remove-Item -Path $module -Recurse -Force -Confirm:$false
122 | }
123 | Install-Module -Force -Name Pester -Verbose -MaximumVersion 5.3.3
124 | }
125 |
126 | Import-Module -Verbose Pester
127 | Write-Host '= TEST: Setting up Pester environment...'
128 | $configuration = [PesterConfiguration]::Default
129 | $configuration.Run.PassThru = $true
130 | $configuration.Run.Path = '.\tests'
131 | $configuration.Run.Exit = $true
132 | $configuration.TestResult.Enabled = $true
133 | $configuration.TestResult.OutputFormat = 'JUnitXml'
134 | $configuration.Output.Verbosity = 'Diagnostic'
135 | $configuration.CodeCoverage.Enabled = $false
136 |
137 | Write-Host '= TEST: Testing all images...'
138 | # Only fail the run afterwards in case of any test failures
139 | $testFailed = $false
140 | Invoke-Expression "$baseDockerCmd config" | yq '.services[].image' | ForEach-Object {
141 | $testFailed = $testFailed -or (Test-Image $_.split(':')[1])
142 | }
143 |
144 | # Fail if any test failures
145 | if($testFailed -ne $false) {
146 | Write-Error 'Test stage failed!'
147 | exit 1
148 | } else {
149 | Write-Host 'Test stage passed!'
150 | }
151 | }
152 | }
153 |
154 | if($target -eq "publish") {
155 | Write-Host "= PUBLISH: push all images and tags"
156 | switch($DryRun) {
157 | $true { Write-Host "(dry-run) $baseDockerCmd push" }
158 | $false { Invoke-Expression "$baseDockerCmd push" }
159 | }
160 |
161 | # Fail if any issues when publising the docker images
162 | if($lastExitCode -ne 0) {
163 | Write-Error "= PUBLISH: failed!"
164 | exit 1
165 | }
166 | }
167 |
168 | if($lastExitCode -ne 0 -and !$DryRun) {
169 | Write-Error 'Build failed!'
170 | } else {
171 | Write-Host 'Build finished successfully'
172 | }
173 | exit $lastExitCode
174 |
--------------------------------------------------------------------------------
/rhel/ubi9/hotspot/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM registry.access.redhat.com/ubi9/ubi:9.6-1747219013 AS jre-build
2 |
3 | ARG JAVA_VERSION=17.0.15_6
4 |
5 | SHELL ["/bin/bash", "-o", "pipefail", "-c"]
6 |
7 | COPY jdk-download-url.sh /usr/bin/jdk-download-url.sh
8 | COPY jdk-download.sh /usr/bin/jdk-download.sh
9 |
10 | RUN dnf install --disableplugin=subscription-manager --setopt=install_weak_deps=0 --setopt=tsflags=nodocs --allowerasing -y \
11 | ca-certificates \
12 | curl \
13 | jq \
14 | && dnf clean --disableplugin=subscription-manager all \
15 | && /usr/bin/jdk-download.sh
16 |
17 | ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}"
18 |
19 | # Generate smaller java runtime without unneeded files
20 | # for now we include the full module path to maintain compatibility
21 | # while still saving space (approx 200mb from the full distribution)
22 | RUN case "$(jlink --version 2>&1)" in \
23 | "17."*) set -- "--compress=2" ;; \
24 | # the compression argument is different for JDK21
25 | "21."*) set -- "--compress=zip-6" ;; \
26 | *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \
27 | esac; \
28 | jlink \
29 | --strip-java-debug-attributes \
30 | "$1" \
31 | --add-modules ALL-MODULE-PATH \
32 | --no-man-pages \
33 | --no-header-files \
34 | --output /javaruntime
35 |
36 | FROM registry.access.redhat.com/ubi9/ubi:9.6-1747219013 AS controller
37 |
38 | ENV LANG=C.UTF-8
39 |
40 | ARG TARGETARCH
41 | ARG COMMIT_SHA
42 |
43 | RUN dnf install --disableplugin=subscription-manager --setopt=install_weak_deps=0 --setopt=tsflags=nodocs -y \
44 | fontconfig \
45 | freetype \
46 | git \
47 | git-lfs \
48 | unzip \
49 | which \
50 | tzdata \
51 | && dnf clean --disableplugin=subscription-manager all
52 |
53 | ARG user=jenkins
54 | ARG group=jenkins
55 | ARG uid=1000
56 | ARG gid=1000
57 | ARG http_port=8080
58 | ARG agent_port=50000
59 | ARG JENKINS_HOME=/var/jenkins_home
60 | ARG REF=/usr/share/jenkins/ref
61 |
62 | ENV JENKINS_HOME=$JENKINS_HOME
63 | ENV JENKINS_SLAVE_AGENT_PORT=${agent_port}
64 | ENV REF=$REF
65 |
66 | # Jenkins is run with user `jenkins`, uid = 1000
67 | # If you bind mount a volume from the host or a data container,
68 | # ensure you use the same uid
69 | RUN mkdir -p $JENKINS_HOME \
70 | && chown ${uid}:${gid} $JENKINS_HOME \
71 | && groupadd -g ${gid} ${group} \
72 | && useradd -N -d "$JENKINS_HOME" -u ${uid} -g ${gid} -l -m -s /bin/bash ${user}
73 |
74 | # Jenkins home directory is a volume, so configuration and build history
75 | # can be persisted and survive image upgrades
76 | VOLUME $JENKINS_HOME
77 |
78 | # $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want
79 | # to set on a fresh new installation. Use it to bundle additional plugins
80 | # or config file with your custom jenkins Docker image.
81 | RUN mkdir -p ${REF}/init.groovy.d
82 |
83 | # Use tini as subreaper in Docker container to adopt zombie processes
84 | ARG TINI_VERSION=v0.19.0
85 | COPY tini_pub.gpg "${JENKINS_HOME}/tini_pub.gpg"
86 | RUN curl -fsSL "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TARGETARCH}" -o /sbin/tini \
87 | && curl -fsSL "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TARGETARCH}.asc" -o /sbin/tini.asc \
88 | && gpg --no-tty --import "${JENKINS_HOME}/tini_pub.gpg" \
89 | && gpg --verify /sbin/tini.asc \
90 | && rm -rf /sbin/tini.asc /root/.gnupg \
91 | && chmod +x /sbin/tini
92 |
93 | # jenkins version being bundled in this docker image
94 | ARG JENKINS_VERSION
95 | ENV JENKINS_VERSION=${JENKINS_VERSION:-2.504}
96 |
97 | # jenkins.war checksum, download will be validated using it
98 | ARG JENKINS_SHA=efc91d6be8d79dd078e7f930fc4a5f135602d0822a5efe9091808fdd74607d32
99 |
100 | # Can be used to customize where jenkins.war get downloaded from
101 | ARG JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war
102 |
103 | # could use ADD but this one does not check Last-Modified header neither does it allow to control checksum
104 | # see https://github.com/docker/docker/issues/8331
105 | RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \
106 | && echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" >/tmp/jenkins_sha \
107 | && sha256sum -c --strict /tmp/jenkins_sha \
108 | && rm -f /tmp/jenkins_sha
109 |
110 | ENV JENKINS_UC=https://updates.jenkins.io
111 | ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
112 | ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
113 | RUN chown -R ${user} "$JENKINS_HOME" "$REF"
114 |
115 | ARG PLUGIN_CLI_VERSION=2.13.2
116 | ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar
117 | RUN curl -fsSL ${PLUGIN_CLI_URL} -o /opt/jenkins-plugin-manager.jar \
118 | && echo "$(curl -fsSL "${PLUGIN_CLI_URL}.sha256") /opt/jenkins-plugin-manager.jar" >/tmp/jenkins_sha \
119 | && sha256sum -c --strict /tmp/jenkins_sha \
120 | && rm -f /tmp/jenkins_sha
121 |
122 | # for main web interface:
123 | EXPOSE ${http_port}
124 |
125 | # will be used by attached agents:
126 | EXPOSE ${agent_port}
127 |
128 | ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log
129 |
130 | ENV JAVA_HOME=/opt/java/openjdk
131 | ENV PATH="${JAVA_HOME}/bin:${PATH}"
132 | COPY --from=jre-build /javaruntime $JAVA_HOME
133 |
134 | USER ${user}
135 |
136 | COPY jenkins-support /usr/local/bin/jenkins-support
137 | COPY jenkins.sh /usr/local/bin/jenkins.sh
138 | COPY jenkins-plugin-cli.sh /bin/jenkins-plugin-cli
139 |
140 | ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"]
141 |
142 | # metadata labels
143 | LABEL \
144 | org.opencontainers.image.vendor="Jenkins project" \
145 | org.opencontainers.image.title="Official Jenkins Docker image" \
146 | org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" \
147 | org.opencontainers.image.version="${JENKINS_VERSION}" \
148 | org.opencontainers.image.url="https://www.jenkins.io/" \
149 | org.opencontainers.image.source="https://github.com/jenkinsci/docker" \
150 | org.opencontainers.image.revision="${COMMIT_SHA}" \
151 | org.opencontainers.image.licenses="MIT"
152 |
--------------------------------------------------------------------------------
/tests/functions.Tests.ps1:
--------------------------------------------------------------------------------
1 | Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1
2 | Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1
3 |
4 | $global:SUT_IMAGE=Get-SutImage
5 | $global:SUT_CONTAINER=Get-SutImage
6 | $global:TEST_TAG=$global:SUT_IMAGE.Replace('pester-jenkins-', '')
7 |
8 | Describe "[functions > $global:TEST_TAG] build image" {
9 | BeforeEach {
10 | Push-Location -StackName 'jenkins' -Path "$PSScriptRoot/.."
11 | }
12 |
13 | It 'builds image' {
14 | $exitCode, $stdout, $stderr = Build-Docker $global:SUT_IMAGE
15 | $exitCode | Should -Be 0
16 | }
17 |
18 | AfterEach {
19 | Pop-Location -StackName 'jenkins'
20 | }
21 | }
22 |
23 | # Only test on Java 21, one JDK is enough to test all versions
24 | Describe "[functions > $global:TEST_TAG] Check-VersionLessThan" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
25 | It 'exit codes work' {
26 | docker run --rm $global:SUT_IMAGE "exit -1"
27 | $LastExitCode | Should -Be -1
28 | }
29 |
30 | It 'has same version' {
31 | 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 }"
32 | $LastExitCode | Should -Be -1
33 | }
34 |
35 | It 'has right side greater' {
36 | 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 }"
37 | $LastExitCode | Should -Be 0
38 | }
39 |
40 | It 'has left side greater' {
41 | 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 }"
42 | $LastExitCode | Should -Be -1
43 | }
44 |
45 | It 'has left side non-final' {
46 | docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.0-beta-1' '1.0')) { exit 0 } else { exit -1 }"
47 | $LastExitCode | Should -Be 0
48 | }
49 |
50 | It 'has right side non-final' {
51 | docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.0' '1.0-beta-1')) { exit 0 } else { exit -1 }"
52 | $LastExitCode | Should -Be -1
53 | }
54 |
55 | It 'has left alpha and right beta' {
56 | docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.0-alpha-1' '1.0-beta-1')) { exit 0 } else { exit -1 }"
57 | $LastExitCode | Should -Be 0
58 | }
59 |
60 | It 'has left beta and right alpha' {
61 | docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan '1.0-beta-1' '1.0-alpha-1')) { exit 0 } else { exit -1 }"
62 | $LastExitCode | Should -Be -1
63 | }
64 |
65 | It "has left 'latest' and right 1.0" {
66 | docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan 'latest' '1.0')) { exit -1 } else { exit 0 }"
67 | $LastExitCode | Should -Be 0
68 | }
69 |
70 | It "has left 'latest' and right 'latest'" {
71 | docker run --rm $global:SUT_IMAGE "Import-Module -DisableNameChecking -Force C:/ProgramData/Jenkins/jenkins-support.psm1 ; if(`$(Compare-VersionLessThan 'latest' 'latest')) { exit -1 } else { exit 0 }"
72 | $LastExitCode | Should -Be 0
73 | }
74 | }
75 |
76 | # Only test on Java 21, one JDK is enough to test all versions
77 | Describe "[functions > $global:TEST_TAG] Copy-ReferenceFile" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
78 | It 'build test image' {
79 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE $PSScriptRoot/functions
80 | $exitCode | Should -Be 0
81 | }
82 |
83 | It 'start container' {
84 | $exitCode, $stdout, $stderr = Run-Program 'docker' "run -d --name $global:SUT_CONTAINER -P $global:SUT_IMAGE"
85 | $exitCode | Should -Be 0
86 | }
87 |
88 | It 'wait for running' {
89 | # give time to eventually fail to initialize
90 | Start-Sleep -Seconds 5
91 | 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
92 | }
93 |
94 | It 'is initialized' {
95 | Retry-Command -RetryCount 30 -Delay 5 -ScriptBlock { Test-Url $global:SUT_CONTAINER "/api/json" } -Verbose | Should -BeTrue
96 | }
97 |
98 | It 'check files in JENKINS_HOME' {
99 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:SUT_CONTAINER powershell -C `"Get-ChildItem `$env:JENKINS_HOME`" | Select-Object -Property 'Name'"
100 | $exitCode | Should -Be 0
101 | $stdout | Should -Match "pester"
102 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:SUT_CONTAINER powershell -C `"Get-ChildItem `$env:JENKINS_HOME/pester`" | Select-Object -Property 'Name'"
103 | $exitCode | Should -Be 0
104 | $stdout | Should -Match "test.override"
105 | }
106 |
107 | It 'cleanup container' {
108 | Cleanup $global:SUT_CONTAINER | Out-Null
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/tests/functions.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | load 'test_helper/bats-support/load'
4 | load 'test_helper/bats-assert/load'
5 | load test_helpers
6 |
7 | SUT_IMAGE=$(get_sut_image)
8 | SUT_DESCRIPTION="${IMAGE}-functions"
9 |
10 | . $BATS_TEST_DIRNAME/../jenkins-support
11 |
12 | @test "[${SUT_DESCRIPTION}] versionLT" {
13 | run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0 1.0"
14 | assert_failure
15 | run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0 1.1"
16 | assert_success
17 | run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.1 1.0"
18 | assert_failure
19 | run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0-beta-1 1.0"
20 | assert_success
21 | run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0 1.0-beta-1"
22 | assert_failure
23 | run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0-alpha-1 1.0-beta-1"
24 | assert_success
25 | run docker run --rm $SUT_IMAGE bash -c "source /usr/local/bin/jenkins-support && versionLT 1.0-beta-1 1.0-alpha-1"
26 | assert_failure
27 | }
28 |
29 | @test "[${SUT_DESCRIPTION}] permissions are propagated from override file" {
30 | local sut_image="${SUT_IMAGE}-functions-${BATS_TEST_NUMBER}"
31 | run docker_build_child "${SUT_IMAGE}" "${sut_image}" $BATS_TEST_DIRNAME/functions
32 | assert_success
33 | # Create a predefined named volume and fill it with a file in an unexpected file mode
34 | local volume_name
35 | volume_name="functions_${BATS_TEST_NUMBER}"
36 | run bash -c "docker volume rm ${volume_name}; docker volume create ${volume_name}"
37 | run docker run --rm --volume "${volume_name}:/sut_data" --user=0 "${sut_image}" \
38 | bash -c "mkdir -p /sut_data/.ssh && touch /sut_data/.ssh/config && chmod 644 /sut_data/.ssh/config && chown -R 1000:1000 /sut_data"
39 | # replace DOS line endings \r\n
40 | run bash -c "docker run --rm --volume "${volume_name}:/var/jenkins_home:rw" "${sut_image}" stat -c '%a' /var/jenkins_home/.ssh/config"
41 | assert_success
42 | assert_line '600'
43 | # Cleanup
44 | run docker volume rm "${volume_name}"
45 | }
46 |
--------------------------------------------------------------------------------
/tests/functions/.ssh/config:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/docker/fdf82f13316521461da816afbec8809ed3500c92/tests/functions/.ssh/config
--------------------------------------------------------------------------------
/tests/functions/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 |
3 | RUN mkdir -p /usr/share/jenkins/ref/.ssh && touch /usr/share/jenkins/ref/.ssh/config.override
4 | RUN chmod 600 /usr/share/jenkins/ref/.ssh/config.override
5 |
--------------------------------------------------------------------------------
/tests/functions/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 | # hadolint shell=powershell
3 |
4 | RUN mkdir C:/ProgramData/Jenkins/Reference/pester ; echo $null >> C:/ProgramData/Jenkins/Reference/pester/test.override
--------------------------------------------------------------------------------
/tests/plugins-cli.Tests.ps1:
--------------------------------------------------------------------------------
1 | Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1
2 | Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1
3 |
4 | $global:SUT_IMAGE=Get-SutImage
5 | $global:SUT_CONTAINER=Get-SutImage
6 | $global:TEST_TAG=$global:SUT_IMAGE.Replace('pester-jenkins-', '')
7 |
8 | $global:WORK = Join-Path $PSScriptRoot "upgrade-plugins/work-${global:SUT_IMAGE}"
9 |
10 | Describe "[plugins-cli > $global:TEST_TAG] build image" {
11 | BeforeEach {
12 | Push-Location -StackName 'jenkins' -Path "$PSScriptRoot/.."
13 | }
14 |
15 | It 'builds image' {
16 | $exitCode, $stdout, $stderr = Build-Docker $global:SUT_IMAGE
17 | $exitCode | Should -Be 0
18 | }
19 |
20 | AfterEach {
21 | Pop-Location -StackName 'jenkins'
22 | }
23 | }
24 |
25 | Describe "[plugins-cli > $global:TEST_TAG] cleanup container" {
26 | It 'cleanup' {
27 | Cleanup $global:SUT_CONTAINER | Out-Null
28 | }
29 | }
30 |
31 | # Only test on Java 21, one JDK is enough to test all versions
32 | Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
33 | It 'builds child image' {
34 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli
35 | $exitCode | Should -Be 0
36 | $stdout | Should -Not -Match "Skipping already installed dependency"
37 | }
38 |
39 | It 'has correct plugins' {
40 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli gci `$env:JENKINS_HOME/plugins | Select-Object -Property Name"
41 | $exitCode | Should -Be 0
42 |
43 | $stdout | Should -Match 'junit.jpi'
44 | $stdout | Should -Match 'junit.jpi.pinned'
45 | $stdout | Should -Match 'ant.jpi'
46 | $stdout | Should -Match 'ant.jpi.pinned'
47 | $stdout | Should -Match 'credentials.jpi'
48 | $stdout | Should -Match 'credentials.jpi.pinned'
49 | $stdout | Should -Match 'mesos.jpi'
50 | $stdout | Should -Match 'mesos.jpi.pinned'
51 | # optional dependencies
52 | $stdout | Should -Not -Match 'metrics.jpi'
53 | $stdout | Should -Not -Match 'metrics.jpi.pinned'
54 | # plugins bundled but under detached-plugins, so need to be installed
55 | $stdout | Should -Match 'mailer.jpi'
56 | $stdout | Should -Match 'mailer.jpi.pinned'
57 | $stdout | Should -Match 'git.jpi'
58 | $stdout | Should -Match 'git.jpi.pinned'
59 | $stdout | Should -Match 'filesystem_scm.jpi'
60 | $stdout | Should -Match 'filesystem_scm.jpi.pinned'
61 | $stdout | Should -Match 'docker-plugin.jpi'
62 | $stdout | Should -Match 'docker-plugin.jpi.pinned'
63 | }
64 | }
65 |
66 | # Only test on Java 21, one JDK is enough to test all versions
67 | Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli with non-default REF" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
68 | It 'builds child image' {
69 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-ref $PSScriptRoot/plugins-cli/ref
70 | $exitCode | Should -Be 0
71 | $stdout | Should -Not -Match "Skipping already installed dependency"
72 | }
73 |
74 | It 'has correct plugins' {
75 | $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"
76 | $exitCode | Should -Be 0
77 |
78 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli gci `$env:JENKINS_HOME/plugins | Select-Object -Property Name"
79 | $exitCode | Should -Be 0
80 |
81 | $stdout | Should -Match 'junit.jpi'
82 | $stdout | Should -Match 'junit.jpi.pinned'
83 | $stdout | Should -Match 'ant.jpi'
84 | $stdout | Should -Match 'ant.jpi.pinned'
85 | $stdout | Should -Match 'credentials.jpi'
86 | $stdout | Should -Match 'credentials.jpi.pinned'
87 | $stdout | Should -Match 'mesos.jpi'
88 | $stdout | Should -Match 'mesos.jpi.pinned'
89 | # optional dependencies
90 | $stdout | Should -Not -Match 'metrics.jpi'
91 | $stdout | Should -Not -Match 'metrics.jpi.pinned'
92 | # plugins bundled but under detached-plugins, so need to be installed
93 | $stdout | Should -Match 'mailer.jpi'
94 | $stdout | Should -Match 'mailer.jpi.pinned'
95 | $stdout | Should -Match 'git.jpi'
96 | $stdout | Should -Match 'git.jpi.pinned'
97 | $stdout | Should -Match 'filesystem_scm.jpi'
98 | $stdout | Should -Match 'filesystem_scm.jpi.pinned'
99 | $stdout | Should -Match 'docker-plugin.jpi'
100 | $stdout | Should -Match 'docker-plugin.jpi.pinned'
101 | }
102 | }
103 |
104 | # Only test on Java 21, one JDK is enough to test all versions
105 | Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli from a plugins file" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
106 | It 'builds child image' {
107 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli
108 | $exitCode | Should -Be 0
109 | }
110 |
111 | It 'builds grandchild image' {
112 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-pluginsfile $PSScriptRoot/plugins-cli/pluginsfile
113 | $exitCode | Should -Be 0
114 | $stdout | Should -Not -Match "Skipping already installed dependency"
115 | }
116 |
117 | It 'has correct plugins' {
118 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm $global:SUT_IMAGE-plugins-cli gci `$env:JENKINS_HOME/plugins | Select-Object -Property Name"
119 | $exitCode | Should -Be 0
120 |
121 | $stdout | Should -Match 'junit.jpi'
122 | $stdout | Should -Match 'junit.jpi.pinned'
123 | $stdout | Should -Match 'ant.jpi'
124 | $stdout | Should -Match 'ant.jpi.pinned'
125 | $stdout | Should -Match 'credentials.jpi'
126 | $stdout | Should -Match 'credentials.jpi.pinned'
127 | $stdout | Should -Match 'mesos.jpi'
128 | $stdout | Should -Match 'mesos.jpi.pinned'
129 | # optional dependencies
130 | $stdout | Should -Not -Match 'metrics.jpi'
131 | $stdout | Should -Not -Match 'metrics.jpi.pinned'
132 | # plugins bundled but under detached-plugins, so need to be installed
133 | $stdout | Should -Match 'mailer.jpi'
134 | $stdout | Should -Match 'mailer.jpi.pinned'
135 | $stdout | Should -Match 'git.jpi'
136 | $stdout | Should -Match 'git.jpi.pinned'
137 | $stdout | Should -Match 'filesystem_scm.jpi'
138 | $stdout | Should -Match 'filesystem_scm.jpi.pinned'
139 | }
140 | }
141 |
142 | # Only test on Java 21, one JDK is enough to test all versions
143 | Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli even when already exist" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
144 | It 'builds child image' {
145 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli
146 | $exitCode | Should -Be 0
147 | }
148 |
149 | It 'builds grandchild image' {
150 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-update $PSScriptRoot/plugins-cli/update --no-cache
151 | $exitCode | Should -Be 0
152 | }
153 |
154 | It 'has the correct version of junit' {
155 | $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'"
156 | $exitCode | Should -Be 0
157 | $stdout | Should -Match 'Plugin-Version: 1.28'
158 | }
159 | }
160 |
161 | Describe "[plugins-cli > $global:TEST_TAG] clean work directory" {
162 | It 'cleanup' {
163 | if(Test-Path $PSScriptRoot/upgrade-plugins/work-$global:SUT_IMAGE) {
164 | Remove-Item -Recurse -Force $PSScriptRoot/upgrade-plugins/work-$global:SUT_IMAGE | Out-Null
165 | }
166 | }
167 | }
168 |
169 | # Only test on Java 21, one JDK is enough to test all versions
170 | Describe "[plugins-cli > $global:TEST_TAG] plugins are getting upgraded but not downgraded" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
171 | It 'builds child image' {
172 | # Initial execution
173 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli
174 | $exitCode | Should -Be 0
175 | }
176 |
177 | It 'has correct version of junit and ant plugins' {
178 | if(-not (Test-Path $global:WORK)) {
179 | New-Item -ItemType Directory -Path $global:WORK
180 | }
181 |
182 | # Image contains junit 1.6 and ant-plugin 1.3
183 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v `"${work}:C:\ProgramData\Jenkins\JenkinsHome`" --rm $global:SUT_IMAGE-plugins-cli exit 0"
184 | $exitCode | Should -Be 0
185 |
186 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE "junit.jpi" $global:WORK
187 | $exitCode | Should -Be 0
188 | $stdout | Should -Match 'Plugin-Version: 1.6'
189 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE "ant.jpi" $global:WORK
190 | $exitCode | Should -Be 0
191 | $stdout | Should -Match 'Plugin-Version: 1.3'
192 | }
193 |
194 | It 'upgrades plugins' {
195 | # Upgrade to new image with different plugins
196 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-upgrade-plugins $PSScriptRoot/upgrade-plugins
197 | $exitCode | Should -Be 0
198 | # Images contains junit 1.28 and ant-plugin 1.2
199 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v `"${work}:C:\ProgramData\Jenkins\JenkinsHome`" --rm $global:SUT_IMAGE-upgrade-plugins exit 0"
200 | $exitCode | Should -Be 0
201 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $global:WORK
202 | $exitCode | Should -Be 0
203 | # Should be updated
204 | $stdout | Should -Match 'Plugin-Version: 1.28'
205 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $global:WORK
206 | $exitCode | Should -Be 0
207 | # 1.2 is older than the existing 1.3, so keep 1.3
208 | $stdout | Should -Match 'Plugin-Version: 1.3'
209 | }
210 | }
211 |
212 | Describe "[plugins-cli > $global:TEST_TAG] clean work directory" {
213 | It 'cleanup' {
214 | if(Test-Path $global:WORK) {
215 | Remove-Item -Recurse -Force $global:WORK | Out-Null
216 | }
217 | }
218 | }
219 |
220 | # Only test on Java 21, one JDK is enough to test all versions
221 | Describe "[plugins-cli > $global:TEST_TAG] do not upgrade if plugin has been manually updated" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
222 |
223 | It 'builds child image' {
224 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli
225 | $exitCode | Should -Be 0
226 | }
227 |
228 | It 'updates plugin manually and then via plugin-cli' {
229 | if(-not (Test-Path $global:WORK)) {
230 | New-Item -ItemType Directory -Path $global:WORK
231 | }
232 |
233 | # Image contains junit 1.8 and ant-plugin 1.3
234 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v `"${work}: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"
235 | $exitCode | Should -Be 0
236 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $global:WORK
237 | $exitCode | Should -Be 0
238 | $stdout | Should -Match 'Plugin-Version: 1.8'
239 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $global:WORK
240 | $exitCode | Should -Be 0
241 | $stdout | Should -Match 'Plugin-Version: 1.3'
242 |
243 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-upgrade-plugins $PSScriptRoot/upgrade-plugins
244 | $exitCode | Should -Be 0
245 |
246 | # Images contains junit 1.28 and ant-plugin 1.2
247 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v `"${work}:C:\ProgramData\Jenkins\JenkinsHome`" --rm $global:SUT_IMAGE-upgrade-plugins exit 0"
248 | $exitCode | Should -Be 0
249 | # junit shouldn't be upgraded
250 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $global:WORK
251 | $exitCode | Should -Be 0
252 | $stdout | Should -Match 'Plugin-Version: 1.8'
253 | $stdout | Should -Not -Match 'Plugin-Version: 1.28'
254 | # ant shouldn't be downgraded
255 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $global:WORK
256 | $exitCode | Should -Be 0
257 | $stdout | Should -Match 'Plugin-Version: 1.3'
258 | $stdout | Should -Not -Match 'Plugin-Version: 1.2'
259 | }
260 | }
261 |
262 | Describe "[plugins-cli > $global:TEST_TAG] clean work directory" {
263 | It 'cleanup' {
264 | if(Test-Path $global:WORK) {
265 | Remove-Item -Recurse -Force $global:WORK | Out-Null
266 | }
267 | }
268 | }
269 |
270 | # Only test on Java 21, one JDK is enough to test all versions
271 | 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.StartsWith('jdk21-')) {
272 | It 'builds child image' {
273 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli $PSScriptRoot/plugins-cli
274 | $exitCode | Should -Be 0
275 | }
276 |
277 | It 'upgrades plugins' {
278 | if(-not (Test-Path $global:WORK)) {
279 | New-Item -ItemType Directory -Path $global:WORK
280 | }
281 |
282 | # Image contains junit 1.6 and ant-plugin 1.3
283 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -v `"${work}: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"
284 | $exitCode | Should -Be 0
285 |
286 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $global:WORK
287 | $exitCode | Should -Be 0
288 | $stdout | Should -Match 'Plugin-Version: 1.8'
289 |
290 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-upgrade-plugins $PSScriptRoot/upgrade-plugins
291 | $exitCode | Should -Be 0
292 |
293 | # Images contains junit 1.28 and ant-plugin 1.2
294 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run -e PLUGINS_FORCE_UPGRADE=true -v ${work}:C:/ProgramData/Jenkins/JenkinsHome --rm $global:SUT_IMAGE-upgrade-plugins exit 0"
295 | $exitCode | Should -Be 0
296 | # junit should be upgraded
297 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'junit.jpi' $global:WORK
298 | $exitCode | Should -Be 0
299 | $stdout | Should -Not -Match 'Plugin-Version: 1.8'
300 | $stdout | Should -Match 'Plugin-Version: 1.28'
301 | # ant shouldn't be downgraded
302 | $exitCode, $stdout, $stderr = Unzip-Manifest $global:SUT_IMAGE 'ant.jpi' $global:WORK
303 | $exitCode | Should -Be 0
304 | $stdout | Should -Match 'Plugin-Version: 1.3'
305 | $stdout | Should -Not -Match 'Plugin-Version: 1.2'
306 | }
307 | }
308 |
309 | Describe "[plugins-cli > $global:TEST_TAG] clean work directory" {
310 | It 'cleanup' {
311 | if(Test-Path $global:WORK) {
312 | Remove-Item -Recurse -Force $global:WORK | Out-Null
313 | }
314 | }
315 | }
316 |
317 | # Only test on Java 21, one JDK is enough to test all versions
318 | Describe "[plugins-cli > $global:TEST_TAG] plugins are installed with jenkins-plugin-cli and no war" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
319 | It 'builds child image' {
320 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-no-war $PSScriptRoot/plugins-cli/no-war
321 | $exitCode | Should -Be 0
322 | }
323 | }
324 |
325 | # Only test on Java 21, one JDK is enough to test all versions
326 | Describe "[plugins-cli > $global:TEST_TAG] Use a custom jenkins.war" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
327 | It 'builds child image' {
328 | $exitCode, $stdout, $stderr = Build-DockerChild $global:SUT_IMAGE-plugins-cli-custom-war $PSScriptRoot/plugins-cli/custom-war --no-cache
329 | $exitCode | Should -Be 0
330 | }
331 | }
332 |
333 | Describe "[plugins-cli > $global:TEST_TAG] clean work directory" {
334 | It 'cleanup' {
335 | if(Test-Path $global:WORK) {
336 | Remove-Item -Recurse -Force $global:WORK | Out-Null
337 | }
338 | }
339 | }
340 |
--------------------------------------------------------------------------------
/tests/plugins-cli.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | load 'test_helper/bats-support/load'
4 | load 'test_helper/bats-assert/load'
5 | load test_helpers
6 |
7 | SUT_IMAGE=$(get_sut_image)
8 | SUT_DESCRIPTION="${IMAGE}-plugins-cli"
9 |
10 | teardown() {
11 | clean_work_directory "${BATS_TEST_DIRNAME}" "${SUT_IMAGE}"
12 | }
13 |
14 | @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli" {
15 | local custom_sut_image
16 | custom_sut_image="$(get_test_image)"
17 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli"
18 | assert_success
19 | refute_line --partial 'Skipping already installed dependency'
20 |
21 | run docker run --rm "${custom_sut_image}" ls --color=never -1 /var/jenkins_home/plugins
22 | assert_success
23 | assert_line 'junit.jpi'
24 | assert_line 'junit.jpi.pinned'
25 | assert_line 'ant.jpi'
26 | assert_line 'ant.jpi.pinned'
27 | assert_line 'credentials.jpi'
28 | assert_line 'credentials.jpi.pinned'
29 | assert_line 'mesos.jpi'
30 | assert_line 'mesos.jpi.pinned'
31 | # optional dependencies
32 | refute_line 'metrics.jpi'
33 | refute_line 'metrics.jpi.pinned'
34 | # plugins bundled but under detached-plugins, so need to be installed
35 | assert_line 'mailer.jpi'
36 | assert_line 'mailer.jpi.pinned'
37 | assert_line 'git.jpi'
38 | assert_line 'git.jpi.pinned'
39 | assert_line 'filesystem_scm.jpi'
40 | assert_line 'filesystem_scm.jpi.pinned'
41 | assert_line 'docker-plugin.jpi'
42 | assert_line 'docker-plugin.jpi.pinned'
43 | }
44 |
45 | @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli with non-default REF" {
46 | local custom_sut_image custom_ref
47 | custom_sut_image="$(get_test_image)"
48 | custom_ref=/var/lib/jenkins/ref
49 |
50 | # Build a custom image to validate the build time behavior
51 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/ref" --build-arg REF="${custom_ref}"
52 | assert_success
53 | refute_line --partial 'Skipping already installed dependency'
54 |
55 | volume_name="$(docker volume create)"
56 | # Start an image with the default entrypoint to test the runtime behavior
57 | run docker run --volume "${volume_name}:/var/jenkins_home" --rm "${custom_sut_image}" true
58 | assert_success
59 |
60 | # Check the content of the resulting data volume (expecting installed plugins as present and pinned)
61 | run bash -c "docker run --rm --volume ${volume_name}:/var/jenkins_home ${custom_sut_image} ls --color=never -1 /var/jenkins_home/plugins \
62 | | tr -d '\r' `# replace DOS line endings \r\n`"
63 | assert_success
64 | assert_line 'junit.jpi'
65 | assert_line 'junit.jpi.pinned'
66 | assert_line 'ant.jpi'
67 | assert_line 'ant.jpi.pinned'
68 | }
69 |
70 | @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli from a plugins file" {
71 | local custom_sut_image
72 | custom_sut_image="$(get_test_image)"
73 |
74 | # Then proceed with child
75 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/pluginsfile"
76 | assert_success
77 | refute_line --partial 'Skipping already installed dependency'
78 | # replace DOS line endings \r\n
79 | run bash -c "docker run --rm ${custom_sut_image} ls --color=never -1 /var/jenkins_home/plugins | tr -d '\r'"
80 | assert_success
81 | assert_line 'junit.jpi'
82 | assert_line 'junit.jpi.pinned'
83 | assert_line 'ant.jpi'
84 | assert_line 'ant.jpi.pinned'
85 | assert_line 'credentials.jpi'
86 | assert_line 'credentials.jpi.pinned'
87 | assert_line 'mesos.jpi'
88 | assert_line 'mesos.jpi.pinned'
89 | # optional dependencies
90 | refute_line 'metrics.jpi'
91 | refute_line 'metrics.jpi.pinned'
92 | # plugins bundled but under detached-plugins, so need to be installed
93 | assert_line 'mailer.jpi'
94 | assert_line 'mailer.jpi.pinned'
95 | assert_line 'git.jpi'
96 | assert_line 'git.jpi.pinned'
97 | assert_line 'filesystem_scm.jpi'
98 | assert_line 'filesystem_scm.jpi.pinned'
99 | }
100 |
101 | @test "[${SUT_DESCRIPTION}] plugins are getting upgraded but not downgraded" {
102 | local custom_sut_image_first custom_sut_image_second
103 | custom_sut_image_first="$(get_test_image)"
104 | custom_sut_image_second="${custom_sut_image_first}-2"
105 |
106 | # Build first image with junit 1.6 and ant-plugin 1.3
107 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_first}" "${BATS_TEST_DIRNAME}/plugins-cli"
108 | assert_success
109 |
110 | local volume_name
111 | volume_name="$(docker volume create)"
112 |
113 | # Generates a jenkins home (in the volume) with the plugins junit 1.6 and ant-plugin 1.3 from first image's reference
114 | run docker run --volume "$volume_name:/var/jenkins_home" --rm "${custom_sut_image_first}" true
115 | assert_success
116 | run unzip_manifest junit.jpi "$volume_name"
117 | assert_line 'Plugin-Version: 1.6'
118 | run unzip_manifest ant.jpi "$volume_name"
119 | assert_line 'Plugin-Version: 1.3'
120 |
121 | # Build second image with junit 1.28 and ant 1.2
122 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_second}" "${BATS_TEST_DIRNAME}/upgrade-plugins"
123 | assert_success
124 |
125 | # Execute the second image with the existing jenkins volume: junit plugin should be updated, and ant shoud NOT be downgraded
126 | run docker run --volume "$volume_name:/var/jenkins_home" --rm "${custom_sut_image_second}" true
127 | assert_success
128 | run unzip_manifest junit.jpi "$volume_name"
129 | assert_success
130 | # Should be updated
131 | assert_line 'Plugin-Version: 1.28'
132 | run unzip_manifest ant.jpi "$volume_name"
133 | # 1.2 is older than the existing 1.3, so keep 1.3
134 | assert_line 'Plugin-Version: 1.3'
135 | }
136 |
137 | @test "[${SUT_DESCRIPTION}] do not upgrade if plugin has been manually updated" {
138 | local custom_sut_image_first custom_sut_image_second
139 | custom_sut_image_first="$(get_test_image)"
140 | custom_sut_image_second="${custom_sut_image_first}-2"
141 |
142 | ## Generates an image with the plugin junit 1.6
143 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_first}" "${BATS_TEST_DIRNAME}/plugins-cli"
144 | assert_success
145 |
146 | ## Image contains junit 1.6, which is manually upgraded to 1.8
147 | local volume_name
148 | volume_name="$(docker volume create)"
149 | run docker run --volume "${volume_name}:/var/jenkins_home" --rm "${custom_sut_image_first}" \
150 | curl --connect-timeout 20 --retry 5 --retry-delay 0 --retry-max-time 60 --silent \
151 | --fail --location https://updates.jenkins.io/download/plugins/junit/1.8/junit.hpi \
152 | --output /var/jenkins_home/plugins/junit.jpi
153 | assert_success
154 | run unzip_manifest junit.jpi "$volume_name"
155 | assert_line 'Plugin-Version: 1.8'
156 |
157 | ## Generates an image with the plugin junit 1.28 (upgraded)
158 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_second}" "${BATS_TEST_DIRNAME}/upgrade-plugins"
159 | assert_success
160 |
161 | # The image with junit 1.28 should not upgrade the version 1.8 in the volume (jenkins_home)
162 | run docker run --volume "${volume_name}:/var/jenkins_home" --rm ${custom_sut_image_second} true
163 | assert_success
164 | # junit shouldn't be upgraded
165 | run unzip_manifest junit.jpi "$volume_name"
166 | assert_success
167 | assert_line 'Plugin-Version: 1.8'
168 | refute_line 'Plugin-Version: 1.28'
169 | }
170 |
171 | @test "[${SUT_DESCRIPTION}] upgrade plugin even if it has been manually updated when PLUGINS_FORCE_UPGRADE=true" {
172 | local custom_sut_image_first custom_sut_image_second
173 | custom_sut_image_first="$(get_test_image)"
174 | custom_sut_image_second="${custom_sut_image_first}-2"
175 |
176 | ## Generates an image with the plugin junit 1.6
177 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_first}" "${BATS_TEST_DIRNAME}/plugins-cli"
178 | assert_success
179 |
180 | ## Image contains junit 1.6, which is manually upgraded to 1.8
181 | local volume_name
182 | volume_name="$(docker volume create)"
183 | run docker run --volume "${volume_name}:/var/jenkins_home" --rm "${custom_sut_image_first}" \
184 | curl --connect-timeout 20 --retry 5 --retry-delay 0 --retry-max-time 60 --silent \
185 | --fail --location https://updates.jenkins.io/download/plugins/junit/1.8/junit.hpi \
186 | --output /var/jenkins_home/plugins/junit.jpi
187 | assert_success
188 | run unzip_manifest junit.jpi "$volume_name"
189 | assert_line 'Plugin-Version: 1.8'
190 |
191 | ## Generates an image with the plugin junit 1.28 (upgraded)
192 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image_second}" "${BATS_TEST_DIRNAME}/upgrade-plugins"
193 | assert_success
194 |
195 | # The image with junit 1.28 should force-upgrade junit in the volume (jenkins_home)
196 | run docker run --volume "${volume_name}:/var/jenkins_home" --env PLUGINS_FORCE_UPGRADE=true --rm ${custom_sut_image_second} true
197 | assert_success
198 | # junit shouldn't be upgraded
199 | run unzip_manifest junit.jpi "$volume_name"
200 | assert_success
201 | refute_line 'Plugin-Version: 1.8'
202 | assert_line 'Plugin-Version: 1.28'
203 | }
204 |
205 |
206 | @test "[${SUT_DESCRIPTION}] plugins are installed with jenkins-plugin-cli and no war" {
207 | local custom_sut_image
208 | custom_sut_image="$(get_test_image)"
209 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/no-war"
210 | assert_success
211 | }
212 |
213 | @test "[${SUT_DESCRIPTION}] Use a custom jenkins.war" {
214 | local custom_sut_image
215 | custom_sut_image="$(get_test_image)"
216 | # Build the image using the right Dockerfile setting a new war with JENKINS_WAR env and with a weird plugin inside
217 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/custom-war"
218 | assert_success
219 | }
220 |
221 | @test "[${SUT_DESCRIPTION}] JAVA_OPTS environment variable is used with jenkins-plugin-cli" {
222 | local custom_sut_image
223 | custom_sut_image="$(get_test_image)"
224 | run docker_build_child "${SUT_IMAGE}" "${custom_sut_image}" "${BATS_TEST_DIRNAME}/plugins-cli/java-opts"
225 | assert_success
226 | # Assert JAVA_OPTS has been used and 'java.opts.test' has been set to JVM
227 | assert_line --regexp 'java.opts.test.*=.*true'
228 | }
229 |
--------------------------------------------------------------------------------
/tests/plugins-cli/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 |
3 | 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
4 |
--------------------------------------------------------------------------------
/tests/plugins-cli/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 | # hadolint shell=powershell
3 |
4 | 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
5 |
6 |
--------------------------------------------------------------------------------
/tests/plugins-cli/custom-war/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 |
3 | # Define a custom location for the war
4 | ENV JENKINS_WAR=/test-custom-dockerfile/my-custom-jenkins.war
5 | WORKDIR /test-custom-dockerfile
6 | # Add there a new weird plugin to assert
7 | COPY --chown=jenkins:jenkins WEB-INF/ WEB-INF/
8 |
9 | USER root
10 | RUN chown jenkins:jenkins /test-custom-dockerfile
11 | USER jenkins
12 |
13 | # Copy the original jenkins.war to the custom location, add the weird plugin to
14 | # the new custom WAR, and run the jenkins-plugin-cli script.
15 | RUN cp /usr/share/jenkins/jenkins.war $JENKINS_WAR \
16 | && chown jenkins:jenkins $JENKINS_WAR \
17 | && jar -uf my-custom-jenkins.war WEB-INF/* \
18 | && jenkins-plugin-cli --war $JENKINS_WAR --plugins junit:1.6
19 |
--------------------------------------------------------------------------------
/tests/plugins-cli/custom-war/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 | # hadolint shell=powershell
3 |
4 | # Define a custom location for the war
5 | ENV JENKINS_WAR=C:/ProgramData/TestCustomDockerfile/my-custom-jenkins.war
6 | WORKDIR C:/ProgramData/TestCustomDockerfile
7 | # Add there a new weird plugin to assert
8 | COPY WEB-INF/ WEB-INF/
9 |
10 | # Copy the original jenkins.war to the custom location
11 | RUN Copy-Item C:/ProgramData/Jenkins/jenkins.war $env:JENKINS_WAR
12 |
13 | # Add the weird plugin to the new custom war
14 | # hadolint ignore=DL3059
15 | RUN jar -uf my-custom-jenkins.war WEB-INF/*
16 |
17 | # Run the jenkins-plugin-cli script
18 | # hadolint ignore=DL3059
19 | RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --war $env:JENKINS_WAR --plugins junit:1.6
20 |
--------------------------------------------------------------------------------
/tests/plugins-cli/custom-war/WEB-INF/plugins/my-happy-plugin.hpi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/docker/fdf82f13316521461da816afbec8809ed3500c92/tests/plugins-cli/custom-war/WEB-INF/plugins/my-happy-plugin.hpi
--------------------------------------------------------------------------------
/tests/plugins-cli/java-opts/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins-plugins-cli
2 |
3 | ENV JAVA_OPTS="-Djava.opts.test=true -XshowSettings:properties"
4 | RUN jenkins-plugin-cli --version
5 |
--------------------------------------------------------------------------------
/tests/plugins-cli/no-war/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 |
3 | COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
4 | USER root
5 | RUN rm -rf /usr/share/jenkins/jenkins.war
6 | USER jenkins
7 | RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt
8 |
--------------------------------------------------------------------------------
/tests/plugins-cli/no-war/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 | # hadolint shell=powershell
3 |
4 | COPY plugins.txt C:/ProgramData/Jenkins/Reference/plugins.txt
5 | RUN Remove-Item -Force C:/ProgramData/Jenkins/jenkins.war
6 | # hadolint ignore=DL3059
7 | RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 -f C:/ProgramData/Jenkins/Reference/plugins.txt
8 |
--------------------------------------------------------------------------------
/tests/plugins-cli/no-war/plugins.txt:
--------------------------------------------------------------------------------
1 | # comment line should be skipped
2 |
3 | # simple case
4 | ant:1.3
5 |
6 | # trailing spaces
7 | junit:1.6
8 |
9 | # leading spaces
10 | mesos:0.13.0
11 |
12 | # leading spaces, and trailing spaces
13 | git:latest
14 |
15 | # with comments at the end
16 | filesystem_scm:experimental # comment at the end
17 |
18 | # empty line
19 |
20 | #
21 | # empty line
22 |
--------------------------------------------------------------------------------
/tests/plugins-cli/pluginsfile/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 |
3 | COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
4 | RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt
5 |
--------------------------------------------------------------------------------
/tests/plugins-cli/pluginsfile/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 | # hadolint shell=powershell
3 |
4 | COPY plugins.txt C:/ProgramData/Jenkins/Reference/plugins.txt
5 | RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose -f C:/ProgramData/Jenkins/Reference/plugins.txt
6 |
--------------------------------------------------------------------------------
/tests/plugins-cli/pluginsfile/plugins.txt:
--------------------------------------------------------------------------------
1 | # comment line should be skipped
2 |
3 | # simple case
4 | ant:1.3
5 |
6 | # trailing spaces
7 | junit:1.6
8 |
9 | # leading spaces
10 | mesos:0.13.0
11 |
12 | # leading spaces, and trailing spaces
13 | git:latest
14 |
15 | # with comments at the end
16 | filesystem_scm:experimental # comment at the end
17 |
18 | # empty line
19 |
20 | #
21 | # empty line
22 |
23 | # from url
24 | subversion:::https://updates.jenkins.io/download/plugins/subversion/2.12.1/subversion.hpi
25 |
--------------------------------------------------------------------------------
/tests/plugins-cli/ref/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins-plugins-cli
2 |
3 | RUN rm -rf /usr/share/jenkins/ref ; jenkins-plugin-cli --plugins junit:1.28 ant:1.3
4 |
--------------------------------------------------------------------------------
/tests/plugins-cli/ref/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 | # hadolint shell=powershell
3 |
4 | RUN Remove-Item -Recurse -Force C:/ProgramData/Jenkins/Reference ; C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose --plugins junit:1.28 ant:1.3
5 |
--------------------------------------------------------------------------------
/tests/plugins-cli/update/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins-plugins-cli
2 |
3 | RUN jenkins-plugin-cli --verbose --plugins junit:1.28 ant:1.3
4 |
--------------------------------------------------------------------------------
/tests/plugins-cli/update/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins-plugins-cli
2 | # hadolint shell=powershell
3 |
4 | RUN C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --verbose --plugins junit:1.28 ant:1.3
5 |
--------------------------------------------------------------------------------
/tests/runtime.Tests.ps1:
--------------------------------------------------------------------------------
1 | Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1
2 | Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1
3 |
4 | $global:SUT_IMAGE=Get-SutImage
5 | $global:SUT_CONTAINER=Get-SutImage
6 | $global:TEST_TAG=$global:SUT_IMAGE.Replace('pester-jenkins-', '')
7 |
8 | Describe "[runtime > $global:TEST_TAG] build image" {
9 | BeforeEach {
10 | Push-Location -StackName 'jenkins' -Path "$PSScriptRoot/.."
11 | }
12 |
13 | It 'builds image' {
14 | $exitCode, $stdout, $stderr = Build-Docker $global:SUT_IMAGE
15 | $exitCode | Should -Be 0
16 | }
17 |
18 | AfterEach {
19 | Pop-Location -StackName 'jenkins'
20 | }
21 | }
22 |
23 | Describe "[runtime > $global:TEST_TAG] cleanup container" {
24 | It 'cleanup' {
25 | Cleanup $global:SUT_CONTAINER | Out-Null
26 | }
27 | }
28 |
29 | # Only test on Java 21, one JDK is enough to test all versions
30 | Describe "[runtime > $global:TEST_TAG] test multiple JENKINS_OPTS" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
31 | It '"--help --version" should return the version, not the help' {
32 | # need the last line of output
33 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm -e JENKINS_OPTS=`"--help --version`" --name $global:SUT_CONTAINER -P $global:SUT_IMAGE"
34 | $exitCode | Should -Be 0
35 | $stdout -split '`n' | %{$_.Trim()} | Select-Object -Last 1 | Should -Be $env:JENKINS_VERSION
36 | }
37 | }
38 |
39 | # Only test on Java 21, one JDK is enough to test all versions
40 | Describe "[runtime > $global:TEST_TAG] test jenkins arguments" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
41 | It 'running --help --version should return the version, not the help' {
42 | # need the last line of output
43 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "run --rm --name $global:SUT_CONTAINER -P $global:SUT_IMAGE --help --version"
44 | $exitCode | Should -Be 0
45 | $stdout -split '`n' | %{$_.Trim()} | Select-Object -Last 1 | Should -Be $env:JENKINS_VERSION
46 | }
47 |
48 | It 'version in docker metadata' {
49 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{index .Config.Labels \`"org.opencontainers.image.version\`"}}`" $global:SUT_IMAGE"
50 | $exitCode | Should -Be 0
51 | $stdout.Trim() | Should -Match $env:JENKINS_VERSION
52 | }
53 |
54 | It 'commit SHA in docker metadata' {
55 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{index .Config.Labels \`"org.opencontainers.image.revision\`"}}`" $global:SUT_IMAGE"
56 | $exitCode | Should -Be 0
57 | $stdout.Trim() | Should -Match $env:COMMIT_SHA
58 | }
59 | }
60 |
61 | # Only test on Java 21, one JDK is enough to test all versions
62 | Describe "[runtime > $global:TEST_TAG] passing JVM parameters" -Skip:(-not $global:TEST_TAG.StartsWith('jdk21-')) {
63 | BeforeAll {
64 | $tzSetting = '-Duser.timezone=Europe/Madrid'
65 | $tzRegex = [regex]::Escape("Europe/Madrid")
66 |
67 | $cspSetting = @'
68 | -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\"
69 | '@
70 | $cspRegex = [regex]::Escape("default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';")
71 |
72 | function Start-With-Opts() {
73 | Param (
74 | [string] $JAVA_OPTS,
75 | [string] $JENKINS_JAVA_OPTS
76 | )
77 |
78 | $cmd = "docker --% run -d --name $global:SUT_CONTAINER -P"
79 | if ($JAVA_OPTS.length -gt 0) {
80 | $cmd += " -e JAVA_OPTS=`"$JAVA_OPTS`""
81 | }
82 | if ($JENKINS_JAVA_OPTS.length -gt 0) {
83 | $cmd += " -e JENKINS_JAVA_OPTS=`"$JENKINS_JAVA_OPTS`""
84 | }
85 | $cmd += " $global:SUT_IMAGE"
86 |
87 | Invoke-Expression $cmd
88 | $lastExitCode | Should -Be 0
89 |
90 | # give time to eventually fail to initialize
91 | Start-Sleep -Seconds 5
92 | 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
93 |
94 | # it takes a while for jenkins to be up enough
95 | Retry-Command -RetryCount 30 -Delay 5 -ScriptBlock { Test-Url $global:SUT_CONTAINER "/api/json" } -Verbose | Should -BeTrue
96 | }
97 |
98 | function Get-Csp-Value() {
99 | return (Run-In-Script-Console $global:SUT_CONTAINER "System.getProperty('hudson.model.DirectoryBrowserSupport.CSP')")
100 | }
101 |
102 | function Get-Timezone-Value() {
103 | return (Run-In-Script-Console $global:SUT_CONTAINER "System.getProperty('user.timezone')")
104 | }
105 | }
106 |
107 | It 'passes JAVA_OPTS' {
108 | Start-With-Opts -JAVA_OPTS "$tzSetting $cspSetting"
109 |
110 | Get-Csp-Value | Should -Match $cspRegex
111 | Get-Timezone-Value | Should -Match $tzRegex
112 | }
113 |
114 | It 'passes JENKINS_JAVA_OPTS' {
115 | Start-With-Opts -JENKINS_JAVA_OPTS "$tzSetting $cspSetting"
116 |
117 | Get-Csp-Value | Should -Match $cspRegex
118 | Get-Timezone-Value | Should -Match $tzRegex
119 | }
120 |
121 | It 'JENKINS_JAVA_OPTS overrides JAVA_OPTS' {
122 | Start-With-Opts -JAVA_OPTS "$tzSetting -Dhudson.model.DirectoryBrowserSupport.CSP=\`"default-src 'self';\`"" -JENKINS_JAVA_OPTS "$cspSetting"
123 |
124 | Get-Csp-Value | Should -Match $cspRegex
125 | Get-Timezone-Value | Should -Match $tzRegex
126 | }
127 |
128 | AfterEach {
129 | Cleanup $global:SUT_CONTAINER | Out-Null
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/tests/runtime.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | load 'test_helper/bats-support/load'
4 | load 'test_helper/bats-assert/load'
5 | load test_helpers
6 |
7 | IMAGE=${IMAGE:-debian_jdk17}
8 | SUT_IMAGE=$(get_sut_image)
9 | SUT_DESCRIPTION="${IMAGE}-runtime"
10 |
11 | teardown() {
12 | cleanup "$(get_sut_container_name)"
13 | }
14 |
15 | @test "[${SUT_DESCRIPTION}] test version in docker metadata" {
16 | local version
17 | version=$(get_jenkins_version)
18 | assert "${version}" docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.version"}}' $SUT_IMAGE
19 | }
20 |
21 | @test "[${SUT_DESCRIPTION}] test commit SHA in docker metadata is not empty" {
22 | run docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision"}}' $SUT_IMAGE
23 | refute_output ""
24 | }
25 |
26 | @test "[${SUT_DESCRIPTION}] test commit SHA in docker metadata" {
27 | local revision
28 | revision=$(get_commit_sha)
29 | assert "${revision}" docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision"}}' $SUT_IMAGE
30 | }
31 |
32 | @test "[${SUT_DESCRIPTION}] test multiple JENKINS_OPTS" {
33 | local container_name version
34 | # running --help --version should return the version, not the help
35 | version=$(get_jenkins_version)
36 | container_name="$(get_sut_container_name)"
37 | cleanup "${container_name}"
38 | # need the last line of output
39 | assert "${version}" docker run --rm --env JENKINS_OPTS="--help --version" --name "${container_name}" -P $SUT_IMAGE | tail -n 1
40 | }
41 |
42 | @test "[${SUT_DESCRIPTION}] test jenkins arguments" {
43 | local container_name version
44 | # running --help --version should return the version, not the help
45 | version=$(get_jenkins_version)
46 | container_name="$(get_sut_container_name)"
47 | cleanup "${container_name}"
48 | # need the last line of output
49 | assert "${version}" docker run --rm --name "${container_name}" -P $SUT_IMAGE --help --version | tail -n 1
50 | }
51 |
52 | @test "[${SUT_DESCRIPTION}] timezones are handled correctly" {
53 | local timezone1 timezone2 container_name
54 | container_name="$(get_sut_container_name)"
55 | cleanup "${container_name}"
56 |
57 | run docker run --rm --name "${container_name}" $SUT_IMAGE bash -c "date +'%Z %z'"
58 | timezone1="${output}"
59 | assert_equal "${timezone1}" "UTC +0000"
60 |
61 | run docker run --rm --name "${container_name}" --env "TZ=Europe/Luxembourg" $SUT_IMAGE bash -c "date +'%Z %z'"
62 | timezone1="${output}"
63 | run docker run --rm --name "${container_name}" --env "TZ=Australia/Sydney" $SUT_IMAGE bash -c "date +'%Z %z'"
64 | timezone2="${output}"
65 |
66 | refute [ "${timezone1}" = "${timezone2}" ]
67 | }
68 |
69 | @test "[${SUT_DESCRIPTION}] has utf-8 locale" {
70 | run docker run --rm "${SUT_IMAGE}" locale charmap
71 | assert_equal "${output}" "UTF-8"
72 | }
73 |
74 | # parameters are passed as docker run parameters
75 | start-jenkins-with-jvm-opts() {
76 | local container_name
77 | container_name="$(get_sut_container_name)"
78 | cleanup "${container_name}"
79 |
80 | run docker run --detach --name "${container_name}" --publish-all "$@" $SUT_IMAGE
81 | assert_success
82 |
83 | # Container is running
84 | sleep 1 # give time to eventually fail to initialize
85 | retry 3 1 assert "true" docker inspect -f '{{.State.Running}}' "${container_name}"
86 |
87 | # Jenkins is initialized
88 | retry 30 5 test_url /api/json
89 | }
90 |
91 | get-csp-value() {
92 | runInScriptConsole "System.getProperty('hudson.model.DirectoryBrowserSupport.CSP')"
93 | }
94 |
95 | get-timezone-value() {
96 | runInScriptConsole "System.getProperty('user.timezone')"
97 | }
98 |
99 | runInScriptConsole() {
100 | SERVER="$(get_jenkins_url)"
101 | COOKIEJAR="$(mktemp)"
102 | PASSWORD="$(get_jenkins_password)"
103 | CRUMB=$(curl -u "admin:$PASSWORD" --cookie-jar "$COOKIEJAR" "$SERVER/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)")
104 |
105 | bash -c "curl -fssL -X POST -u \"admin:$PASSWORD\" --cookie \"$COOKIEJAR\" -H \"$CRUMB\" \"$SERVER\"/scriptText -d script=\"$1\" | sed -e 's/Result: //'"
106 | }
107 |
108 | @test "[${SUT_DESCRIPTION}] passes JAVA_OPTS as JVM options" {
109 | 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';\""
110 |
111 | # JAVA_OPTS are used
112 | assert "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" get-csp-value
113 | assert 'Europe/Madrid' get-timezone-value
114 | }
115 |
116 | @test "[${SUT_DESCRIPTION}] passes JENKINS_JAVA_OPTS as JVM options" {
117 | 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';\""
118 |
119 | # JENKINS_JAVA_OPTS are used
120 | assert "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" get-csp-value
121 | assert 'Europe/Madrid' get-timezone-value
122 | }
123 |
124 | @test "[${SUT_DESCRIPTION}] JENKINS_JAVA_OPTS overrides JAVA_OPTS" {
125 | start-jenkins-with-jvm-opts \
126 | --env JAVA_OPTS="-Duser.timezone=Europe/Madrid -Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'\"" \
127 | --env JENKINS_JAVA_OPTS="-Dhudson.model.DirectoryBrowserSupport.CSP=\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';\""
128 |
129 | # JAVA_OPTS and JENKINS_JAVA_OPTS are used
130 | assert "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" get-csp-value
131 | assert 'Europe/Madrid' get-timezone-value
132 | }
133 |
134 | @test "[${SUT_DESCRIPTION}] ensure that 'ps' command is available" {
135 | command -v ps # Check for binary presence in the current PATH
136 | }
137 |
--------------------------------------------------------------------------------
/tests/test_helpers.bash:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Assert that $1 is the outputof a command $2
4 | function assert {
5 | local expected_output=$1
6 | shift
7 | local actual_output
8 | actual_output=$("$@")
9 | actual_output="${actual_output//[$'\t\r\n']}" # remove newlines
10 | if ! [ "$actual_output" = "$expected_output" ]; then
11 | echo "expected: \"$expected_output\""
12 | echo "actual: \"$actual_output\""
13 | false
14 | fi
15 | }
16 |
17 | # Retry a command $1 times until it succeeds. Wait $2 seconds between retries.
18 | function retry {
19 | local attempts=$1
20 | shift
21 | local delay=$1
22 | shift
23 | local i
24 |
25 | for ((i=0; i < attempts; i++)); do
26 | run "$@"
27 | # shellcheck disable=SC2154
28 | if [ "$status" -eq 0 ]; then
29 | return 0
30 | fi
31 | sleep "${delay}"
32 | done
33 |
34 | # shellcheck disable=SC2154
35 | echo "Command \"$*\" failed $attempts times. Status: $status. Output: $output" >&2
36 | false
37 | }
38 |
39 | function get_sut_image {
40 | test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}"
41 | ## Retrieve the SUT image name from buildx
42 | # Option --print for 'docker buildx bake' prints the JSON configuration on the stdout
43 | # Option --silent for 'make' suppresses the echoing of command so the output is valid JSON
44 | # The image name is the 1st of the "tags" array, on the first "image" found
45 | make --silent show | jq -r ".target.${IMAGE}.tags[0]"
46 | }
47 |
48 | function get_jenkins_version() {
49 | test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}"
50 |
51 | make --silent show | jq -r ".target.${IMAGE}.args.JENKINS_VERSION"
52 | }
53 |
54 | function get_commit_sha() {
55 | test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}"
56 |
57 | make --silent show | jq -r ".target.${IMAGE}.args.COMMIT_SHA"
58 | }
59 |
60 | function get_test_image {
61 | test -n "${BATS_TEST_NUMBER:?"[get_test_image] Please set the variable BATS_TEST_NUMBER."}"
62 | test -n "${SUT_DESCRIPTION:?"[get_test_image] Please set the variable SUT_DESCRIPTION."}"
63 | echo "${SUT_DESCRIPTION}-${BATS_TEST_NUMBER}"
64 | }
65 |
66 | function get_sut_container_name {
67 | echo "$(get_test_image)-container"
68 | }
69 |
70 | function docker_build_child {
71 | local parent=$1; shift
72 | local tag=$1; shift
73 | local dir=$1; shift
74 | local build_opts=("$@")
75 | local tmp
76 | tmp=$(mktemp "$dir/Dockerfile.XXXXXX")
77 | sed -e "s#FROM bats-jenkins.*#FROM ${parent}#g" "$dir/Dockerfile" > "$tmp"
78 | docker build --tag "$tag" --no-cache "${build_opts[@]}" --file "${tmp}" "${dir}" 2>&1
79 | rm "$tmp"
80 | }
81 |
82 | function get_jenkins_url {
83 | if [ -z "${DOCKER_HOST}" ]; then
84 | DOCKER_IP=localhost
85 | else
86 | # shellcheck disable=SC2001
87 | DOCKER_IP=$(echo "$DOCKER_HOST" | sed -e 's|tcp://\(.*\):[0-9]*|\1|')
88 | fi
89 | echo "http://$DOCKER_IP:$(docker port "$(get_sut_container_name)" 8080 | cut -d: -f2)"
90 | }
91 |
92 | function get_jenkins_password {
93 | docker logs "$(get_sut_container_name)" 2>&1 | grep -A 2 "Please use the following password to proceed to installation" | tail -n 1
94 | }
95 |
96 | function test_url {
97 | run curl --user "admin:$(get_jenkins_password)" --output /dev/null --silent --head --fail --connect-timeout 30 --max-time 60 "$(get_jenkins_url)$1"
98 | if [ "$status" -eq 0 ]; then
99 | true
100 | else
101 | echo "URL $(get_jenkins_url)$1 failed" >&2
102 | echo "output: $output" >&2
103 | false
104 | fi
105 | }
106 |
107 | function cleanup {
108 | docker kill "$1" &>/dev/null ||:
109 | docker rm -fv "$1" &>/dev/null ||:
110 | }
111 |
112 | function unzip_manifest {
113 | local plugin=$1
114 | local volume_name=$2
115 | export SUT_IMAGE
116 | docker run --rm --volume "${volume_name}:/var/jenkins_home" --entrypoint unzip "${SUT_IMAGE}" \
117 | -p "/var/jenkins_home/plugins/${plugin}" META-INF/MANIFEST.MF | tr -d '\r'
118 | }
119 |
120 | function clean_work_directory {
121 | local workdir=$1
122 | local sut_image=$2
123 | rm -rf "${workdir}/upgrade-plugins/work-${sut_image}"
124 | }
125 |
--------------------------------------------------------------------------------
/tests/test_helpers.psm1:
--------------------------------------------------------------------------------
1 | Import-Module -DisableNameChecking -Force $PSScriptRoot/../jenkins-support.psm1
2 |
3 | function Test-CommandExists($command) {
4 | $oldPreference = $ErrorActionPreference
5 | $ErrorActionPreference = 'stop'
6 | $res = $false
7 | try {
8 | if(Get-Command $command) {
9 | $res = $true
10 | }
11 | } catch {
12 | $res = $false
13 | } finally {
14 | $ErrorActionPreference=$oldPreference
15 | }
16 | return $res
17 | }
18 |
19 | # check dependencies
20 | if(-Not (Test-CommandExists docker)) {
21 | Write-Error "docker is not available"
22 | }
23 |
24 | function Retry-Command {
25 | [CmdletBinding()]
26 | param (
27 | [parameter(Mandatory, ValueFromPipeline)]
28 | [ValidateNotNullOrEmpty()]
29 | [scriptblock] $ScriptBlock,
30 | [int] $RetryCount = 3,
31 | [int] $Delay = 30,
32 | [string] $SuccessMessage = "Command executed successfully!",
33 | [string] $FailureMessage = "Failed to execute the command"
34 | )
35 |
36 | process {
37 | $Attempt = 1
38 | $Flag = $true
39 |
40 | do {
41 | try {
42 | $PreviousPreference = $ErrorActionPreference
43 | $ErrorActionPreference = 'Stop'
44 | Invoke-Command -NoNewScope -ScriptBlock $ScriptBlock -OutVariable Result 4>&1
45 | $ErrorActionPreference = $PreviousPreference
46 |
47 | # flow control will execute the next line only if the command in the scriptblock executed without any errors
48 | # if an error is thrown, flow control will go to the 'catch' block
49 | Write-Verbose "$SuccessMessage `n"
50 | $Flag = $false
51 | }
52 | catch {
53 | if ($Attempt -gt $RetryCount) {
54 | Write-Verbose "$FailureMessage! Total retry attempts: $RetryCount"
55 | Write-Verbose "[Error Message] $($_.exception.message) `n"
56 | $Flag = $false
57 | } else {
58 | Write-Verbose "[$Attempt/$RetryCount] $FailureMessage. Retrying in $Delay seconds..."
59 | Start-Sleep -Seconds $Delay
60 | $Attempt = $Attempt + 1
61 | }
62 | }
63 | }
64 | While ($Flag)
65 | }
66 | }
67 |
68 | function Get-SutImage {
69 | $DOCKERFILE = Get-EnvOrDefault 'DOCKERFILE' ''
70 | $IMAGENAME = Get-EnvOrDefault 'CONTROLLER_IMAGE' '' # Ex: jdk17-hotspot-windowsservercore-ltsc2019
71 |
72 | $REAL_DOCKERFILE=Resolve-Path -Path "$PSScriptRoot/../${DOCKERFILE}"
73 |
74 | if(!($DOCKERFILE -match '^(?.+)[\\/](?.+)[\\/](?.+)[\\/]Dockerfile$') -or !(Test-Path $REAL_DOCKERFILE)) {
75 | Write-Error "Wrong Dockerfile path format or file does not exist: $DOCKERFILE"
76 | exit 1
77 | }
78 |
79 | return "pester-jenkins-$IMAGENAME"
80 | }
81 |
82 | function Run-Program($cmd, $params, $verbose=$false) {
83 | if($verbose) {
84 | Write-Host "$cmd $params"
85 | }
86 | $psi = New-Object System.Diagnostics.ProcessStartInfo
87 | $psi.CreateNoWindow = $true
88 | $psi.UseShellExecute = $false
89 | $psi.RedirectStandardOutput = $true
90 | $psi.RedirectStandardError = $true
91 | $psi.WorkingDirectory = (Get-Location)
92 | $psi.FileName = $cmd
93 | $psi.Arguments = $params
94 | $proc = New-Object System.Diagnostics.Process
95 | $proc.StartInfo = $psi
96 | [void]$proc.Start()
97 | $stdout = $proc.StandardOutput.ReadToEnd()
98 | $stderr = $proc.StandardError.ReadToEnd()
99 | $proc.WaitForExit()
100 | if($proc.ExitCode -ne 0) {
101 | Write-Host "`n`nstdout:`n$stdout`n`nstderr:`n$stderr`n`n"
102 | }
103 |
104 | return $proc.ExitCode, $stdout, $stderr
105 | }
106 |
107 | function Build-Docker($tag) {
108 | $exitCode, $stdout, $stderr = Run-Program 'docker-compose' '--file=build-windows.yaml build --parallel'
109 | if($exitCode -ne 0) {
110 | return $exitCode, $stdout, $stderr
111 | }
112 | return(Run-Program 'docker' $('tag {0}/{1}:{2} {3}' -f $env:DOCKERHUB_ORGANISATION, $env:DOCKERHUB_REPO, $env:CONTROLLER_IMAGE, $tag))
113 | }
114 |
115 | function Build-DockerChild($tag, $dir) {
116 | Get-Content "$dir/Dockerfile-windows" | ForEach-Object{$_ -replace "FROM bats-jenkins","FROM $(Get-SutImage)" } | Out-File -FilePath "$dir/Dockerfile-windows.tmp" -Encoding ASCII
117 | return (Run-Program 'docker.exe' "build -t `"$tag`" $args -f `"$dir/Dockerfile-windows.tmp`" `"$dir`"")
118 | }
119 |
120 | function Get-JenkinsUrl($Container) {
121 | $DOCKER_IP=(Get-EnvOrDefault 'DOCKER_HOST' 'localhost') | %{$_ -replace 'tcp://(.*):[0-9]*','$1'} | Select-Object -First 1
122 | $port = (docker port "$CONTAINER" 8080 | %{$_ -split ':'})[1]
123 | return "http://$($DOCKER_IP):$($port)"
124 | }
125 |
126 | function Get-JenkinsPassword($Container) {
127 | $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 }'
128 | if($lastExitCode -eq 0) {
129 | return $res
130 | }
131 | return $null
132 | }
133 |
134 | function Run-In-Script-Console($Container, $Script) {
135 | $jenkinsPassword = Get-JenkinsPassword $Container
136 | $jenkinsUrl = Get-JenkinsUrl $Container
137 | if($null -ne $jenkinsPassword) {
138 | $pair = "admin:$($jenkinsPassword)"
139 | $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
140 | $basicAuthValue = "Basic $encodedCreds"
141 | $Headers = @{ Authorization = $basicAuthValue }
142 |
143 | $crumb = (Invoke-RestMethod -Uri $('{0}{1}' -f $jenkinsUrl, '/crumbIssuer/api/json') -Headers $Headers -TimeoutSec 60 -Method Get -SessionVariable session -UseBasicParsing).crumb
144 | if ($null -ne $crumb) {
145 | $Headers += @{ "Jenkins-Crumb" = $crumb }
146 | }
147 | $body = @{ script = $Script }
148 | $res = Invoke-WebRequest -Uri $('{0}{1}' -f $jenkinsUrl, '/scriptText') -Headers $Headers -TimeoutSec 60 -Method Post -WebSession $session -UseBasicParsing -Body $body
149 | if ($res.StatusCode -eq 200) {
150 | return $res.Content.replace('Result: ', '')
151 | }
152 | }
153 | return $null
154 | }
155 |
156 | function Test-Url($Container, $Url) {
157 | $jenkinsPassword = Get-JenkinsPassword $Container
158 | $jenkinsUrl = Get-JenkinsUrl $Container
159 | if($null -ne $jenkinsPassword) {
160 | $pair = "admin:$($jenkinsPassword)"
161 | $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
162 | $basicAuthValue = "Basic $encodedCreds"
163 | $Headers = @{ Authorization = $basicAuthValue }
164 |
165 | $res = Invoke-WebRequest -Uri $('{0}{1}' -f $jenkinsUrl, $Url) -Headers $Headers -TimeoutSec 60 -Method Head -UseBasicParsing
166 | if($res.StatusCode -eq 200) {
167 | return $true
168 | }
169 | }
170 | Write-Error "URL $(Get-JenkinsUrl $Container)$Url failed"
171 | return $false
172 | }
173 |
174 | function Cleanup($image) {
175 | docker kill "$image" 2>&1 | Out-Null
176 | docker rm -fv "$image" 2>&1 | Out-Null
177 | }
178 |
179 | function Unzip-Manifest($Container, $Plugin, $Work) {
180 | return (Run-Program "docker.exe" "run --rm -v `"${Work}: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")
181 | }
182 |
--------------------------------------------------------------------------------
/tests/upgrade-plugins/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 |
3 | RUN jenkins-plugin-cli --plugins junit:1.28 ant:1.2
4 |
--------------------------------------------------------------------------------
/tests/upgrade-plugins/Dockerfile-windows:
--------------------------------------------------------------------------------
1 | FROM bats-jenkins
2 | # hadolint shell=powershell
3 |
4 | RUN & C:/ProgramData/Jenkins/jenkins-plugin-cli.ps1 --plugins junit:1.28 ant:1.2
5 |
--------------------------------------------------------------------------------
/tini_pub.gpg:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 |
3 | mQINBFANDtsBEACpb69Ul0Ko7D4XxRIvPGnDMuGdocb8PxR+EGbnHe0uS2tCbsfj
4 | TOoWWUrjufrWYxGlKNqOxbEhzFA2wSQ6VD6xROPQT5dAdKaGnSCiaUg7XTzcb9u3
5 | a5Qbx99EDZWaYDNMnLZnIElDX+YmkkEyrrmjiML63m+1P88Bz7ag18hLkqpCiIVM
6 | TMRfQluBJVvndX7Stzm35utugN+xeTQryjLx74CO6TUWyC7hAjvQhR5IdAk4H0oT
7 | RsOKZ9OQmpO0CJ1XXpKkDdDc60WVrLp1jwq2M7fx/Nz+z13nTHa3fDw8j10+1k0+
8 | c2HafM+GLR5CHlXVMqveWJrimII1ZILxRj/86fFCEC8ZhVW1ym4j+mqEENrzP4I7
9 | L3OnyKLxNKIY9CFDhfzLhNAuNeuIp6KgynzuyxWnJO4q7m/B0zcRIBcjXPrpblIx
10 | QlT3qQ/vFdcylDDSdbgtjD+9URG6bFR9PVlRTllBDPGQEK8vjV44pxLCenm/TzdB
11 | Y4RlEePf+3y7wVrkjg+l4rIDH57Vl188RODuWVGeLZ3IYWqvRUnYxHmta27UH6zY
12 | 7FNN5p7H2VqP6v9GFhiHOCTKdUbQhOoPLmUTyBas0WsC8sXdwpTy3mJthzfUwgVN
13 | 2SIXPnndz7RcHwZtW1x9ZtVMDr6ll99kT63+sdZJHmUdlnDr+EGEd/L61QARAQAB
14 | tCBUaG9tYXMgT3JvemNvIDx0aG9tYXNAb3JvemNvLmZyPokBHAQQAQIABgUCUA8L
15 | RQAKCRBJYaTFxD1O6EKGB/0Y6xVDkvKFdTCTeaGnqzn2IUSC+JBSuVOQD5bxVkUs
16 | 9OmtalVNU+vN0nkcL1Apxr4Hz0DBXH1PktIGTNRI3zdkZ37mwJDmUafy7uUpZr4T
17 | T7z4ppYn9V6zSCH3tp8GI0NI/1E7JtAjGTpIFwC8Cj1L07vjyGjH8C2kEexImsTB
18 | ScPzkGVxl0BqL5FOuF+uSx8IyDo6WnvZMifkDkomEqxtl+gasgjsB/Vohs1VokJ3
19 | JcJZ46KIsbhgD1ma/J0jn1kycsq5k00BpwNTfLporn9sSjbVUf0onldKDmDFY3k4
20 | tSgKC0sGa4YGiogWVT7jZt8A3UWRrIG3T4lpru55vkKyiQI4BBMBAgAiBQJQDQ7b
21 | AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCahBWdcAGk5WJgD/90NC3h
22 | /s0Ztk2dDQOA4aAFCMBpt5q6nodfVQFUwGZTc4QqRRCgDrYCLHyQ7PX0cly46XRj
23 | eShnuk6t//f2z62Vkq9W5fsXpEWImnHX7x/5/gh9AQ8lGK6vCQSnbPBNB8Q6Wjgn
24 | O/nuktS2bIlWfEwOLa8V5S576P0F4+nbRnVkRowEkOOYxlqjeJUJW9IdmdLby+QP
25 | n7LxVRvKEfh3JoO27yxvTq8K7a4Ptmwx8NO12QNZGo4uzwV1qZkt/w/wAPoBfZw+
26 | GEBIxYXtPLaRG+HMeuK6mXMNQGqh3fyIvGgLaXnGgOM0xmxjSr105cFLlGwrCzar
27 | Gsktr8XrvaWnGUwD1eebQswI0xydet6V1SGXXzNU2h8gMZ7HnI/bR2M7GXtaxDrR
28 | UEDNtuIYtorJ+SjkK5Ka6XNMk05Q3EdOJeNKLpDPn7mWHTBiccW6WGJgTXoRfbn+
29 | uG8PM3kGCxPcp3KT9dP+SUgHmTh4tcrw+AyxHOn4UNVQvscozUekcxjrmB1JHzJm
30 | 5fMpdULjcwU9gkyuN3GgQlcmOfOF/4o7qOTKudrQ5CgzVhMfr/lf1ehfNDPxMN7u
31 | km3hLbs9kK/U2G6o2/uc+FQA2vucUxHOtvHYM0fLFSvE++7I2ZjH8qSgDPDfJpN+
32 | tsVdTpxzCxdZbrN1zIsRgjZmoYN8tLvOSOZGxrkBDQRTM1XNAQgArFZl/nlHrvoz
33 | chRGDHw6+3z4XczhOP6J949yljkkdKP2waQlG6Bb03J7lZ6G4mMVg4SEquWMJM/+
34 | aOcTuJCpVPJgLUCv5xO01Clp1a9BHYxb9TOKZ1mMG1nF20YUJZHbTeO2cb4sZGxH
35 | sfJHoDw4EouXSF5NIfH3Z/50kIYRJkl5WBjj3E06UbgguXw2x5iFoPfpBBlF4ucJ
36 | rPLZo56nYf+PPu+fPJ7fxAyR0dyNrD9Z9AdYMrMzlaV/7lMsX/ZVM+Upya2fRAVH
37 | D+/DqSPCy/14JDdOEif8VX/Jx1SSE5/P1K7ZohvBgLgLpeLzZHEkpuepQY4TF4I8
38 | baW65afJBwARAQABiQNEBBgBAgAPBQJTM1XNAhsCBQkDwmcAASkJEJqEFZ1wAaTl
39 | wF0gBBkBAgAGBQJTM1XNAAoJECqK4Nq/8uWmyHMIAKdDGd3BctlhbCJc6Ji+ZQhT
40 | 2Y8rKdRba99PBoikUEhieosCNWw+ynB/HvNgKgy1D5Qr134mHK2zy8Jo/Akc+oIK
41 | sFs//L0rbNF1wMDUNhb9y65vByy3KmY7oSHqILrFHFrkgdmiArpT5Oup/8QZ8j9j
42 | +xzcoFVhJ0uSeABJ1WvlNCW1Uf0uzENSjsFrfzYKLSD9WYqRSG39oVGQR+8yuuM4
43 | 0QZvILCTaFm7ArVoTpjB6re1vOsHI4XjCogh7p7WnA/PuVyGgZibtcnu5ySlsaKV
44 | miO+72u7FsaXy2Ktj8GPRRliMBIM0JxddEy+uLYVmEPJYwyh5EuPszDzGl5aOvs2
45 | Gg//YADXocfBGN89Cjl8Nd7t4Zch5vBaFuvWcBhC0vY8cV4cb+KYRWjNx+0Q0221
46 | XCr8lx+PKIhwoDSeIZmauhAI7QdHNGztPzkhNZ9Zyf8epqiTqCYuHpAnp1oLsT0y
47 | x7TvNgznZLb9xLiix/NmPgkQLafzZW6or1+rNHnwYKQ+f2uyHCWMSQyAR9h9O8CT
48 | +2byMAKlJ0fdsKlI/asvoUG2b5G4vzNsC5yDwEJpU+g6G2nffboxYKfEftB2IeXV
49 | 1PgOCarhSW5c6/IxljscjVyrhFdbl5cbmYbQBNCHUkiEglRoQ/N1oS0/ks5WooTj
50 | bbQxUJZZqT/bs72YAWb6UEDl3w7wxZbYRGqnDJgxaD8NW9A/+kH8PrA7gOnsIwMw
51 | uXUuzMdpv2hGjy9bZxY1VLqZD/nqOBLXSdF7yj9VBE593UN7qyTmT3KmTbLaILum
52 | nuyKZ7cyyfONHR8pzGMIMBfVKimL1DOTKkzrpYc7jpRqafQZ4vs6FzCNyhRHMlzL
53 | m57WN54qaZHsBN7GWATzusDie4xlqTW5XPSzoWxvV6RggoUQWrASmo2fIqAGu+0Y
54 | DzKf1mnjAX7qW5ooyrCm3HSrU+Yxlpw30TrZd07IYFpVc+rn30XdHrTj3OjTihZY
55 | xbuztXXRWf+mpGQHhuGFTNq2JQdZW5qaGlHxKWKBniRY8dm5Ag0EUA0O2wEQAMky
56 | 8GHJYQGguCL2c72GBtsvGIuMqei0rNGm7mbv2uGma2oQ3aTQA+ahF1RSsTZjyx1n
57 | nnWNPRMTY7WPvSotX1zmm4rqrJXcFlv06BDzCrIIUWOYh09AASCcPcr3ZI6Hwy6W
58 | olb74SzFKoMG3aJVsvLNC6SVsgk6DidUrxmCzAjJ7CIdV6HfGBwbEm1VvdCMpLPG
59 | GPQZgzpfuajmuPPEQVXKIld3xkfv3LgUdSv60lATMlu9YAYkKU/H1zOH0OvDav5O
60 | Cr7a/8SQCjJd2/fPu4KGH/2zfehC2ywnlNV8BsLTpCw+nnlEkKQZtOsFCBEHxdqJ
61 | 08mEb2whuFWCtF6OfmSYrkv/wAKeBtJTjM7U1Nqq4tkZkzMjslvH9B4bYNtWqNXo
62 | EsDdf5aJ4z0hRrW+L8JeIp53OU4y/v4Jf03vrLhaWzjOxPBdMjOY8IOjKwb7vkMC
63 | gVlbVcmUx4Q4Y+EEOVHpfjup0sbM4jXg0lkQueLXj4iKHpW/GG9RaYl9OdP7MT49
64 | ZvTcLQQewXy5NioE9qD0tjDZwOq8/fVtRKJX5nlhH7iwNx+JVPjStcQJ3XvnYhrI
65 | A14vu4b+AG+X7osfNvdWEjkguSSdW6aZVjSvLxikADSvUZ8ET+YUsTePBOpiQzGe
66 | v9INrcciqXuoGH2T0CFMwh3y1dfqneU7qN3pN57BABEBAAGJAh8EGAECAAkFAlAN
67 | DtsCGwwACgkQmoQVnXABpOVN4g/9Hr/QQCorIhLGfH/r5ULLaPmaPi/xv4pq5NN1
68 | 5cmE/fuLYQyyiwapQPUsyjxa/0DPtc5ID/aVbi/xQdCiUbCywsWb0vgMNNkEfwyA
69 | 4XMH77Ac3GAigPa728LZ56GEx9WUJwf5ha3NNKz3j7mAj3FzLyt6OQ74AeFdgNHS
70 | evkwzTvoJYSNa05GBl0dtdaKWav9J0qch2+RcNe81x3u0LqLMh+cPE3v/ht5Y4We
71 | //WzQkvMgUi60W2qpgMLxj/gnGgxwAuGain4XJiT5FQdPm4Z4Nq4m6ePB0FLdVjk
72 | 2sxzDgvFqlXQ5ATHG7xEAxxhfHoVmagEyUUj4npHlkxLrLVMosoimrjoKw5LPZVf
73 | VIBpy106le405ct7Zy/SZizBYYBRF5tyZyY3j2f0kSfUg3yGFrAWAW5h1RaOljEr
74 | Yro1nS8WBEeRGd6L3Gt/aZbuhvW8rDN4Fga9mP/EgS78W/eSLNLq3Uh4wkpQqWmx
75 | 6YA5tb6wQ7SMs56Uv5l9Q758iYv7R8XFDLOR80qTl2nHO0+U2a/cNjopN11zUvW4
76 | 4YLOcCayDwmnS/Q6YIHZeXXlY/DCdiLcKQsZsRwr2y0Wa7alm4R5HYO5UaXPc2sG
77 | 7Vb/fhxd7K51RPG956G69+SNgExdV8QzfkEcRSqAK0Dj1vlkzxKliCpTeiZWw3Pt
78 | GMwAh7e5Ag0EVqgZAQEQALKlBucLl8yGkJ+3pq6BeedXOYSHRJ7bAHFS6bcMwWVT
79 | jwMlg2USwpBiyDMS9prJMjSADVnJ4ODcTgWeTrKxCbNb0En/dDalrQYoiS9c6Auf
80 | C9JdKdNOorfMIO4kjQ2Pi72Ajxtft3g8epDLIXM9aOIhuD21YduFPzDAJprLWIao
81 | JmxNrwjDK97RZpX7ti1ElB5cR9j1nqgQaqOKztAk15/UwrLWBLUqr7iyq93CCjd/
82 | cAVI1HXiBqBi/abIDfR6F/gTPMiLx2H0MBNzhPzoIG4rdOzMgstJTAWetRVPTSPe
83 | yvZaKpavEir+lqTLzM0RXdDS9p/aSF4tJlFJmyhUfUB8KxUePg6hYfcE4DN2vnCx
84 | M+xXYdVHQ70r3w2gDR/jl6p8wUGIZWwW4lqYElq0+xNVVdnp9H1oMMrJJIHnjRZ6
85 | AegAHBqL6zgWFS2ONlqss8mjjv+AWbEXscdTI0hhgqPmP32HTTNwztc60p7IfMe9
86 | 9m+a7XnS7YNxxj1dfnF3MOxa4F5AlDtV3JVVM+hkYgFnUzDcQh8XIDNi+CVMIqgw
87 | wcfr3XY+ewwtxtyYX/ukniut6GnxuR9LtHcEE0yCUta2hkhgFJagrw2XQ7IHNDmW
88 | w+9F1Qe4x7qvHADXWBGfHjIt33VtZHD/EgHDHPomr+WTTJKB26b+Ehw6as12Dvez
89 | ABEBAAGJBD4EGAECAAkFAlaoGQECGwICKQkQmoQVnXABpOXBXSAEGQECAAYFAlao
90 | GQEACgkQC1iN/wUnqbe49A/+OidDlCs4Q34Ht11iai0DCPrAv3CGzutyBv5Ml8is
91 | myTIFAb6QfKZYQGI4Oj8ByqQqCUbNM1xoMbXneXfz8hD8uj2bFmaQ0e0sPpKuRsa
92 | M2sEiSxFJH6FZuvxSMD9a6oCYNC8VZ5ubIXinp14QB/dGy/YyX9rSW8LpPJZF2x0
93 | zJaUM+2Upif2raoeMJVfh7abi4HfxWrd+jz5ilqCzkpEL0rIaBkUQbLcXfun7hT+
94 | sNP63/qgq6jn/xFYCLF5Y1YiWLsND0NsDjD2p3AvgobByc4JwIKVP0qooDcw52yv
95 | 51wAX4uQ2sbfK+en55nsscXo9UkCRedIs7O4RnNen3SkD7VLDtBYZanJX+/oJI+z
96 | Fkics+ME8mZ1WGMyuZraz1IYqzaTh6K8N4rgavqgXcZErdVDOxE8NiT2+vzBJsAF
97 | HhCHoXL9aIwTVLIR1yzZRZz54ZnTJrtASRCrPsK+OnpV7NS1dYWDLbylWSAQ5ESy
98 | qiDKmNtKfJt6KzpHO/F/SDrzV9aNZJNwwEZcpO+Bbtecuob1cxZ3HSfqlrqznIue
99 | SgznNhyMv5XrANVDCcX26hqWVw7XU8SOwQx4reDPRjzUSYu4c3rjXUgrVxZ/EAge
100 | 6fA6g97ufMY4fYRfX4iQK/g88moyWgRwgg+XA/QgvFNo+7LXvB53Skfd29FmCW8n
101 | upBT8A//c2GHkCNS0xEj6YOAmleli4zLu012AoC+AYgvVs/7Uucu0/b7u7LU+iTf
102 | 3fz94AnABP2w6GkbepE+zXnOPi1zbT8mzKluTjTfYSFEVYYvlMG1h/0tWX12gViE
103 | YdSADNERZzDyXuHT+nlU0+b0rSQSULDZ83fPvHCt5I9rVYZWyox0zedz8Hjihxkc
104 | Wqt32SDo3hqXojGRsvsNgBSN+GOSE5BdldT4ws9UD2V+UXjr3iVuuvsXgbCfN8Xz
105 | 9I7D0sdWH0rdqCejqMcHrg/9Wq2Rrhrn70ApwSSiNYQcOgtTwExRcNlm706QVIm3
106 | OoHIDO2LR2wsT7wYZ9qzjtvf+uN/qrIq+Hb6I/D4jrMcfiTssqkiIr4yRDb5l8zM
107 | HeND0080BzrcAv9hucXlcNIXEYFGRwVmjnStklXlYcDen5QwhKyOg2LMTtqAuojY
108 | k+coHMHdpAUWkjdCKK9aEp+w7TZW3OX7VOOpF5a7ipLW7AltKwt5y0DL7grZYWdE
109 | 1nWb+v396fMSHIqjzM6ZQFTNUuFFH8YbtLUybEHwVI5mHtoeDXFtNhW+rrYBbprQ
110 | 8q8SmfWDTC88SgpD7jX91z+J5aCeRmIokkXEOyEoabF8POdadmqc4RIvP/eFsbJT
111 | jWeTRQ/MeWRRnCwuj1QF2v19RVi+VhTKUGnZ1XSMRzmovraDKkA=
112 | =YltV
113 | -----END PGP PUBLIC KEY BLOCK-----
114 |
--------------------------------------------------------------------------------
/tools/hadolint:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | exec docker run --rm \
4 | -w "${PWD}" \
5 | -v "${PWD}:${PWD}" \
6 | ghcr.io/hadolint/hadolint:v2.12.0-debian hadolint "$@"
7 |
--------------------------------------------------------------------------------
/tools/shellcheck:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | exec docker run --rm \
4 | -w "${PWD}" \
5 | -v "${PWD}:${PWD}" \
6 | koalaman/shellcheck:v0.10.0 "$@"
7 |
--------------------------------------------------------------------------------
/updatecli/updatecli.d/alpine.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bump Alpine version
3 |
4 | scms:
5 | default:
6 | kind: github
7 | spec:
8 | user: "{{ .github.user }}"
9 | email: "{{ .github.email }}"
10 | owner: "{{ .github.owner }}"
11 | repository: "{{ .github.repository }}"
12 | token: "{{ requiredEnv .github.token }}"
13 | username: "{{ .github.username }}"
14 | branch: "{{ .github.branch }}"
15 |
16 | sources:
17 | latestVersion:
18 | kind: githubrelease
19 | name: "Get the latest Alpine Linux version"
20 | spec:
21 | owner: "alpinelinux"
22 | repository: "aports" # Its release process follows Alpine's
23 | token: "{{ requiredEnv .github.token }}"
24 | username: "{{ .github.username }}"
25 | versionfilter:
26 | kind: semver
27 | pattern: "~3"
28 | transformers:
29 | - trimprefix: "v"
30 |
31 | conditions:
32 | testDockerfileArg:
33 | name: "Does the Dockerfile have an ARG instruction for the Alpine Linux version?"
34 | kind: dockerfile
35 | disablesourceinput: true
36 | spec:
37 | file: alpine/hotspot/Dockerfile
38 | instruction:
39 | keyword: "ARG"
40 | matcher: "ALPINE_TAG"
41 | testDockerImageExists:
42 | name: "Does the Docker Image exist on the Docker Hub?"
43 | kind: dockerimage
44 | sourceid: latestVersion
45 | spec:
46 | image: "alpine"
47 | # tag come from the source
48 | architecture: amd64
49 |
50 | targets:
51 | updateDockerBake:
52 | name: "Update the value of the base image (ARG ALPINE_TAG) in the docker-bake.hcl"
53 | kind: hcl
54 | spec:
55 | file: docker-bake.hcl
56 | path: variable.ALPINE_FULL_TAG.default
57 | scmid: default
58 | updateDockerfile:
59 | name: "Update the value of the JDK base image (ARG ALPINE_TAG) in the Dockerfile"
60 | kind: dockerfile
61 | spec:
62 | file: alpine/hotspot/Dockerfile
63 | instruction:
64 | keyword: "ARG"
65 | matcher: "ALPINE_TAG"
66 | scmid: default
67 | actions:
68 | default:
69 | kind: github/pullrequest
70 | scmid: default
71 | title: Bump Alpine Linux Version to {{ source "latestVersion" }}
72 | spec:
73 | labels:
74 | - dependencies
75 | - alpine
76 |
--------------------------------------------------------------------------------
/updatecli/updatecli.d/debian.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bump Debian Bookworm version
3 |
4 | scms:
5 | default:
6 | kind: github
7 | spec:
8 | user: "{{ .github.user }}"
9 | email: "{{ .github.email }}"
10 | owner: "{{ .github.owner }}"
11 | repository: "{{ .github.repository }}"
12 | token: "{{ requiredEnv .github.token }}"
13 | username: "{{ .github.username }}"
14 | branch: "{{ .github.branch }}"
15 |
16 | sources:
17 | bookwormLatestVersion:
18 | kind: dockerimage
19 | name: "Get the latest Debian Bookworm Linux version"
20 | transformers:
21 | - trimprefix: "bookworm-"
22 | spec:
23 | image: "debian"
24 | tagfilter: "bookworm-*"
25 | versionfilter:
26 | kind: regex
27 | pattern: >-
28 | bookworm-\d+$
29 |
30 | conditions:
31 | testDockerfileArg:
32 | name: "Does the Dockerfile have an ARG instruction for the Debian Bookworm & Bookworm Slim Linux version?"
33 | kind: dockerfile
34 | disablesourceinput: true
35 | spec:
36 | files:
37 | - debian/bookworm/hotspot/Dockerfile
38 | - debian/bookworm-slim/hotspot/Dockerfile
39 | instruction:
40 | keyword: "ARG"
41 | matcher: "BOOKWORM_TAG"
42 | testVersionInBakeFile:
43 | name: "Does the bake file have variable BOOKWORM_TAG"
44 | kind: file
45 | disablesourceinput: true
46 | spec:
47 | file: docker-bake.hcl
48 | matchpattern: "(.*BOOKWORM_TAG.*)"
49 |
50 | targets:
51 | updateDockerBake:
52 | name: "Update the value of the base image (ARG BOOKWORM_TAG) in the docker-bake.hcl"
53 | kind: file
54 | sourceid: bookwormLatestVersion
55 | spec:
56 | file: docker-bake.hcl
57 | matchpattern: >-
58 | variable(.*)"BOOKWORM_TAG"(.*){(.*)(\r\n|\r|\n)(.*)default(.*)=(.*)
59 | replacepattern: >-
60 | variable${1}"BOOKWORM_TAG"${2}{${3}${4}${5}default${6}= "{{ source "bookwormLatestVersion" }}"
61 | scmid: default
62 | updateDockerfile:
63 | name: "Update the value of the base image (ARG BOOKWORM_TAG) in the Dockerfiles"
64 | kind: dockerfile
65 | sourceid: bookwormLatestVersion
66 | spec:
67 | files:
68 | - debian/bookworm/hotspot/Dockerfile
69 | - debian/bookworm-slim/hotspot/Dockerfile
70 | instruction:
71 | keyword: "ARG"
72 | matcher: "BOOKWORM_TAG"
73 | scmid: default
74 | actions:
75 | default:
76 | kind: github/pullrequest
77 | scmid: default
78 | title: Bump Debian Bookworm Linux Version to {{ source "bookwormLatestVersion" }}
79 | spec:
80 | labels:
81 | - dependencies
82 | - debian
83 |
--------------------------------------------------------------------------------
/updatecli/updatecli.d/git-lfs.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bump `git-lfs` version
3 |
4 | scms:
5 | default:
6 | kind: github
7 | spec:
8 | user: "{{ .github.user }}"
9 | email: "{{ .github.email }}"
10 | owner: "{{ .github.owner }}"
11 | repository: "{{ .github.repository }}"
12 | token: "{{ requiredEnv .github.token }}"
13 | username: "{{ .github.username }}"
14 | branch: "{{ .github.branch }}"
15 |
16 | sources:
17 | lastReleaseVersion:
18 | kind: githubrelease
19 | name: Get the latest `git-lfs` release version
20 | spec:
21 | owner: "git-lfs"
22 | repository: "git-lfs"
23 | token: "{{ requiredEnv .github.token }}"
24 | username: "{{ .github.username }}"
25 | versionfilter:
26 | kind: semver
27 | transformers:
28 | - trimprefix: v
29 |
30 | targets:
31 | updateVersion:
32 | name: Update `git-lfs` version in debian dockerfiles
33 | sourceid: lastReleaseVersion
34 | kind: dockerfile
35 | spec:
36 | files:
37 | - debian/bookworm/hotspot/Dockerfile
38 | - debian/bookworm-slim/hotspot/Dockerfile
39 | instruction:
40 | keyword: "ARG"
41 | matcher: "GIT_LFS_VERSION"
42 | scmid: default
43 |
44 | actions:
45 | default:
46 | kind: github/pullrequest
47 | title: Bump `git-lfs` version to {{ source "lastReleaseVersion" }}
48 | scmid: default
49 | spec:
50 | labels:
51 | - dependencies
52 | - git-lfs
53 |
--------------------------------------------------------------------------------
/updatecli/updatecli.d/hadolint.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bump hadolint version
3 |
4 | scms:
5 | default:
6 | kind: github
7 | spec:
8 | user: "{{ .github.user }}"
9 | email: "{{ .github.email }}"
10 | owner: "{{ .github.owner }}"
11 | repository: "{{ .github.repository }}"
12 | token: "{{ requiredEnv .github.token }}"
13 | username: "{{ .github.username }}"
14 | branch: "{{ .github.branch }}"
15 |
16 | sources:
17 | lastReleaseVersion:
18 | kind: githubrelease
19 | name: Get the latest hadolint release version
20 | spec:
21 | owner: "hadolint"
22 | repository: "hadolint"
23 | token: "{{ requiredEnv .github.token }}"
24 | username: "{{ .github.username }}"
25 | versionfilter:
26 | kind: semver
27 | transformers:
28 | - trimprefix: v
29 |
30 | targets:
31 | updateVersion:
32 | name: "Update the `hadolint` version in the tools/hadolint script"
33 | sourceid: lastReleaseVersion
34 | kind: file
35 | spec:
36 | file: "tools/hadolint"
37 | matchpattern: "ghcr.io/hadolint/hadolint:v(.*)-debian"
38 | content: 'ghcr.io/hadolint/hadolint:v{{ source `lastReleaseVersion` }}-debian'
39 | scmid: default
40 |
41 | actions:
42 | default:
43 | kind: github/pullrequest
44 | title: Bump `hadolint` version to {{ source "lastReleaseVersion" }}
45 | scmid: default
46 | spec:
47 | labels:
48 | - dependencies
49 | - hadolint
50 |
--------------------------------------------------------------------------------
/updatecli/updatecli.d/jdk17.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bump JDK17 version
3 |
4 | scms:
5 | default:
6 | kind: github
7 | spec:
8 | user: "{{ .github.user }}"
9 | email: "{{ .github.email }}"
10 | owner: "{{ .github.owner }}"
11 | repository: "{{ .github.repository }}"
12 | token: "{{ requiredEnv .github.token }}"
13 | username: "{{ .github.username }}"
14 | branch: "{{ .github.branch }}"
15 |
16 | sources:
17 | lastVersion:
18 | kind: temurin
19 | name: Get the latest Adoptium JDK17 version
20 | spec:
21 | featureversion: 17
22 | transformers:
23 | - trimprefix: "jdk-"
24 |
25 | conditions:
26 | checkTemurinAllReleases:
27 | name: Check if the "" is available for all platforms
28 | kind: temurin
29 | sourceid: lastVersion
30 | spec:
31 | featureversion: 17
32 | platforms:
33 | - alpine-linux/x64
34 | - linux/x64
35 | - linux/aarch64
36 | - linux/ppc64le
37 | - linux/s390x
38 | - windows/x64
39 |
40 | targets:
41 | ## Global config files
42 | setJDK17VersionDockerBake:
43 | name: "Bump JDK17 version for Linux images in the docker-bake.hcl file"
44 | kind: hcl
45 | transformers:
46 | - replacer:
47 | from: "+"
48 | to: "_"
49 | spec:
50 | file: docker-bake.hcl
51 | path: variable.JAVA17_VERSION.default
52 | scmid: default
53 | setJDK17VersionWindowsDockerCompose:
54 | name: "Bump JDK17 version in build-windows.yaml"
55 | kind: yaml
56 | transformers:
57 | - replacer:
58 | from: "+"
59 | to: "_"
60 | spec:
61 | files:
62 | - build-windows.yaml
63 | key: $.services.jdk17.build.args.JAVA_VERSION
64 | scmid: default
65 | ## Dockerfiles
66 | # Setting default JAVA_VERSION ARG to current Jenkins default JDK17
67 | setJDK17VersionAlpine:
68 | name: "Bump JDK17 version for Linux images in the Alpine Linux Dockerfile"
69 | kind: dockerfile
70 | transformers:
71 | - replacer:
72 | from: "+"
73 | to: "_"
74 | spec:
75 | file: alpine/hotspot/Dockerfile
76 | instruction:
77 | keyword: ARG
78 | matcher: JAVA_VERSION
79 | scmid: default
80 | setJDK17VersionDebian:
81 | name: "Bump JDK17 version for Linux images in the Debian Dockerfiles"
82 | kind: dockerfile
83 | transformers:
84 | - replacer:
85 | from: "+"
86 | to: "_"
87 | spec:
88 | files:
89 | - debian/bookworm/hotspot/Dockerfile
90 | - debian/bookworm-slim/hotspot/Dockerfile
91 | instruction:
92 | keyword: ARG
93 | matcher: JAVA_VERSION
94 | scmid: default
95 | setJDK17VersionRhel:
96 | name: "Bump JDK17 version for Linux images in the Rhel Dockerfile"
97 | kind: dockerfile
98 | transformers:
99 | - replacer:
100 | from: "+"
101 | to: "_"
102 | spec:
103 | file: rhel/ubi9/hotspot/Dockerfile
104 | instruction:
105 | keyword: ARG
106 | matcher: JAVA_VERSION
107 | scmid: default
108 | setJDK17VersionWindowsDockerImage:
109 | name: "Bump default JDK17 version for Linux images in the Windows Dockerfile"
110 | kind: dockerfile
111 | transformers:
112 | - replacer:
113 | from: "+"
114 | to: "_"
115 | spec:
116 | file: windows/windowsservercore/hotspot/Dockerfile
117 | instruction:
118 | keyword: ARG
119 | matcher: JAVA_VERSION
120 | scmid: default
121 |
122 | actions:
123 | default:
124 | kind: github/pullrequest
125 | scmid: default
126 | title: Bump JDK17 version to {{ source "lastVersion" }}
127 | spec:
128 | labels:
129 | - dependencies
130 | - jdk17
131 |
--------------------------------------------------------------------------------
/updatecli/updatecli.d/jdk21.yaml:
--------------------------------------------------------------------------------
1 | name: Bump JDK21 version
2 |
3 | scms:
4 | default:
5 | kind: github
6 | spec:
7 | user: "{{ .github.user }}"
8 | email: "{{ .github.email }}"
9 | owner: "{{ .github.owner }}"
10 | repository: "{{ .github.repository }}"
11 | token: "{{ requiredEnv .github.token }}"
12 | username: "{{ .github.username }}"
13 | branch: "{{ .github.branch }}"
14 |
15 | sources:
16 | lastTemurin21Version:
17 | kind: temurin
18 | name: Get the latest Adoptium JDK21 version via the API
19 | spec:
20 | featureversion: 21
21 | transformers:
22 | - trimprefix: "jdk-"
23 |
24 | conditions:
25 | checkTemurinAllReleases:
26 | name: Check if the "" is available for all platforms
27 | kind: temurin
28 | sourceid: lastTemurin21Version
29 | spec:
30 | featureversion: 21
31 | platforms:
32 | - alpine-linux/x64
33 | - alpine-linux/aarch64
34 | - linux/x64
35 | - linux/aarch64
36 | - linux/ppc64le
37 | - linux/s390x
38 | - windows/x64
39 |
40 | targets:
41 | setJDK21VersionDockerBake:
42 | name: "Bump JDK21 version for Linux images in the docker-bake.hcl file"
43 | kind: hcl
44 | transformers:
45 | - replacer:
46 | from: "+"
47 | to: "_"
48 | spec:
49 | file: docker-bake.hcl
50 | path: variable.JAVA21_VERSION.default
51 | scmid: default
52 |
53 | setJDK21VersionWindowsDockerCompose:
54 | name: "Bump JDK21 version in build-windows.yaml"
55 | kind: yaml
56 | transformers:
57 | - replacer:
58 | from: "+"
59 | to: "_"
60 | spec:
61 | files:
62 | - build-windows.yaml
63 | key: $.services.jdk21.build.args.JAVA_VERSION
64 | scmid: default
65 |
66 | actions:
67 | default:
68 | kind: github/pullrequest
69 | scmid: default
70 | title: Bump JDK21 version to {{ source "lastTemurin21Version" }}
71 | spec:
72 | labels:
73 | - dependencies
74 | - jdk21
75 |
--------------------------------------------------------------------------------
/updatecli/updatecli.d/shellcheck.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bump shellcheck version
3 |
4 | scms:
5 | default:
6 | kind: github
7 | spec:
8 | user: "{{ .github.user }}"
9 | email: "{{ .github.email }}"
10 | owner: "{{ .github.owner }}"
11 | repository: "{{ .github.repository }}"
12 | token: "{{ requiredEnv .github.token }}"
13 | username: "{{ .github.username }}"
14 | branch: "{{ .github.branch }}"
15 |
16 | sources:
17 | lastReleaseVersion:
18 | kind: githubrelease
19 | name: Get the latest shellcheck release version
20 | spec:
21 | owner: "koalaman"
22 | repository: "shellcheck"
23 | token: "{{ requiredEnv .github.token }}"
24 | username: "{{ .github.username }}"
25 | versionfilter:
26 | kind: semver
27 | transformers:
28 | - trimprefix: v
29 |
30 | targets:
31 | updateVersion:
32 | name: "Update the `shellcheck` version in the tools/shellcheck script"
33 | sourceid: lastReleaseVersion
34 | kind: file
35 | spec:
36 | file: "tools/shellcheck"
37 | matchpattern: "koalaman/shellcheck:v(.*) "
38 | content: 'koalaman/shellcheck:v{{ source `lastReleaseVersion` }} '
39 | scmid: default
40 |
41 | actions:
42 | default:
43 | kind: github/pullrequest
44 | title: Bump `shellcheck` version to {{ source "lastReleaseVersion" }}
45 | scmid: default
46 | spec:
47 | labels:
48 | - dependencies
49 | - shellcheck
50 |
--------------------------------------------------------------------------------
/updatecli/values.github-action.yaml:
--------------------------------------------------------------------------------
1 | github:
2 | user: "GitHub Actions"
3 | email: "41898282+github-actions[bot]@users.noreply.github.com"
4 | username: "github-actions"
5 | token: "UPDATECLI_GITHUB_TOKEN"
6 | branch: "master"
7 | owner: "jenkinsci"
8 | repository: "docker"
9 |
--------------------------------------------------------------------------------
/windows/windowsservercore/hotspot/Dockerfile:
--------------------------------------------------------------------------------
1 | # escape=`
2 | # hadolint shell=powershell
3 |
4 | ARG JAVA_VERSION=17.0.15_6
5 | ARG WINDOWS_VERSION=ltsc2019
6 | ARG TOOLS_WINDOWS_VERSION=1809
7 |
8 | FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION}" AS jre-build
9 |
10 | # $ProgressPreference: https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251261324
11 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
12 |
13 | ARG JAVA_VERSION=17.0.15_6
14 |
15 | RUN New-Item -ItemType Directory -Path C:\temp | Out-Null ; `
16 | $javaMajorVersion = $env:JAVA_VERSION.substring(0,2) ; `
17 | $msiUrl = 'https://api.adoptium.net/v3/installer/version/jdk-{0}/windows/x64/jdk/hotspot/normal/eclipse?project=jdk' -f $env:JAVA_VERSION.Replace('_', '%2B') ; `
18 | Invoke-WebRequest $msiUrl -OutFile 'C:\temp\jdk.msi' ; `
19 | $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 ; `
20 | $proc.WaitForExit() ; `
21 | Remove-Item -Path C:\temp -Recurse | Out-Null
22 |
23 | RUN Write-Host 'javac --version' ; javac --version ; `
24 | Write-Host 'java --version' ; java --version
25 |
26 | RUN $version = (jlink --version) ; `
27 | $compressArg = '--compress=2' ; `
28 | switch ($version.Substring(0,3)) { `
29 | '17.' {} `
30 | # the compression argument is different for JDK21
31 | '21.' { $compressArg = '--compress=zip-6' } `
32 | Default { `
33 | Write-Error 'ERROR: unmanaged jlink version pattern' ; `
34 | exit 1 ; `
35 | } `
36 | } `
37 | & jlink `
38 | --strip-java-debug-attributes `
39 | $compressArg `
40 | --add-modules ALL-MODULE-PATH `
41 | --no-man-pages `
42 | --no-header-files `
43 | --output /javaruntime
44 |
45 | FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION}" AS controller
46 |
47 | ARG JAVA_HOME="C:/openjdk-17"
48 | ENV JAVA_HOME=${JAVA_HOME}
49 |
50 | COPY --from=jre-build /javaruntime $JAVA_HOME
51 |
52 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
53 |
54 | # Add java in PATH
55 | RUN $CurrentPath = (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path ; `
56 | $NewPath = $CurrentPath + $(';{0}/bin' -f $env:JAVA_HOME) ; `
57 | Set-ItemProperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path -Value $NewPath
58 |
59 | ARG user=jenkins
60 | ARG http_port=8080
61 | ARG agent_port=50000
62 | ARG JENKINS_HOME=C:/ProgramData/Jenkins/JenkinsHome
63 |
64 | ARG COMMIT_SHA
65 |
66 | ENV JENKINS_HOME=$JENKINS_HOME
67 | ENV JENKINS_AGENT_PORT=${agent_port}
68 |
69 | # Jenkins home directory is a volume, so configuration and build history
70 | # can be persisted and survive image upgrades
71 | VOLUME $JENKINS_HOME
72 |
73 | # Jenkins is run with user `jenkins`
74 | # If you bind mount a volume from the host or a data container,
75 | # ensure you use the same uid
76 | # hadolint ignore=DL4006
77 | RUN New-LocalUser -Name $env:user -AccountNeverExpires -Description 'Jenkins User' -NoPassword -UserMayNotChangePassword | Out-Null ; `
78 | Set-Localuser -Name $env:user -PasswordNeverExpires $true | Out-Null ; `
79 | Add-LocalGroupMember -Group "Administrators" -Member "${env:user}" ; `
80 | New-Item -Type Directory -Force -Path "C:/ProgramData/Jenkins" | Out-Null ; `
81 | icacls.exe "C:/ProgramData/Jenkins" /setowner ${env:user} | Out-Null ; `
82 | icacls.exe "C:/ProgramData/Jenkins" /inheritance:r | Out-Null ; `
83 | icacls.exe "C:/ProgramData/Jenkins" /grant:r $('{0}:(CI)(OI)(F)' -f $env:user) /grant 'Administrators:(CI)(OI)(F)' | Out-Null ; `
84 | icacls.exe "$env:JENKINS_HOME" /setowner ${env:user} | Out-Null ; `
85 | icacls.exe "$env:JENKINS_HOME" /grant:r $('{0}:(CI)(OI)(F)' -f $env:user) /grant 'Administrators:(CI)(OI)(F)' | Out-Null
86 |
87 | USER ${user}
88 |
89 | # `C:/ProgramData/Jenkins/Reference/` contains all reference configuration we want
90 | # to set on a fresh new installation. Use it to bundle additional plugins
91 | # or config file with your custom jenkins Docker image.
92 | # hadolint ignore=DL4006
93 | RUN New-Item -ItemType Directory -Force -Path C:/ProgramData/Jenkins/Reference/init.groovy.d | Out-Null
94 |
95 | # jenkins version being bundled in this docker image
96 | ARG JENKINS_VERSION
97 | ENV JENKINS_VERSION=${JENKINS_VERSION:-2.504}
98 |
99 | # jenkins.war checksum, download will be validated using it
100 | ARG JENKINS_SHA=efc91d6be8d79dd078e7f930fc4a5f135602d0822a5efe9091808fdd74607d32
101 |
102 | # Can be used to customize where jenkins.war get downloaded from
103 | ARG JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war
104 |
105 | # could use ADD but this one does not check Last-Modified header neither does it allow to control checksum
106 | # see https://github.com/docker/docker/issues/8331
107 | RUN Invoke-WebRequest -Uri "$env:JENKINS_URL" -OutFile C:/ProgramData/Jenkins/jenkins.war ; `
108 | if ((Get-FileHash C:/ProgramData/Jenkins/jenkins.war -Algorithm SHA256).Hash -ne $env:JENKINS_SHA) {exit 1}
109 |
110 | ENV JENKINS_UC=https://updates.jenkins.io
111 | ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
112 | ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
113 |
114 | ARG PLUGIN_CLI_VERSION=2.13.2
115 | ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/${PLUGIN_CLI_VERSION}/jenkins-plugin-manager-${PLUGIN_CLI_VERSION}.jar
116 | RUN $sha256sum = [System.Text.Encoding]::UTF8.GetString((Invoke-WebRequest -Uri ($env:PLUGIN_CLI_URL + '.sha256') -UseBasicParsing).Content); `
117 | Invoke-WebRequest -Uri "$env:PLUGIN_CLI_URL" -OutFile C:/ProgramData/Jenkins/jenkins-plugin-manager.jar; `
118 | if ((Get-FileHash -Path C:/ProgramData/Jenkins/jenkins-plugin-manager.jar -Algorithm SHA256).Hash -ne $sha256sum) {exit 1}
119 |
120 | # for main web interface:
121 | EXPOSE ${http_port}
122 |
123 | # will be used by attached agents:
124 | EXPOSE ${agent_port}
125 |
126 | ENV COPY_REFERENCE_FILE_LOG=$JENKINS_HOME/copy_reference_file.log
127 |
128 | COPY jenkins-support.psm1 C:/ProgramData/Jenkins
129 | COPY jenkins.ps1 C:/ProgramData/Jenkins
130 | # 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
131 | COPY jenkins-plugin-cli.ps1 C:/ProgramData/Jenkins
132 |
133 | ENTRYPOINT ["powershell.exe", "-f", "C:/ProgramData/Jenkins/jenkins.ps1"]
134 |
135 | # metadata labels
136 | LABEL `
137 | org.opencontainers.image.vendor="Jenkins project" `
138 | org.opencontainers.image.title="Official Jenkins Docker image" `
139 | org.opencontainers.image.description="The Jenkins Continuous Integration and Delivery server" `
140 | org.opencontainers.image.version="${JENKINS_VERSION}" `
141 | org.opencontainers.image.url="https://www.jenkins.io/" `
142 | org.opencontainers.image.source="https://github.com/jenkinsci/docker" `
143 | org.opencontainers.image.revision="${COMMIT_SHA}" `
144 | org.opencontainers.image.licenses="MIT"
145 |
--------------------------------------------------------------------------------