├── .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 | --------------------------------------------------------------------------------