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