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