├── .ci
└── publish.sh
├── .dockerignore
├── .git-blame-ignore-revs
├── .gitattributes
├── .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
├── Dockerfile-alpine
├── Dockerfile-jdk17
├── Dockerfile-jdk21
├── Dockerfile-slim
├── HACKING.adoc
├── Jenkinsfile
├── Jenkinsfile.mwaite
├── LICENSE.txt
├── Makefile
├── README.md
├── SECURITY.md
├── alpine
└── hotspot
│ └── Dockerfile
├── build-windows.yaml
├── debian
├── bookworm-slim
│ └── hotspot
│ │ └── Dockerfile
└── bookworm
│ └── hotspot
│ └── Dockerfile
├── docker-bake.hcl
├── docker_build.py
├── docker_notify
├── docker_run.py
├── 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
├── ref
├── README-mwaite.md
└── configuration-as-code
│ └── README.md
├── 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
├── create-missing-legacyIds
├── 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 | gcp-data.txt
3 | bats/
4 | target/
5 | tests/functions/init.groovy.d/
6 | tests/functions/java_cp/
7 | tests/functions/copy_reference_file.log
8 | tests/**/work-*/
9 | manifest-tool
10 | multiarch/qemu-*
11 | multiarch/Dockerfile-*
12 | /docker.iml
13 | work-pester-jenkins-windows/
14 | /.idea/
15 |
16 | /**/windows/**/jenkins.ps1
17 | /**/windows/**/jenkins-plugin-cli.ps1
18 | /**/windows/**/jenkins-support.psm1
19 |
20 | tests/**/Dockerfile\.*
21 |
22 | /__pycache__/
23 | /ref/plugins/*.bak
24 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Dockerfile-alpine:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.504.2-alpine
2 |
3 | LABEL maintainer="mark.earl.waite@gmail.com"
4 |
5 | # Modified jenkins.sh to allow use inside a dockerfile based Jenkins job
6 | COPY jenkins.sh /usr/local/bin/jenkins.sh
7 |
8 | ENV CASC_JENKINS_CONFIG=${JENKINS_HOME}/configuration-as-code/
9 |
10 | USER root
11 |
12 | # Check that expected utilities are available in the image
13 | RUN test -x /usr/bin/pgrep && \
14 | test -x /usr/bin/wget && \
15 | test -x /usr/bin/git-lfs
16 |
17 | # Create cache directory for git repository mirrors
18 | RUN mkdir -p /var/cache/jenkins && chown jenkins:jenkins /var/cache/jenkins
19 |
20 | USER jenkins
21 |
22 | COPY --chown=jenkins:jenkins ref /usr/share/jenkins/ref/
23 |
--------------------------------------------------------------------------------
/Dockerfile-jdk17:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.504.2-rhel-ubi9-jdk17
2 |
3 | LABEL maintainer="mark.earl.waite@gmail.com"
4 |
5 | # Modified jenkins.sh to allow use inside a dockerfile based Jenkins job
6 | COPY jenkins.sh /usr/local/bin/jenkins.sh
7 |
8 | ENV CASC_JENKINS_CONFIG=${JENKINS_HOME}/configuration-as-code/
9 |
10 | USER root
11 |
12 | # Install and check that expected utilities are available in the image
13 | # hadolint ignore=DL3008
14 | RUN dnf install --disableplugin=subscription-manager --setopt=install_weak_deps=0 --setopt=tsflags=nodocs -y \
15 | gnupg \
16 | make \
17 | procps \
18 | wget \
19 | && dnf clean --disableplugin=subscription-manager all \
20 | && test -x /usr/bin/pgrep \
21 | && test -x /usr/bin/wget \
22 | && test -x /usr/bin/git-lfs
23 |
24 | # Create cache directory for git repository mirrors
25 | RUN mkdir -p /var/cache/jenkins && chown jenkins:jenkins /var/cache/jenkins
26 |
27 | USER jenkins
28 |
29 | COPY --chown=jenkins:jenkins ref /usr/share/jenkins/ref/
30 |
--------------------------------------------------------------------------------
/Dockerfile-jdk21:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.504.2-slim-jdk21
2 |
3 | LABEL maintainer="mark.earl.waite@gmail.com"
4 |
5 | # Modified jenkins.sh to allow use inside a dockerfile based Jenkins job
6 | COPY jenkins.sh /usr/local/bin/jenkins.sh
7 |
8 | ENV CASC_JENKINS_CONFIG=${JENKINS_HOME}/configuration-as-code/
9 |
10 | USER root
11 |
12 | # Install and check that expected utilities are available in the image
13 | # hadolint ignore=DL3008
14 | RUN apt-get clean \
15 | && apt-get update \
16 | && apt-get install -y auto-apt-proxy --no-install-recommends \
17 | && auto-apt-proxy \
18 | && apt-get install -y --no-install-recommends \
19 | gnupg \
20 | make \
21 | procps \
22 | wget \
23 | && rm -rf /var/lib/apt/lists/* \
24 | && test -x /usr/bin/pgrep \
25 | && test -x /usr/bin/wget \
26 | && test -x /usr/local/bin/git-lfs
27 |
28 | # Create cache directory for git repository mirrors
29 | RUN mkdir -p /var/cache/jenkins && chown jenkins:jenkins /var/cache/jenkins
30 |
31 | USER jenkins
32 |
33 | COPY --chown=jenkins:jenkins ref /usr/share/jenkins/ref/
34 |
--------------------------------------------------------------------------------
/Dockerfile-slim:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.504.2-slim
2 |
3 | LABEL maintainer="mark.earl.waite@gmail.com"
4 |
5 | # Modified jenkins.sh to allow use inside a dockerfile based Jenkins job
6 | COPY jenkins.sh /usr/local/bin/jenkins.sh
7 |
8 | ENV CASC_JENKINS_CONFIG=${JENKINS_HOME}/configuration-as-code/
9 |
10 | USER root
11 |
12 | # Install and check that expected utilities are available in the image
13 | # hadolint ignore=DL3008
14 | RUN apt-get clean \
15 | && apt-get update \
16 | && apt-get install -y auto-apt-proxy --no-install-recommends \
17 | && auto-apt-proxy \
18 | && apt-get install -y --no-install-recommends \
19 | gnupg \
20 | make \
21 | procps \
22 | wget \
23 | && rm -rf /var/lib/apt/lists/* \
24 | && test -x /usr/bin/pgrep \
25 | && test -x /usr/bin/wget \
26 | && test -x /usr/local/bin/git-lfs
27 |
28 | # Create cache directory for git repository mirrors
29 | RUN mkdir -p /var/cache/jenkins && chown jenkins:jenkins /var/cache/jenkins
30 |
31 | USER jenkins
32 |
33 | COPY --chown=jenkins:jenkins ref /usr/share/jenkins/ref/
34 |
--------------------------------------------------------------------------------
/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 | pipeline {
2 | agent none
3 | stages {
4 | stage('Launch') {
5 | agent {
6 | label '!windows'
7 | }
8 | steps {
9 | sh 'echo Branch is ${GIT_LOCAL_BRANCH}'
10 | }
11 | }
12 | stage('Containers') {
13 | parallel {
14 | stage('Alpine JDK 17') {
15 | agent {
16 | dockerfile {
17 | filename 'Dockerfile-alpine'
18 | }
19 | }
20 | steps {
21 | sh 'java -jar /usr/share/jenkins/jenkins.war --version; cat /etc/os-release; java --version'
22 | }
23 | }
24 | stage('Slim JDK 17') {
25 | agent {
26 | dockerfile {
27 | filename 'Dockerfile-slim'
28 | }
29 | }
30 | steps {
31 | sh 'java -jar /usr/share/jenkins/jenkins.war --version; cat /etc/os-release; java --version'
32 | }
33 | }
34 | stage('UBI-9 JDK 17') {
35 | agent {
36 | dockerfile {
37 | filename 'Dockerfile-jdk17'
38 | }
39 | }
40 | steps {
41 | sh 'java -jar /usr/share/jenkins/jenkins.war --version; cat /etc/os-release; java --version'
42 | }
43 | }
44 | stage('Debian JDK 21') {
45 | agent {
46 | dockerfile {
47 | filename 'Dockerfile-jdk21'
48 | }
49 | }
50 | steps {
51 | sh 'java -jar /usr/share/jenkins/jenkins.war --version; cat /etc/os-release; java --version'
52 | }
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Jenkinsfile.mwaite:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | label 'docker && python'
4 | }
5 |
6 | stages {
7 | stage('Hello') {
8 | steps {
9 | // sshagent(['mark.earl.waite-gmail.com-ed25519-private-key-with-passphrase']) {
10 | sh '''
11 | if [ -d ref/plugins ]; then
12 | ls -altr ref/plugins/git.jpi
13 | fi
14 | ls -altr docker*
15 | '''
16 | // }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docker_build.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python3
2 |
3 | import fnmatch
4 | import getpass
5 | import optparse
6 | import os
7 | import re
8 | import socket
9 | import string
10 | import subprocess
11 | import sys
12 |
13 | #-----------------------------------------------------------------------
14 |
15 | def get_current_branch():
16 | branch_list = os.popen("git branch", "r").readlines()
17 | for branch_line in branch_list:
18 | branch = branch_line.strip()
19 | if branch.startswith("* "):
20 | return branch[2:]
21 | return "unknown branch"
22 |
23 | #-----------------------------------------------------------------------
24 |
25 | def get_all_branches():
26 | branches = [ ]
27 | branch_list = os.popen("git branch", "r").readlines()
28 | for branch_line in branch_list:
29 | branch = branch_line.strip()
30 | if branch.startswith("* "):
31 | branches.append(branch[2:])
32 | else:
33 | branches.append(branch)
34 | return branches
35 |
36 | #-----------------------------------------------------------------------
37 |
38 | def get_dockerfile(branch_name):
39 | if "jdk21" in branch_name:
40 | return "Dockerfile-jdk21"
41 | if "alpine" in branch_name:
42 | return "Dockerfile-alpine"
43 | if "slim" in branch_name:
44 | return "Dockerfile-slim"
45 | if "jdk17" in branch_name:
46 | return "Dockerfile-jdk17"
47 | if "-and-nodes-rc" in branch_name:
48 | return "Dockerfile-jdk17"
49 | if "lts-with-" in branch_name:
50 | return "Dockerfile-jdk17"
51 | return "Dockerfile-jdk21"
52 |
53 | #-----------------------------------------------------------------------
54 |
55 | def compute_jenkins_base_version(branch_name, numeric_only):
56 | dockerfile_name = get_dockerfile(branch_name)
57 | dockerfile_contents = open(dockerfile_name, "r").read()
58 | version_matcher = "([-A-Za-z0-9.]+)"
59 | if numeric_only:
60 | version_matcher = "([0-9.]+)"
61 | m = re.search("FROM jenkins/jenkins:" + version_matcher, dockerfile_contents)
62 | if m:
63 | return m.group(1).strip()
64 | m = re.search("FROM cloudbees/cloudbees-jenkins-distribution:([-A-Za-z0-9.]+)", dockerfile_contents)
65 | if m:
66 | return m.group(1).strip()
67 | m = re.search("JENKINS_VERSION.*JENKINS_VERSION:-([0-9.]*)", dockerfile_contents)
68 | if m:
69 | return m.group(1).strip()
70 | return "latest"
71 |
72 | #-----------------------------------------------------------------------
73 |
74 | def compute_tag(branch_name):
75 | jenkins_base_version = compute_jenkins_base_version(branch_name, False)
76 | return "markewaite/" + branch_name + ":" + jenkins_base_version
77 |
78 | #-----------------------------------------------------------------------
79 |
80 | def is_home_network():
81 | from socket import socket, SOCK_DGRAM, AF_INET
82 | s = socket(AF_INET, SOCK_DGRAM)
83 | s.settimeout(1.0)
84 | try:
85 | s.connect(("google.com", 0))
86 | except:
87 | return True
88 | return s.getsockname()[0].startswith("172")
89 |
90 | #-----------------------------------------------------------------------
91 |
92 | def get_fqdn():
93 | fqdn = socket.getfqdn()
94 | if not "." in fqdn:
95 | if is_home_network():
96 | fqdn = fqdn + ".markwaite.net"
97 | else:
98 | fqdn = fqdn + ".example.com"
99 | return fqdn
100 |
101 | #-----------------------------------------------------------------------
102 |
103 | # Fully qualified domain name of the host running this script
104 | fqdn = get_fqdn()
105 |
106 | #-----------------------------------------------------------------------
107 |
108 | def replace_text_recursively(find, replace, include_pattern):
109 | print(("Replacing '" + find + "' with '" + replace + "', in files matching '" + include_pattern + "'"))
110 | # Thanks to https://stackoverflow.com/questions/4205854/python-way-to-recursively-find-and-replace-string-in-text-files
111 | for path, dirs, files in os.walk(os.path.abspath("ref")):
112 | for filename in fnmatch.filter(files, include_pattern):
113 | filepath = os.path.join(path, filename)
114 | with open(filepath) as f:
115 | s = f.read()
116 | s = s.replace(find, replace)
117 | with open(filepath, "w") as f:
118 | f.write(s)
119 |
120 | #-----------------------------------------------------------------------
121 |
122 | def replace_constants_in_ref():
123 | if not os.path.isdir("ref"):
124 | return
125 | replacements = { "localhost" : fqdn, "JENKINS_ADVERTISED_HOSTNAME" : fqdn, "JENKINS_HOSTNAME" : fqdn, "LOGNAME" : getpass.getuser() }
126 | for find in replacements:
127 | replace_text_recursively(find, replacements[find], "*.xml")
128 |
129 | #-----------------------------------------------------------------------
130 |
131 | def undo_replace_constants_in_ref():
132 | if not os.path.isdir("ref"):
133 | return
134 | command = [ "git", "checkout", "--", "ref" ]
135 | subprocess.check_call(command)
136 |
137 | #-----------------------------------------------------------------------
138 |
139 | def get_available_updates_command(base_jenkins_version):
140 | available_updates_command = [ "./jenkins-plugin-cli.sh", "--jenkins-version", base_jenkins_version,
141 | "-d", "ref/plugins",
142 | "-f", "plugins.txt",
143 | "--no-download",
144 | "--available-updates",
145 | ]
146 | return available_updates_command
147 |
148 | #-----------------------------------------------------------------------
149 |
150 | def get_download_updates_command(base_jenkins_version):
151 | download_updates_command = [ "./jenkins-plugin-cli.sh", "--jenkins-version", base_jenkins_version,
152 | "-d", "ref/plugins",
153 | "-f", "plugins.txt",
154 | ]
155 | return download_updates_command
156 |
157 | #-----------------------------------------------------------------------
158 |
159 | def get_update_plugins_commands(base_jenkins_version):
160 | commands = [ " ".join(get_available_updates_command(base_jenkins_version) + ["-o", "txt"]) + " > x && mv x plugins.txt",
161 | " ".join(get_download_updates_command(base_jenkins_version)) ]
162 | return commands
163 |
164 | #-----------------------------------------------------------------------
165 |
166 | def report_update_plugins_commands(base_jenkins_version):
167 | commands = get_update_plugins_commands(base_jenkins_version)
168 | for command in commands:
169 | print("Run " + command)
170 |
171 | #-----------------------------------------------------------------------
172 |
173 | def update_plugins(base_jenkins_version):
174 | if not os.path.isdir("ref"):
175 | return
176 |
177 | update_plugins_output = subprocess.check_output(get_available_updates_command(base_jenkins_version)).strip().decode("utf-8")
178 | if "has an available update" in update_plugins_output:
179 | undo_replace_constants_in_ref()
180 | print("Plugin update available")
181 | print("Stopping because a plugin update is available: " + update_plugins_output)
182 | report_update_plugins_commands(base_jenkins_version)
183 | quit()
184 |
185 | #-----------------------------------------------------------------------
186 |
187 | def build_one_image(branch_name, clean):
188 | replace_constants_in_ref()
189 | if branch_name in ["lts-with-plugins", "weekly-with-plugins", "lts-with-plugins-weekly", "lts-with-plugins-add-credentials-and-nodes-rc", "lts-with-plugins-add-credentials-and-nodes-weekly"]:
190 | base_jenkins_version = compute_jenkins_base_version(branch_name, True)
191 | print(("Updating plugins for " + base_jenkins_version))
192 | update_plugins(base_jenkins_version)
193 | tag = compute_tag(branch_name)
194 | print("Building " + tag + " from " + get_dockerfile(tag))
195 | if os.path.exists('ref/jobs'):
196 | subprocess.check_call(['tools/create-missing-legacyIds']) # Avoid RunIdMigrator warnings
197 | command = [ "docker", "build",
198 | "--file", get_dockerfile(tag),
199 | "--tag", tag,
200 | ]
201 | if clean:
202 | command.extend([ "--pull", "--no-cache" ])
203 | command.extend([ ".", ])
204 | subprocess.check_call(command)
205 | undo_replace_constants_in_ref()
206 |
207 | #-----------------------------------------------------------------------
208 |
209 | def get_predecessor_branch(current_branch, all_branches):
210 | last = "upstream/" + current_branch
211 | if current_branch == "lts":
212 | last = "upstream/master"
213 | if current_branch == "weekly":
214 | last = "upstream/master"
215 | for branch in all_branches:
216 | if branch == current_branch:
217 | return last
218 | if current_branch.startswith(branch):
219 | last = branch
220 | return last
221 |
222 | #-----------------------------------------------------------------------
223 |
224 | def merge_predecessor_branch(current_branch, all_branches):
225 | predecessor_branch = get_predecessor_branch(current_branch, all_branches)
226 | command = [ "git", "merge", "--no-edit", predecessor_branch ]
227 | print(("Merging from " + predecessor_branch + " to " + current_branch))
228 | subprocess.check_call(command)
229 |
230 | #-----------------------------------------------------------------------
231 |
232 | def push_current_branch():
233 | status_output = subprocess.check_output([ "git", "status"]).strip().decode("utf-8")
234 | if "Your branch is ahead of " in status_output:
235 | command = [ "git", "push" ]
236 | print("Pushing current branch")
237 | subprocess.check_call(command)
238 |
239 | #-----------------------------------------------------------------------
240 |
241 | def checkout_branch(target_branch):
242 | subprocess.check_call(["git", "clean", "-xffd"])
243 | subprocess.check_call(["git", "reset", "--hard", "HEAD"])
244 | # -with-plugins branches contain large binaries
245 | if target_branch.endswith("-with-plugins"):
246 | subprocess.check_call(["git", "lfs", "fetch", "public", "public/" + target_branch])
247 | # cjt-with-plugins-add-credentials contains some large binaries
248 | if target_branch == "cjt-with-plugins-add-credentials":
249 | subprocess.check_call(["git", "lfs", "fetch", "private", "private/" + target_branch])
250 | subprocess.check_call(["git", "checkout", target_branch])
251 | subprocess.check_call(["git", "pull"])
252 |
253 | #-----------------------------------------------------------------------
254 |
255 | def docker_build(args = []):
256 | help_text = """%prog [options] [host(s)]
257 | Build docker images. Use -h for help."""
258 | parser = optparse.OptionParser(usage=help_text)
259 |
260 | # keep at optparse for 2.6. compatibility
261 | parser.add_option("-a", "--all", action="store_true", default=False, help="build all images")
262 | parser.add_option("-c", "--clean", action="store_true", default=False, help="Pull the base image even if it is already cached")
263 | parser.add_option("-r", "--report", action="store_true", default=False, help="Report the command to update plugins and exit without building the image")
264 |
265 | options, arg_hosts = parser.parse_args()
266 |
267 | original_branch = get_current_branch()
268 | all_branches = get_all_branches()
269 |
270 | if options.all:
271 | branches = all_branches
272 | else:
273 | branches = [ original_branch, ]
274 |
275 | if options.report:
276 | base_jenkins_version = compute_jenkins_base_version(original_branch, True)
277 | report_update_plugins_commands(base_jenkins_version)
278 | quit()
279 |
280 | for branch in branches:
281 | print(("Building " + branch))
282 | checkout_branch(branch)
283 | merge_predecessor_branch(branch, all_branches)
284 | build_one_image(branch, options.clean)
285 | push_current_branch()
286 |
287 | if original_branch != get_current_branch():
288 | checkout_branch(original_branch)
289 |
290 | #-----------------------------------------------------------------------
291 |
292 | if __name__ == "__main__": docker_build(sys.argv[1:])
293 |
--------------------------------------------------------------------------------
/docker_notify:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ "$1" == "" ]; then
4 | directory=ref/jobs
5 | else
6 | directory=$1
7 | fi
8 |
9 | JENKINS_USERNAME=${JENKINS_USERNAME=:-unspecified-user-name}
10 | JENKINS_APITOKEN=${JENKINS_APITOKEN=:-unspecified-api-token}
11 |
12 | tokenarg='&token=insert-our-jenkins-token-here'
13 |
14 | # Scan each multi-branch pipeline (URL encode job path before passing as arg to curl
15 |
16 | scan_delay=7
17 |
18 | git grep -l -F org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject -- $directory | sed -e 's,^ref,,g' -e 's,/jobs/,/job/,g' -e 's,/config.xml$,,g' | shuf | while read multibranch_pipeline
19 | do
20 | scan_delay=$((scan_delay+23))
21 | url_encoded_multibranch_pipeline=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$multibranch_pipeline")
22 | # echo
23 | # echo $multibranch_pipeline to $url_encoded_multibranch_pipeline
24 | # echo http://localhost:8080$url_encoded_multibranch_pipeline?delay=0
25 | echo curl -X POST "http://localhost:8080$url_encoded_multibranch_pipeline/build?delay=${scan_delay}sec"
26 | curl -X POST --user $JENKINS_USERNAME:$JENKINS_APITOKEN "http://localhost:8080$url_encoded_multibranch_pipeline/build?delay=${scan_delay}sec"
27 | # exit 0
28 | done
29 |
30 | # Jobs which are disrupted by notifyCommit pushing a specific SHA1 should be executed again
31 | git grep -l -E 'JENKINS-44087|JENKINS-44041|JENKINS-33238|JENKINS-20941' -- $directory | sed -e 's,^ref,,g' -e 's,/jobs/,/job/,g' -e 's,/config.xml$,,g' | shuf | while read build_now_job
32 | do
33 | scan_delay=$((scan_delay+37))
34 | url_encoded_build_now_job=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$build_now_job")
35 | # echo
36 | # echo $build_now_job to $url_encoded_build_now_job
37 | # echo http://localhost:8080$url_encoded_build_now_job?delay=0
38 | echo curl -X POST "http://localhost:8080$url_encoded_build_now_job/build?delay=${scan_delay}sec"
39 | curl -X POST --user $JENKINS_USERNAME:$JENKINS_APITOKEN "http://localhost:8080$url_encoded_build_now_job/build?delay=${scan_delay}sec"
40 | # exit 0
41 | done
42 |
43 | # Notify commit each git URL
44 |
45 | for url in $(git grep '.*' -- $directory | grep -F config.xml | grep -v JENKINS_HOSTNAME | grep -v JENKINS_ADVERTISED_HOSTNAME | grep -v site/jacoco/index.html | sed -e 's!.*!!g' -e 's!.*$!!g' -e 's!/$!!g' -e 's!.git$!!g' | sort -u | shuf); do
46 | if [[ $url == *"{"* ]]; then
47 | continue
48 | fi
49 | curl -s http://localhost:8080/git/notifyCommit?url=$url$tokenarg
50 | done
51 |
--------------------------------------------------------------------------------
/docker_run.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python3
2 |
3 | # If the ~/.m2 directory is not owned current user, the container
4 | # assumption that it can be written won't be met. In that case, the
5 | # directory is not used. That will slow the initial builds on the machine,
6 | # because they will need to populate the .m2 cache instead of using the
7 | # existing cache.
8 |
9 | import optparse
10 | import os
11 | import re
12 | import socket
13 | import subprocess
14 | import shlex
15 | import shutil
16 | import string
17 | import sys
18 |
19 | import psutil # Install from pip
20 |
21 | import docker_build
22 |
23 | jenkins_home_dir = os.path.expanduser("~/docker-jenkins-home")
24 |
25 | #-----------------------------------------------------------------------
26 |
27 | def is_home_network():
28 | from socket import socket, SOCK_DGRAM, AF_INET
29 | s = socket(AF_INET, SOCK_DGRAM)
30 | s.settimeout(1.0)
31 | try:
32 | s.connect(("google.com", 0))
33 | except:
34 | return True
35 | return s.getsockname()[0].startswith("172.16.16.")
36 |
37 | #-----------------------------------------------------------------------
38 |
39 | def volume_available(lhs):
40 | stat_info = os.stat(lhs)
41 | gid = stat_info.st_gid
42 | if gid not in os.getgroups():
43 | return False
44 | return True
45 |
46 | #-----------------------------------------------------------------------
47 |
48 | def get_user_content_volume_map():
49 | lhs = os.path.expanduser("~/public_html")
50 | rhs = os.path.expanduser("/var/jenkins_home/userContent/")
51 | return lhs + ":" + rhs
52 |
53 | #-----------------------------------------------------------------------
54 |
55 | def get_jenkins_home_volume_map():
56 | if not os.path.exists(jenkins_home_dir):
57 | os.mkdir(jenkins_home_dir)
58 | lhs = jenkins_home_dir
59 | if not volume_available(lhs):
60 | return None
61 | rhs = os.path.expanduser("/var/jenkins_home")
62 | return lhs + ":" + rhs
63 |
64 | #-----------------------------------------------------------------------
65 |
66 | def get_git_reference_repo_volume_map():
67 | lhs = os.path.expanduser("~/git/bare/")
68 | rhs = os.path.expanduser("/var/cache/git/mwaite")
69 | return lhs + ":" + rhs
70 |
71 | #-----------------------------------------------------------------------
72 |
73 | def get_dns_server():
74 | if is_home_network():
75 | return "172.16.16.20"
76 | return "8.8.8.8"
77 |
78 | #-----------------------------------------------------------------------
79 |
80 | def get_windows_dir():
81 | return string.ascii_uppercase[hash(get_fqdn()) % len(string.ascii_uppercase)] + string.ascii_uppercase[hash(get_an_ip_address()) % len(string.ascii_uppercase)]
82 |
83 | #-----------------------------------------------------------------------
84 |
85 | def get_jagent_java_home():
86 | if "jdk21" in docker_build.get_current_branch():
87 | return "/home/jagent/tools/jdk-21.0.7+6"
88 | if "weekly" in docker_build.get_current_branch():
89 | return "/home/jagent/tools/jdk-21.0.7+6"
90 | return "/home/jagent/tools/jdk-17.0.15+6"
91 |
92 | #-----------------------------------------------------------------------
93 |
94 | def get_java_gc_args():
95 | # Use generational GC with Java 21
96 | if "jdk21" in docker_build.get_current_branch():
97 | return [ "-XX:+UseZGC", "-XX:+ZGenerational", ]
98 | if "weekly" in docker_build.get_current_branch():
99 | return [ "-XX:+UseZGC", "-XX:+ZGenerational", ]
100 | return [ "-XX:+UseG1GC", ]
101 |
102 | #-----------------------------------------------------------------------
103 |
104 | def memory_scale(upper_bound):
105 | mem = psutil.virtual_memory()
106 | eight_GB = 8 * 1024 * 1024 * 1024
107 | if mem.total < eight_GB:
108 | return str(int(upper_bound / 2))
109 | return str(upper_bound)
110 |
111 | #-----------------------------------------------------------------------
112 |
113 | def docker_execute(docker_tag, http_port=8080, jnlp_port=50000, ssh_port=18022, debug_port=None, detach=False, quiet=False, access_mode=None):
114 | dns_server = get_dns_server()
115 | user_content_volume_map = get_user_content_volume_map()
116 | git_reference_repo_volume_map = get_git_reference_repo_volume_map()
117 | jenkins_home_volume_map = get_jenkins_home_volume_map()
118 | docker_command = [
119 | "docker", "run", "-i",
120 | "--dns", dns_server,
121 | "--publish", str(http_port) + ":8080",
122 | "--publish", str(ssh_port) + ":18022",
123 | "--publish", str(jnlp_port) + ":50000",
124 | "--restart", "on-failure",
125 | ]
126 | if debug_port != None:
127 | docker_command.extend(["--publish", str(debug_port) + ":5678"])
128 | if jenkins_home_volume_map != None and http_port == 8080:
129 | docker_command.extend(["--volume", jenkins_home_volume_map])
130 | if git_reference_repo_volume_map != None:
131 | docker_command.extend(["--volume", git_reference_repo_volume_map])
132 | if user_content_volume_map != None:
133 | docker_command.extend(["--volume", user_content_volume_map])
134 | if (detach):
135 | docker_command.extend([ "--detach" ])
136 | java_opts = [
137 | "-XX:+AlwaysPreTouch",
138 | "-XX:+HeapDumpOnOutOfMemoryError",
139 | "-XX:HeapDumpPath=/var/jenkins_home/logs",
140 | ]
141 | java_opts.extend(get_java_gc_args())
142 | java_opts.extend([
143 | "-XX:+UseStringDeduplication",
144 | "-XX:+ParallelRefProcEnabled",
145 | "-XX:+DisableExplicitGC",
146 | "-XX:+UnlockDiagnosticVMOptions",
147 | "-XX:+UnlockExperimentalVMOptions",
148 | "-Xms" + memory_scale(3) + "g",
149 | "-Xmx" + memory_scale(7) + "g",
150 | "-XshowSettings:vm"
151 | # "-Dhudson.model.DownloadService.noSignatureCheck=true",
152 | "-Dhudson.lifecycle=hudson.lifecycle.ExitLifecycle", # Temp until https://github.com/jenkinsci/docker/pull/1268
153 | "-Dhudson.model.ParametersAction.keepUndefinedParameters=false",
154 | "-Dhudson.model.ParametersAction.safeParameters=DESCRIPTION_SETTER_DESCRIPTION",
155 | "-Dhudson.TcpSlaveAgentListener.hostName=" + get_base_hostname(),
156 | "-Djava.awt.headless=true",
157 | "-Djenkins.install.runSetupWizard=false",
158 | "-Djenkins.model.Jenkins.buildsDir='/var/jenkins_home/builds/${ITEM_FULL_NAME}'",
159 | "-Djenkins.model.Jenkins.workspacesDir='/var/jenkins_home/workspace/${ITEM_FULL_NAME}'",
160 | "-Djenkins.plugins.git.AbstractGitSCMSource.cacheRootDir=/var/cache/jenkins/git-cache",
161 | "-Dorg.jenkinsci.plugins.github_branch_source.GitHubSCMSource.cacheRootDir=/var/cache/jenkins/github-cache",
162 | "-Dorg.jenkinsci.plugins.gitclient.CliGitAPIImpl.useSETSID=true",
163 | "-Dorg.jenkinsci.plugins.gitclient.GitClient.quietRemoteBranches=true",
164 | "-Dorg.jenkinsci.plugins.gitclient.Git.timeOut=11",
165 | ])
166 | if jnlp_port != None:
167 | java_opts.append("-Dhudson.TcpSlaveAgentListener.port=" + str(jnlp_port)) # NOT THE HTTP PORT
168 | if debug_port != None:
169 | java_opts.append("-Xdebug")
170 | java_opts.append("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5678")
171 | if access_mode != None:
172 | java_opts.append("--illegal-access=" + access_mode)
173 | docker_command.extend([
174 | "--env", 'CASC_YAML_MAX_ALIASES=100',
175 | "--env", 'JAGENT_JAVA_HOME=' + get_jagent_java_home(),
176 | "--env", 'JAVA_OPTS=' + shlex.quote(" ".join(java_opts)),
177 | "--env", "JENKINS_ADVERTISED_HOSTNAME=" + get_fqdn(),
178 | "--env", "JENKINS_EXTERNAL_URL=" + "http://" + get_fqdn() + ":" + str(http_port) + "/",
179 | "--env", "JENKINS_HOSTNAME=" + get_fqdn(),
180 | # Avoid Jetty 12 limitation of total parameter count
181 | # Jetty 10 set limit based on unique parameter count
182 | # https://issues.jenkins.io/browse/JENKINS-73285
183 | "--env", 'JENKINS_OPTS=--maxParamCount=10000',
184 | "--env", "JENKINS_WINDOWS_DIR=" + get_windows_dir(),
185 | "--env", "LANG=C.UTF-8",
186 | "--env", "START_QUIET=" + str(quiet),
187 | "--env", "PLUGINS_FORCE_UPGRADE=true", # https://community.jenkins.io/t/saml-plugin-failure-blocking-upgrade-of-4000-jenkins-instances-2-462-3-to-2-479-3/29626/2
188 | "--env", "TRY_UPGRADE_IF_NO_MARKER=true", # https://community.jenkins.io/t/saml-plugin-failure-blocking-upgrade-of-4000-jenkins-instances-2-462-3-to-2-479-3/29626/2
189 | "--env", "TZ=America/Boise",
190 | "--env", "user.timezone=America/Denver",
191 | "-t", docker_tag,
192 | ])
193 | # Using shell=True and the single string form passes quoted args correctly.
194 | # Since input to command is program controlled, shell=True is safe (for now)
195 | str_command = " ".join(map(str, docker_command))
196 | print(str_command)
197 | subprocess.check_call(str_command, shell=True)
198 |
199 | #-----------------------------------------------------------------------
200 |
201 | def get_fqdn():
202 | fqdn = socket.getfqdn()
203 | if not "." in fqdn:
204 | if is_home_network():
205 | fqdn = fqdn + ".markwaite.net"
206 | else:
207 | fqdn = fqdn + ".example.com"
208 | return fqdn
209 |
210 | #-----------------------------------------------------------------------
211 |
212 | def get_base_hostname():
213 | base_hostname = get_fqdn()
214 | dot = base_hostname.find('.')
215 | if dot > 0:
216 | base_hostname = base_hostname[0:dot]
217 | return base_hostname
218 |
219 | #-----------------------------------------------------------------------
220 |
221 | def get_an_ip_address():
222 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
223 | s.connect((get_dns_server(), 80))
224 | return s.getsockname()[0]
225 |
226 | #-----------------------------------------------------------------------
227 |
228 | def docker_run(args = []):
229 | help_text = """%prog [options] [host(s)]
230 | Run a docker image. Use -h for help."""
231 | parser = optparse.OptionParser(usage=help_text)
232 |
233 | # keep at optparse for 2.6. compatibility
234 | parser.add_option("-c", "--clean", action="store_true", default=False, help="clean prior file system image")
235 | parser.add_option("-d", "--detach", action="store_true", default=False, help="detach from typical stdin and stdout")
236 | parser.add_option("-q", "--quiet", action="store_true", default=False, help="start in quiet down state, process no jobs until shutdown is cancelled")
237 |
238 | parser.add_option("-a", "--access",action="store", dest='access_mode',default=None, type="string", help="report illegal accesses")
239 | parser.add_option("-g", "--debug", action="store", dest='debug_port', default=None, type="int", help="debug port")
240 | parser.add_option("-j", "--jnlp", action="store", dest='jnlp_port', default=50000, type="int", help="jnlp port")
241 | parser.add_option("-p", "--port", action="store", dest='http_port', default=8080, type="int", help="http port")
242 | parser.add_option("-s", "--ssh", action="store", dest='ssh_port', default=18022, type="int", help="ssh port")
243 | parser.add_option("-t", "--tag", action="store", dest='docker_tag', default=None, type="string", help="Docker tag")
244 |
245 | options, arg_hosts = parser.parse_args()
246 |
247 | if options.clean and os.path.exists(jenkins_home_dir):
248 | shutil.rmtree(jenkins_home_dir)
249 | if options.clean and not os.path.exists(jenkins_home_dir):
250 | os.mkdir(jenkins_home_dir)
251 |
252 | # Always detach, since `--restart always` prevents `--rm` and causes Jenkins processes to fork and exec
253 | options.detach = True
254 | if options.detach:
255 | print("Detaching from stdin / stdout")
256 |
257 | if options.quiet:
258 | print("Job processing disabled")
259 |
260 | if options.docker_tag == None:
261 | current_branch = docker_build.get_current_branch()
262 | options.docker_tag = docker_build.compute_tag(current_branch)
263 | docker_execute(options.docker_tag, options.http_port, options.jnlp_port, options.ssh_port, options.debug_port, options.detach, options.quiet, options.access_mode)
264 |
265 | #-----------------------------------------------------------------------
266 |
267 | if __name__ == "__main__": docker_run(sys.argv[1:])
268 |
--------------------------------------------------------------------------------
/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 | # Wrong permissions, just run the passed in command
16 | exec "$@"
17 | fi
18 | touch "${COPY_REFERENCE_FILE_LOG}" || { echo "Can not write to ${COPY_REFERENCE_FILE_LOG}. Wrong volume permissions?"; exit 1; }
19 | echo "--- Copying files at $(date)" >> "$COPY_REFERENCE_FILE_LOG"
20 | find "${REF}" \( -type f -o -type l \) -exec bash -c '. /usr/local/bin/jenkins-support; for arg; do copy_reference_file "$arg"; done' _ {} +
21 | echo "--- Copied files finished at $(date)" >> "$COPY_REFERENCE_FILE_LOG"
22 |
23 | # if `docker run` first argument start with `--` the user is passing jenkins launcher arguments
24 | if [[ $# -lt 1 ]] || [[ "$1" == "--"* ]]; then
25 |
26 | # shellcheck disable=SC2001
27 | effective_java_opts=$(sed -e 's/^ $//' <<<"$JAVA_OPTS $JENKINS_JAVA_OPTS")
28 |
29 | # read JAVA_OPTS and JENKINS_OPTS into arrays to avoid need for eval (and associated vulnerabilities)
30 | java_opts_array=()
31 | while IFS= read -r -d '' item; do
32 | java_opts_array+=( "$item" )
33 | done < <([[ $effective_java_opts ]] && xargs printf '%s\0' <<<"$effective_java_opts")
34 |
35 | readonly agent_port_property='jenkins.model.Jenkins.slaveAgentPort'
36 | if [ -n "${JENKINS_SLAVE_AGENT_PORT:-}" ] && [[ "${effective_java_opts:-}" != *"${agent_port_property}"* ]]; then
37 | java_opts_array+=( "-D${agent_port_property}=${JENKINS_SLAVE_AGENT_PORT}" )
38 | fi
39 |
40 | readonly lifecycle_property='hudson.lifecycle'
41 | if [[ "${JAVA_OPTS:-}" != *"${lifecycle_property}"* ]]; then
42 | java_opts_array+=( "-D${lifecycle_property}=hudson.lifecycle.ExitLifecycle" )
43 | fi
44 |
45 | if [[ "$DEBUG" ]] ; then
46 | java_opts_array+=( \
47 | '-Xdebug' \
48 | '-Xrunjdwp:server=y,transport=dt_socket,address=*:5005,suspend=y' \
49 | )
50 | fi
51 |
52 | jenkins_opts_array=( )
53 | while IFS= read -r -d '' item; do
54 | jenkins_opts_array+=( "$item" )
55 | done < <([[ $JENKINS_OPTS ]] && xargs printf '%s\0' <<<"$JENKINS_OPTS")
56 |
57 | exec java -Duser.home="$JENKINS_HOME" "${java_opts_array[@]}" -jar "${JENKINS_WAR}" "${jenkins_opts_array[@]}" "$@"
58 | fi
59 |
60 | # As argument is not jenkins, assume user wants to run a different process, for example a `bash` shell to explore this image
61 | exec "$@"
62 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ref/README-mwaite.md:
--------------------------------------------------------------------------------
1 | Copied from the ref directory
2 |
--------------------------------------------------------------------------------
/ref/configuration-as-code/README.md:
--------------------------------------------------------------------------------
1 | # Configuration as code directory
2 |
3 | Directory of configuration files for configuration as code of this Jenkins controller.
4 |
--------------------------------------------------------------------------------
/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/MarkEWaite/docker-lfs/abedbbf6f5203de204e50cd2b8228b86f9c2b1de/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.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:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:cff656b99fbc92b45f5820945ab2a1ba6430fe919f73a5b895b6ea013b8a0b35
3 | size 598
4 |
--------------------------------------------------------------------------------
/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/create-missing-legacyIds:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Tool does not have desired results because it relies on jobs having
4 | # a builds directory. Jobs that have been run at least once will have a
5 | # builds directory. Jobs that are newly defined will not have a builds
6 | # directory until they have been run once.
7 |
8 | JENKINS_HOME=ref
9 |
10 | if [ ! -d "${JENKINS_HOME}/jobs" ]; then
11 | echo "JENKINS_HOME=${JENKINS_HOME} seems wrong"
12 | fi
13 |
14 | # Find all Job builds directory with a missing 'legacyIds' file.
15 | comm -1 -3 --zero-terminated \
16 | <(find ${JENKINS_HOME}/jobs -depth -type f -iname 'legacyIds' -printf '%h\0' | sort -z) \
17 | <(find ${JENKINS_HOME}/jobs -depth -type d -iname 'builds' -prune -print0 | sort -z) | while read -d $'\0' file;
18 | do
19 | echo "Migrating build records in ${file}"
20 | touch "${file}/legacyIds"
21 | done
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------