├── .github ├── CODEOWNERS ├── FUNDING.yml ├── release-drafter.yml ├── dependabot.yml └── workflows │ ├── release-drafter.yml │ ├── updatecli.yaml │ └── update-dockerhub-description.yaml ├── .gitignore ├── images ├── screen-1.png ├── screen-2.png ├── screen-3.png ├── screen-4.png ├── screen-5.png └── screen-6.png ├── tests ├── netcat-helper │ ├── Dockerfile │ └── Dockerfile-windows ├── tags.bats ├── golden │ ├── expected_tags_windows.txt │ ├── expected_tags_linux.txt │ ├── expected_tags_windows_on_tag.txt │ └── expected_tags_linux_on_tag.txt ├── update-golden-file.sh ├── tests_inbound-agent.bats ├── test_helpers.bash ├── test_helpers.psm1 ├── tests_agent.bats ├── inbound-agent.Tests.ps1 └── agent.Tests.ps1 ├── .gitmodules ├── updatecli ├── values.github-action.yaml ├── updatecli.d │ ├── bats.yaml │ ├── rhel-ubi9.yaml │ ├── debian.yaml │ ├── jdk25.yaml │ ├── jdk21.yaml │ ├── git-lfs.yaml │ ├── adoptium-scripts.yaml │ ├── git-windows.yaml │ ├── jdk17.yaml │ ├── alpine.yaml │ └── remoting.yaml └── scripts │ ├── ubi9-tags.sh │ └── ubi9-latest-tag.sh ├── LICENSE ├── adoptium-install-jdk.sh ├── adoptium-get-jdk-link.sh ├── Makefile ├── rhel └── ubi9 │ └── Dockerfile ├── jenkins-agent ├── Jenkinsfile ├── alpine └── Dockerfile ├── debian └── Dockerfile ├── README_agent.md ├── README_inbound-agent.md ├── windows ├── windowsservercore │ └── Dockerfile └── nanoserver │ └── Dockerfile ├── jenkins-agent.ps1 ├── docker-bake.hcl └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/team-docker-packaging 2 | */debian @ksalerno99 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | bats-core/ 3 | bats/ 4 | target/ 5 | build-windows_*.yaml 6 | -------------------------------------------------------------------------------- /images/screen-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-agent/HEAD/images/screen-1.png -------------------------------------------------------------------------------- /images/screen-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-agent/HEAD/images/screen-2.png -------------------------------------------------------------------------------- /images/screen-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-agent/HEAD/images/screen-3.png -------------------------------------------------------------------------------- /images/screen-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-agent/HEAD/images/screen-4.png -------------------------------------------------------------------------------- /images/screen-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-agent/HEAD/images/screen-5.png -------------------------------------------------------------------------------- /images/screen-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-agent/HEAD/images/screen-6.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | community_bridge: jenkins 2 | custom: ["https://jenkins.io/donate/#why-donate"] 3 | -------------------------------------------------------------------------------- /tests/netcat-helper/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.11 2 | 3 | RUN apk update --no-cache \ 4 | && apk add --no-cache \ 5 | coreutils \ 6 | netcat-openbsd 7 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 2 | _extends: .github 3 | 4 | name-template: 'next' 5 | tag-template: 'next' 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/test_helper/bats-assert"] 2 | path = tests/test_helper/bats-assert 3 | url = https://github.com/bats-core/bats-assert.git 4 | [submodule "tests/test_helper/bats-support"] 5 | path = tests/test_helper/bats-support 6 | url = https://github.com/bats-core/bats-support.git 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Per https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | 5 | # GitHub actions 6 | - package-ecosystem: "github-actions" 7 | target-branch: master 8 | directory: "/" 9 | schedule: 10 | # Check for updates to GitHub Actions every week 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /updatecli/values.github-action.yaml: -------------------------------------------------------------------------------- 1 | github: 2 | user: "GitHub Actions" 3 | email: "41898282+github-actions[bot]@users.noreply.github.com" 4 | username: "github-actions" 5 | token: "UPDATECLI_GITHUB_TOKEN" 6 | branch: "master" 7 | owner: "jenkinsci" 8 | repository: "docker-agent" 9 | temurin: 10 | version_pattern: "^jdk-[17|21|25].(\\d*).(\\d*).(\\d*)(.(\\d*))\\+(\\d*)?$" 11 | -------------------------------------------------------------------------------- /tests/tags.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | 5 | SUT_DESCRIPTION="tags" 6 | 7 | @test "[${SUT_DESCRIPTION}] Default Linux tags unchanged" { 8 | assert_matches_golden expected_tags_linux make --silent tags-linux 9 | } 10 | 11 | @test "[${SUT_DESCRIPTION}] 'ON_TAG' Linux tags unchanged" { 12 | assert_matches_golden expected_tags_linux_on_tag make --silent tags-linux ON_TAG=true 13 | } 14 | 15 | @test "[${SUT_DESCRIPTION}] Default Windows tags unchanged" { 16 | assert_matches_golden expected_tags_windows make --silent tags-windows 17 | } 18 | 19 | @test "[${SUT_DESCRIPTION}] 'ON_TAG' Windows tags unchanged" { 20 | assert_matches_golden expected_tags_windows_on_tag make --silent tags-windows ON_TAG=true 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Automates creation of Release Drafts using Release Drafter 2 | # Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 3 | 4 | name: Release Drafter (Changelog) 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | # Allow to be run manually 11 | workflow_dispatch: 12 | # Prepare the new "next release" once a release is created 13 | release: 14 | 15 | # Only allow 1 release-drafter build at a time to avoid creating multiple "next" releases 16 | concurrency: "release-drafter" 17 | 18 | jobs: 19 | update_release_draft: 20 | runs-on: ubuntu-latest 21 | steps: 22 | # Drafts your next Release notes as Pull Requests are merged into the default branch 23 | - uses: release-drafter/release-drafter@v6.1.0 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/updatecli.yaml: -------------------------------------------------------------------------------- 1 | name: updatecli 2 | on: 3 | # Allow to be run manually 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 1 * * *' # Once a day at 01:00am UTC 7 | push: 8 | pull_request: 9 | jobs: 10 | updatecli: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v6 15 | 16 | - name: Install Updatecli in the runner 17 | uses: updatecli/updatecli-action@v2.98.0 18 | 19 | - name: Run Updatecli in Dry Run mode 20 | run: updatecli diff --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml 21 | env: 22 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Run Updatecli in Apply mode 25 | if: github.ref == 'refs/heads/master' 26 | run: updatecli apply --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml 27 | env: 28 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2022 Jenkins project contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/update-dockerhub-description.yaml: -------------------------------------------------------------------------------- 1 | name: Update Docker Hub Description 2 | on: 3 | release: 4 | types: [ published ] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | dockerHubDescription: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v6 12 | - name: Update Docker Hub description for agent 13 | uses: peter-evans/dockerhub-description@v5 14 | with: 15 | username: ${{ secrets.DOCKERHUB_USERNAME }} 16 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 17 | enable-url-completion: true 18 | short-description: This is a base image, which provides the Jenkins agent executable (agent.jar) 19 | repository: jenkins/agent 20 | readme-filepath: ./README_agent.md 21 | - name: Update Docker Hub description for inbound-agent 22 | uses: peter-evans/dockerhub-description@v5 23 | with: 24 | username: ${{ secrets.DOCKERHUB_USERNAME }} 25 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 26 | enable-url-completion: true 27 | short-description: This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller 28 | repository: jenkins/inbound-agent 29 | readme-filepath: ./README_inbound-agent.md 30 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/bats.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump bats version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | lastVersion: 18 | kind: githubrelease 19 | name: Get the latest bats version 20 | spec: 21 | owner: bats-core 22 | repository: bats-core 23 | token: "{{ requiredEnv .github.token }}" 24 | username: "{{ .github.username }}" 25 | versionfilter: 26 | kind: semver 27 | 28 | targets: 29 | setBatsVersionInMakefile: 30 | kind: file 31 | name: Update Makefile 32 | spec: 33 | file: Makefile 34 | matchpattern: > 35 | git clone --branch (.*) https://github.com/bats-core/bats-core ./bats 36 | replacepattern: > 37 | git clone --branch {{ source "lastVersion" }} https://github.com/bats-core/bats-core ./bats 38 | scmid: default 39 | 40 | actions: 41 | default: 42 | kind: github/pullrequest 43 | scmid: default 44 | title: Bump bats version to {{ source "lastVersion" }} 45 | spec: 46 | labels: 47 | - dependencies 48 | - tests 49 | - bats 50 | -------------------------------------------------------------------------------- /tests/golden/expected_tags_windows.txt: -------------------------------------------------------------------------------- 1 | docker.io/jenkins/agent:jdk17-nanoserver-1809 2 | docker.io/jenkins/agent:jdk17-nanoserver-ltsc2019 3 | docker.io/jenkins/agent:jdk17-nanoserver-ltsc2022 4 | docker.io/jenkins/agent:jdk17-windowsservercore-ltsc2019 5 | docker.io/jenkins/agent:jdk17-windowsservercore-ltsc2022 6 | docker.io/jenkins/agent:jdk21-nanoserver-1809 7 | docker.io/jenkins/agent:jdk21-nanoserver-ltsc2019 8 | docker.io/jenkins/agent:jdk21-nanoserver-ltsc2022 9 | docker.io/jenkins/agent:jdk21-windowsservercore-ltsc2019 10 | docker.io/jenkins/agent:jdk21-windowsservercore-ltsc2022 11 | docker.io/jenkins/agent:jdk25-nanoserver-1809 12 | docker.io/jenkins/agent:jdk25-nanoserver-ltsc2019 13 | docker.io/jenkins/agent:jdk25-nanoserver-ltsc2022 14 | docker.io/jenkins/agent:jdk25-windowsservercore-ltsc2019 15 | docker.io/jenkins/agent:jdk25-windowsservercore-ltsc2022 16 | docker.io/jenkins/inbound-agent:jdk17-nanoserver-1809 17 | docker.io/jenkins/inbound-agent:jdk17-nanoserver-ltsc2019 18 | docker.io/jenkins/inbound-agent:jdk17-nanoserver-ltsc2022 19 | docker.io/jenkins/inbound-agent:jdk17-windowsservercore-ltsc2019 20 | docker.io/jenkins/inbound-agent:jdk17-windowsservercore-ltsc2022 21 | docker.io/jenkins/inbound-agent:jdk21-nanoserver-1809 22 | docker.io/jenkins/inbound-agent:jdk21-nanoserver-ltsc2019 23 | docker.io/jenkins/inbound-agent:jdk21-nanoserver-ltsc2022 24 | docker.io/jenkins/inbound-agent:jdk21-windowsservercore-ltsc2019 25 | docker.io/jenkins/inbound-agent:jdk21-windowsservercore-ltsc2022 26 | docker.io/jenkins/inbound-agent:jdk25-nanoserver-1809 27 | docker.io/jenkins/inbound-agent:jdk25-nanoserver-ltsc2019 28 | docker.io/jenkins/inbound-agent:jdk25-nanoserver-ltsc2022 29 | docker.io/jenkins/inbound-agent:jdk25-windowsservercore-ltsc2019 30 | docker.io/jenkins/inbound-agent:jdk25-windowsservercore-ltsc2022 31 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/rhel-ubi9.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump UBI9 version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | latestVersion: 18 | name: "Get the latest UBI9 Linux version" 19 | kind: shell 20 | spec: 21 | command: bash -x updatecli/scripts/ubi9-latest-tag.sh 22 | 23 | conditions: 24 | checkUbi9DockerImage: 25 | kind: dockerimage 26 | name: Check if the container image "ubi9" is available 27 | sourceid: latestVersion # Provides the found tag as "input" 28 | spec: 29 | architectures: 30 | - linux/amd64 31 | - linux/arm64 32 | - linux/s390x 33 | - linux/ppc64le 34 | image: registry.access.redhat.com/ubi9 35 | 36 | 37 | targets: 38 | updateDockerfile: 39 | name: "Update the value of the base image (ARG UBI9_TAG) in the Dockerfile" 40 | kind: dockerfile 41 | sourceid: latestVersion 42 | spec: 43 | file: rhel/ubi9/Dockerfile 44 | instruction: 45 | keyword: ARG 46 | matcher: UBI9_TAG 47 | scmid: default 48 | updateDockerBake: 49 | name: "Update the default value of the variable UBI9_TAG in the docker-bake.hcl" 50 | kind: hcl 51 | sourceid: latestVersion 52 | spec: 53 | file: docker-bake.hcl 54 | path: variable.UBI9_TAG.default 55 | scmid: default 56 | 57 | actions: 58 | default: 59 | kind: github/pullrequest 60 | scmid: default 61 | title: Bump UBI9 version to {{ source "latestVersion" }} 62 | spec: 63 | labels: 64 | - dependencies 65 | - rhel-ubi9 66 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/debian.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump Debian Trixie version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | trixieLatestVersion: 18 | kind: dockerimage 19 | name: "Get latest Debian trixie Linux version" 20 | spec: 21 | image: "debian" 22 | tagfilter: "trixie-*" 23 | versionfilter: 24 | kind: regex 25 | pattern: >- 26 | trixie-\d+$ 27 | 28 | conditions: 29 | checkArchitecturesAvailability: 30 | kind: dockerimage 31 | name: Check if container image is available for all architectures 32 | sourceid: trixieLatestVersion 33 | spec: 34 | image: "debian" 35 | architectures: 36 | - linux/amd64 37 | - linux/arm64 38 | - linux/arm/v7 39 | - linux/s390x 40 | - linux/ppc64le 41 | 42 | targets: 43 | updateDockerfile: 44 | name: "Update value of base image (ARG DEBIAN_RELEASE) in Dockerfile" 45 | kind: dockerfile 46 | sourceid: trixieLatestVersion 47 | spec: 48 | file: debian/Dockerfile 49 | instruction: 50 | keyword: "ARG" 51 | matcher: "DEBIAN_RELEASE" 52 | scmid: default 53 | updateDockerBake: 54 | name: "Update default value of variable DEBIAN_RELEASE in docker-bake.hcl" 55 | kind: hcl 56 | sourceid: trixieLatestVersion 57 | spec: 58 | file: docker-bake.hcl 59 | path: variable.DEBIAN_RELEASE.default 60 | scmid: default 61 | 62 | actions: 63 | default: 64 | kind: github/pullrequest 65 | scmid: default 66 | title: Bump Debian trixie Linux version to {{ source "trixieLatestVersion" }} 67 | spec: 68 | labels: 69 | - dependencies 70 | - debian 71 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/jdk25.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump JDK25 version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | temurin25-binaries: 16 | kind: "github" 17 | spec: 18 | user: "{{ .github.user }}" 19 | email: "{{ .github.email }}" 20 | owner: "adoptium" 21 | repository: "temurin25-binaries" 22 | token: '{{ requiredEnv .github.token }}' 23 | branch: "main" 24 | 25 | sources: 26 | latestJDK25Version: 27 | kind: temurin 28 | name: Get the latest Adoptium JDK25 version via the API 29 | spec: 30 | featureversion: 25 31 | transformers: 32 | - trimprefix: "jdk-" 33 | 34 | # Architectures must match those of the targets in docker-bake.hcl 35 | conditions: 36 | checkTemurinAllReleases: 37 | name: Check if the "" is available for all platforms 38 | kind: temurin 39 | sourceid: latestJDK25Version 40 | spec: 41 | featureversion: 25 42 | platforms: 43 | - alpine-linux/x64 44 | - alpine-linux/aarch64 45 | - linux/x64 46 | - linux/aarch64 47 | - linux/ppc64le 48 | - linux/s390x 49 | - windows/x64 50 | 51 | targets: 52 | setJDK25VersionDockerBake: 53 | name: "Bump JDK25 version in the docker-bake.hcl file" 54 | kind: hcl 55 | transformers: 56 | - replacer: 57 | from: "+" 58 | to: "_" 59 | spec: 60 | file: docker-bake.hcl 61 | path: variable.JAVA25_VERSION.default 62 | scmid: default 63 | 64 | actions: 65 | default: 66 | kind: github/pullrequest 67 | scmid: default 68 | title: Bump JDK25 version to {{ source "latestJDK25Version" }} 69 | spec: 70 | labels: 71 | - dependencies 72 | - jdk25 73 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/jdk21.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump JDK21 version for all Linux images 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | temurin21-binaries: 16 | kind: "github" 17 | spec: 18 | user: "{{ .github.user }}" 19 | email: "{{ .github.email }}" 20 | owner: "adoptium" 21 | repository: "temurin21-binaries" 22 | token: '{{ requiredEnv .github.token }}' 23 | branch: "main" 24 | 25 | sources: 26 | latestJDK21Version: 27 | kind: temurin 28 | name: Get the latest Adoptium JDK21 version via the API 29 | spec: 30 | featureversion: 21 31 | transformers: 32 | - trimprefix: "jdk-" 33 | 34 | # Architectures must match those of the targets in docker-bake.hcl 35 | conditions: 36 | checkTemurinAllReleases: 37 | name: Check if the "" is available for all platforms 38 | kind: temurin 39 | sourceid: latestJDK21Version 40 | spec: 41 | featureversion: 21 42 | platforms: 43 | - alpine-linux/x64 44 | - alpine-linux/aarch64 45 | - linux/x64 46 | - linux/aarch64 47 | - linux/ppc64le 48 | - linux/s390x 49 | - windows/x64 50 | 51 | targets: 52 | setJDK21VersionDockerBake: 53 | name: "Bump JDK21 version for Linux images in the docker-bake.hcl file" 54 | kind: hcl 55 | transformers: 56 | - replacer: 57 | from: "+" 58 | to: "_" 59 | spec: 60 | file: docker-bake.hcl 61 | path: variable.JAVA21_VERSION.default 62 | scmid: default 63 | 64 | actions: 65 | default: 66 | kind: github/pullrequest 67 | scmid: default 68 | title: Bump JDK21 version to {{ source "latestJDK21Version" }} 69 | spec: 70 | labels: 71 | - dependencies 72 | - jdk21 73 | -------------------------------------------------------------------------------- /tests/netcat-helper/Dockerfile-windows: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | # The MIT License 4 | # 5 | # Copyright (c) 2019-2020, Alex Earl and other Jenkins Contributors 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | # Available tags: https://mcr.microsoft.com/v2/windows/servercore/tags/list 26 | ARG WINDOWS_VERSION_TAG=1809 27 | FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" 28 | 29 | SHELL ["powershell.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 30 | 31 | ARG NMAP_VERSION=7.80 32 | ENV NMAP_VERSION $NMAP_VERSION 33 | 34 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 35 | $url = $('https://nmap.org/dist/nmap-{0}-setup.exe' -f $env:NMAP_VERSION) ; ` 36 | Write-Host "Retrieving $url..." ; ` 37 | Invoke-WebRequest $url -OutFile 'nmap-install.exe' -UseBasicParsing ; ` 38 | $proc = Start-Process "C:\nmap-install.exe" -PassThru -ArgumentList '/S' ; ` 39 | $proc.WaitForExit() ; ` 40 | Remove-Item -Path nmap-install.exe 41 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/git-lfs.yaml: -------------------------------------------------------------------------------- 1 | name: Bump `git-lfs` version 2 | 3 | scms: 4 | default: 5 | kind: github 6 | spec: 7 | user: "{{ .github.user }}" 8 | email: "{{ .github.email }}" 9 | owner: "{{ .github.owner }}" 10 | repository: "{{ .github.repository }}" 11 | token: "{{ requiredEnv .github.token }}" 12 | username: "{{ .github.username }}" 13 | branch: "{{ .github.branch }}" 14 | 15 | sources: 16 | lastVersion: 17 | kind: githubrelease 18 | name: Get latest `git-lfs` version 19 | spec: 20 | owner: git-lfs 21 | repository: git-lfs 22 | token: "{{ requiredEnv .github.token }}" 23 | username: "{{ .github.username }}" 24 | versionfilter: 25 | kind: semver 26 | transformers: 27 | - trimprefix: "v" 28 | 29 | targets: 30 | setGitLfsVersion: 31 | name: Update `git-lfs` version in Dockerfiles 32 | kind: dockerfile 33 | spec: 34 | files: 35 | - alpine/Dockerfile 36 | - debian/Dockerfile 37 | - rhel/ubi9/Dockerfile 38 | - windows/nanoserver/Dockerfile 39 | - windows/windowsservercore/Dockerfile 40 | instruction: 41 | keyword: ARG 42 | matcher: GIT_LFS_VERSION 43 | scmid: default 44 | setGitLfsVersionLinuxTests: 45 | name: Update `git-lfs` version in Linux tests 46 | kind: file 47 | spec: 48 | file: tests/tests_agent.bats 49 | matchpattern: > 50 | GIT_LFS_VERSION=(.*) 51 | replacepattern: > 52 | GIT_LFS_VERSION='{{ source "lastVersion" }}' 53 | scmid: default 54 | setGitLfsVersionWindowsTests: 55 | name: Update `git-lfs` version in Windows tests 56 | kind: file 57 | spec: 58 | file: tests/agent.Tests.ps1 59 | matchpattern: > 60 | global:GITLFSVERSION =(.*) 61 | replacepattern: > 62 | global:GITLFSVERSION = '{{ source "lastVersion" }}' 63 | scmid: default 64 | 65 | actions: 66 | default: 67 | kind: github/pullrequest 68 | title: Bump `git-lfs` version to {{ source "lastVersion" }} 69 | scmid: default 70 | spec: 71 | labels: 72 | - enhancement 73 | - git-lfs 74 | -------------------------------------------------------------------------------- /adoptium-install-jdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | ### IMPORTANT: this script is synchronized with https://github.com/jenkins-infra/shared-tools/, please modify its content in that repository only. 5 | 6 | # Check if curl and tar are installed 7 | if ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1 ; then 8 | echo "curl and tar are required but not installed. Exiting with status 1." >&2 9 | exit 1 10 | fi 11 | 12 | # Check required variable 13 | : "${JAVA_VERSION:?}" 14 | 15 | # Set the OS to "standard" by default 16 | OS="standard" 17 | 18 | # If a second argument is provided, use it as the OS 19 | if [ $# -eq 1 ]; then 20 | OS="${1}" 21 | fi 22 | 23 | # Call adoptium-get-jdk-link.sh with JAVA_VERSION and OS as arguments 24 | # The two scripts should be in the same directory. 25 | # That's why we're trying to find the directory of the current script and use it to call the other script. 26 | SCRIPT_DIR=$(cd "$(dirname "$0")" || exit; pwd) 27 | if ! DOWNLOAD_URL=$("${SCRIPT_DIR}"/adoptium-get-jdk-link.sh "${JAVA_VERSION}" "${OS}"); then 28 | echo "Error: Failed to fetch the URL. Exiting with status 1." >&2 29 | exit 1 30 | fi 31 | 32 | # Use curl to download the JDK archive from the URL 33 | if ! curl --silent --show-error --location --retry 5 --retry-connrefused --output /tmp/jdk.tar.gz "${DOWNLOAD_URL}"; then 34 | echo "Error: Failed to download the JDK archive. Exiting with status 1." >&2 35 | exit 1 36 | fi 37 | 38 | # Extract the archive to the /opt/ directory 39 | if ! tar -xzf /tmp/jdk.tar.gz -C /opt/; then 40 | echo "Error: Failed to extract the JDK archive. Exiting with status 1." >&2 41 | exit 1 42 | fi 43 | 44 | # Get the name of the extracted directory 45 | EXTRACTED_DIR=$(tar -tzf /tmp/jdk.tar.gz | head -n 1 | cut -f1 -d"/") 46 | 47 | # Rename the extracted directory to /opt/jdk-${JAVA_VERSION} 48 | if [ "${EXTRACTED_DIR}" != "jdk-${JAVA_VERSION}" ]; then 49 | if ! mv "/opt/${EXTRACTED_DIR}" "/opt/jdk-${JAVA_VERSION}"; then 50 | echo "Error: Failed to rename the extracted directory. Exiting with status 1." >&2 51 | exit 1 52 | fi 53 | fi 54 | 55 | # Remove the downloaded archive 56 | if ! rm -f /tmp/jdk.tar.gz; then 57 | echo "Error: Failed to remove the downloaded archive. Exiting with status 1." >&2 58 | exit 1 59 | fi 60 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/adoptium-scripts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update `adoptium-install-jdk.sh` script content 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | lastScriptContent: 18 | kind: file 19 | name: Get the latest `adoptium-install-jdk.sh` script content from jenkins-infra/shared-tools 20 | spec: 21 | file: https://raw.githubusercontent.com/jenkins-infra/shared-tools/main/adoptium/adoptium-install-jdk.sh 22 | 23 | targets: 24 | updateScriptContent: 25 | name: Update script content 26 | kind: file 27 | spec: 28 | file: adoptium-install-jdk.sh 29 | scmid: default 30 | 31 | actions: 32 | default: 33 | kind: github/pullrequest 34 | title: Update `adoptium-install-jdk.sh` script content 35 | scmid: default 36 | spec: 37 | labels: 38 | - chore 39 | - adoptium-scripts 40 | --- 41 | name: Update `adoptium-get-jdk-link.sh` script content 42 | 43 | scms: 44 | default: 45 | kind: github 46 | spec: 47 | user: "{{ .github.user }}" 48 | email: "{{ .github.email }}" 49 | owner: "{{ .github.owner }}" 50 | repository: "{{ .github.repository }}" 51 | token: "{{ requiredEnv .github.token }}" 52 | username: "{{ .github.username }}" 53 | branch: "{{ .github.branch }}" 54 | 55 | sources: 56 | lastScriptContent: 57 | kind: file 58 | name: Get the latest `adoptium-get-jdk-link.sh` script content from jenkins-infra/shared-tools 59 | spec: 60 | file: https://raw.githubusercontent.com/jenkins-infra/shared-tools/main/adoptium/adoptium-get-jdk-link.sh 61 | 62 | targets: 63 | updateScriptContent: 64 | name: Update script content 65 | kind: file 66 | spec: 67 | file: adoptium-get-jdk-link.sh 68 | scmid: default 69 | 70 | actions: 71 | default: 72 | kind: github/pullrequest 73 | title: Update `adoptium-get-jdk-link.sh` script content 74 | scmid: default 75 | spec: 76 | labels: 77 | - chore 78 | - adoptium-scripts 79 | -------------------------------------------------------------------------------- /tests/update-golden-file.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # This script runs a specified command, captures its output, 5 | # and compares it against a "golden file" representing the expected output. 6 | # If the output differs, the script shows a diff and allows the user to update the golden file. 7 | # If the output matches, it reports that the golden file is up-to-date. 8 | # 9 | # Usage: 10 | # ./update-golden-file.sh 11 | # 12 | # Arguments: 13 | # Name of the test, used to determine the golden file path. 14 | # The corresponding golden file will be stored as: 15 | # golden/.txt 16 | # 17 | # Command to run, whose stdout will be compared to the golden file. 18 | # This can include arguments, e.g.: 19 | # ./update-golden-file.sh expected_tags make tags 20 | # 21 | # Notes: 22 | # - Requires Bash 4+ for `BASH_SOURCE` handling. 23 | # - The script is safe to run from any directory; golden files are always relative to the script's own location. 24 | 25 | if [[ $# -lt 2 ]]; then 26 | echo "Usage: $0 " 27 | echo "Example:" 28 | echo " $0 expected_tags make tags" 29 | exit 1 30 | fi 31 | 32 | name="$1" 33 | shift 34 | 35 | # Ensure golden folder is always relative to this script 36 | script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 37 | golden_file="${name}.txt" 38 | golden_path="${script_dir}/golden/${golden_file}" 39 | tmp="$(mktemp)" 40 | 41 | echo 42 | echo "Golden file path:" 43 | echo " ${golden_path}" 44 | echo 45 | echo "Running command:" 46 | echo " $*" 47 | echo 48 | 49 | "$@" > "${tmp}" 50 | 51 | action="create" 52 | if [[ -f "${golden_path}" ]]; then 53 | if diff -u "${golden_path}" "${tmp}" > /dev/null; then 54 | echo "Golden file '${golden_file}' is already up-to-date." 55 | rm "${tmp}" 56 | exit 0 57 | fi 58 | echo "Diff against existing golden file '${golden_file}':" 59 | diff -u "${golden_path}" "${tmp}" || true 60 | action="update" 61 | else 62 | echo "Golden file '${golden_file}' does not exist yet." 63 | fi 64 | 65 | echo 66 | echo "Golden file to ${action}: '${golden_file}'" 67 | read -rp "Proceed? [y/N] " answer 68 | 69 | if [[ "${answer}" =~ ^[Yy]$ ]]; then 70 | mkdir -p "$(dirname "${golden_path}")" 71 | mv "${tmp}" "${golden_path}" 72 | echo "Golden file '${golden_file}' ${action}d." 73 | else 74 | rm "${tmp}" 75 | echo "Aborted. Golden file '${golden_file}' unchanged." 76 | fi 77 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/git-windows.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump Git version on Windows 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | lastVersion: 18 | kind: githubrelease 19 | name: Get the latest Git version 20 | spec: 21 | owner: "git-for-windows" 22 | repository: "git" 23 | token: "{{ requiredEnv .github.token }}" 24 | username: "{{ .github.username }}" 25 | versionfilter: 26 | kind: regex 27 | ## Latest stable v{x.y.z}.windows. 28 | pattern: 'v(\d*)\.(\d*)\.(\d*)\.windows\.(\d*)$' 29 | transformers: 30 | - trimprefix: "v" 31 | 32 | targets: 33 | setGitVersionNanoserver: 34 | name: Update the Git Windows version for Windows Nanoserver 35 | transformers: 36 | - findsubmatch: 37 | pattern: '(.*).windows\.(\d*)$' 38 | captureindex: 1 39 | kind: dockerfile 40 | spec: 41 | file: windows/nanoserver/Dockerfile 42 | instruction: 43 | keyword: ARG 44 | matcher: GIT_VERSION 45 | scmid: default 46 | setGitPackagePatchNanoserver: 47 | name: Update the Git Package Windows patch for Windows Nanoserver 48 | transformers: 49 | - findsubmatch: 50 | pattern: '(.*).windows\.(\d*)$' 51 | captureindex: 2 52 | kind: dockerfile 53 | spec: 54 | file: windows/nanoserver/Dockerfile 55 | instruction: 56 | keyword: ARG 57 | matcher: GIT_PATCH_VERSION 58 | scmid: default 59 | setGitVersionWindowsServerCore: 60 | name: Update the Git Windows version for Windows Server Core 61 | transformers: 62 | - findsubmatch: 63 | pattern: '(.*).windows\.(\d*)$' 64 | captureindex: 1 65 | kind: dockerfile 66 | spec: 67 | file: windows/windowsservercore/Dockerfile 68 | instruction: 69 | keyword: ARG 70 | matcher: GIT_VERSION 71 | scmid: default 72 | setGitPackagePatchWindowsServerCore: 73 | name: Update the Git Package Windows patch for Windows Server Core 74 | transformers: 75 | - findsubmatch: 76 | pattern: '(.*).windows\.(\d*)$' 77 | captureindex: 2 78 | kind: dockerfile 79 | spec: 80 | file: windows/windowsservercore/Dockerfile 81 | instruction: 82 | keyword: ARG 83 | matcher: GIT_PATCH_VERSION 84 | scmid: default 85 | 86 | actions: 87 | default: 88 | kind: github/pullrequest 89 | title: Bump Git version on Windows to {{ source "lastVersion" }} 90 | scmid: default 91 | spec: 92 | labels: 93 | - enhancement 94 | - git 95 | - windows 96 | -------------------------------------------------------------------------------- /updatecli/scripts/ubi9-tags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script fetches all tags from the Red Hat Container Catalog API for UBI9 images. 4 | # It ensures that `jq` and `curl` are installed, fetches the tags, and processes them to find all unique tags. 5 | 6 | # Correct URL of the Red Hat Container Catalog API for UBI9 7 | URL="https://catalog.redhat.com/api/containers/v1/repositories/registry/registry.access.redhat.com/repository/ubi9/images?page_size=100&page=0&sort_by=last_update_date%5Bdesc%5D" 8 | 9 | # Check if jq and curl are installed 10 | if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then 11 | echo "jq and curl are required but not installed. Exiting with status 1." >&2 12 | exit 1 13 | fi 14 | 15 | # Fetch the tags using curl 16 | response=$(curl -s "$URL" -H 'accept: application/json') 17 | 18 | # Check if the response is empty or null 19 | if [ -z "$response" ] || [ "$response" == "null" ]; then 20 | echo "Error: Failed to fetch tags from the Red Hat Container Catalog API." 21 | exit 1 22 | fi 23 | 24 | # Parse the JSON response using jq to find all tags and their published dates 25 | all_tags=$(echo "$response" | jq -c '.data[] | {published_date: .repositories[].published_date, tags: .repositories[].signatures[].tags}') 26 | 27 | # Check if the all_tags is empty 28 | if [ -z "$all_tags" ]; then 29 | echo "Error: No valid tags found." 30 | exit 1 31 | fi 32 | 33 | # Declare an associative array to store tags and their published dates 34 | declare -A tag_dates 35 | 36 | # Declare an array to maintain the original order of tags 37 | ordered_tags=() 38 | 39 | # Iterate over the parsed JSON to populate the associative array and ordered array 40 | while IFS= read -r line; do 41 | published_date=$(echo "$line" | jq -r '.published_date') 42 | tags=$(echo "$line" | jq -r '.tags[]') 43 | for tag in $tags; do 44 | # Filter tags that contain a hyphen 45 | if [[ $tag == *-* ]]; then 46 | # Update the published_date if the current date is more recent or the same 47 | if [[ -z "${tag_dates[$tag]}" || ! "$published_date" < "${tag_dates[$tag]}" ]]; then 48 | tag_dates[$tag]=$published_date 49 | fi 50 | # Add or replace the tag in the ordered array 51 | IFS='.' read -r part1 part2 _ <<< "$tag" 52 | base_tag="${part1}.${part2}" 53 | # echo "Base tag is $base_tag and tag is $tag" 54 | replaced=false 55 | for i in "${!ordered_tags[@]}"; do 56 | if [[ "${ordered_tags[i]}" == "$base_tag" ]]; then 57 | ordered_tags[i]="$tag" 58 | replaced=true 59 | break 60 | fi 61 | done 62 | if ! $replaced; then 63 | ordered_tags+=("$tag") 64 | fi 65 | fi 66 | done 67 | done <<< "$all_tags" 68 | 69 | # Custom sort function with correct order 70 | sort_tags() { 71 | printf "%s\n" "${ordered_tags[@]}" | sort -t '-' -k1,1n -k2,2n -k2.1,2.3n -k2.5,2.12n 72 | } 73 | 74 | # Print the sorted array and their corresponding published dates 75 | for tag in $(sort_tags | tac); do 76 | echo "$tag" 77 | done 78 | -------------------------------------------------------------------------------- /updatecli/scripts/ubi9-latest-tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script fetches the latest tag from the Red Hat Container Catalog API for UBI9 images. 4 | # 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. 5 | 6 | # The Swagger API endpoints for the Red Hat Container Catalog API are documented at: 7 | # https://catalog.redhat.com/api/containers/v1/ui/#/Repositories/graphql.images.get_images_by_repo 8 | 9 | # The script uses the following parameters for the API request: 10 | # - registry: registry.access.redhat.com 11 | # - repository: ubi9 12 | # - page_size: 100 13 | # - page: 0 14 | # - sort_by: last_update_date[desc] 15 | 16 | # The curl command fetches the JSON data containing the tags for the UBI9 images, then parses it using `jq` to find the version associated with the "latest" tag. 17 | # It focuses on tags that contain a hyphen, as these represent the long-form tag names. 18 | # The script ensures that only one instance of each tag is kept, in case of duplicates. 19 | 20 | # Correct URL of the Red Hat Container Catalog API for UBI9 21 | URL="https://catalog.redhat.com/api/containers/v1/repositories/registry/registry.access.redhat.com/repository/ubi9/images?page_size=100&page=0&sort_by=last_update_date%5Bdesc%5D" 22 | 23 | # Check if jq and curl are installed 24 | # If they are not installed, exit the script with an error message 25 | if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then 26 | >&2 echo "jq and curl are required but not installed. Exiting with status 1." >&2 27 | exit 1 28 | fi 29 | 30 | # Fetch `ubi9` from registry.access.redhat.com sorted by most recent update date, and keeping only the first page. 31 | response=$(curl --silent --fail --location --connect-timeout 10 --retry 3 --retry-delay 2 --max-time 30 --header 'accept: application/json' "$URL") 32 | 33 | # Check if the response is empty or null 34 | if [ -z "$response" ] || [ "$response" == "null" ]; then 35 | >&2 echo "Error: Failed to fetch tags from the Red Hat Container Catalog API." 36 | exit 1 37 | fi 38 | 39 | # Parse the JSON response using jq to find the version associated with the "latest" tag 40 | # - The response is expected to be a JSON object containing repository data. 41 | # - The script uses `jq` to: 42 | # 1. Iterate over all repositories in the `data` array. 43 | # 2. Select repositories where at least one tag has the name "latest". 44 | # 3. From those repositories, select tags that: 45 | # - Do not have the name "latest". 46 | # - Contain a hyphen in their name (indicating a long-form tag). 47 | # 4. Extract the `name` of the matching tags. 48 | # 5. Sort the tag names uniquely (`sort -u`). 49 | # 6. Take the last tag in the sorted list (`tail -n 1`), which is assumed to be the most recent valid tag. 50 | latest_tag=$(echo "$response" | jq -r '.data[].repositories[] | select(.tags[].name == "latest") | .tags[] | select(.name != "latest" and (.name | contains("-"))) | .name' | sort -u | tail -n 1) 51 | 52 | 53 | # Check if the latest_tag is empty 54 | if [ -z "$latest_tag" ]; then 55 | echo "Error: No valid tags found." 56 | exit 1 57 | fi 58 | 59 | # Output the latest tag version 60 | echo "$latest_tag" 61 | exit 0 62 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/jdk17.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump JDK17 version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | lastVersion: 18 | kind: temurin 19 | name: Get the latest Adoptium JDK17 version via the API 20 | spec: 21 | featureversion: 17 22 | transformers: 23 | - trimprefix: "jdk-" 24 | lastVersionWithUnderscore: 25 | kind: temurin 26 | name: Get the latest Adoptium JDK17 version via the API with + replaced by _ 27 | spec: 28 | featureversion: 17 29 | transformers: 30 | - trimprefix: "jdk-" 31 | - replacer: 32 | from: "+" 33 | to: "_" 34 | 35 | conditions: 36 | checkTemurinAllReleases: 37 | name: Check if the "" is available for all platforms 38 | kind: temurin 39 | sourceid: lastVersion 40 | spec: 41 | featureversion: 17 42 | platforms: 43 | - alpine-linux/x64 44 | - linux/x64 45 | - linux/aarch64 46 | - linux/ppc64le 47 | - linux/s390x 48 | - windows/x64 49 | - linux/arm 50 | 51 | targets: 52 | setJDK17VersionDockerBake: 53 | name: "Bump JDK17 version for Linux images in the docker-bake.hcl file" 54 | kind: hcl 55 | sourceid: lastVersionWithUnderscore 56 | spec: 57 | file: docker-bake.hcl 58 | path: variable.JAVA17_VERSION.default 59 | scmid: default 60 | ## Update Dockerfiles default JAVA_VERSION argument as JDK17 is the current default Java version 61 | setJDK17VersionLinux: 62 | name: "Bump JDK17 default ARG version in Linux images" 63 | kind: dockerfile 64 | sourceid: lastVersionWithUnderscore 65 | spec: 66 | files: 67 | - alpine/Dockerfile 68 | - debian/Dockerfile 69 | - rhel/ubi9/Dockerfile 70 | instruction: 71 | keyword: ARG 72 | matcher: JAVA_VERSION 73 | scmid: default 74 | setJDK17VersionWindows: 75 | name: "Bump JDK17 default ARG version in Windows images" 76 | kind: dockerfile 77 | sourceid: lastVersion 78 | spec: 79 | files: 80 | - windows/nanoserver/Dockerfile 81 | - windows/windowsservercore/Dockerfile 82 | instruction: 83 | keyword: ARG 84 | matcher: JAVA_VERSION 85 | scmid: default 86 | setJDK17VersionReadme: 87 | name: "Bump JDK17 version in README.md file" 88 | kind: file 89 | sourceid: lastVersionWithUnderscore 90 | spec: 91 | file: README.md 92 | matchpattern: '"JAVA_VERSION": "17\.[0-9]+\.[0-9]+[\+_][0-9]+"' 93 | replacepattern: '"JAVA_VERSION": "{{ source "lastVersionWithUnderscore" }}"' 94 | scmid: default 95 | 96 | actions: 97 | default: 98 | kind: github/pullrequest 99 | scmid: default 100 | title: Bump JDK17 version to {{ source "lastVersionWithUnderscore" }} 101 | spec: 102 | labels: 103 | - dependencies 104 | - jdk17 105 | -------------------------------------------------------------------------------- /tests/tests_inbound-agent.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | AGENT_CONTAINER=bats-jenkins-jnlp-agent 4 | 5 | load test_helpers 6 | 7 | buildNetcatImage 8 | 9 | SUT_IMAGE="$(get_sut_image)" 10 | 11 | @test "[${SUT_IMAGE}] image has installed jenkins-agent in PATH" { 12 | local sut_cid 13 | sut_cid="$(docker run -d -it -P "${SUT_IMAGE}" /bin/bash)" 14 | 15 | is_agent_container_running "${sut_cid}" 16 | 17 | run docker exec "${sut_cid}" which jenkins-agent 18 | [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] 19 | 20 | run docker exec "${sut_cid}" which jenkins-agent 21 | [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] 22 | 23 | cleanup "${sut_cid}" 24 | } 25 | 26 | @test "[${SUT_IMAGE}] image starts jenkins-agent correctly (slow test)" { 27 | local netcat_cid sut_cid 28 | # Spin off a helper image which launches the netcat utility, listening at port 5000 for 30 sec 29 | netcat_cid="$(docker run -d -it netcat-helper:latest /bin/sh -c "timeout 30s nc -l 5000")" 30 | 31 | # Run jenkins agent which tries to connect to the netcat-helper container at port 5000 32 | sut_cid="$(docker run -d --link "${netcat_cid}" "${SUT_IMAGE}" -url "http://${netcat_cid}:5000" aaa bbb)" 33 | 34 | # Wait for the whole process to take place (in resource-constrained environments it can take 100s of milliseconds) 35 | sleep 5 36 | docker logs "${netcat_cid}" 37 | docker logs "${sut_cid}" 38 | 39 | # Capture the logs output from netcat and check the header of the first HTTP request with the expected one 40 | run docker logs "${netcat_cid}" 41 | echo "${output}" | grep 'GET /tcpSlaveAgentListener/ HTTP/1.1' 42 | 43 | cleanup "${netcat_cid}" 44 | cleanup "${sut_cid}" 45 | } 46 | 47 | @test "[${SUT_IMAGE}] image parses \$REMOTING_OPTS correctly (slow test)" { 48 | local netcat_cid sut_cid 49 | netcat_cid="$(docker run -d -it netcat-helper:latest /bin/sh -c "while :; do (echo 'HTTP/1.1 200 OK'; echo) | nc -l 5000; done")" 50 | sut_cid="$(docker run -d --link "${netcat_cid}" -e REMOTING_OPTS="-url http://${netcat_cid}:5000 -name xxx -secret xxx -webSocket -webSocketHeader \"Cookie=x=1; y=2\"" "${SUT_IMAGE}")" 51 | sleep 5 52 | docker logs "${netcat_cid}" 53 | docker logs "${sut_cid}" 54 | run docker logs "${netcat_cid}" 55 | echo "${output}" | grep 'Cookie:x=1; y=2' 56 | cleanup "${netcat_cid}" 57 | cleanup "${sut_cid}" 58 | } 59 | 60 | @test "[${SUT_IMAGE}] use build args correctly" { 61 | cd "${BATS_TEST_DIRNAME}"/.. || false 62 | 63 | local TEST_VERSION TEST_USER sut_image sut_cid 64 | 65 | # Old version used to test overriding the build arguments. 66 | TEST_VERSION="3180.v3dd999d24861" 67 | TEST_USER="root" 68 | 69 | sut_image="${SUT_IMAGE}-tests-${BATS_TEST_NUMBER}" 70 | 71 | docker buildx bake \ 72 | --set "${IMAGE}.args.VERSION=${TEST_VERSION}" \ 73 | --set "${IMAGE}.args.user=${TEST_USER}" \ 74 | --set "${IMAGE}.platform=linux/${ARCH}" \ 75 | --set "${IMAGE}.tags=${sut_image}" \ 76 | --load \ 77 | "${IMAGE}" 78 | 79 | sut_cid="$(docker run -d -it --name "${AGENT_CONTAINER}" -P "${sut_image}" /bin/sh)" 80 | 81 | is_agent_container_running "${sut_cid}" 82 | 83 | run docker exec "${sut_cid}" sh -c "java -jar /usr/share/jenkins/agent.jar -version" 84 | [ "${TEST_VERSION}" = "${lines[0]}" ] 85 | 86 | run docker exec "${AGENT_CONTAINER}" sh -c "id -u -n ${TEST_USER}" 87 | [ "${TEST_USER}" = "${lines[0]}" ] 88 | 89 | cleanup "${sut_cid}" 90 | } 91 | -------------------------------------------------------------------------------- /tests/golden/expected_tags_linux.txt: -------------------------------------------------------------------------------- 1 | docker.io/jenkins/agent:alpine 2 | docker.io/jenkins/agent:alpine-jdk17 3 | docker.io/jenkins/agent:alpine-jdk21 4 | docker.io/jenkins/agent:alpine-jdk25 5 | docker.io/jenkins/agent:alpine3.23 6 | docker.io/jenkins/agent:alpine3.23-jdk17 7 | docker.io/jenkins/agent:alpine3.23-jdk21 8 | docker.io/jenkins/agent:alpine3.23-jdk25 9 | docker.io/jenkins/agent:jdk17 10 | docker.io/jenkins/agent:jdk21 11 | docker.io/jenkins/agent:jdk25 12 | docker.io/jenkins/agent:latest 13 | docker.io/jenkins/agent:latest-alpine 14 | docker.io/jenkins/agent:latest-alpine-jdk17 15 | docker.io/jenkins/agent:latest-alpine-jdk21 16 | docker.io/jenkins/agent:latest-alpine-jdk25 17 | docker.io/jenkins/agent:latest-alpine3.23 18 | docker.io/jenkins/agent:latest-alpine3.23-jdk17 19 | docker.io/jenkins/agent:latest-alpine3.23-jdk21 20 | docker.io/jenkins/agent:latest-alpine3.23-jdk25 21 | docker.io/jenkins/agent:latest-jdk17 22 | docker.io/jenkins/agent:latest-jdk21 23 | docker.io/jenkins/agent:latest-jdk25 24 | docker.io/jenkins/agent:latest-rhel-ubi9 25 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk17 26 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk21 27 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk25 28 | docker.io/jenkins/agent:latest-trixie 29 | docker.io/jenkins/agent:latest-trixie-jdk17 30 | docker.io/jenkins/agent:latest-trixie-jdk21 31 | docker.io/jenkins/agent:latest-trixie-jdk25 32 | docker.io/jenkins/agent:rhel-ubi9 33 | docker.io/jenkins/agent:rhel-ubi9-jdk17 34 | docker.io/jenkins/agent:rhel-ubi9-jdk21 35 | docker.io/jenkins/agent:rhel-ubi9-jdk25 36 | docker.io/jenkins/agent:trixie 37 | docker.io/jenkins/agent:trixie-jdk17 38 | docker.io/jenkins/agent:trixie-jdk21 39 | docker.io/jenkins/agent:trixie-jdk25 40 | docker.io/jenkins/inbound-agent:alpine 41 | docker.io/jenkins/inbound-agent:alpine-jdk17 42 | docker.io/jenkins/inbound-agent:alpine-jdk21 43 | docker.io/jenkins/inbound-agent:alpine-jdk25 44 | docker.io/jenkins/inbound-agent:alpine3.23 45 | docker.io/jenkins/inbound-agent:alpine3.23-jdk17 46 | docker.io/jenkins/inbound-agent:alpine3.23-jdk21 47 | docker.io/jenkins/inbound-agent:alpine3.23-jdk25 48 | docker.io/jenkins/inbound-agent:jdk17 49 | docker.io/jenkins/inbound-agent:jdk21 50 | docker.io/jenkins/inbound-agent:jdk25 51 | docker.io/jenkins/inbound-agent:latest 52 | docker.io/jenkins/inbound-agent:latest-alpine 53 | docker.io/jenkins/inbound-agent:latest-alpine-jdk17 54 | docker.io/jenkins/inbound-agent:latest-alpine-jdk21 55 | docker.io/jenkins/inbound-agent:latest-alpine-jdk25 56 | docker.io/jenkins/inbound-agent:latest-alpine3.23 57 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk17 58 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk21 59 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk25 60 | docker.io/jenkins/inbound-agent:latest-jdk17 61 | docker.io/jenkins/inbound-agent:latest-jdk21 62 | docker.io/jenkins/inbound-agent:latest-jdk25 63 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9 64 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk17 65 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk21 66 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk25 67 | docker.io/jenkins/inbound-agent:latest-trixie 68 | docker.io/jenkins/inbound-agent:latest-trixie-jdk17 69 | docker.io/jenkins/inbound-agent:latest-trixie-jdk21 70 | docker.io/jenkins/inbound-agent:latest-trixie-jdk25 71 | docker.io/jenkins/inbound-agent:rhel-ubi9 72 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk17 73 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk21 74 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk25 75 | docker.io/jenkins/inbound-agent:trixie 76 | docker.io/jenkins/inbound-agent:trixie-jdk17 77 | docker.io/jenkins/inbound-agent:trixie-jdk21 78 | docker.io/jenkins/inbound-agent:trixie-jdk25 79 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/alpine.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump Alpine version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | latestVersion: 18 | kind: githubrelease 19 | name: "Get the latest Alpine Linux version" 20 | spec: 21 | owner: "alpinelinux" 22 | repository: "aports" # Its release process follows Alpine's 23 | token: "{{ requiredEnv .github.token }}" 24 | username: "{{ .github.username }}" 25 | versionfilter: 26 | kind: semver 27 | pattern: "~3" 28 | transformers: 29 | - trimprefix: "v" 30 | majorMinorVersion: 31 | kind: githubrelease 32 | name: "Extract major and minor version from latest Alpine version" 33 | spec: 34 | owner: "alpinelinux" 35 | repository: "aports" # Its release process follows Alpine's 36 | token: "{{ requiredEnv .github.token }}" 37 | username: "{{ .github.username }}" 38 | versionfilter: 39 | kind: semver 40 | pattern: "~3" 41 | transformers: 42 | - trimprefix: "v" 43 | - findsubmatch: 44 | pattern: >- 45 | ^(3.*)\..*$ 46 | captureindex: 1 47 | 48 | conditions: 49 | testDockerfileArg: 50 | name: "Does the Dockerfile have an ARG instruction for the Alpine Linux version?" 51 | kind: dockerfile 52 | disablesourceinput: true 53 | spec: 54 | file: alpine/Dockerfile 55 | instruction: 56 | keyword: "ARG" 57 | matcher: "ALPINE_TAG" 58 | testDockerImageExists: 59 | name: "Does the Docker Image exist on the Docker Hub?" 60 | kind: dockerimage 61 | sourceid: latestVersion 62 | spec: 63 | image: "alpine" 64 | # tag come from the source 65 | architecture: amd64 66 | 67 | targets: 68 | updateDockerfile: 69 | name: "Update the value of the base image (ARG ALPINE_TAG) in the Dockerfile" 70 | kind: dockerfile 71 | spec: 72 | file: alpine/Dockerfile 73 | instruction: 74 | keyword: "ARG" 75 | matcher: "ALPINE_TAG" 76 | scmid: default 77 | sourceid: latestVersion 78 | updateDockerBake: 79 | name: "Update the value of the base image (ARG ALPINE_TAG) in the docker-bake.hcl" 80 | kind: hcl 81 | spec: 82 | file: docker-bake.hcl 83 | path: variable.ALPINE_FULL_TAG.default 84 | scmid: default 85 | sourceid: latestVersion 86 | updateReadmeAlpineTag: 87 | name: "Update the ALPINE_TAG value in the README.md file" 88 | kind: file 89 | spec: 90 | file: README.md 91 | matchpattern: '"ALPINE_TAG": "3\.[0-9]+\.[0-9]+"' 92 | replacepattern: '"ALPINE_TAG": "{{ source "latestVersion" }}"' 93 | scmid: default 94 | sourceid: latestVersion 95 | updateReadmeAlpineShortTags: 96 | name: "Update the Alpine version in the image tags in the README.md file" 97 | kind: file 98 | spec: 99 | file: README.md 100 | matchpattern: >- 101 | alpine3\.[0-9]+ 102 | replacepattern: 'alpine{{ source "majorMinorVersion" }}' 103 | scmid: default 104 | sourceid: majorMinorVersion 105 | 106 | actions: 107 | default: 108 | kind: github/pullrequest 109 | scmid: default 110 | title: Bump Alpine Linux Version to {{ source "latestVersion" }} 111 | spec: 112 | labels: 113 | - dependencies 114 | -------------------------------------------------------------------------------- /tests/test_helpers.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | # check dependencies 6 | ( 7 | type docker &>/dev/null || ( echo "docker is not available"; exit 1 ) 8 | )>&2 9 | 10 | function printMessage { 11 | echo "# ${*}" >&3 12 | } 13 | 14 | # Assert that $1 is the output of a command $2 15 | function assert { 16 | local expected_output 17 | local actual_output 18 | expected_output="${1}" 19 | shift 20 | actual_output=$("${@}") 21 | if ! [[ "${actual_output}" = "${expected_output}" ]]; then 22 | printMessage "Expected: '${expected_output}', actual: '${actual_output}'" 23 | false 24 | fi 25 | } 26 | 27 | # Assert that golden file $1 matches the output of a command $2 28 | assert_matches_golden() { 29 | local golden="$1" 30 | shift 31 | local golden_path="tests/golden/${golden}.txt" 32 | 33 | if [[ ! -f "${golden_path}" ]]; then 34 | echo "Golden file '${golden_path}' does not exist" 35 | return 1 36 | fi 37 | 38 | # Run the command passed as arguments and capture its output 39 | local output 40 | output="$(mktemp)" 41 | "$@" > "${output}" 42 | 43 | # Compare with golden file 44 | diff -u "${golden_path}" <(cat "${output}") 45 | } 46 | 47 | function get_sut_image { 48 | test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" 49 | ## Retrieve the SUT image name from buildx 50 | # Option --print for 'docker buildx bake' prints the JSON configuration on the stdout 51 | # Option --silent for 'make' suppresses the echoing of command so the output is valid JSON 52 | # The image name is the 1st of the "tags" array, on the first "image" found 53 | make --silent show | jq -r ".target.\"${IMAGE}\".tags[0]" 54 | } 55 | 56 | function get_remoting_version() { 57 | test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" 58 | 59 | make --silent show | jq -r ".target.\"${IMAGE}\".args.VERSION" 60 | } 61 | 62 | function cleanup { 63 | docker kill "$1" &>/dev/null ||: 64 | docker rm -fv "$1" &>/dev/null ||: 65 | } 66 | 67 | function get_dockerfile_directory() { 68 | test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" 69 | 70 | DOCKERFILE=$(make --silent show | jq -r ".target.\"${IMAGE}\".dockerfile") 71 | echo "${DOCKERFILE%"/Dockerfile"}" 72 | } 73 | 74 | # Retry a command $1 times until it succeeds. Wait $2 seconds between retries. 75 | function retry { 76 | local attempts 77 | local delay 78 | local i 79 | attempts="${1}" 80 | shift 81 | delay="${1}" 82 | shift 83 | 84 | for ((i=0; i < attempts; i++)); do 85 | run "${@}" 86 | if [[ "${status}" -eq 0 ]]; then 87 | return 0 88 | fi 89 | sleep "${delay}" 90 | done 91 | 92 | printMessage "Command '${*}' failed $attempts times. Status: ${status}. Output: ${output}" 93 | 94 | false 95 | } 96 | 97 | function is_agent_container_running { 98 | test -n "${1}" 99 | sleep 1 100 | retry 3 1 assert "true" docker inspect -f '{{.State.Running}}' "${1}" 101 | } 102 | 103 | function buildNetcatImage() { 104 | if ! docker inspect --type=image netcat-helper:latest &>/dev/null; then 105 | docker build -t netcat-helper:latest tests/netcat-helper/ &>/dev/null 106 | fi 107 | } 108 | 109 | function clean_test_container { 110 | docker kill "${AGENT_CONTAINER}" "${NETCAT_HELPER_CONTAINER}" &>/dev/null || : 111 | docker rm -fv "${AGENT_CONTAINER}" "${NETCAT_HELPER_CONTAINER}" &>/dev/null || : 112 | } 113 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/remoting.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump the Jenkins remoting version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | lastVersion: 18 | kind: githubrelease 19 | name: Get the latest version of the Jenkins remoting 20 | spec: 21 | owner: jenkinsci 22 | repository: remoting 23 | token: "{{ requiredEnv .github.token }}" 24 | username: "{{ .github.username }}" 25 | versionfilter: 26 | kind: latest 27 | 28 | conditions: 29 | checkJarIsAvailable: 30 | kind: shell 31 | name: Check that the JAR file for remoting is available 32 | disablesourceinput: true 33 | spec: 34 | command: curl --silent --show-error --location --verbose --output /dev/null --fail https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/{{ source "lastVersion" }}/remoting-{{ source "lastVersion" }}.jar 35 | 36 | targets: 37 | setAlpineDockerImage: 38 | name: Bump the Jenkins remoting version on Alpine 39 | kind: dockerfile 40 | spec: 41 | file: alpine/Dockerfile 42 | instruction: 43 | keyword: ARG 44 | matcher: VERSION 45 | scmid: default 46 | setDebianDockerImage: 47 | name: Bump the Jenkins remoting version on Debian Bullseye 48 | kind: dockerfile 49 | spec: 50 | file: debian/Dockerfile 51 | instruction: 52 | keyword: ARG 53 | matcher: VERSION 54 | scmid: default 55 | setNanoserverDockerImage: 56 | name: Bump the Jenkins remoting version on Windows Nanoserver 57 | kind: dockerfile 58 | spec: 59 | file: windows/nanoserver/Dockerfile 60 | instruction: 61 | keyword: ARG 62 | matcher: VERSION 63 | scmid: default 64 | setWindowsServerCoreDockerImage: 65 | name: Bump the Jenkins remoting version on Windows Server Core 66 | kind: dockerfile 67 | spec: 68 | file: windows/windowsservercore/Dockerfile 69 | instruction: 70 | keyword: ARG 71 | matcher: VERSION 72 | scmid: default 73 | setDockerBakeDefaultParentImage: 74 | name: Bump the Jenkins remoting version on the docker-bake.hcl file 75 | kind: hcl 76 | spec: 77 | file: docker-bake.hcl 78 | path: variable.REMOTING_VERSION.default 79 | scmid: default 80 | setLinuxBuildShRemotingVersion: 81 | name: Bump the Jenkins remoting version on the linux build.sh file 82 | kind: file 83 | spec: 84 | file: build.sh 85 | matchpattern: >- 86 | remoting_version="(.*)" 87 | replacepattern: >- 88 | remoting_version="{{ source "lastVersion" }}" 89 | scmid: default 90 | setWindowsBuildPwshRemotingVersion: 91 | name: Bump the Jenkins remoting version on the windows build.ps1 file 92 | kind: file 93 | spec: 94 | file: build.ps1 95 | matchpattern: >- 96 | \$RemotingVersion(.*)=(.*), 97 | replacepattern: >- 98 | $$RemotingVersion${1}= '{{ source "lastVersion" }}', 99 | scmid: default 100 | updateReadmeRemotingVersion: 101 | name: "Update the remoting version in the README.md file" 102 | kind: file 103 | spec: 104 | file: README.md 105 | matchpattern: >- 106 | (.*)"VERSION":\s"(.*)"(.*) 107 | replacepattern: '$1"VERSION": "{{ source "lastVersion" }}"$3' 108 | scmid: default 109 | 110 | actions: 111 | default: 112 | kind: github/pullrequest 113 | scmid: default 114 | title: Bump the Jenkins remoting version to {{ source "lastVersion" }} 115 | spec: 116 | labels: 117 | - dependencies 118 | - remoting 119 | -------------------------------------------------------------------------------- /adoptium-get-jdk-link.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### IMPORTANT: this script is synchronized with https://github.com/jenkins-infra/shared-tools/, please modify its content in that repository only. 4 | 5 | # Check if at least one argument was passed to the script 6 | # If one argument was passed and JAVA_VERSION is set, assign the argument to OS 7 | # If two arguments were passed, assign them to JAVA_VERSION and OS respectively 8 | # If three arguments were passed, assign them to JAVA_VERSION, OS and ARCHS respectively 9 | # If not, check if JAVA_VERSION and OS are already set. If they're not set, exit the script with an error message 10 | if [ $# -eq 1 ] && [ -n "${JAVA_VERSION}" ]; then 11 | OS="${1}" 12 | elif [ $# -eq 2 ]; then 13 | JAVA_VERSION="${1}" 14 | OS="${2}" 15 | elif [ $# -eq 3 ]; then 16 | JAVA_VERSION="${1}" 17 | OS="${2}" 18 | ARCHS=$3 19 | elif [ -z "${JAVA_VERSION}" ] && [ -z "${OS}" ]; then 20 | echo "Error: No Java version and OS specified. Please set the JAVA_VERSION and OS environment variables or pass them as arguments." >&2 21 | exit 1 22 | elif [ -z "${JAVA_VERSION}" ]; then 23 | echo "Error: No Java version specified. Please set the JAVA_VERSION environment variable or pass it as an argument." >&2 24 | exit 1 25 | elif [ -z "${OS}" ]; then 26 | OS="${1}" 27 | if [ -z "${OS}" ]; then 28 | echo "Error: No OS specified. Please set the OS environment variable or pass it as an argument." >&2 29 | exit 1 30 | fi 31 | fi 32 | 33 | # Check if ARCHS is set. If it's not set, assign the current architecture to it 34 | if [ -z "${ARCHS}" ]; then 35 | ARCHS=$(uname -m | sed -e 's/x86_64/x64/' -e 's/armv7l/arm/') 36 | else 37 | # Convert ARCHS to an array 38 | OLD_IFS="${IFS}" 39 | IFS=',' 40 | set -- "${ARCHS}" 41 | ARCHS="" 42 | for arch in "$@"; do 43 | ARCHS="${ARCHS} ${arch}" 44 | done 45 | IFS="${OLD_IFS}" 46 | fi 47 | 48 | # Check if jq and curl are installed 49 | # If they are not installed, exit the script with an error message 50 | if ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then 51 | echo "jq and curl are required but not installed. Exiting with status 1." >&2 52 | exit 1 53 | fi 54 | 55 | # Replace underscores with plus signs in JAVA_VERSION 56 | ARCHIVE_DIRECTORY=$(echo "${JAVA_VERSION}" | tr '_' '+') 57 | 58 | # URL encode ARCHIVE_DIRECTORY 59 | ENCODED_ARCHIVE_DIRECTORY=$(echo "${ARCHIVE_DIRECTORY}" | xargs -I {} printf %s {} | jq "@uri" -jRr) 60 | 61 | # Determine the OS type for the URL 62 | OS_TYPE="linux" 63 | if [ "${OS}" = "alpine" ]; then 64 | OS_TYPE="alpine-linux" 65 | fi 66 | if [ "${OS}" = "windows" ]; then 67 | OS_TYPE="windows" 68 | fi 69 | 70 | # Initialize a variable to store the URL for the first architecture 71 | FIRST_ARCH_URL="" 72 | 73 | # Loop over the array of architectures 74 | for ARCH in ${ARCHS}; do 75 | # Fetch the download URL from the Adoptium API 76 | URL="https://api.adoptium.net/v3/binary/version/jdk-${ENCODED_ARCHIVE_DIRECTORY}/${OS_TYPE}/${ARCH}/jdk/hotspot/normal/eclipse?project=jdk" 77 | 78 | if ! RESPONSE=$(curl --fail --silent --show-error --retry 5 --retry-connrefused --head "${URL}"); then 79 | echo "Error: Failed to fetch the URL for architecture ${ARCH} for ${URL}. Exiting with status 1." >&2 80 | echo "Response: ${RESPONSE}" >&2 81 | exit 1 82 | fi 83 | 84 | # Extract the redirect URL from the HTTP response 85 | REDIRECTED_URL=$(echo "${RESPONSE}" | grep -i location | awk '{print $2}' | tr -d '\r') 86 | 87 | # If no redirect URL was found, exit the script with an error message 88 | if [ -z "${REDIRECTED_URL}" ]; then 89 | echo "Error: No redirect URL found for architecture ${ARCH} for ${URL}. Exiting with status 1." >&2 90 | echo "Response: ${RESPONSE}" >&2 91 | exit 1 92 | fi 93 | 94 | # Use curl to check if the URL is reachable 95 | # If the URL is not reachable, print an error message and exit the script with status 1 96 | if ! curl --fail --silent --show-error --retry 5 --retry-connrefused "${REDIRECTED_URL}" >/dev/null 2>&1; then 97 | echo "${REDIRECTED_URL}" is not reachable for architecture "${ARCH}". >&2 98 | exit 1 99 | fi 100 | 101 | # If FIRST_ARCH_URL is empty, store the current URL 102 | if [ -z "${FIRST_ARCH_URL}" ]; then 103 | FIRST_ARCH_URL=${REDIRECTED_URL} 104 | fi 105 | done 106 | 107 | # If all downloads are successful, print the URL for the first architecture 108 | echo "${FIRST_ARCH_URL}" 109 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | ## For Docker <=20.04 4 | export DOCKER_BUILDKIT=1 5 | ## For Docker <=20.04 6 | export DOCKER_CLI_EXPERIMENTAL=enabled 7 | ## Required to have docker build output always printed on stdout 8 | export BUILDKIT_PROGRESS=plain 9 | 10 | current_arch := $(shell uname -m) 11 | export ARCH ?= $(shell case $(current_arch) in (x86_64) echo "amd64" ;; (i386) echo "386";; (aarch64|arm64) echo "arm64" ;; (armv6*) echo "arm/v6";; (armv7*) echo "arm/v7";; (s390*|riscv*|ppc64le) echo $(current_arch);; (*) echo "UNKNOWN-CPU";; esac) 12 | 13 | ##### Macros 14 | ## Check the presence of a CLI in the current PATH 15 | check_cli = type "$(1)" >/dev/null 2>&1 || { echo "Error: command '$(1)' required but not found. Exiting." ; exit 1 ; } 16 | ## Check if a given image exists in the current manifest docker-bake.hcl 17 | check_image = make --silent list | grep -w '$(1)' >/dev/null 2>&1 || { echo "Error: the image '$(1)' does not exist in manifest for the platform 'linux/$(ARCH)'. Please check the output of 'make list'. Exiting." ; exit 1 ; } 18 | ## Base "docker buildx base" command to be reused everywhere 19 | bake_base_cli := docker buildx bake --file docker-bake.hcl 20 | bake_cli := $(bake_base_cli) --load 21 | 22 | .PHONY: build 23 | .PHONY: test test-alpine test-debian 24 | 25 | check-reqs: 26 | ## Build requirements 27 | @$(call check_cli,bash) 28 | @$(call check_cli,git) 29 | @$(call check_cli,docker) 30 | @docker info | grep 'buildx:' >/dev/null 2>&1 || { echo "Error: Docker BuildX plugin required but not found. Exiting." ; exit 1 ; } 31 | ## Test requirements 32 | @$(call check_cli,curl) 33 | @$(call check_cli,jq) 34 | 35 | ## This function is specific to Jenkins infrastructure and isn't required in other contexts 36 | docker-init: check-reqs 37 | ifeq ($(CI),true) 38 | ifeq ($(wildcard /etc/buildkitd.toml),) 39 | echo 'WARNING: /etc/buildkitd.toml not found, using default configuration.' 40 | docker buildx create --use --bootstrap --driver docker-container 41 | else 42 | docker buildx create --use --bootstrap --driver docker-container --config /etc/buildkitd.toml 43 | endif 44 | else 45 | docker buildx create --use --bootstrap --driver docker-container 46 | endif 47 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 48 | 49 | build: check-reqs 50 | @set -x; $(bake_cli) $(shell make --silent list) --set '*.platform=linux/$(ARCH)' 51 | 52 | build-%: 53 | @$(call check_image,$*) 54 | @echo "== building $*" 55 | @set -x; $(bake_cli) '$*' --set '*.platform=linux/$(ARCH)' 56 | 57 | every-build: check-reqs 58 | @set -x; $(bake_base_cli) linux 59 | 60 | show: 61 | @$(bake_base_cli) --progress=quiet linux --print | jq 62 | 63 | tags: 64 | @make show | jq -r '.target[].tags[]' | LC_ALL=C sort 65 | 66 | show-%: 67 | @$(bake_cli) $* --progress=quiet --print | jq 68 | 69 | tags-%: 70 | @make show-$* | jq -r '.target[].tags[]' | LC_ALL=C sort 71 | 72 | list: check-reqs 73 | @set -x; make --silent show | jq -r '.target | path(.. | select(.platforms[] | contains("linux/$(ARCH)"))?) | add' 74 | 75 | bats: 76 | git clone --branch v1.13.0 https://github.com/bats-core/bats-core ./bats 77 | 78 | prepare-test: bats check-reqs 79 | git submodule update --init --recursive 80 | mkdir -p target 81 | 82 | publish: 83 | @set -x; $(bake_base_cli) linux --push 84 | 85 | ## Define bats options based on environment 86 | # common flags for all tests 87 | bats_flags := "" 88 | # if DISABLE_PARALLEL_TESTS true, then disable parallel execution 89 | ifneq (true,$(DISABLE_PARALLEL_TESTS)) 90 | # If the GNU 'parallel' command line is absent, then disable parallel execution 91 | parallel_cli := $(shell command -v parallel 2>/dev/null) 92 | ifneq (,$(parallel_cli)) 93 | # If parallel execution is enabled, then set 2 tests per core available for the Docker Engine 94 | test-%: PARALLEL_JOBS ?= $(shell echo $$(( $(shell docker run --rm alpine grep -c processor /proc/cpuinfo) * 2))) 95 | test-%: bats_flags += --jobs $(PARALLEL_JOBS) 96 | endif 97 | endif 98 | test-%: prepare-test 99 | # Check that the image exists in the manifest 100 | @$(call check_image,$*) 101 | # Ensure that the image is built 102 | @make --silent build-$* 103 | @echo "== testing $*" 104 | set -x 105 | # Each type of image ("agent" or "inbound-agent") has its own tests suite 106 | ifeq ($(CI), true) 107 | # Execute the test harness and write result to a TAP file 108 | IMAGE=$* bats/bin/bats $(CURDIR)/tests/tests_$(shell echo $* | cut -d "_" -f 1).bats $(bats_flags) --formatter junit | tee target/junit-results-$*.xml 109 | else 110 | IMAGE=$* bats/bin/bats $(CURDIR)/tests/tests_$(shell echo $* | cut -d "_" -f 1).bats $(bats_flags) 111 | endif 112 | 113 | test: prepare-test 114 | @make --silent list | while read image; do make --silent "test-$${image}"; done 115 | -------------------------------------------------------------------------------- /rhel/ubi9/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG UBI9_TAG=9.7-1764794285 2 | FROM registry.access.redhat.com/ubi9/ubi:"${UBI9_TAG}" AS jre-build 3 | SHELL ["/bin/bash", "-e", "-u", "-o", "pipefail", "-c"] 4 | 5 | # This Build ARG is populated by Docker 6 | # Ref. https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope 7 | ARG TARGETPLATFORM 8 | 9 | COPY adoptium-get-jdk-link.sh /usr/bin/local/adoptium-get-jdk-link.sh 10 | COPY adoptium-install-jdk.sh /usr/bin/local/adoptium-install-jdk.sh 11 | 12 | ARG JAVA_VERSION=17.0.17_10 13 | 14 | RUN dnf install --disableplugin=subscription-manager --setopt=install_weak_deps=0 --setopt=tsflags=nodocs --allowerasing -y \ 15 | ca-certificates \ 16 | curl \ 17 | jq \ 18 | && dnf clean --disableplugin=subscription-manager all \ 19 | && /usr/bin/local/adoptium-install-jdk.sh 20 | 21 | ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}" 22 | 23 | # Generate smaller java runtime without unneeded files 24 | # for now we include the full module path to maintain compatibility 25 | # while still saving space (approx 200mb from the full distribution) 26 | RUN case "$(jlink --version 2>&1 | cut -c1-2)" in \ 27 | "17") options="--compress=2 --add-modules ALL-MODULE-PATH --module-path /opt/jdk-${JAVA_VERSION}/jmods" ;; \ 28 | "21") options="--compress=zip-6 --add-modules ALL-MODULE-PATH --module-path /opt/jdk-${JAVA_VERSION}/jmods" ;; \ 29 | "25") options="--compress=zip-6 --add-modules java.base,java.logging,java.xml,java.se" ;; \ 30 | *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \ 31 | esac; \ 32 | jlink \ 33 | --strip-java-debug-attributes \ 34 | ${options} \ 35 | --no-man-pages \ 36 | --no-header-files \ 37 | --output /javaruntime 38 | 39 | FROM registry.access.redhat.com/ubi9/ubi:"${UBI9_TAG}" AS agent 40 | 41 | ARG user=jenkins 42 | ARG group=jenkins 43 | ARG uid=1000 44 | ARG gid=1000 45 | 46 | RUN groupadd -g "${gid}" "${group}" \ 47 | && useradd -l -c "Jenkins user" -d /home/"${user}" -u "${uid}" -g "${gid}" -m "${user}" || echo "user ${user} already exists." 48 | 49 | ARG AGENT_WORKDIR=/home/"${user}"/agent 50 | ENV TZ=Etc/UTC 51 | 52 | RUN dnf install --disableplugin=subscription-manager --setopt=install_weak_deps=0 --setopt=tsflags=nodocs -y \ 53 | ca-certificates \ 54 | fontconfig \ 55 | git \ 56 | less \ 57 | patch \ 58 | tzdata \ 59 | && dnf clean --disableplugin=subscription-manager all 60 | 61 | ARG VERSION=3261.v9c670a_4748a_9 62 | ADD --chown="${user}":"${group}" "https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/${VERSION}/remoting-${VERSION}.jar" /usr/share/jenkins/agent.jar 63 | RUN chmod 0644 /usr/share/jenkins/agent.jar \ 64 | && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar 65 | 66 | ARG GIT_LFS_VERSION=3.7.1 67 | RUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g') \ 68 | && 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" \ 69 | && tar xzf git-lfs.tgz \ 70 | && bash git-lfs-*/install.sh \ 71 | && git lfs install --skip-repo --system \ 72 | && rm -rf git-lfs* 73 | 74 | ENV LANG=C.UTF-8 75 | 76 | ENV JAVA_HOME=/opt/java/openjdk 77 | COPY --from=jre-build /javaruntime "$JAVA_HOME" 78 | ENV PATH="${JAVA_HOME}/bin:${PATH}" 79 | 80 | USER "${user}" 81 | ENV AGENT_WORKDIR=${AGENT_WORKDIR} 82 | RUN mkdir -p /home/"${user}"/.jenkins && mkdir -p "${AGENT_WORKDIR}" 83 | 84 | VOLUME /home/"${user}"/.jenkins 85 | VOLUME "${AGENT_WORKDIR}" 86 | WORKDIR /home/"${user}" 87 | ENV USER=${user} 88 | LABEL \ 89 | org.opencontainers.image.vendor="Jenkins project" \ 90 | org.opencontainers.image.title="Official Jenkins Agent Base container image for UBI 9" \ 91 | org.opencontainers.image.description="This is a base image, which provides the Jenkins agent executable (agent.jar)" \ 92 | org.opencontainers.image.version="${VERSION}" \ 93 | org.opencontainers.image.url="https://www.jenkins.io/" \ 94 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ 95 | org.opencontainers.image.licenses="MIT" 96 | 97 | ## Inbound Agent image target 98 | FROM agent AS inbound-agent 99 | 100 | ARG user=jenkins 101 | 102 | USER root 103 | COPY ../../jenkins-agent /usr/local/bin/jenkins-agent 104 | RUN chmod +x /usr/local/bin/jenkins-agent &&\ 105 | ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave 106 | USER ${user} 107 | 108 | LABEL \ 109 | org.opencontainers.image.vendor="Jenkins project" \ 110 | org.opencontainers.image.title="Official Jenkins Inbound Agent Base container image for UBI 9" \ 111 | org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" \ 112 | org.opencontainers.image.version="${VERSION}" \ 113 | org.opencontainers.image.url="https://www.jenkins.io/" \ 114 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ 115 | org.opencontainers.image.licenses="MIT" 116 | 117 | ENTRYPOINT ["/usr/local/bin/jenkins-agent"] 118 | -------------------------------------------------------------------------------- /tests/test_helpers.psm1: -------------------------------------------------------------------------------- 1 | function Test-CommandExists($command) { 2 | $oldPreference = $ErrorActionPreference 3 | $ErrorActionPreference = 'stop' 4 | $res = $false 5 | try { 6 | if (Get-Command $command) { 7 | $res = $true 8 | } 9 | } catch { 10 | $res = $false 11 | } finally { 12 | $ErrorActionPreference=$oldPreference 13 | } 14 | return $res 15 | } 16 | 17 | # check dependencies 18 | if (-Not (Test-CommandExists docker)) { 19 | Write-Error 'docker is not available' 20 | } 21 | 22 | function Get-EnvOrDefault($name, $def) { 23 | $entry = Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Select-Object -First 1 24 | if (($null -ne $entry) -and ![System.String]::IsNullOrWhiteSpace($entry.Value)) { 25 | return $entry.Value 26 | } 27 | return $def 28 | } 29 | 30 | function Retry-Command { 31 | [CmdletBinding()] 32 | param ( 33 | [parameter(Mandatory, ValueFromPipeline)] 34 | [ValidateNotNullOrEmpty()] 35 | [scriptblock] $ScriptBlock, 36 | [int] $RetryCount = 3, 37 | [int] $Delay = 30, 38 | [string] $SuccessMessage = 'Command executed successfuly!', 39 | [string] $FailureMessage = 'Failed to execute the command' 40 | ) 41 | 42 | process { 43 | $Attempt = 1 44 | $Flag = $true 45 | 46 | do { 47 | try { 48 | $PreviousPreference = $ErrorActionPreference 49 | $ErrorActionPreference = 'Stop' 50 | Invoke-Command -NoNewScope -ScriptBlock $ScriptBlock -OutVariable Result 4>&1 51 | $ErrorActionPreference = $PreviousPreference 52 | 53 | # flow control will execute the next line only if the command in the scriptblock executed without any errors 54 | # if an error is thrown, flow control will go to the 'catch' block 55 | Write-Verbose "$SuccessMessage `n" 56 | $Flag = $false 57 | } 58 | catch { 59 | if ($Attempt -gt $RetryCount) { 60 | Write-Verbose "$FailureMessage! Total retry attempts: $RetryCount" 61 | Write-Verbose "[Error Message] $($_.exception.message) `n" 62 | $Flag = $false 63 | } else { 64 | Write-Verbose "[$Attempt/$RetryCount] $FailureMessage. Retrying in $Delay seconds..." 65 | Start-Sleep -Seconds $Delay 66 | $Attempt = $Attempt + 1 67 | } 68 | } 69 | } 70 | While ($Flag) 71 | } 72 | } 73 | 74 | function Cleanup($name='') { 75 | if ([System.String]::IsNullOrWhiteSpace($name)) { 76 | $name = Get-EnvOrDefault 'AGENT_IMAGE' '' 77 | } 78 | 79 | if (![System.String]::IsNullOrWhiteSpace($name)) { 80 | docker kill "$name" 2>&1 | Out-Null 81 | docker rm -fv "$name" 2>&1 | Out-Null 82 | } 83 | } 84 | 85 | function Is-ContainerRunning($container='') { 86 | if ([System.String]::IsNullOrWhiteSpace($container)) { 87 | $container = Get-EnvOrDefault 'AGENT_CONTAINER' '' 88 | } 89 | 90 | Start-Sleep -Seconds 5 91 | Retry-Command -RetryCount 10 -Delay 3 -ScriptBlock { 92 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{.State.Running}}`" $container" 93 | if (($exitCode -ne 0) -or (-not $stdout.Contains('true')) ) { 94 | throw('Exit code incorrect, or invalid value for running state') 95 | } 96 | return $true 97 | } 98 | } 99 | 100 | function Run-Program($cmd, $params) { 101 | $psi = New-Object System.Diagnostics.ProcessStartInfo 102 | $psi.CreateNoWindow = $true 103 | $psi.UseShellExecute = $false 104 | $psi.RedirectStandardOutput = $true 105 | $psi.RedirectStandardError = $true 106 | $psi.WorkingDirectory = (Get-Location) 107 | $psi.FileName = $cmd 108 | $psi.Arguments = $params 109 | $proc = New-Object System.Diagnostics.Process 110 | $proc.StartInfo = $psi 111 | [void]$proc.Start() 112 | $stdout = $proc.StandardOutput.ReadToEnd() 113 | $stderr = $proc.StandardError.ReadToEnd() 114 | $proc.WaitForExit() 115 | if (($env:TESTS_DEBUG -eq 'debug') -or ($env:TESTS_DEBUG -eq 'verbose')) { 116 | Write-Host -ForegroundColor DarkBlue "[cmd] $cmd $params" 117 | if ($env:TESTS_DEBUG -eq 'verbose') { 118 | Write-Host -ForegroundColor DarkGray "[stdout] $stdout" 119 | } 120 | if ($proc.ExitCode -ne 0){ 121 | Write-Host -ForegroundColor DarkRed "[stderr] $stderr" 122 | } 123 | } 124 | 125 | return $proc.ExitCode, $stdout, $stderr 126 | } 127 | 128 | function BuildNcatImage($windowsVersionTag) { 129 | Write-Host "Building nmap image (Windows version '${windowsVersionTag}') for testing" 130 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' 'inspect --type=image nmap' $true 131 | if ($exitCode -ne 0) { 132 | Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." 133 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "build -t nmap --build-arg `"WINDOWS_VERSION_TAG=${windowsVersionTag}`" -f ./tests/netcat-helper/Dockerfile-windows ./tests/netcat-helper" 134 | $exitCode | Should -Be 0 135 | Pop-Location -StackName 'agent' 136 | } 137 | } 138 | 139 | function CleanupNetwork($name) { 140 | docker network rm $name 2>&1 | Out-Null 141 | } 142 | -------------------------------------------------------------------------------- /tests/golden/expected_tags_windows_on_tag.txt: -------------------------------------------------------------------------------- 1 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk17-nanoserver-1809 2 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk17-nanoserver-ltsc2019 3 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk17-nanoserver-ltsc2022 4 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk17-windowsservercore-ltsc2019 5 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk17-windowsservercore-ltsc2022 6 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk21-nanoserver-1809 7 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk21-nanoserver-ltsc2019 8 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk21-nanoserver-ltsc2022 9 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk21-windowsservercore-ltsc2019 10 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk21-windowsservercore-ltsc2022 11 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk25-nanoserver-1809 12 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk25-nanoserver-ltsc2019 13 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk25-nanoserver-ltsc2022 14 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk25-windowsservercore-ltsc2019 15 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk25-windowsservercore-ltsc2022 16 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-nanoserver-1809 17 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-nanoserver-ltsc2019 18 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-nanoserver-ltsc2022 19 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-windowsservercore-ltsc2019 20 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-windowsservercore-ltsc2022 21 | docker.io/jenkins/agent:jdk17-nanoserver-1809 22 | docker.io/jenkins/agent:jdk17-nanoserver-ltsc2019 23 | docker.io/jenkins/agent:jdk17-nanoserver-ltsc2022 24 | docker.io/jenkins/agent:jdk17-windowsservercore-ltsc2019 25 | docker.io/jenkins/agent:jdk17-windowsservercore-ltsc2022 26 | docker.io/jenkins/agent:jdk21-nanoserver-1809 27 | docker.io/jenkins/agent:jdk21-nanoserver-ltsc2019 28 | docker.io/jenkins/agent:jdk21-nanoserver-ltsc2022 29 | docker.io/jenkins/agent:jdk21-windowsservercore-ltsc2019 30 | docker.io/jenkins/agent:jdk21-windowsservercore-ltsc2022 31 | docker.io/jenkins/agent:jdk25-nanoserver-1809 32 | docker.io/jenkins/agent:jdk25-nanoserver-ltsc2019 33 | docker.io/jenkins/agent:jdk25-nanoserver-ltsc2022 34 | docker.io/jenkins/agent:jdk25-windowsservercore-ltsc2019 35 | docker.io/jenkins/agent:jdk25-windowsservercore-ltsc2022 36 | docker.io/jenkins/agent:nanoserver-1809 37 | docker.io/jenkins/agent:nanoserver-ltsc2019 38 | docker.io/jenkins/agent:nanoserver-ltsc2022 39 | docker.io/jenkins/agent:windowsservercore-ltsc2019 40 | docker.io/jenkins/agent:windowsservercore-ltsc2022 41 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk17-nanoserver-1809 42 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk17-nanoserver-ltsc2019 43 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk17-nanoserver-ltsc2022 44 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk17-windowsservercore-ltsc2019 45 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk17-windowsservercore-ltsc2022 46 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk21-nanoserver-1809 47 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk21-nanoserver-ltsc2019 48 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk21-nanoserver-ltsc2022 49 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk21-windowsservercore-ltsc2019 50 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk21-windowsservercore-ltsc2022 51 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk25-nanoserver-1809 52 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk25-nanoserver-ltsc2019 53 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk25-nanoserver-ltsc2022 54 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk25-windowsservercore-ltsc2019 55 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk25-windowsservercore-ltsc2022 56 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-nanoserver-1809 57 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-nanoserver-ltsc2019 58 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-nanoserver-ltsc2022 59 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-windowsservercore-ltsc2019 60 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-windowsservercore-ltsc2022 61 | docker.io/jenkins/inbound-agent:jdk17-nanoserver-1809 62 | docker.io/jenkins/inbound-agent:jdk17-nanoserver-ltsc2019 63 | docker.io/jenkins/inbound-agent:jdk17-nanoserver-ltsc2022 64 | docker.io/jenkins/inbound-agent:jdk17-windowsservercore-ltsc2019 65 | docker.io/jenkins/inbound-agent:jdk17-windowsservercore-ltsc2022 66 | docker.io/jenkins/inbound-agent:jdk21-nanoserver-1809 67 | docker.io/jenkins/inbound-agent:jdk21-nanoserver-ltsc2019 68 | docker.io/jenkins/inbound-agent:jdk21-nanoserver-ltsc2022 69 | docker.io/jenkins/inbound-agent:jdk21-windowsservercore-ltsc2019 70 | docker.io/jenkins/inbound-agent:jdk21-windowsservercore-ltsc2022 71 | docker.io/jenkins/inbound-agent:jdk25-nanoserver-1809 72 | docker.io/jenkins/inbound-agent:jdk25-nanoserver-ltsc2019 73 | docker.io/jenkins/inbound-agent:jdk25-nanoserver-ltsc2022 74 | docker.io/jenkins/inbound-agent:jdk25-windowsservercore-ltsc2019 75 | docker.io/jenkins/inbound-agent:jdk25-windowsservercore-ltsc2022 76 | docker.io/jenkins/inbound-agent:nanoserver-1809 77 | docker.io/jenkins/inbound-agent:nanoserver-ltsc2019 78 | docker.io/jenkins/inbound-agent:nanoserver-ltsc2022 79 | docker.io/jenkins/inbound-agent:windowsservercore-ltsc2019 80 | docker.io/jenkins/inbound-agent:windowsservercore-ltsc2022 81 | -------------------------------------------------------------------------------- /tests/golden/expected_tags_linux_on_tag.txt: -------------------------------------------------------------------------------- 1 | docker.io/jenkins/agent:3355.v388858a_47b_33-1 2 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine 3 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine-jdk17 4 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine-jdk21 5 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine-jdk25 6 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine3.23 7 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine3.23-jdk17 8 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine3.23-jdk21 9 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-alpine3.23-jdk25 10 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk17 11 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk21 12 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-jdk25 13 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-rhel-ubi9 14 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-rhel-ubi9-jdk17 15 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-rhel-ubi9-jdk21 16 | docker.io/jenkins/agent:3355.v388858a_47b_33-1-rhel-ubi9-jdk25 17 | docker.io/jenkins/agent:alpine 18 | docker.io/jenkins/agent:alpine-jdk17 19 | docker.io/jenkins/agent:alpine-jdk21 20 | docker.io/jenkins/agent:alpine-jdk25 21 | docker.io/jenkins/agent:alpine3.23 22 | docker.io/jenkins/agent:alpine3.23-jdk17 23 | docker.io/jenkins/agent:alpine3.23-jdk21 24 | docker.io/jenkins/agent:alpine3.23-jdk25 25 | docker.io/jenkins/agent:jdk17 26 | docker.io/jenkins/agent:jdk21 27 | docker.io/jenkins/agent:jdk25 28 | docker.io/jenkins/agent:latest 29 | docker.io/jenkins/agent:latest-alpine 30 | docker.io/jenkins/agent:latest-alpine-jdk17 31 | docker.io/jenkins/agent:latest-alpine-jdk21 32 | docker.io/jenkins/agent:latest-alpine-jdk25 33 | docker.io/jenkins/agent:latest-alpine3.23 34 | docker.io/jenkins/agent:latest-alpine3.23-jdk17 35 | docker.io/jenkins/agent:latest-alpine3.23-jdk21 36 | docker.io/jenkins/agent:latest-alpine3.23-jdk25 37 | docker.io/jenkins/agent:latest-jdk17 38 | docker.io/jenkins/agent:latest-jdk21 39 | docker.io/jenkins/agent:latest-jdk25 40 | docker.io/jenkins/agent:latest-rhel-ubi9 41 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk17 42 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk21 43 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk25 44 | docker.io/jenkins/agent:latest-trixie 45 | docker.io/jenkins/agent:latest-trixie-jdk17 46 | docker.io/jenkins/agent:latest-trixie-jdk21 47 | docker.io/jenkins/agent:latest-trixie-jdk25 48 | docker.io/jenkins/agent:rhel-ubi9 49 | docker.io/jenkins/agent:rhel-ubi9-jdk17 50 | docker.io/jenkins/agent:rhel-ubi9-jdk21 51 | docker.io/jenkins/agent:rhel-ubi9-jdk25 52 | docker.io/jenkins/agent:trixie 53 | docker.io/jenkins/agent:trixie-jdk17 54 | docker.io/jenkins/agent:trixie-jdk21 55 | docker.io/jenkins/agent:trixie-jdk25 56 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1 57 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine 58 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine-jdk17 59 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine-jdk21 60 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine-jdk25 61 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine3.23 62 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine3.23-jdk17 63 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine3.23-jdk21 64 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-alpine3.23-jdk25 65 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk17 66 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk21 67 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-jdk25 68 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-rhel-ubi9 69 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-rhel-ubi9-jdk17 70 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-rhel-ubi9-jdk21 71 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-1-rhel-ubi9-jdk25 72 | docker.io/jenkins/inbound-agent:alpine 73 | docker.io/jenkins/inbound-agent:alpine-jdk17 74 | docker.io/jenkins/inbound-agent:alpine-jdk21 75 | docker.io/jenkins/inbound-agent:alpine-jdk25 76 | docker.io/jenkins/inbound-agent:alpine3.23 77 | docker.io/jenkins/inbound-agent:alpine3.23-jdk17 78 | docker.io/jenkins/inbound-agent:alpine3.23-jdk21 79 | docker.io/jenkins/inbound-agent:alpine3.23-jdk25 80 | docker.io/jenkins/inbound-agent:jdk17 81 | docker.io/jenkins/inbound-agent:jdk21 82 | docker.io/jenkins/inbound-agent:jdk25 83 | docker.io/jenkins/inbound-agent:latest 84 | docker.io/jenkins/inbound-agent:latest-alpine 85 | docker.io/jenkins/inbound-agent:latest-alpine-jdk17 86 | docker.io/jenkins/inbound-agent:latest-alpine-jdk21 87 | docker.io/jenkins/inbound-agent:latest-alpine-jdk25 88 | docker.io/jenkins/inbound-agent:latest-alpine3.23 89 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk17 90 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk21 91 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk25 92 | docker.io/jenkins/inbound-agent:latest-jdk17 93 | docker.io/jenkins/inbound-agent:latest-jdk21 94 | docker.io/jenkins/inbound-agent:latest-jdk25 95 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9 96 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk17 97 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk21 98 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk25 99 | docker.io/jenkins/inbound-agent:latest-trixie 100 | docker.io/jenkins/inbound-agent:latest-trixie-jdk17 101 | docker.io/jenkins/inbound-agent:latest-trixie-jdk21 102 | docker.io/jenkins/inbound-agent:latest-trixie-jdk25 103 | docker.io/jenkins/inbound-agent:rhel-ubi9 104 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk17 105 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk21 106 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk25 107 | docker.io/jenkins/inbound-agent:trixie 108 | docker.io/jenkins/inbound-agent:trixie-jdk17 109 | docker.io/jenkins/inbound-agent:trixie-jdk21 110 | docker.io/jenkins/inbound-agent:trixie-jdk25 111 | -------------------------------------------------------------------------------- /jenkins-agent: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # The MIT License 4 | # 5 | # Copyright (c) 2015-2020, CloudBees, Inc. 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | # Usage jenkins-agent.sh [options] -url http://jenkins -secret [SECRET] -name [AGENT_NAME] 26 | # Optional environment variables : 27 | # * JENKINS_JAVA_BIN : Java executable to use instead of the default in PATH or obtained from JAVA_HOME 28 | # * JENKINS_JAVA_OPTS : Java Options to use for the remoting process, otherwise obtained from JAVA_OPTS 29 | # * JENKINS_AGENT_FILE : Jenkins agent jar file location, /usr/share/jenkins/agent.jar will be used if this is not set 30 | # * REMOTING_OPTS : Generic way to pass additional CLI options to agent.jar (see -help) 31 | # 32 | # Deprecated environment variables (prefer setting REMOTING_OPTS) 33 | # * JENKINS_TUNNEL : HOST:PORT for a tunnel to route TCP traffic to jenkins host, when jenkins can't be directly accessed over network 34 | # * JENKINS_URL : alternate jenkins URL 35 | # * JENKINS_SECRET : agent secret, if not set as an argument 36 | # * JENKINS_AGENT_NAME : agent name, if not set as an argument 37 | # * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir 38 | # * JENKINS_WEB_SOCKET: true if the connection should be made via WebSocket rather than TCP 39 | # * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. 40 | # Value: ":" 41 | # * JENKINS_INSTANCE_IDENTITY: The base64 encoded InstanceIdentity byte array of the Jenkins controller. When this is set, 42 | # the agent skips connecting to an HTTP(S) port for connection info. 43 | # * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. 44 | 45 | if [ $# -eq 1 ] && [ "${1#-}" = "$1" ] ; then 46 | 47 | # if `docker run` only has one arguments and it is not an option as `-help`, we assume user is running alternate command like `bash` to inspect the image 48 | exec "$@" 49 | 50 | else 51 | 52 | # if -tunnel is not provided, try env vars 53 | case "$@" in 54 | *"-tunnel "*) ;; 55 | *) 56 | if [ ! -z "$JENKINS_TUNNEL" ]; then 57 | TUNNEL="-tunnel $JENKINS_TUNNEL" 58 | fi ;; 59 | esac 60 | 61 | # if -workDir is not provided, try env vars 62 | if [ ! -z "$JENKINS_AGENT_WORKDIR" ]; then 63 | case "$@" in 64 | *"-workDir"*) echo "Warning: Work directory is defined twice in command-line arguments and the environment variable" ;; 65 | *) 66 | WORKDIR="-workDir $JENKINS_AGENT_WORKDIR" ;; 67 | esac 68 | fi 69 | 70 | if [ -n "$JENKINS_URL" ]; then 71 | URL="-url $JENKINS_URL" 72 | fi 73 | 74 | if [ -n "$JENKINS_NAME" ]; then 75 | JENKINS_AGENT_NAME="$JENKINS_NAME" 76 | fi 77 | 78 | if [ "$JENKINS_WEB_SOCKET" = true ]; then 79 | WEB_SOCKET=-webSocket 80 | fi 81 | 82 | if [ -n "$JENKINS_PROTOCOLS" ]; then 83 | PROTOCOLS="-protocols $JENKINS_PROTOCOLS" 84 | fi 85 | 86 | if [ -n "$JENKINS_DIRECT_CONNECTION" ]; then 87 | DIRECT="-direct $JENKINS_DIRECT_CONNECTION" 88 | fi 89 | 90 | if [ -n "$JENKINS_INSTANCE_IDENTITY" ]; then 91 | INSTANCE_IDENTITY="-instanceIdentity $JENKINS_INSTANCE_IDENTITY" 92 | fi 93 | 94 | if [ "$JENKINS_JAVA_BIN" ]; then 95 | JAVA_BIN="$JENKINS_JAVA_BIN" 96 | else 97 | # if java home is defined, use it 98 | JAVA_BIN="java" 99 | if [ "$JAVA_HOME" ]; then 100 | JAVA_BIN="$JAVA_HOME/bin/java" 101 | fi 102 | fi 103 | 104 | if [ "$JENKINS_JAVA_OPTS" ]; then 105 | JAVA_OPTIONS="$JENKINS_JAVA_OPTS" 106 | else 107 | # if JAVA_OPTS is defined, use it 108 | if [ "$JAVA_OPTS" ]; then 109 | JAVA_OPTIONS="$JAVA_OPTS" 110 | fi 111 | fi 112 | 113 | if [ "$JENKINS_AGENT_FILE" ]; then 114 | AGENT_FILE="$JENKINS_AGENT_FILE" 115 | else 116 | AGENT_FILE="/usr/share/jenkins/agent.jar" 117 | fi 118 | 119 | # if both required options are defined, do not pass the parameters 120 | if [ -n "$JENKINS_SECRET" ]; then 121 | case "$@" in 122 | *"${JENKINS_SECRET}"*) echo "Warning: SECRET is defined twice in command-line arguments and the environment variable" ;; 123 | *) 124 | SECRET="-secret ${JENKINS_SECRET}" ;; 125 | esac 126 | fi 127 | 128 | if [ -n "$JENKINS_AGENT_NAME" ]; then 129 | case "$@" in 130 | *"${JENKINS_AGENT_NAME}"*) echo "Warning: AGENT_NAME is defined twice in command-line arguments and the environment variable" ;; 131 | *) 132 | AGENT_NAME="-name ${JENKINS_AGENT_NAME}" ;; 133 | esac 134 | fi 135 | 136 | #TODO: Handle the case when the command-line and Environment variable contain different values. 137 | #It is fine it blows up for now since it should lead to an error anyway. 138 | 139 | eval "REMOTING_OPTS_A=($REMOTING_OPTS)" 140 | exec $JAVA_BIN $JAVA_OPTIONS -jar $AGENT_FILE $SECRET $AGENT_NAME $TUNNEL $URL $WORKDIR $WEB_SOCKET $DIRECT $PROTOCOLS $INSTANCE_IDENTITY "${REMOTING_OPTS_A[@]}" "$@" 141 | 142 | fi 143 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | final String cronExpr = env.BRANCH_IS_PRIMARY ? '@daily' : '' 2 | 3 | properties([ 4 | buildDiscarder(logRotator(numToKeepStr: '10')), 5 | disableConcurrentBuilds(abortPrevious: true), 6 | pipelineTriggers([cron(cronExpr)]), 7 | ]) 8 | 9 | def agentSelector(String imageType) { 10 | // Linux agent 11 | if (imageType == 'linux') { 12 | // This function is defined in the jenkins-infra/pipeline-library 13 | if (infra.isTrusted()) { 14 | return 'linux' 15 | } else { 16 | // Need Docker and a LOT of memory for faster builds (due to multi archs) or fallback to linux (trusted.ci) 17 | return 'docker-highmem' 18 | } 19 | } 20 | // Windows Server Core 2022 agent 21 | if (imageType.contains('2022')) { 22 | return 'windows-2022' 23 | } 24 | // Windows Server Core 2019 agent (for nanoserver 1809 & ltsc2019 and for windowservercore ltsc2019) 25 | return 'windows-2019' 26 | } 27 | 28 | // Ref. https://github.com/jenkins-infra/pipeline-library/pull/917 29 | def spotAgentSelector(String agentLabel, int counter) { 30 | // This function is defined in the jenkins-infra/pipeline-library 31 | if (infra.isTrusted()) { 32 | // Return early if on trusted (no spot agent) 33 | return agentLabel 34 | } 35 | 36 | if (counter > 1) { 37 | return agentLabel + ' && nonspot' 38 | } 39 | 40 | return agentLabel + ' && spot' 41 | } 42 | 43 | // Specify parallel stages 44 | def parallelStages = [failFast: false] 45 | [ 46 | 'linux', 47 | 'nanoserver-1809', 48 | 'nanoserver-ltsc2019', 49 | 'nanoserver-ltsc2022', 50 | 'windowsservercore-1809', 51 | 'windowsservercore-ltsc2019', 52 | 'windowsservercore-ltsc2022' 53 | ].each { imageType -> 54 | parallelStages[imageType] = { 55 | withEnv(["IMAGE_TYPE=${imageType}", "REGISTRY_ORG=${infra.isTrusted() ? 'jenkins' : 'jenkins4eval'}"]) { 56 | int retryCounter = 0 57 | retry(count: 2, conditions: [agent(), nonresumable()]) { 58 | // Use local variable to manage concurrency and increment BEFORE spinning up any agent 59 | final String resolvedAgentLabel = spotAgentSelector(agentSelector(imageType), retryCounter) 60 | retryCounter++ 61 | node(resolvedAgentLabel) { 62 | timeout(time: 60, unit: 'MINUTES') { 63 | checkout scm 64 | if (imageType == "linux") { 65 | stage('Prepare Docker') { 66 | sh 'make docker-init' 67 | } 68 | } 69 | // This function is defined in the jenkins-infra/pipeline-library 70 | if (infra.isTrusted()) { 71 | // trusted.ci.jenkins.io builds (e.g. publication to DockerHub) 72 | stage('Deploy to DockerHub') { 73 | String[] tagItems = env.TAG_NAME.split('-') 74 | if(tagItems.length == 2) { 75 | withEnv([ 76 | "ON_TAG=true", 77 | "REMOTING_VERSION=${tagItems[0]}", 78 | "BUILD_NUMBER=${tagItems[1]}", 79 | ]) { 80 | // This function is defined in the jenkins-infra/pipeline-library 81 | infra.withDockerCredentials { 82 | if (isUnix()) { 83 | sh 'make publish' 84 | } else { 85 | powershell '& ./build.ps1 build' 86 | powershell '& ./build.ps1 publish' 87 | } 88 | } 89 | } 90 | } else { 91 | error("The deployment to Docker Hub failed because the tag doesn't contain any '-'.") 92 | } 93 | } 94 | } else { 95 | // ci.jenkins.io builds (e.g. no publication) 96 | stage('Build') { 97 | if (isUnix()) { 98 | sh './build.sh' 99 | } else { 100 | powershell '& ./build.ps1 build' 101 | archiveArtifacts artifacts: 'build-windows_*.yaml', allowEmptyArchive: true 102 | } 103 | } 104 | stage('Test') { 105 | if (isUnix()) { 106 | sh './build.sh test' 107 | } else { 108 | powershell '& ./build.ps1 test' 109 | } 110 | junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results*.xml') 111 | } 112 | // If the tests are passing for Linux AMD64, then we can build all the CPU architectures 113 | if (isUnix()) { 114 | stage('Multi-Arch Build') { 115 | 116 | sh 'make every-build' 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | // Execute parallel stages 128 | parallel parallelStages 129 | // // vim: ft=groovy 130 | -------------------------------------------------------------------------------- /alpine/Dockerfile: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2015-2020, CloudBees, Inc. and other Jenkins contributors 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | ARG ALPINE_TAG=3.23.2 23 | FROM alpine:"${ALPINE_TAG}" AS jre-build 24 | 25 | SHELL ["/bin/ash", "-eo", "pipefail", "-c"] 26 | 27 | # This Build ARG is populated by Docker 28 | # Ref. https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope 29 | ARG TARGETPLATFORM 30 | 31 | COPY adoptium-get-jdk-link.sh /usr/bin/local/adoptium-get-jdk-link.sh 32 | COPY adoptium-install-jdk.sh /usr/bin/local/adoptium-install-jdk.sh 33 | 34 | ARG JAVA_VERSION=17.0.17_10 35 | # hadolint ignore=DL3018 36 | RUN apk add --no-cache \ 37 | ca-certificates \ 38 | jq \ 39 | curl \ 40 | && /usr/bin/local/adoptium-install-jdk.sh alpine 41 | 42 | ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}" 43 | 44 | RUN case "$(jlink --version 2>&1 | cut -c1-2)" in \ 45 | "17") options="--compress=2 --add-modules ALL-MODULE-PATH" ;; \ 46 | "21") options="--compress=zip-6 --add-modules ALL-MODULE-PATH" ;; \ 47 | "25") options="--compress=zip-6 --add-modules java.base,java.logging,java.xml,java.se" ;; \ 48 | *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \ 49 | esac; \ 50 | jlink \ 51 | --strip-java-debug-attributes \ 52 | ${options} \ 53 | --no-man-pages \ 54 | --no-header-files \ 55 | --output /javaruntime 56 | 57 | ## Agent image target 58 | FROM alpine:"${ALPINE_TAG}" AS agent 59 | 60 | ARG user=jenkins 61 | ARG group=jenkins 62 | ARG uid=1000 63 | ARG gid=1000 64 | 65 | RUN addgroup -g "${gid}" "${group}" \ 66 | && adduser -h /home/"${user}" -u "${uid}" -G "${group}" -D "${user}" || echo "user ${user} already exists." 67 | 68 | ARG AGENT_WORKDIR=/home/"${user}"/agent 69 | 70 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' 71 | ENV TZ=Etc/UTC 72 | 73 | ## Always use the latest Alpine packages: no need for versions 74 | # hadolint ignore=DL3018 75 | RUN apk add --no-cache \ 76 | curl \ 77 | bash \ 78 | git \ 79 | musl-locales \ 80 | openssh-client \ 81 | openssl \ 82 | procps \ 83 | tzdata \ 84 | tzdata-utils \ 85 | && rm -rf /tmp/*.apk /tmp/gcc /tmp/gcc-libs.tar* /tmp/libz /tmp/libz.tar.xz /var/cache/apk/* 86 | 87 | ARG VERSION=3355.v388858a_47b_33 88 | ADD --chown="${user}":"${group}" "https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/${VERSION}/remoting-${VERSION}.jar" /usr/share/jenkins/agent.jar 89 | RUN chmod 0644 /usr/share/jenkins/agent.jar \ 90 | && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar 91 | 92 | ARG GIT_LFS_VERSION=3.7.1 93 | RUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g') \ 94 | && 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" \ 95 | && tar xzf git-lfs.tgz \ 96 | && bash git-lfs-*/install.sh \ 97 | && git lfs install --skip-repo --system \ 98 | && rm -rf git-lfs* 99 | 100 | ENV JAVA_HOME=/opt/java/openjdk 101 | COPY --from=jre-build /javaruntime "$JAVA_HOME" 102 | ENV PATH="${JAVA_HOME}/bin:${PATH}" 103 | 104 | USER "${user}" 105 | ENV AGENT_WORKDIR="${AGENT_WORKDIR}" 106 | RUN mkdir -p /home/"${user}"/.jenkins && mkdir -p "${AGENT_WORKDIR}" 107 | 108 | VOLUME /home/"${user}"/.jenkins 109 | VOLUME "${AGENT_WORKDIR}" 110 | WORKDIR /home/"${user}" 111 | ENV USER=${user} 112 | LABEL \ 113 | org.opencontainers.image.vendor="Jenkins project" \ 114 | org.opencontainers.image.title="Official Jenkins Agent Base Docker image" \ 115 | org.opencontainers.image.description="This is a base image, which provides the Jenkins agent executable (agent.jar)" \ 116 | org.opencontainers.image.version="${VERSION}" \ 117 | org.opencontainers.image.url="https://www.jenkins.io/" \ 118 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ 119 | org.opencontainers.image.licenses="MIT" 120 | 121 | ## Inbound Agent image target 122 | FROM agent AS inbound-agent 123 | 124 | ARG user=jenkins 125 | 126 | USER root 127 | COPY ../../jenkins-agent /usr/local/bin/jenkins-agent 128 | RUN chmod +x /usr/local/bin/jenkins-agent &&\ 129 | ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave 130 | USER ${user} 131 | 132 | LABEL \ 133 | org.opencontainers.image.vendor="Jenkins project" \ 134 | org.opencontainers.image.title="Official Jenkins Agent Base Docker image" \ 135 | org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" \ 136 | org.opencontainers.image.version="${VERSION}" \ 137 | org.opencontainers.image.url="https://www.jenkins.io/" \ 138 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ 139 | org.opencontainers.image.licenses="MIT" 140 | 141 | ENTRYPOINT ["/usr/local/bin/jenkins-agent"] 142 | -------------------------------------------------------------------------------- /tests/tests_agent.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helpers 4 | load 'test_helper/bats-support/load' # this is required by bats-assert! 5 | load 'test_helper/bats-assert/load' 6 | 7 | IMAGE=${IMAGE:-debian_jdk17} 8 | SUT_IMAGE=$(get_sut_image) 9 | 10 | ARCH=${ARCH:-x86_64} 11 | 12 | GIT_LFS_VERSION='3.7.1' 13 | 14 | @test "[${SUT_IMAGE}] test version in docker metadata" { 15 | local expected_version 16 | expected_version=$(get_remoting_version) 17 | 18 | local actual_version 19 | actual_version=$(docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.version"}}' "${SUT_IMAGE}") 20 | 21 | assert_equal "${expected_version}" "${actual_version}" 22 | } 23 | 24 | @test "[${SUT_IMAGE}] checking image metadata" { 25 | local VOLUMES_MAP 26 | VOLUMES_MAP="$(docker inspect -f '{{.Config.Volumes}}' "${SUT_IMAGE}")" 27 | 28 | echo "${VOLUMES_MAP}" | grep '/home/jenkins/.jenkins' 29 | echo "${VOLUMES_MAP}" | grep '/home/jenkins/agent' 30 | } 31 | 32 | @test "[${SUT_IMAGE}] has utf-8 locale" { 33 | run docker run --rm "${SUT_IMAGE}" locale charmap 34 | assert_equal "${output}" "UTF-8" 35 | } 36 | 37 | @test "[${SUT_IMAGE}] image has bash, curl, ssh, java and git-lfs (and thus git) installed and in the PATH" { 38 | local cid 39 | cid="$(docker run -d -it -P "${SUT_IMAGE}" /bin/bash)" 40 | 41 | is_agent_container_running "${cid}" 42 | 43 | run docker exec "${cid}" sh -c "command -v bash" 44 | assert_success 45 | run docker exec "${cid}" bash --version 46 | assert_success 47 | 48 | run docker exec "${cid}" sh -c "command -v curl" 49 | assert_success 50 | run docker exec "${cid}" curl --version 51 | assert_success 52 | 53 | run docker exec "${cid}" sh -c "command -v java" 54 | assert_success 55 | 56 | run docker exec "${cid}" java -version 57 | assert_success 58 | 59 | run docker exec "${cid}" sh -c "command -v ssh" 60 | assert_success 61 | run docker exec "${cid}" ssh -V 62 | assert_success 63 | 64 | run docker exec "${cid}" sh -c "command -v git" 65 | assert_success 66 | run docker exec "${cid}" git lfs env 67 | assert_output --partial "${GIT_LFS_VERSION}" 68 | refute_output --regexp 'git config filter\.lfs\.\w+ = ""' 69 | 70 | run docker exec "${cid}" sh -c "printenv | grep AGENT_WORKDIR" 71 | assert_equal "${output}" "AGENT_WORKDIR=/home/jenkins/agent" 72 | 73 | cleanup "$cid" 74 | } 75 | 76 | @test "[${SUT_IMAGE}] check user access to folders" { 77 | local cid 78 | cid="$(docker run -d -it -P "${SUT_IMAGE}" /bin/sh)" 79 | 80 | is_agent_container_running "${cid}" 81 | 82 | run docker exec "${cid}" touch /home/jenkins/a 83 | assert_success 84 | 85 | run docker exec "${cid}" touch /home/jenkins/.jenkins/a 86 | assert_success 87 | 88 | run docker exec "${cid}" touch /home/jenkins/agent/a 89 | assert_success 90 | 91 | cleanup "$cid" 92 | } 93 | 94 | @test "[${SUT_IMAGE}] Another user 'root' or 'jenkins' is able to start an agent process" { 95 | run docker run --rm --user=2222:2222 --entrypoint='' "${SUT_IMAGE}" java -jar /usr/share/jenkins/agent.jar -version 96 | assert_success 97 | } 98 | 99 | @test "[${SUT_IMAGE}] use build args correctly" { 100 | cd "${BATS_TEST_DIRNAME}"/.. || false 101 | 102 | local TEST_VERSION="3025.vf64a_a_3da_6b_55" # Older version, must work with JDK17 and should contain https://github.com/jenkinsci/remoting/pull/532 103 | local TEST_USER="test-user" 104 | local TEST_GROUP="test-group" 105 | local TEST_UID=2000 106 | local TEST_GID=3000 107 | local TEST_AGENT_WORKDIR="/home/test-user/something" 108 | local sut_image="${SUT_IMAGE}-tests-${BATS_TEST_NUMBER}" 109 | 110 | # false positive detecting platform 111 | # shellcheck disable=SC2140 112 | docker buildx bake \ 113 | --set "${IMAGE}".args.VERSION="${TEST_VERSION}" \ 114 | --set "${IMAGE}".args.user="${TEST_USER}" \ 115 | --set "${IMAGE}".args.group="${TEST_GROUP}" \ 116 | --set "${IMAGE}".args.uid="${TEST_UID}" \ 117 | --set "${IMAGE}".args.gid="${TEST_GID}" \ 118 | --set "${IMAGE}".args.AGENT_WORKDIR="${TEST_AGENT_WORKDIR}" \ 119 | --set "${IMAGE}".platform="linux/${ARCH}" \ 120 | --set "${IMAGE}".tags="${sut_image}" \ 121 | --load `# Image should be loaded on the Docker engine`\ 122 | "${IMAGE}" 123 | 124 | local cid 125 | cid="$(docker run -d -it -P "${sut_image}" /bin/sh)" 126 | 127 | is_agent_container_running "${cid}" 128 | 129 | run docker exec "${cid}" sh -c "java -jar /usr/share/jenkins/agent.jar -version" 130 | assert_line --index 0 "${TEST_VERSION}" 131 | 132 | run docker exec "${cid}" sh -c "id -u -n ${TEST_USER}" 133 | assert_line --index 0 "${TEST_USER}" 134 | 135 | run docker exec "${cid}" sh -c "id -g -n ${TEST_USER}" 136 | assert_line --index 0 "${TEST_GROUP}" 137 | 138 | run docker exec "${cid}" sh -c "id -u ${TEST_USER}" 139 | assert_line --index 0 "${TEST_UID}" 140 | 141 | run docker exec "${cid}" sh -c "id -g ${TEST_USER}" 142 | assert_line --index 0 "${TEST_GID}" 143 | 144 | run docker exec "${cid}" sh -c "printenv | grep AGENT_WORKDIR" 145 | assert_line --index 0 "AGENT_WORKDIR=/home/${TEST_USER}/something" 146 | 147 | run docker exec "${cid}" sh -c 'stat -c "%U:%G" "${AGENT_WORKDIR}"' 148 | assert_line --index 0 "${TEST_USER}:${TEST_GROUP}" 149 | 150 | run docker exec "${cid}" touch /home/test-user/a 151 | assert_success 152 | 153 | run docker exec "${cid}" touch /home/test-user/.jenkins/a 154 | assert_success 155 | 156 | run docker exec "${cid}" touch /home/test-user/something/a 157 | assert_success 158 | 159 | cleanup "$cid" 160 | } 161 | 162 | @test "[${SUT_IMAGE}] 'tzdata' is correctly installed" { 163 | local cid 164 | cid="$(docker run -d -it -P "${SUT_IMAGE}" /bin/bash)" 165 | 166 | is_agent_container_running "${cid}" 167 | 168 | run docker exec "${cid}" sh -c "command -v zdump" 169 | assert_success 170 | run docker exec "${cid}" zdump --version 171 | assert_success 172 | 173 | run docker exec "${cid}" sh -c "command -v zic" 174 | assert_success 175 | run docker exec "${cid}" zic --version 176 | assert_success 177 | 178 | cleanup "$cid" 179 | } 180 | 181 | @test "[${SUT_IMAGE}] default user is exposed in the environment" { 182 | docker inspect --format '{{ .Config.Env }}' "${SUT_IMAGE}" | grep 'USER=jenkins' 183 | } 184 | -------------------------------------------------------------------------------- /debian/Dockerfile: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2015-2023, CloudBees, Inc. and other Jenkins contributors 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | ARG DEBIAN_RELEASE=trixie-20251208 23 | FROM debian:"${DEBIAN_RELEASE}"-slim AS jre-build 24 | 25 | SHELL ["/bin/bash", "-e", "-u", "-o", "pipefail", "-c"] 26 | 27 | # This Build ARG is populated by Docker 28 | # Ref. https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope 29 | ARG TARGETPLATFORM 30 | 31 | COPY adoptium-get-jdk-link.sh /usr/bin/local/adoptium-get-jdk-link.sh 32 | COPY adoptium-install-jdk.sh /usr/bin/local/adoptium-install-jdk.sh 33 | 34 | ARG JAVA_VERSION=17.0.17_10 35 | # hadolint ignore=DL3008 36 | RUN set -x; apt-get update \ 37 | && apt-get install --no-install-recommends -y \ 38 | ca-certificates \ 39 | jq \ 40 | curl \ 41 | && /usr/bin/local/adoptium-install-jdk.sh 42 | 43 | ENV PATH="/opt/jdk-${JAVA_VERSION}/bin:${PATH}" 44 | 45 | # Generate smaller java runtime without unneeded files 46 | # for now we include the full module path to maintain compatibility 47 | # while still saving space (approx 200mb from the full distribution) 48 | RUN if [[ "${TARGETPLATFORM}" != "linux/arm/v7" ]]; then \ 49 | case "$(jlink --version 2>&1 | cut -c1-2)" in \ 50 | "17") options="--compress=2 --add-modules ALL-MODULE-PATH" ;; \ 51 | "21") options="--compress=zip-6 --add-modules ALL-MODULE-PATH" ;; \ 52 | "25") options="--compress=zip-6 --add-modules java.base,java.logging,java.xml,java.se" ;; \ 53 | *) echo "ERROR: unmanaged jlink version pattern" && exit 1 ;; \ 54 | esac; \ 55 | jlink \ 56 | --strip-java-debug-attributes \ 57 | ${options} \ 58 | --no-man-pages \ 59 | --no-header-files \ 60 | --output /javaruntime; \ 61 | else \ 62 | # It is acceptable to have a larger image in arm/v7 (arm 32 bits) environment. 63 | # Because jlink fails with the error "jmods: Value too large for defined data type" error. 64 | cp -r "/opt/jdk-${JAVA_VERSION}" /javaruntime; \ 65 | fi 66 | 67 | ## Agent image target 68 | FROM debian:"${DEBIAN_RELEASE}"-slim AS agent 69 | 70 | ARG user=jenkins 71 | ARG group=jenkins 72 | ARG uid=1000 73 | ARG gid=1000 74 | 75 | RUN groupadd -g "${gid}" "${group}" \ 76 | && useradd -l -c "Jenkins user" -d /home/"${user}" -u "${uid}" -g "${gid}" -m "${user}" || echo "user ${user} already exists." 77 | 78 | ARG AGENT_WORKDIR=/home/"${user}"/agent 79 | ENV TZ=Etc/UTC 80 | 81 | ## Always use the latest Debian packages: no need for versions 82 | # hadolint ignore=DL3008 83 | RUN apt-get update \ 84 | && apt-get --yes --no-install-recommends install \ 85 | ca-certificates \ 86 | curl \ 87 | fontconfig \ 88 | git \ 89 | less \ 90 | netbase \ 91 | openssh-client \ 92 | patch \ 93 | tzdata \ 94 | && apt-get clean \ 95 | && rm -rf /tmp/* /var/cache/* /var/lib/apt/lists/* 96 | 97 | ARG VERSION=3355.v388858a_47b_33 98 | ADD --chown="${user}":"${group}" "https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/${VERSION}/remoting-${VERSION}.jar" /usr/share/jenkins/agent.jar 99 | RUN chmod 0644 /usr/share/jenkins/agent.jar \ 100 | && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar 101 | 102 | ARG GIT_LFS_VERSION=3.7.1 103 | RUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g' -e 's/armv7l/arm/g') \ 104 | && 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" \ 105 | && tar xzf git-lfs.tgz \ 106 | && bash git-lfs-*/install.sh \ 107 | && git lfs install --skip-repo --system \ 108 | && rm -rf git-lfs* 109 | 110 | ENV LANG=C.UTF-8 111 | 112 | ENV JAVA_HOME=/opt/java/openjdk 113 | COPY --from=jre-build /javaruntime "$JAVA_HOME" 114 | ENV PATH="${JAVA_HOME}/bin:${PATH}" 115 | 116 | USER "${user}" 117 | ENV AGENT_WORKDIR=${AGENT_WORKDIR} 118 | RUN mkdir -p /home/"${user}"/.jenkins && mkdir -p "${AGENT_WORKDIR}" 119 | 120 | VOLUME /home/"${user}"/.jenkins 121 | VOLUME "${AGENT_WORKDIR}" 122 | WORKDIR /home/"${user}" 123 | ENV USER=${user} 124 | LABEL \ 125 | org.opencontainers.image.vendor="Jenkins project" \ 126 | org.opencontainers.image.title="Official Jenkins Agent Base Docker image" \ 127 | org.opencontainers.image.description="This is a base image, which provides the Jenkins agent executable (agent.jar)" \ 128 | org.opencontainers.image.version="${VERSION}" \ 129 | org.opencontainers.image.url="https://www.jenkins.io/" \ 130 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ 131 | org.opencontainers.image.licenses="MIT" 132 | 133 | ## Inbound Agent image target 134 | FROM agent AS inbound-agent 135 | 136 | ARG user=jenkins 137 | 138 | USER root 139 | COPY ../../jenkins-agent /usr/local/bin/jenkins-agent 140 | RUN chmod +x /usr/local/bin/jenkins-agent &&\ 141 | ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave 142 | USER ${user} 143 | 144 | LABEL \ 145 | org.opencontainers.image.vendor="Jenkins project" \ 146 | org.opencontainers.image.title="Official Jenkins Inbound Agent Base Docker image" \ 147 | org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" \ 148 | org.opencontainers.image.version="${VERSION}" \ 149 | org.opencontainers.image.url="https://www.jenkins.io/" \ 150 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ 151 | org.opencontainers.image.licenses="MIT" 152 | 153 | ENTRYPOINT ["/usr/local/bin/jenkins-agent"] 154 | -------------------------------------------------------------------------------- /README_agent.md: -------------------------------------------------------------------------------- 1 | # Jenkins Agent Docker image 2 | 3 | [![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) 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/agent.svg)](https://hub.docker.com/r/jenkins/agent/) 5 | [![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-agent/releases/latest) 6 | 7 | This is a base image for Docker, which includes Java and the Jenkins agent executable (agent.jar). 8 | This executable is an instance of the [Jenkins Remoting library](https://github.com/jenkinsci/remoting). 9 | Java version depends on the image and the platform, see the _Configurations_ section below. 10 | 11 | ## Usage 12 | 13 | This image is used as the basis for the [Docker Inbound Agent](https://github.com/jenkinsci/docker-agent/tree/master/README_inbound-agent.md) image. 14 | In that image, the container is launched externally and attaches to Jenkins. 15 | 16 | This image may instead be used to launch an agent using the **Launch method** of **Launch agent via execution of command on the controller**. For example on Linux you can try 17 | 18 | ```sh 19 | docker run -i --rm --name agent --init jenkins/agent java -jar /usr/share/jenkins/agent.jar 20 | ``` 21 | 22 | after setting **Remote root directory** to `/home/jenkins/agent`. 23 | 24 | or if using Windows Containers 25 | 26 | ```powershell 27 | docker run -i --rm --name agent --init jenkins/agent:jdk17-windowsservercore-ltsc2019 java -jar C:/ProgramData/Jenkins/agent.jar 28 | ``` 29 | 30 | after setting **Remote root directory** to `C:\Users\jenkins\Agent`. 31 | 32 | ### Agent Work Directories 33 | 34 | Starting from [Remoting 3.8](https://github.com/jenkinsci/remoting/blob/master/CHANGELOG.md#38) there is a support of Work directories, 35 | which provides logging by default and change the JAR Caching behavior. 36 | 37 | Call example for Linux: 38 | 39 | ```sh 40 | docker run -i --rm --name agent1 --init -v agent1-workdir:/home/jenkins/agent jenkins/agent java -jar /usr/share/jenkins/agent.jar -workDir /home/jenkins/agent 41 | ``` 42 | 43 | Call example for Windows Containers: 44 | 45 | ```powershell 46 | docker run -i --rm --name agent1 --init -v agent1-workdir:C:/Users/jenkins/Work jenkins/agent:jdk17-windowsservercore-ltsc2019 java -jar C:/ProgramData/Jenkins/agent.jar -workDir C:/Users/jenkins/Work 47 | ``` 48 | 49 | ## Configurations 50 | 51 | The image has several supported configurations, which can be accessed via the following tags: 52 | 53 | * Linux Images: 54 | * Java 17 (default): 55 | * `jenkins/agent:latest`: Based on `debian:trixie-${builddate}` 56 | * Also tagged as: 57 | * `jenkins/agent:jdk17` 58 | * `jenkins/agent:trixie-jdk17` 59 | * `jenkins/agent:latest-trixie` 60 | * `jenkins/agent:latest-trixie-jdk17` 61 | * `jenkins/agent:latest-jdk17` 62 | * alpine (Small image based on Alpine Linux, based on `alpine:${version}`): 63 | * `jenkins/agent:alpine` 64 | * `jenkins/agent:alpine-jdk17` 65 | * `jenkins/agent:latest-alpine` 66 | * `jenkins/agent:latest-alpine-jdk17` 67 | * rhel-ubi9 (Based on Red Hat Universal Base Image 9) 68 | * `jenkins/agent:rhel-ubi9` 69 | * `jenkins/agent:rhel-ubi9-jdk17` 70 | * `jenkins/agent:latest-rhel-ubi9` 71 | * `jenkins/agent:latest-rhel-ubi9-jdk17` 72 | * Java 21: 73 | * trixie (Based on `debian:trixie-${builddate}`): 74 | * `jenkins/agent:trixie` 75 | * `jenkins/agent:trixie-jdk21` 76 | * `jenkins/agent:jdk21` 77 | * `jenkins/agent:latest-trixie-jdk21` 78 | * alpine (Small image based on Alpine Linux, based on `alpine:${version}`): 79 | * `jenkins/agent:alpine-jdk21` 80 | * `jenkins/agent:latest-alpine` 81 | * `jenkins/agent:latest-alpine-jdk21` 82 | * rhel-ubi9 (Based on Red Hat Universal Base Image 9) 83 | * `jenkins/agent:rhel-ubi9-jdk21` 84 | * `jenkins/agent:latest-rhel-ubi9-jdk21` 85 | 86 | * Windows Images: 87 | * Java 17 (default): 88 | * Latest Jenkins agent version on Windows Nano Server and Java 17: 89 | * `jenkins/agent:jdk17-nanoserver-1809` 90 | * `jenkins/agent:jdk17-nanoserver-ltsc2019` 91 | * `jenkins/agent:jdk17-nanoserver-ltsc2022` 92 | * Java 21: 93 | * Latest Jenkins agent version on Windows Nano Server and Java 21: 94 | * `jenkins/agent:jdk21-nanoserver-1809` 95 | * `jenkins/agent:jdk21-nanoserver-ltsc2019` 96 | * `jenkins/agent:jdk21-nanoserver-ltsc2022` 97 | * Latest Jenkins agent version on Windows Server Core with Java 21: 98 | * `jenkins/agent:jdk21-windowsservercore-1809` 99 | * `jenkins/agent:jdk21-windowsservercore-ltsc2019` 100 | * `jenkins/agent:jdk21-windowsservercore-ltsc2022` 101 | 102 | The file [docker-bake.hcl](https://github.com/jenkinsci/docker-agent/blob/master/docker-bake.hcl) defines all the configuration for Linux images and their associated tags. 103 | 104 | There are also versioned tags in DockerHub, and they are recommended for production use. 105 | See the full list at [https://hub.docker.com/r/jenkins/agent/tags](https://hub.docker.com/r/jenkins/agent/tags) 106 | 107 | ## Timezones 108 | 109 | By default, the image is using the `Etc/UTC` timezone. 110 | If you want to use the timezone of your machine, you can mount the `/etc/localtime` file from the host (as per [this comment](https://github.com/moby/moby/issues/12084#issuecomment-89697533)) and the `/etc/timezone` from the host too. 111 | 112 | ```bash 113 | docker run --rm --tty --interactive --entrypoint=date --volume=/etc/localtime:/etc/localtime:ro --volume=/etc/timezone:/etc/timezone:ro jenkins/agent 114 | Fri Nov 25 18:27:22 CET 2022 115 | ``` 116 | 117 | You can also set the `TZ` environment variable to the desired timezone. 118 | `TZ` is a standard POSIX environment variable used by many images, see [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for a list of valid values. 119 | The next command is run on a machine using the `Europe/Paris` timezone a few seconds after the previous one. 120 | 121 | ```bash 122 | docker run --rm --tty --interactive --env TZ=Asia/Shanghai --entrypoint=date jenkins/agent 123 | Sat Nov 26 01:27:58 CST 2022 124 | ``` 125 | 126 | ### Using the `jenkins/agent` image as a base image 127 | 128 | Should you want to adapt the `jenkins/agent` image to your local timezone while creating your own image based on it, you could use the following command (inspired by issue #[291](https://github.com/jenkinsci/docker-inbound-agent/issues/291)): 129 | 130 | ```dockerfile 131 | FROM jenkins/agent as agent 132 | [...] 133 | ENV TZ=Asia/Shanghai 134 | [...] 135 | RUN ln -snf /usr/share/zoneinfo/"${TZ}" /etc/localtime && echo "${TZ}" > /etc/timezone \ 136 | && dpkg-reconfigure -f noninteractive tzdata \ 137 | [...] 138 | ``` 139 | 140 | ## Changelog 141 | 142 | See [GitHub releases](https://github.com/jenkinsci/docker-agent/releases) for versions `3.35-1` and above. 143 | There is no changelog for previous versions, see the commit history. 144 | 145 | Jenkins remoting changelogs are available at [https://github.com/jenkinsci/remoting/releases](https://github.com/jenkinsci/remoting/releases). 146 | 147 | -------------------------------------------------------------------------------- /README_inbound-agent.md: -------------------------------------------------------------------------------- 1 | # Docker image for inbound Jenkins agents 2 | 3 | [![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) 4 | [![GitHub stars](https://img.shields.io/github/stars/jenkinsci/docker-agent?label=GitHub%20stars)](https://github.com/jenkinsci/docker-agent) 5 | [![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/inbound-agent.svg)](https://hub.docker.com/r/jenkins/inbound-agent/) 6 | [![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-agent/releases/latest) 7 | 8 | :exclamation: **Warning!** This image used to be published as [jenkinsci/jnlp-slave](https://hub.docker.com/r/jenkinsci/jnlp-slave/) and [jenkins/jnlp-slave](https://hub.docker.com/r/jenkins/jnlp-slave/). 9 | These images are deprecated, use [jenkins/inbound-agent](https://hub.docker.com/r/jenkins/inbound-agent/). 10 | 11 | This is an image for [Jenkins](https://jenkins.io) agents using TCP or WebSockets to establish inbound connection to the Jenkins controller. 12 | This agent is powered by the [Jenkins Remoting library](https://github.com/jenkinsci/remoting), which version is being taken from the base [Docker Agent](https://github.com/jenkinsci/docker-agent/README_agent.md) image. 13 | 14 | See [Using Agents](https://www.jenkins.io/doc/book/using/using-agents/) for more info. 15 | 16 | ## Configuring agents with this container image 17 | 18 | ### Setup the agent on Jenkins 19 | 20 | 1. Go to your Jenkins dashboard 21 | 2. Go to `Manage Jenkins` option in main menu 22 | 3. Go to `Nodes` item in `System Configuration` 23 | ![image](images/screen-4.png) 24 | 4. Go to `New Node` option in side menu 25 | 5. Fill the Node(agent) name and select the type; (e.g. Name: agent1, Type: Permanent Agent) 26 | 6. Now fill the fields like remote root directory, labels, # of executors, etc. 27 | * **`Launch method` is `Launch agent by connecting it to the controller`** 28 | ![image](images/screen-1.png) 29 | 7. Press the `Save` button and the agent1 will be registered, but offline for the time being. Click on it. 30 | ![image](images/screen-2.png) 31 | 8. You should now see the secret. Use the secret value to pass it to the argument of container, or set to `JENKINS_SECRET` as environment variable. 32 | ![image](images/screen-3.png) 33 | 34 | ### Running this container 35 | 36 | To run a Docker container 37 | > **Note** 38 | > Remember to replace the `` and `` for secret and agent name, which can be you can get(and set) from [above section](#Setup-the-agent-on-Jenkins). 39 | > Your agent node should be possible to connect to Jenkins controller with agent port (not Jenkins server's port like 80, 443, 8080), which can be set in `Manage Jenkins` > `Security` > `Agent`. Default port is 50000. 40 | 41 | Linux agent: 42 | 43 | docker run --init jenkins/inbound-agent -url http://jenkins-server:port 44 | Note: `--init` is necessary for correct subprocesses handling (zombie reaping) 45 | 46 | Windows agent: 47 | 48 | docker run jenkins/inbound-agent:windowsservercore-ltsc2019 -Url http://jenkins-server:port -Secret -Name 49 | 50 | To run a Docker container with [Work Directory](https://github.com/jenkinsci/remoting/blob/master/docs/workDir.md) 51 | 52 | Linux agent: 53 | 54 | docker run --init jenkins/inbound-agent -url http://jenkins-server:port -workDir=/home/jenkins/agent 55 | 56 | Windows agent: 57 | 58 | docker run jenkins/inbound-agent:windowsservercore-ltsc2019 -Url http://jenkins-server:port -WorkDir=C:/Jenkins/agent -Secret -Name 59 | 60 | Optional environment variables: 61 | 62 | * `JENKINS_JAVA_BIN`: Path to Java executable to use instead of the default in PATH or obtained from JAVA_HOME 63 | * `JENKINS_JAVA_OPTS` : Java Options to use for the remoting process, otherwise obtained from JAVA_OPTS, **Warning** :exclamation: For more information on Windows usage, please see the **Windows Jenkins Java Opts** [section below](#windows-jenkins-java-opts). 64 | * `JENKINS_AGENT_FILE` : Jenkins agent jar file location, `/usr/share/jenkins/agent.jar` will be used if this is not set 65 | * `REMOTING_OPTS` : Generic way to pass additional CLI options to agent.jar (see -help) 66 | 67 | Deprecated environment variables (prefer setting `REMOTING_OPTS`) 68 | 69 | * `JENKINS_URL`: url for the Jenkins server, can be used as a replacement to `-url` option, or to set alternate jenkins URL 70 | * `JENKINS_TUNNEL`: (`HOST:PORT`) connect to this agent host and port instead of Jenkins server, assuming this one do route TCP traffic to Jenkins controller. Useful when when Jenkins runs behind a load balancer, reverse proxy, etc. 71 | * `JENKINS_SECRET`: (use only if not set as an argument) the secret as shown on the controller after creating the agent 72 | * `JENKINS_AGENT_NAME`: (use only if not set as an argument) the name of the agent, it should match the name you specified when creating the agent on the controller 73 | * `JENKINS_AGENT_WORKDIR`: agent work directory, if not set by optional parameter `-workDir` 74 | * `JENKINS_WEB_SOCKET`: `true` if the connection should be made via WebSocket rather than TCP 75 | * `JENKINS_DIRECT_CONNECTION`: (`HOST:PORT`) Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. 76 | * `JENKINS_INSTANCE_IDENTITY`: The base64 encoded InstanceIdentity byte array of the Jenkins controller. When this is set, the agent skips connecting to an HTTP(S) port for connection info. 77 | * `JENKINS_PROTOCOLS`: Specify the remoting protocols to attempt when `JENKINS_INSTANCE_IDENTITY` is provided. 78 | 79 | #### Example 80 | 81 | 1. Enter the command above. 82 | ![image](images/screen-5.png) 83 | 2. Check the Jenkins dashboard if the agent is connected well. 84 | ![image](images/screen-6.png) 85 | 86 | 87 | ## Windows Jenkins Java Opts 88 | 89 | The processing of the JENKINS_JAVA_OPTS environment variable or -JenkinsJavaOpts command line parameter follow the [command parsing semantics of Powershell](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing?view=powershell-7.3). This means that if a parameter contains any characters that are part of an expression in Powershell, it will need to be surrounded by quotes. 90 | For example: 91 | 92 | -XX:+PrintCommandLineFlags --show-version 93 | 94 | This would need to be escaped with quotes like this: 95 | 96 | "-XX:+PrintCommandLineFlags" --show-version 97 | 98 | Or another example: 99 | -Dsome.property=some value --show-version 100 | 101 | This would need to be escaped like this: 102 | 103 | "-Dsome.property='some value'" --show-version 104 | 105 | 106 | ## Configuration specifics 107 | 108 | ### Enabled JNLP protocols 109 | 110 | As of version 3.40-1 this image only supports the [JNLP4-connect](https://github.com/jenkinsci/remoting/blob/master/docs/protocols.md#jnlp4-connect) protocol. 111 | Earlier, long-unsupported protocols have been removed. 112 | As a result, Jenkins versions prior to 2.32 are no longer supported. 113 | 114 | ### Amazon ECS 115 | 116 | Make sure your ECS container agent is [updated](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-update.html) before running. Older versions do not properly handle the entryPoint parameter. See the [entryPoint](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions) definition for more information. 117 | -------------------------------------------------------------------------------- /windows/windowsservercore/Dockerfile: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | # The MIT License 4 | # 5 | # Copyright (c) 2020, Alex Earl and other Jenkins Contributors 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | ARG WINDOWS_VERSION_TAG=ltsc2019 26 | ARG TOOLS_WINDOWS_VERSION # Empty (only used on nanoserver) 27 | FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" AS jdk-core 28 | 29 | # $ProgressPreference: https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251261324 30 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 31 | 32 | ARG JAVA_VERSION=17.0.17+10 33 | RUN New-Item -ItemType Directory -Path C:\temp | Out-Null ; ` 34 | $javaMajorVersion = $env:JAVA_VERSION.substring(0,2) ; ` 35 | $msiUrl = 'https://api.adoptium.net/v3/installer/version/jdk-{0}/windows/x64/jdk/hotspot/normal/eclipse?project=jdk' -f $env:JAVA_VERSION ; ` 36 | Invoke-WebRequest $msiUrl -OutFile 'C:\temp\jdk.msi' ; ` 37 | $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 ; ` 38 | $proc.WaitForExit() ; ` 39 | Remove-Item -Path C:\temp -Recurse | Out-Null 40 | 41 | ## Agent image target 42 | FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" AS agent 43 | 44 | ARG JAVA_HOME="C:\openjdk-17" 45 | ENV JAVA_HOME=${JAVA_HOME} 46 | 47 | COPY --from=jdk-core $JAVA_HOME $JAVA_HOME 48 | 49 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 50 | 51 | ARG GIT_VERSION=2.52.0 52 | ARG GIT_PATCH_VERSION=1 53 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 54 | # The patch "windows.1" always have a different URL than the subsequent patch (ZIP filename is different) 55 | if($env:GIT_PATCH_VERSION -eq 1) { $url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION); } ` 56 | else {$url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}.{1}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION)} ; ` 57 | Write-Host "Retrieving $url..." ; ` 58 | Invoke-WebRequest $url -OutFile 'mingit.zip' -UseBasicParsing ; ` 59 | Expand-Archive mingit.zip -DestinationPath c:\mingit ; ` 60 | Remove-Item mingit.zip -Force 61 | 62 | # Add git and java in PATH 63 | RUN $CurrentPath = (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path ; ` 64 | $NewPath = $CurrentPath + $(';{0}\bin;C:\mingit\cmd' -f $env:JAVA_HOME) ; ` 65 | Set-ItemProperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path -Value $NewPath 66 | 67 | ARG GIT_LFS_VERSION=3.7.1 68 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 69 | $url = $('https://github.com/git-lfs/git-lfs/releases/download/v{0}/git-lfs-windows-amd64-v{0}.zip' -f $env:GIT_LFS_VERSION) ; ` 70 | Write-Host "Retrieving $url..." ; ` 71 | Invoke-WebRequest $url -OutFile 'GitLfs.zip' -UseBasicParsing ; ` 72 | Expand-Archive GitLfs.zip -DestinationPath c:\mingit\mingw64\bin ; ` 73 | $gitLfsFolder = 'c:\mingit\mingw64\bin\git-lfs-{0}' -f $env:GIT_LFS_VERSION ; ` 74 | Move-Item -Path "${gitLfsFolder}\git-lfs.exe" -Destination c:\mingit\mingw64\bin\ ; ` 75 | Remove-Item -Path $gitLfsFolder -Recurse -Force ; ` 76 | Remove-Item GitLfs.zip -Force ; ` 77 | & c:\mingit\mingw64\bin\git-lfs.exe install 78 | 79 | ARG user=jenkins 80 | 81 | ARG AGENT_FILENAME=agent.jar 82 | ARG AGENT_HASH_FILENAME=$AGENT_FILENAME.sha1 83 | 84 | RUN net accounts /maxpwage:unlimited ; ` 85 | net user "$env:USER" /add /expire:never /passwordreq:no ; ` 86 | net localgroup Administrators /add $env:USER ; ` 87 | Set-LocalUser -Name $env:USER -PasswordNeverExpires 1; ` 88 | New-Item -ItemType Directory -Path C:/ProgramData/Jenkins | Out-Null 89 | 90 | ARG AGENT_ROOT=C:/Users/$user 91 | ARG AGENT_WORKDIR=${AGENT_ROOT}/Work 92 | 93 | ENV AGENT_WORKDIR=${AGENT_WORKDIR} 94 | 95 | # Get the Agent from the Jenkins Artifacts Repository 96 | ARG VERSION=3355.v388858a_47b_33 97 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 98 | Invoke-WebRequest $('https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/{0}/remoting-{0}.jar' -f $env:VERSION) -OutFile $(Join-Path C:/ProgramData/Jenkins $env:AGENT_FILENAME) -UseBasicParsing ; ` 99 | Invoke-WebRequest $('https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/{0}/remoting-{0}.jar.sha1' -f $env:VERSION) -OutFile (Join-Path C:/ProgramData/Jenkins $env:AGENT_HASH_FILENAME) -UseBasicParsing ; ` 100 | if ((Get-FileHash (Join-Path C:/ProgramData/Jenkins $env:AGENT_FILENAME) -Algorithm SHA1).Hash -ne (Get-Content (Join-Path C:/ProgramData/Jenkins $env:AGENT_HASH_FILENAME))) {exit 1} ; ` 101 | Remove-Item -Force (Join-Path C:/ProgramData/Jenkins $env:AGENT_HASH_FILENAME) 102 | 103 | USER $user 104 | 105 | RUN New-Item -Type Directory $('{0}/.jenkins' -f $env:AGENT_ROOT) | Out-Null ; ` 106 | New-Item -Type Directory $env:AGENT_WORKDIR | Out-Null 107 | 108 | RUN git config --system core.longpaths true ; ` 109 | Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name LongPathsEnabled -type DWord -Value 1 110 | 111 | VOLUME "${AGENT_ROOT}"/.jenkins 112 | VOLUME "${AGENT_WORKDIR}" 113 | WORKDIR "${AGENT_ROOT}" 114 | ENV USER=${user} 115 | LABEL ` 116 | org.opencontainers.image.vendor="Jenkins project" ` 117 | org.opencontainers.image.title="Official Jenkins Agent Base Docker image" ` 118 | org.opencontainers.image.description="This is a base image, which provides the Jenkins agent executable (agent.jar)" ` 119 | org.opencontainers.image.version="${VERSION}" ` 120 | org.opencontainers.image.url="https://www.jenkins.io/" ` 121 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" ` 122 | org.opencontainers.image.licenses="MIT" 123 | 124 | ## Inbound Agent image target 125 | FROM agent AS inbound-agent 126 | 127 | COPY jenkins-agent.ps1 C:/ProgramData/Jenkins 128 | 129 | LABEL ` 130 | org.opencontainers.image.vendor="Jenkins project" ` 131 | org.opencontainers.image.title="Official Jenkins Inbound Agent Base Docker image" ` 132 | org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" ` 133 | org.opencontainers.image.version="${VERSION}" ` 134 | org.opencontainers.image.url="https://www.jenkins.io/" ` 135 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" ` 136 | org.opencontainers.image.licenses="MIT" 137 | 138 | ENTRYPOINT ["powershell.exe", "-f", "C:/ProgramData/Jenkins/jenkins-agent.ps1"] 139 | -------------------------------------------------------------------------------- /windows/nanoserver/Dockerfile: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | # The MIT License 4 | # 5 | # Copyright (c) 2020, Alex Earl and other Jenkins Contributors 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | ARG WINDOWS_VERSION_TAG=ltsc2019 26 | ARG TOOLS_WINDOWS_VERSION=1809 27 | FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" AS jdk-core 28 | 29 | # $ProgressPreference: https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251261324 30 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 31 | 32 | ARG JAVA_VERSION=17.0.17+10 33 | RUN New-Item -ItemType Directory -Path C:\temp | Out-Null ; ` 34 | $javaMajorVersion = $env:JAVA_VERSION.substring(0,2) ; ` 35 | $msiUrl = 'https://api.adoptium.net/v3/installer/version/jdk-{0}/windows/x64/jdk/hotspot/normal/eclipse?project=jdk' -f $env:JAVA_VERSION ; ` 36 | Invoke-WebRequest $msiUrl -OutFile 'C:\temp\jdk.msi' ; ` 37 | $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 ; ` 38 | $proc.WaitForExit() ; ` 39 | Remove-Item -Path C:\temp -Recurse | Out-Null 40 | 41 | FROM mcr.microsoft.com/powershell:nanoserver-"${TOOLS_WINDOWS_VERSION}" AS pwsh-source 42 | 43 | ## Agent image target 44 | FROM mcr.microsoft.com/windows/nanoserver:"${WINDOWS_VERSION_TAG}" AS agent 45 | 46 | ARG JAVA_HOME="C:\openjdk-17" 47 | ENV PSHOME="C:\Program Files\PowerShell" 48 | ENV PATH="C:\Windows\system32;C:\Windows;${PSHOME};" 49 | 50 | # The nanoserver image is nice and small, but we need a couple of things to get SSH working 51 | COPY --from=jdk-core /windows/system32/netapi32.dll /windows/system32/netapi32.dll 52 | COPY --from=jdk-core /windows/system32/whoami.exe /windows/system32/whoami.exe 53 | COPY --from=jdk-core $JAVA_HOME $JAVA_HOME 54 | COPY --from=pwsh-source $PSHOME $PSHOME 55 | 56 | SHELL ["pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 57 | USER ContainerAdministrator 58 | 59 | ARG GIT_VERSION=2.52.0 60 | ARG GIT_PATCH_VERSION=1 61 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 62 | # The patch "windows.1" always have a different URL than the subsequent patch (ZIP filename is different) 63 | if($env:GIT_PATCH_VERSION -eq 1) { $url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION); } ` 64 | else {$url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}.{1}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION)} ; ` 65 | Write-Host "Retrieving $url..." ; ` 66 | Invoke-WebRequest $url -OutFile 'mingit.zip' -UseBasicParsing ; ` 67 | Expand-Archive mingit.zip -DestinationPath c:\mingit ; ` 68 | Remove-Item mingit.zip -Force 69 | 70 | ENV ProgramFiles="C:\Program Files" ` 71 | WindowsPATH="C:\Windows\system32;C:\Windows" ` 72 | JAVA_HOME="${JAVA_HOME}" 73 | ENV PATH="${WindowsPATH};${ProgramFiles}\PowerShell;${JAVA_HOME}\bin;C:\mingit\cmd" 74 | 75 | ARG GIT_LFS_VERSION=3.7.1 76 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 77 | $url = $('https://github.com/git-lfs/git-lfs/releases/download/v{0}/git-lfs-windows-amd64-v{0}.zip' -f $env:GIT_LFS_VERSION) ; ` 78 | Write-Host "Retrieving $url..." ; ` 79 | Invoke-WebRequest $url -OutFile 'GitLfs.zip' -UseBasicParsing ; ` 80 | Expand-Archive GitLfs.zip -DestinationPath c:\mingit\mingw64\bin ; ` 81 | $gitLfsFolder = 'c:\mingit\mingw64\bin\git-lfs-{0}' -f $env:GIT_LFS_VERSION ; ` 82 | Move-Item -Path "${gitLfsFolder}\git-lfs.exe" -Destination c:\mingit\mingw64\bin\ ; ` 83 | Remove-Item -Path $gitLfsFolder -Recurse -Force ; ` 84 | Remove-Item GitLfs.zip -Force ; ` 85 | & c:\mingit\mingw64\bin\git-lfs.exe install 86 | 87 | ARG user=jenkins 88 | 89 | ARG AGENT_FILENAME=agent.jar 90 | ARG AGENT_HASH_FILENAME=$AGENT_FILENAME.sha1 91 | 92 | RUN net accounts /maxpwage:unlimited ; ` 93 | net user "$env:USER" /add /expire:never /passwordreq:no ; ` 94 | net localgroup Administrators /add $env:USER ; ` 95 | New-Item -ItemType Directory -Path C:/ProgramData/Jenkins | Out-Null 96 | 97 | ARG AGENT_ROOT=C:/Users/$user 98 | ARG AGENT_WORKDIR=${AGENT_ROOT}/Work 99 | 100 | ENV AGENT_WORKDIR=${AGENT_WORKDIR} 101 | 102 | # Get the Agent from the Jenkins Artifacts Repository 103 | ARG VERSION=3355.v388858a_47b_33 104 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 105 | Invoke-WebRequest $('https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/{0}/remoting-{0}.jar' -f $env:VERSION) -OutFile $(Join-Path C:/ProgramData/Jenkins $env:AGENT_FILENAME) -UseBasicParsing ; ` 106 | Invoke-WebRequest $('https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/{0}/remoting-{0}.jar.sha1' -f $env:VERSION) -OutFile (Join-Path C:/ProgramData/Jenkins $env:AGENT_HASH_FILENAME) -UseBasicParsing ; ` 107 | if ((Get-FileHash (Join-Path C:/ProgramData/Jenkins $env:AGENT_FILENAME) -Algorithm SHA1).Hash -ne (Get-Content (Join-Path C:/ProgramData/Jenkins $env:AGENT_HASH_FILENAME))) {exit 1} ; ` 108 | Remove-Item -Force (Join-Path C:/ProgramData/Jenkins $env:AGENT_HASH_FILENAME) 109 | 110 | USER $user 111 | 112 | RUN New-Item -Type Directory $('{0}/.jenkins' -f $env:AGENT_ROOT) | Out-Null ; ` 113 | New-Item -Type Directory $env:AGENT_WORKDIR | Out-Null 114 | 115 | RUN git config --system core.longpaths true ; ` 116 | Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name LongPathsEnabled -type DWord -Value 1 117 | 118 | VOLUME "${AGENT_ROOT}"/.jenkins 119 | VOLUME "${AGENT_WORKDIR}" 120 | WORKDIR "${AGENT_ROOT}" 121 | ENV USER=${user} 122 | LABEL ` 123 | org.opencontainers.image.vendor="Jenkins project" ` 124 | org.opencontainers.image.title="Official Jenkins Agent Base Docker image" ` 125 | org.opencontainers.image.description="This is a base image, which provides the Jenkins agent executable (agent.jar)" ` 126 | org.opencontainers.image.version="${VERSION}" ` 127 | org.opencontainers.image.url="https://www.jenkins.io/" ` 128 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" ` 129 | org.opencontainers.image.licenses="MIT" 130 | 131 | ## Inbound Agent image target 132 | FROM agent AS inbound-agent 133 | 134 | COPY jenkins-agent.ps1 C:/ProgramData/Jenkins 135 | 136 | LABEL ` 137 | org.opencontainers.image.vendor="Jenkins project" ` 138 | org.opencontainers.image.title="Official Jenkins Inbound Agent Base Docker image" ` 139 | org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" ` 140 | org.opencontainers.image.version="${VERSION}" ` 141 | org.opencontainers.image.url="https://www.jenkins.io/" ` 142 | org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" ` 143 | org.opencontainers.image.licenses="MIT" 144 | 145 | ENTRYPOINT ["pwsh.exe", "-f", "C:/ProgramData/Jenkins/jenkins-agent.ps1"] 146 | -------------------------------------------------------------------------------- /jenkins-agent.ps1: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2019-2020, Alex Earl 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | [CmdletBinding()] 24 | Param( 25 | $Cmd = '', # this must be specified explicitly 26 | $Url = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_URL) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_DIRECT_CONNECTION)) { throw ("Url is required") } else { '' } ), 27 | [Parameter(Position=0)]$Secret = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_SECRET)) { throw ("Secret is required") } else { '' } ), 28 | [Parameter(Position=1)]$Name = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_AGENT_NAME)) { throw ("Name is required") } else { '' } ), 29 | $Tunnel = '', 30 | $WorkDir = '', 31 | [switch] $WebSocket = $false, 32 | $DirectConnection = '', 33 | $InstanceIdentity = '', 34 | $Protocols = '', 35 | $JenkinsJavaBin = '', 36 | $JavaHome = $env:JAVA_HOME, 37 | $JenkinsJavaOpts = '', 38 | $RemotingOpts = '' 39 | ) 40 | 41 | # Usage jenkins-agent.ps1 [options] -Url http://jenkins -Secret [SECRET] -Name [AGENT_NAME] 42 | # Optional environment variables : 43 | # * JENKINS_JAVA_BIN : Java executable to use instead of the default in PATH or obtained from JAVA_HOME 44 | # * JENKINS_JAVA_OPTS : Java Options to use for the remoting process, otherwise obtained from JAVA_OPTS 45 | # * REMOTING_OPTS : Generic way to pass additional CLI options to agent.jar (see -help) 46 | # 47 | # Deprecated environment variables (prefer setting REMOTING_OPTS) 48 | # * JENKINS_TUNNEL : HOST:PORT for a tunnel to route TCP traffic to jenkins host, when jenkins can't be directly accessed over network 49 | # * JENKINS_URL : alternate jenkins URL 50 | # * JENKINS_SECRET : agent secret, if not set as an argument 51 | # * JENKINS_AGENT_NAME : agent name, if not set as an argument 52 | # * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir 53 | # * JENKINS_WEB_SOCKET : true if the connection should be made via WebSocket rather than TCP 54 | # * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. 55 | # Value: ":" 56 | # * JENKINS_INSTANCE_IDENTITY: The base64 encoded InstanceIdentity byte array of the Jenkins controller. When this is set, 57 | # the agent skips connecting to an HTTP(S) port for connection info. 58 | # * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. 59 | 60 | if(![System.String]::IsNullOrWhiteSpace($Cmd)) { 61 | Invoke-Expression "$Cmd" 62 | } else { 63 | 64 | # this maps the variable name from the CmdletBinding to environment variables 65 | $ParamMap = @{ 66 | 'JenkinsJavaBin' = 'JENKINS_JAVA_BIN'; 67 | 'JenkinsJavaOpts' = 'JENKINS_JAVA_OPTS'; 68 | 'Tunnel' = 'JENKINS_TUNNEL'; 69 | 'Url' = 'JENKINS_URL'; 70 | 'Secret' = 'JENKINS_SECRET'; 71 | 'Name' = 'JENKINS_AGENT_NAME'; 72 | 'WorkDir' = 'JENKINS_AGENT_WORKDIR'; 73 | 'WebSocket' = 'JENKINS_WEB_SOCKET'; 74 | 'DirectConnection' = 'JENKINS_DIRECT_CONNECTION'; 75 | 'InstanceIdentity' = 'JENKINS_INSTANCE_IDENTITY'; 76 | 'Protocols' = 'JENKINS_PROTOCOLS'; 77 | 'RemotingOpts' = 'REMOTING_OPTS'; 78 | } 79 | 80 | # this does some trickery to update the variable from the CmdletBinding 81 | # with the value of the 82 | foreach($p in $ParamMap.Keys) { 83 | $var = Get-Variable $p 84 | $envVar = Get-ChildItem -Path "env:$($ParamMap[$p])" -ErrorAction 'SilentlyContinue' 85 | 86 | if(($null -ne $envVar) -and ((($envVar.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($envVar.Value))) -or ($null -ne $envVar.Value))) { 87 | if(($null -ne $var) -and ((($var.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($var.Value))))) { 88 | Write-Warning "${p} is defined twice; in command-line arguments (-${p}) and in the environment variable ${envVar.Name}" 89 | } 90 | if($var.Value -is [System.String]) { 91 | $var.Value = $envVar.Value 92 | } elseif($var.Value -is [System.Management.Automation.SwitchParameter]) { 93 | $var.Value = [bool]$envVar.Value 94 | } 95 | } 96 | if($var.Value -is [System.String]) { 97 | $var.Value = $var.Value.Trim() 98 | } 99 | } 100 | 101 | $AgentArguments = @() 102 | 103 | if(![System.String]::IsNullOrWhiteSpace($JenkinsJavaOpts)) { 104 | # this magic will basically process the $JenkinsJavaOpts like a command line 105 | # and split into an array, the command line processing follows the PowerShell 106 | # commnd line processing, which means for things like -Dsomething.something=something, 107 | # you need to quote the string like this: "-Dsomething.something=something" or else it 108 | # will get parsed incorrectly. 109 | $AgentArguments += Invoke-Expression "echo $JenkinsJavaOpts" 110 | } 111 | 112 | $AgentArguments += @("-jar", "C:/ProgramData/Jenkins/agent.jar") 113 | $AgentArguments += @("-secret", $Secret) 114 | $AgentArguments += @("-name", $Name) 115 | 116 | if(![System.String]::IsNullOrWhiteSpace($RemotingOpts)) { 117 | $AgentArguments += Invoke-Expression "echo $RemotingOpts" 118 | } 119 | 120 | if(![System.String]::IsNullOrWhiteSpace($Tunnel)) { 121 | $AgentArguments += @("-tunnel", "`"$Tunnel`"") 122 | } 123 | 124 | if(![System.String]::IsNullOrWhiteSpace($WorkDir)) { 125 | $AgentArguments += @("-workDir", "`"$WorkDir`"") 126 | } else { 127 | $AgentArguments += @("-workDir", "`"C:/Users/jenkins/Work`"") 128 | } 129 | 130 | if($WebSocket) { 131 | $AgentArguments += @("-webSocket") 132 | } 133 | 134 | if(![System.String]::IsNullOrWhiteSpace($Url)) { 135 | $AgentArguments += @("-url", "`"$Url`"") 136 | } 137 | 138 | if(![System.String]::IsNullOrWhiteSpace($DirectConnection)) { 139 | $AgentArguments += @('-direct', $DirectConnection) 140 | } 141 | 142 | if(![System.String]::IsNullOrWhiteSpace($InstanceIdentity)) { 143 | $AgentArguments += @('-instanceIdentity', $InstanceIdentity) 144 | } 145 | 146 | if(![System.String]::IsNullOrWhiteSpace($Protocols)) { 147 | $AgentArguments += @('-protocols', $Protocols) 148 | } 149 | 150 | if(![System.String]::IsNullOrWhiteSpace($JenkinsJavaBin)) { 151 | $JAVA_BIN = $JenkinsJavaBin 152 | } else { 153 | # if java home is defined, use it 154 | $JAVA_BIN = "java.exe" 155 | if (![System.String]::IsNullOrWhiteSpace($JavaHome)) { 156 | $JAVA_BIN = "$JavaHome/bin/java.exe" 157 | } 158 | } 159 | 160 | #TODO: Handle the case when the command-line and Environment variable contain different values. 161 | #It is fine it blows up for now since it should lead to an error anyway. 162 | Start-Process -FilePath $JAVA_BIN -Wait -NoNewWindow -ArgumentList $AgentArguments 163 | } 164 | -------------------------------------------------------------------------------- /tests/inbound-agent.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 2 | 3 | $global:IMAGE_NAME = Get-EnvOrDefault 'IMAGE_NAME' '' # Ex: jenkins/inbound-agent:jdk17-nanoserver-1809 4 | $global:VERSION = Get-EnvOrDefault 'VERSION' '' 5 | $global:JAVA_VERSION = Get-EnvOrDefault 'JAVA_VERSION' '' 6 | 7 | Write-Host "= TESTS: Preparing $global:IMAGE_NAME with Remoting $global:VERSION and Java $global:JAVA_VERSION" 8 | 9 | $imageItems = $global:IMAGE_NAME.Split(':') 10 | $GLOBAL:IMAGE_TAG = $imageItems[1] 11 | 12 | $items = $global:IMAGE_TAG.Split('-') 13 | # Remove the 'jdk' prefix (3 first characters) 14 | $global:JAVAMAJORVERSION = $items[0].Remove(0,3) 15 | $global:WINDOWSFLAVOR = $items[1] 16 | $global:WINDOWSVERSIONTAG = $items[2] 17 | 18 | $random = Get-Random 19 | $global:CONTAINERNAME = 'pester-jenkins-inbound-agent_{0}_{1}' -f $global:IMAGE_TAG, $random 20 | Write-Host "= TESTS: container name $global:CONTAINERNAME" 21 | 22 | $global:CONTAINERSHELL = 'powershell.exe' 23 | if ($global:WINDOWSFLAVOR -eq 'nanoserver') { 24 | $global:CONTAINERSHELL = 'pwsh.exe' 25 | } 26 | 27 | # # Uncomment to help debugging when working on this script 28 | # Write-Host "= DEBUG: global vars" 29 | # Get-Variable -Scope Global | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } 30 | # Write-Host "= DEBUG: env vars" 31 | # Get-ChildItem Env: | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } 32 | 33 | Cleanup($global:CONTAINERNAME) 34 | Cleanup('nmap') 35 | CleanupNetwork('jnlp-network') 36 | 37 | BuildNcatImage($global:WINDOWSVERSIONTAG) 38 | 39 | Describe "[$global:IMAGE_NAME] build image" { 40 | It 'builds image' { 41 | $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg `"VERSION=${global:VERSION}`" --build-arg `"JAVA_VERSION=${global:JAVA_VERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --tag=${global:IMAGE_TAG} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." 42 | $exitCode | Should -Be 0 43 | } 44 | } 45 | 46 | Describe "[$global:IMAGE_NAME] check default user account" { 47 | BeforeAll { 48 | docker run --detach --tty --name "$global:CONTAINERNAME" "$global:IMAGE_NAME" -Cmd "$global:CONTAINERSHELL" 49 | $LASTEXITCODE | Should -Be 0 50 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 51 | } 52 | 53 | It 'has a password that never expires' { 54 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password expires') -match 'Never') { exit 0 } else { net user jenkins ; exit -1 }`"" 55 | $exitCode | Should -Be 0 56 | } 57 | 58 | It 'has password policy of "not required"' { 59 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password required') -match 'No') { exit 0 } else { net user jenkins ; exit -1 }`"" 60 | $exitCode | Should -Be 0 61 | } 62 | 63 | AfterAll { 64 | Cleanup($global:CONTAINERNAME) 65 | } 66 | } 67 | 68 | Describe "[$global:IMAGE_NAME] image has jenkins-agent.ps1 in the correct location" { 69 | BeforeAll { 70 | docker run --detach --tty --name "$global:CONTAINERNAME" "$global:IMAGE_NAME" -Cmd "$global:CONTAINERSHELL" 71 | $LASTEXITCODE | Should -Be 0 72 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 73 | } 74 | 75 | It 'has jenkins-agent.ps1 in C:/ProgramData/Jenkins' { 76 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if (Test-Path 'C:/ProgramData/Jenkins/jenkins-agent.ps1') { exit 0 } else { exit 1 }`"" 77 | $exitCode | Should -Be 0 78 | } 79 | 80 | AfterAll { 81 | Cleanup($global:CONTAINERNAME) 82 | } 83 | } 84 | 85 | Describe "[$global:IMAGE_NAME] image starts jenkins-agent.ps1 correctly (slow test)" { 86 | It 'connects to the nmap container' { 87 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'network create --driver nat jnlp-network' 88 | # Launch the netcat utility, listening at port 5000 for 30 sec 89 | # bats will capture the output from netcat and compare the first line 90 | # of the header of the first HTTP request with the expected one 91 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000' 92 | $exitCode | Should -Be 0 93 | Is-ContainerRunning "nmap" | Should -BeTrue 94 | 95 | # get the ip address of the nmap container 96 | $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format `"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`" nmap" 97 | $exitCode | Should -Be 0 98 | $nmap_ip = $stdout.Trim() 99 | 100 | # run Jenkins agent which tries to connect to the nmap container at port 5000 101 | $secret = "aaa" 102 | $name = "bbb" 103 | $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --network=jnlp-network --name $global:CONTAINERNAME $global:IMAGE_NAME -Url http://${nmap_ip}:5000 $secret $name" 104 | $exitCode | Should -Be 0 105 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 106 | 107 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'wait nmap' 108 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'logs nmap' 109 | $exitCode | Should -Be 0 110 | $stdout | Should -Match "GET /tcpSlaveAgentListener/ HTTP/1.1`r" 111 | } 112 | 113 | AfterAll { 114 | Cleanup($global:CONTAINERNAME) 115 | Cleanup('nmap') 116 | CleanupNetwork('jnlp-network') 117 | } 118 | } 119 | 120 | Describe "[$global:IMAGE_NAME] custom build args" { 121 | BeforeAll { 122 | Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." 123 | # Old version used to test overriding the build arguments. 124 | # This old version must have the same tag suffixes as the current windows images (`-jdk17-nanoserver` etc.), and the same Windows version (2019, 2022, etc.) 125 | $TEST_VERSION = '3206.vb_15dcf73f6a_9' 126 | $customImageName = "custom-${global:IMAGE_NAME}" 127 | } 128 | 129 | It 'builds image with arguments' { 130 | $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg `"VERSION=${TEST_VERSION}`" --build-arg `"JAVA_VERSION=${global:JAVA_VERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg WINDOWS_FLAVOR=${global:WINDOWSFLAVOR} --build-arg CONTAINER_SHELL=${global:CONTAINERSHELL} --tag=${customImageName} --file=./windows/${global:WINDOWSFLAVOR}/Dockerfile ." 131 | $exitCode | Should -Be 0 132 | 133 | $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name $global:CONTAINERNAME $customImageName -Cmd $global:CONTAINERSHELL" 134 | $exitCode | Should -Be 0 135 | Is-ContainerRunning "$global:CONTAINERNAME" | Should -BeTrue 136 | } 137 | 138 | It 'has the correct agent.jar version' { 139 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -c `"java -jar C:/ProgramData/Jenkins/agent.jar -version`"" 140 | $exitCode | Should -Be 0 141 | $stdout | Should -Match $TEST_VERSION 142 | } 143 | 144 | AfterAll { 145 | Cleanup($global:CONTAINERNAME) 146 | Pop-Location -StackName 'agent' 147 | } 148 | } 149 | 150 | Describe "[$global:IMAGE_NAME] passing JVM options (slow test)" { 151 | It "shows the java version ${global:JAVAMAJORVERSION} with --show-version" { 152 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'network create --driver nat jnlp-network' 153 | # Launch the netcat utility, listening at port 5000 for 30 sec 154 | # bats will capture the output from netcat and compare the first line 155 | # of the header of the first HTTP request with the expected one 156 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000' 157 | $exitCode | Should -Be 0 158 | Is-ContainerRunning 'nmap' | Should -BeTrue 159 | 160 | # get the ip address of the nmap container 161 | $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format `"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`" nmap" 162 | $exitCode | Should -Be 0 163 | $nmap_ip = $stdout.Trim() 164 | 165 | # run Jenkins agent which tries to connect to the nmap container at port 5000 166 | $secret = 'aaa' 167 | $name = 'bbb' 168 | $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --network=jnlp-network --name $global:CONTAINERNAME $global:IMAGE_NAME -Url http://${nmap_ip}:5000 -JenkinsJavaOpts `"--show-version`" $secret $name" 169 | $exitCode | Should -Be 0 170 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 171 | Start-Sleep -Seconds 20 172 | $exitCode, $stdout, $stderr = Run-Program 'docker' "logs $global:CONTAINERNAME" 173 | $exitCode | Should -Be 0 174 | $stdout | Should -Match "OpenJDK Runtime Environment Temurin-${global:JAVAMAJORVERSION}" 175 | } 176 | 177 | AfterAll { 178 | Cleanup($global:CONTAINERNAME) 179 | Cleanup('nmap') 180 | CleanupNetwork('jnlp-network') 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /tests/agent.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 2 | 3 | $global:IMAGE_NAME = Get-EnvOrDefault 'IMAGE_NAME' '' # Ex: jenkins/agent:jdk17-nanoserver-1809 4 | $global:VERSION = Get-EnvOrDefault 'VERSION' '' 5 | $global:JAVA_VERSION = Get-EnvOrDefault 'JAVA_VERSION' '' 6 | 7 | Write-Host "= TESTS: Preparing $global:IMAGE_NAME with Remoting $global:VERSION and Java $global:JAVA_VERSION" 8 | 9 | $imageItems = $global:IMAGE_NAME.Split(':') 10 | $GLOBAL:IMAGE_TAG = $imageItems[1] 11 | 12 | $items = $global:IMAGE_TAG.Split('-') 13 | # Remove the 'jdk' prefix 14 | $global:JAVAMAJORVERSION = $items[0].Remove(0,3) 15 | $global:WINDOWSFLAVOR = $items[1] 16 | $global:WINDOWSVERSIONTAG = $items[2] 17 | $global:WINDOWSVERSIONFALLBACKTAG = $items[2] 18 | if ($items[2] -eq 'ltsc2019') { 19 | $global:WINDOWSVERSIONFALLBACKTAG = '1809' 20 | } 21 | 22 | $random = Get-Random 23 | $global:CONTAINERNAME = 'pester-jenkins-agent_{0}_{1}' -f $global:IMAGE_TAG, $random 24 | Write-Host "= TESTS: container name $global:CONTAINERNAME" 25 | 26 | $global:CONTAINERSHELL = 'powershell.exe' 27 | if ($global:WINDOWSFLAVOR -eq 'nanoserver') { 28 | $global:CONTAINERSHELL = 'pwsh.exe' 29 | } 30 | 31 | $global:GITLFSVERSION = '3.7.1' 32 | 33 | # # Uncomment to help debugging when working on this script 34 | # Write-Host "= DEBUG: global vars" 35 | # Get-Variable -Scope Global | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } 36 | # Write-Host "= DEBUG: env vars" 37 | # Get-ChildItem Env: | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } 38 | 39 | Cleanup($global:CONTAINERNAME) 40 | 41 | Describe "[$global:IMAGE_NAME] image is present" { 42 | It 'inspects image' { 43 | $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect $global:IMAGE_NAME" 44 | $exitCode | Should -Be 0 45 | } 46 | } 47 | 48 | Describe "[$global:IMAGE_NAME] correct image metadata" { 49 | It 'has correct volumes' { 50 | $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format='{{.Config.Volumes}}' $global:IMAGE_NAME" 51 | $stdout = $stdout.Trim() 52 | $stdout | Should -Match 'C:/Users/jenkins/.jenkins' 53 | $stdout | Should -Match 'C:/Users/jenkins/Work' 54 | } 55 | } 56 | 57 | Describe "[$global:IMAGE_NAME] image has correct applications in the PATH" { 58 | BeforeAll { 59 | docker run --detach --interactive --tty --name "$global:CONTAINERNAME" "$global:IMAGE_NAME" "$global:CONTAINERSHELL" 60 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 61 | } 62 | 63 | It 'has java installed and in the path' { 64 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if (`$null -eq (Get-Command java.exe -ErrorAction SilentlyContinue)) { exit -1 } else { exit 0 }`"" 65 | $exitCode | Should -Be 0 66 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"`$global:VERSION = java -version 2>&1 ; Write-Host `$global:VERSION`"" 67 | $r = [regex] "^openjdk version `"(?\d+)" 68 | $m = $r.Match($stdout) 69 | $m | Should -Not -Be $null 70 | $m.Groups['major'].ToString() | Should -Be $global:JAVAMAJORVERSION 71 | } 72 | 73 | It 'has AGENT_WORKDIR in the environment' { 74 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"Get-ChildItem env:`"" 75 | $exitCode | Should -Be 0 76 | $stdout.Trim() | Should -Match 'AGENT_WORKDIR.*C:/Users/jenkins/Work' 77 | } 78 | 79 | It 'has user in the environment' { 80 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"Get-ChildItem env:`"" 81 | $exitCode | Should -Be 0 82 | $stdout.Trim() | Should -Match 'user.*jenkins' 83 | } 84 | 85 | It 'has git-lfs (and thus git) installed' { 86 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"`& git lfs env`"" 87 | $exitCode | Should -Be 0 88 | $r = [regex] "^git-lfs/(?\d+\.\d+\.\d+)" 89 | $m = $r.Match($stdout) 90 | $m | Should -Not -Be $null 91 | $m.Groups['version'].ToString() | Should -Be "$global:GITLFSVERSION" 92 | } 93 | 94 | It 'does not include jenkins-agent.ps1 (inbound-agent)' { 95 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if (Test-Path C:/ProgramData/Jenkins/jenkins-agent.ps1) { exit -1 } else { exit 0 }`"" 96 | $exitCode | Should -Be 0 97 | } 98 | 99 | AfterAll { 100 | Cleanup($global:CONTAINERNAME) 101 | } 102 | } 103 | 104 | Describe "[$global:IMAGE_NAME] check user account" { 105 | BeforeAll { 106 | docker run -d -it --name "$global:CONTAINERNAME" -P "$global:IMAGE_NAME" "$global:CONTAINERSHELL" 107 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 108 | } 109 | 110 | It 'Password never expires' { 111 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password expires') -match 'Never') { exit 0 } else { exit -1 }`"" 112 | $exitCode | Should -Be 0 113 | } 114 | 115 | It 'Password not required' { 116 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password required') -match 'No') { exit 0 } else { exit -1 }`"" 117 | $exitCode | Should -Be 0 118 | } 119 | 120 | AfterAll { 121 | Cleanup($global:CONTAINERNAME) 122 | } 123 | } 124 | 125 | Describe "[$global:IMAGE_NAME] check user access to directories" { 126 | BeforeAll { 127 | docker run -d -it --name "$global:CONTAINERNAME" -P "$global:IMAGE_NAME" "$global:CONTAINERSHELL" 128 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 129 | } 130 | 131 | It 'can write to HOME' { 132 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/a.txt | Out-Null ; if (Test-Path C:/Users/jenkins/a.txt) { exit 0 } else { exit -1 }`"" 133 | $exitCode | Should -Be 0 134 | } 135 | 136 | It 'can write to HOME/.jenkins' { 137 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/.jenkins/a.txt | Out-Null ; if (Test-Path C:/Users/jenkins/.jenkins/a.txt) { exit 0 } else { exit -1 }`"" 138 | $exitCode | Should -Be 0 139 | } 140 | 141 | It 'can write to HOME/Work' { 142 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/Work/a.txt | Out-Null ; if (Test-Path C:/Users/jenkins/Work/a.txt) { exit 0 } else { exit -1 }`"" 143 | $exitCode | Should -Be 0 144 | } 145 | 146 | AfterAll { 147 | Cleanup($global:CONTAINERNAME) 148 | } 149 | } 150 | 151 | $global:TEST_VERSION = '4.0' 152 | $global:TEST_USER = 'test-user' 153 | $global:TEST_AGENT_WORKDIR = 'C:/test-user/something' 154 | 155 | Describe "[$global:IMAGE_NAME] can be built with custom build arguments" { 156 | BeforeAll { 157 | Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." 158 | 159 | $exitCode, $stdout, $stderr = Run-Program 'docker' "build --target agent --build-arg `"VERSION=${global:TEST_VERSION}`" --build-arg `"JAVA_VERSION=${global:JAVA_VERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg `"TOOLS_WINDOWS_VERSION=${global:WINDOWSVERSIONFALLBACKTAG}`" --build-arg `"user=${global:TEST_USER}`" --build-arg `"AGENT_WORKDIR=${global:TEST_AGENT_WORKDIR}`" --tag ${global:IMAGE_NAME} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." 160 | $exitCode | Should -Be 0 161 | 162 | $exitCode, $stdout, $stderr = Run-Program 'docker' "run -d -it --name $global:CONTAINERNAME -P $global:IMAGE_NAME $global:CONTAINERSHELL" 163 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 164 | } 165 | 166 | It 'has the correct version of remoting' { 167 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"`$global:VERSION = java -jar C:/ProgramData/Jenkins/agent.jar -version ; Write-Host `$global:VERSION`"" 168 | $exitCode | Should -Be 0 169 | $stdout.Trim() | Should -Match $global:TEST_VERSION 170 | } 171 | 172 | It 'has correct user' { 173 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"(Get-ChildItem env:\ | Where-Object { `$_.Name -eq 'USERNAME' }).Value`"" 174 | $exitCode | Should -Be 0 175 | $stdout.Trim() | Should -Match $global:TEST_USER 176 | } 177 | 178 | It 'has correct AGENT_WORKDIR' { 179 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"Get-ChildItem env:`"" 180 | $exitCode | Should -Be 0 181 | $stdout | Should -Match "AGENT_WORKDIR.*${global:TEST_AGENT_WORKDIR}" 182 | } 183 | 184 | It 'can write to HOME' { 185 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/${TEST_USER}/a.txt | Out-Null ; if (Test-Path C:/Users/${TEST_USER}/a.txt) { exit 0 } else { exit -1 }`"" 186 | $exitCode | Should -Be 0 187 | } 188 | 189 | It 'can write to HOME/.jenkins' { 190 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/${TEST_USER}/.jenkins/a.txt | Out-Null ; if (Test-Path C:/Users/${TEST_USER}/.jenkins/a.txt) { exit 0 } else { exit -1 }`"" 191 | $exitCode | Should -Be 0 192 | } 193 | 194 | It 'can write to HOME/Work' { 195 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path ${TEST_AGENT_WORKDIR}/a.txt | Out-Null ; if (Test-Path ${TEST_AGENT_WORKDIR}/a.txt) { exit 0 } else { exit -1 }`"" 196 | $exitCode | Should -Be 0 197 | } 198 | 199 | It 'version in docker metadata' { 200 | $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format=`"{{index .Config.Labels \`"org.opencontainers.image.version\`"}}`" $global:IMAGE_NAME" 201 | $exitCode | Should -Be 0 202 | $stdout.Trim() | Should -Match $global:sTEST_VERSION 203 | } 204 | 205 | AfterAll { 206 | Pop-Location -StackName 'agent' 207 | Cleanup($global:CONTAINERNAME) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /docker-bake.hcl: -------------------------------------------------------------------------------- 1 | ## Variables 2 | variable "agent_types_to_build" { 3 | default = ["agent", "inbound-agent"] 4 | } 5 | 6 | variable "jdks_to_build" { 7 | default = [17, 21, 25] 8 | } 9 | variable "default_jdk" { 10 | default = 17 11 | } 12 | 13 | variable "jdks_in_preview" { 14 | default = [] 15 | } 16 | 17 | variable "JAVA17_VERSION" { 18 | default = "17.0.17_10" 19 | } 20 | 21 | variable "JAVA21_VERSION" { 22 | default = "21.0.9_10" 23 | } 24 | 25 | variable "JAVA25_VERSION" { 26 | default = "25.0.1_8" 27 | } 28 | 29 | variable "REMOTING_VERSION" { 30 | default = "3355.v388858a_47b_33" 31 | } 32 | 33 | variable "REGISTRY" { 34 | default = "docker.io" 35 | } 36 | 37 | variable "REGISTRY_ORG" { 38 | default = "jenkins" 39 | } 40 | 41 | variable "REGISTRY_REPO_AGENT" { 42 | default = "agent" 43 | } 44 | 45 | variable "REGISTRY_REPO_INBOUND_AGENT" { 46 | default = "inbound-agent" 47 | } 48 | 49 | variable "BUILD_NUMBER" { 50 | default = "1" 51 | } 52 | 53 | variable "ON_TAG" { 54 | default = "false" 55 | } 56 | 57 | variable "ALPINE_FULL_TAG" { 58 | default = "3.23.2" 59 | } 60 | 61 | variable "ALPINE_SHORT_TAG" { 62 | default = regex_replace(ALPINE_FULL_TAG, "\\.\\d+$", "") 63 | } 64 | 65 | variable "DEBIAN_RELEASE" { 66 | default = "trixie-20251208" 67 | } 68 | 69 | variable "UBI9_TAG" { 70 | default = "9.7-1764794285" 71 | } 72 | 73 | # Set this value to a specific Windows version to override Windows versions to build returned by windowsversions function 74 | variable "WINDOWS_VERSION_OVERRIDE" { 75 | default = "" 76 | } 77 | 78 | # Set this value to a specific agent type to override agent type to build returned by windowsagenttypes function 79 | variable "WINDOWS_AGENT_TYPE_OVERRIDE" { 80 | default = "" 81 | } 82 | 83 | variable "jdk_versions" { 84 | default = { 85 | 17 = JAVA17_VERSION 86 | 21 = JAVA21_VERSION 87 | 25 = JAVA25_VERSION 88 | } 89 | } 90 | 91 | ## Targets 92 | target "alpine" { 93 | matrix = { 94 | type = agent_types_to_build 95 | jdk = jdks_to_build 96 | } 97 | name = "${type}_alpine_jdk${jdk}" 98 | target = type 99 | dockerfile = "alpine/Dockerfile" 100 | context = "." 101 | args = { 102 | ALPINE_TAG = ALPINE_FULL_TAG 103 | VERSION = REMOTING_VERSION 104 | JAVA_VERSION = "${javaversion(jdk)}" 105 | } 106 | tags = concat(linux_tags(type, jdk, "alpine"), linux_tags(type, jdk, "alpine${ALPINE_SHORT_TAG}")) 107 | platforms = alpine_platforms(jdk) 108 | } 109 | 110 | target "debian" { 111 | matrix = { 112 | type = agent_types_to_build 113 | jdk = jdks_to_build 114 | } 115 | name = "${type}_debian_jdk${jdk}" 116 | target = type 117 | dockerfile = "debian/Dockerfile" 118 | context = "." 119 | args = { 120 | VERSION = REMOTING_VERSION 121 | DEBIAN_RELEASE = DEBIAN_RELEASE 122 | JAVA_VERSION = "${javaversion(jdk)}" 123 | } 124 | tags = linux_tags(type, jdk, "debian") 125 | platforms = debian_platforms(jdk) 126 | } 127 | 128 | target "rhel_ubi9" { 129 | matrix = { 130 | type = agent_types_to_build 131 | jdk = jdks_to_build 132 | } 133 | name = "${type}_rhel_ubi9_jdk${jdk}" 134 | target = type 135 | dockerfile = "rhel/ubi9/Dockerfile" 136 | context = "." 137 | args = { 138 | UBI9_TAG = UBI9_TAG 139 | VERSION = REMOTING_VERSION 140 | JAVA_VERSION = "${javaversion(jdk)}" 141 | } 142 | tags = linux_tags(type, jdk, "rhel-ubi9") 143 | platforms = rhel_ubi9_platforms(jdk) 144 | } 145 | 146 | target "nanoserver" { 147 | matrix = { 148 | type = windowsagenttypes(WINDOWS_AGENT_TYPE_OVERRIDE) 149 | jdk = jdks_to_build 150 | windows_version = windowsversions("nanoserver") 151 | } 152 | name = "${type}_nanoserver-${windows_version}_jdk${jdk}" 153 | dockerfile = "windows/nanoserver/Dockerfile" 154 | context = "." 155 | args = { 156 | JAVA_HOME = "C:/openjdk-${jdk}" 157 | JAVA_VERSION = "${replace(javaversion(jdk), "_", "+")}" 158 | TOOLS_WINDOWS_VERSION = "${toolsversion(windows_version)}" 159 | VERSION = REMOTING_VERSION 160 | WINDOWS_VERSION_TAG = windows_version 161 | } 162 | target = type 163 | tags = windows_tags(type, jdk, "nanoserver-${windows_version}") 164 | platforms = ["windows/amd64"] 165 | } 166 | 167 | target "windowsservercore" { 168 | matrix = { 169 | type = windowsagenttypes(WINDOWS_AGENT_TYPE_OVERRIDE) 170 | jdk = jdks_to_build 171 | windows_version = windowsversions("windowsservercore") 172 | } 173 | name = "${type}_windowsservercore-${windows_version}_jdk${jdk}" 174 | dockerfile = "windows/windowsservercore/Dockerfile" 175 | context = "." 176 | args = { 177 | JAVA_HOME = "C:/openjdk-${jdk}" 178 | JAVA_VERSION = "${replace(javaversion(jdk), "_", "+")}" 179 | TOOLS_WINDOWS_VERSION = "${toolsversion(windows_version)}" 180 | VERSION = REMOTING_VERSION 181 | WINDOWS_VERSION_TAG = windows_version 182 | } 183 | target = type 184 | tags = windows_tags(type, jdk, "windowsservercore-${windows_version}") 185 | platforms = ["windows/amd64"] 186 | } 187 | 188 | ## Groups 189 | group "linux" { 190 | targets = [ 191 | "alpine", 192 | "debian", 193 | "rhel_ubi9" 194 | ] 195 | } 196 | 197 | group "windows" { 198 | targets = [ 199 | "nanoserver", 200 | "windowsservercore" 201 | ] 202 | } 203 | 204 | group "linux-arm64" { 205 | targets = [ 206 | "alpine_jdk21", 207 | "debian", 208 | "rhel_ubi9" 209 | ] 210 | } 211 | 212 | group "linux-arm32" { 213 | targets = [ 214 | "debian_jdk17" 215 | ] 216 | } 217 | 218 | group "linux-s390x" { 219 | targets = [ 220 | "debian_jdk21" 221 | ] 222 | } 223 | 224 | group "linux-ppc64le" { 225 | targets = [ 226 | "debian", 227 | "rhel_ubi9" 228 | ] 229 | } 230 | 231 | 232 | ## Common functions 233 | # Return the registry organization and repository depending on the agent type 234 | function "orgrepo" { 235 | params = [agentType] 236 | result = equal("agent", agentType) ? "${REGISTRY_ORG}/${REGISTRY_REPO_AGENT}" : "${REGISTRY_ORG}/${REGISTRY_REPO_INBOUND_AGENT}" 237 | } 238 | 239 | # Return "true" if the jdk passed as parameter is the same as the default jdk, "false" otherwise 240 | function "is_default_jdk" { 241 | params = [jdk] 242 | result = equal(default_jdk, jdk) ? true : false 243 | } 244 | 245 | # Return the complete Java version corresponding to the jdk passed as parameter 246 | function "javaversion" { 247 | params = [jdk] 248 | result = lookup(jdk_versions, jdk, "Unsupported JDK version") 249 | } 250 | 251 | ## Specific functions 252 | # Return an array of Alpine platforms to use depending on the jdk passed as parameter 253 | function "alpine_platforms" { 254 | params = [jdk] 255 | result = (equal(17, jdk) 256 | ? ["linux/amd64"] 257 | : ["linux/amd64", "linux/arm64"]) 258 | } 259 | 260 | # Return an array of Debian platforms to use depending on the jdk passed as parameter 261 | function "debian_platforms" { 262 | params = [jdk] 263 | result = (equal(17, jdk) 264 | ? ["linux/amd64", "linux/arm64", "linux/ppc64le", "linux/arm/v7"] 265 | : ["linux/amd64", "linux/arm64", "linux/ppc64le", "linux/s390x"]) 266 | } 267 | 268 | # Return array of Windows version(s) to build 269 | # There is no mcr.microsoft.com/windows/servercore:1809 image 270 | # Can be overriden by setting WINDOWS_VERSION_OVERRIDE to a specific Windows version 271 | # Ex: WINDOWS_VERSION_OVERRIDE=1809 docker buildx bake windows 272 | function "windowsversions" { 273 | params = [flavor] 274 | result = (notequal(WINDOWS_VERSION_OVERRIDE, "") 275 | ? [WINDOWS_VERSION_OVERRIDE] 276 | : (equal(flavor, "windowsservercore") 277 | ? ["ltsc2019", "ltsc2022"] 278 | : ["1809", "ltsc2019", "ltsc2022"])) 279 | } 280 | 281 | # Return array of agent type(s) to build 282 | # Can be overriden to a specific agent type 283 | function "windowsagenttypes" { 284 | params = [override] 285 | result = (notequal(override, "") 286 | ? [override] 287 | : agent_types_to_build) 288 | } 289 | 290 | # Return the Windows version to use as base image for the Windows version passed as parameter 291 | # There is no mcr.microsoft.com/powershell ltsc2019 base image, using a "1809" instead 292 | function "toolsversion" { 293 | params = [version] 294 | result = (equal("ltsc2019", version) 295 | ? "1809" 296 | : version) 297 | } 298 | 299 | # Return an array of RHEL UBI 9 platforms to use depending on the jdk passed as parameter 300 | # Note: Jenkins controller container image only supports jdk17 and jdk21 for ubi9 301 | function "rhel_ubi9_platforms" { 302 | params = [jdk] 303 | result = ["linux/amd64", "linux/arm64", "linux/ppc64le"] 304 | } 305 | 306 | # Return the distribution followed by a dash if it is not the default distribution 307 | function distribution_prefix { 308 | params = [distribution] 309 | result = (equal("debian", distribution) 310 | ? "" 311 | : "${distribution}-") 312 | } 313 | 314 | # Return a dash followed by the distribution if it is not the default distribution 315 | function distribution_suffix { 316 | params = [distribution] 317 | result = (equal("debian", distribution) 318 | ? "" 319 | : "-${distribution}") 320 | } 321 | 322 | # Return the official name of the default distribution 323 | function distribution_name { 324 | params = [distribution] 325 | result = (equal("debian", distribution) 326 | ? "trixie" 327 | : distribution) 328 | } 329 | 330 | # Return the tag suffixed by "-preview" if the jdk passed as parameter is in the jdks_in_preview list 331 | function preview_tag { 332 | params = [jdk] 333 | result = (contains(jdks_in_preview, jdk) 334 | ? "${jdk}-preview" 335 | : jdk) 336 | } 337 | 338 | # Return an array of tags depending on the agent type, the jdk and the Linux distribution passed as parameters 339 | function "linux_tags" { 340 | params = [type, jdk, distribution] 341 | result = [ 342 | ## All 343 | # If there is a tag, add versioned tag suffixed by the jdk 344 | equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}${distribution_suffix(distribution)}-jdk${preview_tag(jdk)}" : "", 345 | 346 | # If there is a tag and if the jdk is the default one, add versioned short tag 347 | equal(ON_TAG, "true") ? (is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}${distribution_suffix(distribution)}" : "") : "", 348 | 349 | # If the jdk is the default one, add distribution and latest short tags 350 | is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:${distribution_name(distribution)}" : "", 351 | is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:latest${distribution_suffix(distribution)}" : "", 352 | # Needed for the ":latest-trixie" case. For other distributions, result in the same tag as above (not an issue, deduplicated at the end) 353 | is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:latest-${distribution_name(distribution)}" : "", 354 | 355 | # Tags always added 356 | "${REGISTRY}/${orgrepo(type)}:${distribution_name(distribution)}-jdk${preview_tag(jdk)}", 357 | "${REGISTRY}/${orgrepo(type)}:latest-${distribution_name(distribution)}-jdk${preview_tag(jdk)}", 358 | # ":jdkN" and ":latest-jdkN" short tags for the default distribution. For other distributions, result in the tags above (not an issue, deduplicated at the end) 359 | "${REGISTRY}/${orgrepo(type)}:${distribution_prefix(distribution)}jdk${preview_tag(jdk)}", 360 | "${REGISTRY}/${orgrepo(type)}:latest-${distribution_prefix(distribution)}jdk${preview_tag(jdk)}", 361 | ] 362 | } 363 | 364 | # Return an array of tags depending on the agent type, the jdk and the flavor and version of Windows passed as parameters 365 | function "windows_tags" { 366 | params = [type, jdk, flavor_and_version] 367 | result = [ 368 | # If there is a tag, add versioned tag containing the jdk 369 | equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk${preview_tag(jdk)}-${flavor_and_version}" : "", 370 | # If there is a tag and if the jdk is the default one, add versioned and short tags 371 | equal(ON_TAG, "true") ? (is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-${flavor_and_version}" : "") : "", 372 | equal(ON_TAG, "true") ? (is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:${flavor_and_version}" : "") : "", 373 | "${REGISTRY}/${orgrepo(type)}:jdk${preview_tag(jdk)}-${flavor_and_version}", 374 | ] 375 | } 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jenkins Agent and Inbound Agent Docker images 2 | 3 | [![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) 4 | [![GitHub stars](https://img.shields.io/github/stars/jenkinsci/docker-agent?label=GitHub%20stars)](https://github.com/jenkinsci/docker-agent) 5 | [![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-agent/releases/latest) 6 | 7 | This repository contains the definition of Jenkins agent and inbound agent Docker images. 8 | 9 | ## agent 10 | [![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/agent.svg)](https://hub.docker.com/r/jenkins/agent/) 11 | 12 | This is a base image for Docker, which includes JDK and the Jenkins agent executable (agent.jar). 13 | 14 | See [the `agent` README](./README_agent.md) 15 | 16 | ## inbound-agent 17 | [![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/inbound-agent.svg)](https://hub.docker.com/r/jenkins/inbound-agent/) 18 | 19 | This is an image based on `agent` for [Jenkins](https://jenkins.io) agents using TCP or WebSockets to establish inbound connection to the Jenkins controller. 20 | 21 | See [the `inbound-agent` README](./README_inbound-agent.md) 22 | 23 | ## Building 24 | 25 | ### Building and testing on Linux 26 | 27 | #### Target images 28 | 29 | If you want to see the target images (matching your current architecture) that will be built, you can issue the following command: 30 | 31 | ```bash 32 | $ make list 33 | agent_alpine_jdk17 34 | agent_alpine_jdk21 35 | agent_debian_jdk17 36 | agent_debian_jdk21 37 | agent_rhel_ubi9_jdk17 38 | agent_rhel_ubi9_jdk21 39 | inbound-agent_alpine_jdk17 40 | inbound-agent_alpine_jdk21 41 | inbound-agent_debian_jdk17 42 | inbound-agent_debian_jdk21 43 | inbound-agent_rhel_ubi9_jdk17 44 | inbound-agent_rhel_ubi9_jdk21 45 | ``` 46 | 47 | #### Building a specific image 48 | 49 | If you want to build a specific image, you can issue the following command: 50 | 51 | ```bash 52 | make build-__ 53 | ``` 54 | 55 | That would give for an image of an inbound agent with JDK 17 on Debian: 56 | 57 | ```bash 58 | make build-inbound-agent_debian_jdk17 59 | ``` 60 | 61 | #### Building images supported by your current architecture 62 | 63 | Then, you can build the images supported by your current architecture by running: 64 | 65 | ```bash 66 | make build 67 | ``` 68 | 69 | #### Testing all images 70 | 71 | If you want to test these images, you can run: 72 | 73 | ```bash 74 | make test 75 | ``` 76 | #### Testing a specific image 77 | 78 | If you want to test a specific image, you can run: 79 | 80 | ```bash 81 | make test-__ 82 | ``` 83 | 84 | That would give for an image of an inbound agent with JDK 17 on Debian: 85 | 86 | ```bash 87 | make test-inbound-agent_debian_jdk17 88 | ``` 89 | 90 | #### Building all images 91 | 92 | You can build all images (even those unsupported by your current architecture) by running: 93 | 94 | ```bash 95 | make every-build 96 | ``` 97 | 98 | #### Other `make` targets 99 | 100 | `show` (and `show-windows`) gives us a detailed view of the images that could be built, with tags, platforms, and Dockerfiles. 101 | 102 | ```bash 103 | $ make show 104 | { 105 | "group": { 106 | "alpine": { 107 | "targets": [ 108 | "agent_alpine_jdk17", 109 | "agent_alpine_jdk21", 110 | "inbound-agent_alpine_jdk17", 111 | "inbound-agent_alpine_jdk21" 112 | ] 113 | }, 114 | "debian": { 115 | "targets": [ 116 | "agent_debian_jdk17", 117 | "agent_debian_jdk21", 118 | "inbound-agent_debian_jdk17", 119 | "inbound-agent_debian_jdk21" 120 | ] 121 | }, 122 | "default": { 123 | "targets": [ 124 | "linux" 125 | ] 126 | }, 127 | "linux": { 128 | "targets": [ 129 | "alpine", 130 | "debian", 131 | "rhel_ubi9" 132 | ] 133 | }, 134 | "rhel_ubi9": { 135 | "targets": [ 136 | "agent_rhel_ubi9_jdk17", 137 | "agent_rhel_ubi9_jdk21", 138 | "inbound-agent_rhel_ubi9_jdk17", 139 | "inbound-agent_rhel_ubi9_jdk21" 140 | ] 141 | } 142 | }, 143 | "target": { 144 | "agent_alpine_jdk17": { 145 | "context": ".", 146 | "dockerfile": "alpine/Dockerfile", 147 | "args": { 148 | "ALPINE_TAG": "3.23.2", 149 | "JAVA_VERSION": "17.0.17_10", 150 | "VERSION": "3355.v388858a_47b_33" 151 | }, 152 | "tags": [ 153 | "docker.io/jenkins/agent:alpine", 154 | "docker.io/jenkins/agent:latest-alpine", 155 | "docker.io/jenkins/agent:alpine-jdk17", 156 | "docker.io/jenkins/agent:latest-alpine-jdk17", 157 | "docker.io/jenkins/agent:alpine3.23", 158 | "docker.io/jenkins/agent:latest-alpine3.23", 159 | "docker.io/jenkins/agent:alpine3.23-jdk17", 160 | "docker.io/jenkins/agent:latest-alpine3.23-jdk17" 161 | ], 162 | "target": "agent", 163 | "platforms": [ 164 | "linux/amd64" 165 | ], 166 | "output": [ 167 | { 168 | "type": "docker" 169 | } 170 | ] 171 | }, 172 | [...] 173 | } 174 | } 175 | ``` 176 | 177 | To view all tags, set `ON_TAG` (and eventually `BUILD_NUMBER`): 178 | ```bash 179 | $ ON_TAG=true BUILD_NUMBER=3 make tags 180 | ``` 181 |
Output 182 | 183 | ``` 184 | docker.io/jenkins/agent:3355.v388858a_47b_33-3 185 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine 186 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine-jdk17 187 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine-jdk21 188 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine-jdk25 189 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine3.23 190 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine3.23-jdk17 191 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine3.23-jdk21 192 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-alpine3.23-jdk25 193 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-jdk17 194 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-jdk21 195 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-jdk25 196 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-rhel-ubi9 197 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-rhel-ubi9-jdk17 198 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-rhel-ubi9-jdk21 199 | docker.io/jenkins/agent:3355.v388858a_47b_33-3-rhel-ubi9-jdk25 200 | docker.io/jenkins/agent:alpine 201 | docker.io/jenkins/agent:alpine-jdk17 202 | docker.io/jenkins/agent:alpine-jdk21 203 | docker.io/jenkins/agent:alpine-jdk25 204 | docker.io/jenkins/agent:alpine3.23 205 | docker.io/jenkins/agent:alpine3.23-jdk17 206 | docker.io/jenkins/agent:alpine3.23-jdk21 207 | docker.io/jenkins/agent:alpine3.23-jdk25 208 | docker.io/jenkins/agent:jdk17 209 | docker.io/jenkins/agent:jdk21 210 | docker.io/jenkins/agent:jdk25 211 | docker.io/jenkins/agent:latest 212 | docker.io/jenkins/agent:latest-alpine 213 | docker.io/jenkins/agent:latest-alpine-jdk17 214 | docker.io/jenkins/agent:latest-alpine-jdk21 215 | docker.io/jenkins/agent:latest-alpine-jdk25 216 | docker.io/jenkins/agent:latest-alpine3.23 217 | docker.io/jenkins/agent:latest-alpine3.23-jdk17 218 | docker.io/jenkins/agent:latest-alpine3.23-jdk21 219 | docker.io/jenkins/agent:latest-alpine3.23-jdk25 220 | docker.io/jenkins/agent:latest-jdk17 221 | docker.io/jenkins/agent:latest-jdk21 222 | docker.io/jenkins/agent:latest-jdk25 223 | docker.io/jenkins/agent:latest-rhel-ubi9 224 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk17 225 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk21 226 | docker.io/jenkins/agent:latest-rhel-ubi9-jdk25 227 | docker.io/jenkins/agent:latest-trixie 228 | docker.io/jenkins/agent:latest-trixie-jdk17 229 | docker.io/jenkins/agent:latest-trixie-jdk21 230 | docker.io/jenkins/agent:latest-trixie-jdk25 231 | docker.io/jenkins/agent:rhel-ubi9 232 | docker.io/jenkins/agent:rhel-ubi9-jdk17 233 | docker.io/jenkins/agent:rhel-ubi9-jdk21 234 | docker.io/jenkins/agent:rhel-ubi9-jdk25 235 | docker.io/jenkins/agent:trixie 236 | docker.io/jenkins/agent:trixie-jdk17 237 | docker.io/jenkins/agent:trixie-jdk21 238 | docker.io/jenkins/agent:trixie-jdk25 239 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3 240 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine 241 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine-jdk17 242 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine-jdk21 243 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine-jdk25 244 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine3.23 245 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine3.23-jdk17 246 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine3.23-jdk21 247 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-alpine3.23-jdk25 248 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-jdk17 249 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-jdk21 250 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-jdk25 251 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-rhel-ubi9 252 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-rhel-ubi9-jdk17 253 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-rhel-ubi9-jdk21 254 | docker.io/jenkins/inbound-agent:3355.v388858a_47b_33-3-rhel-ubi9-jdk25 255 | docker.io/jenkins/inbound-agent:alpine 256 | docker.io/jenkins/inbound-agent:alpine-jdk17 257 | docker.io/jenkins/inbound-agent:alpine-jdk21 258 | docker.io/jenkins/inbound-agent:alpine-jdk25 259 | docker.io/jenkins/inbound-agent:alpine3.23 260 | docker.io/jenkins/inbound-agent:alpine3.23-jdk17 261 | docker.io/jenkins/inbound-agent:alpine3.23-jdk21 262 | docker.io/jenkins/inbound-agent:alpine3.23-jdk25 263 | docker.io/jenkins/inbound-agent:jdk17 264 | docker.io/jenkins/inbound-agent:jdk21 265 | docker.io/jenkins/inbound-agent:jdk25 266 | docker.io/jenkins/inbound-agent:latest 267 | docker.io/jenkins/inbound-agent:latest-alpine 268 | docker.io/jenkins/inbound-agent:latest-alpine-jdk17 269 | docker.io/jenkins/inbound-agent:latest-alpine-jdk21 270 | docker.io/jenkins/inbound-agent:latest-alpine-jdk25 271 | docker.io/jenkins/inbound-agent:latest-alpine3.23 272 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk17 273 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk21 274 | docker.io/jenkins/inbound-agent:latest-alpine3.23-jdk25 275 | docker.io/jenkins/inbound-agent:latest-jdk17 276 | docker.io/jenkins/inbound-agent:latest-jdk21 277 | docker.io/jenkins/inbound-agent:latest-jdk25 278 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9 279 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk17 280 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk21 281 | docker.io/jenkins/inbound-agent:latest-rhel-ubi9-jdk25 282 | docker.io/jenkins/inbound-agent:latest-trixie 283 | docker.io/jenkins/inbound-agent:latest-trixie-jdk17 284 | docker.io/jenkins/inbound-agent:latest-trixie-jdk21 285 | docker.io/jenkins/inbound-agent:latest-trixie-jdk25 286 | docker.io/jenkins/inbound-agent:rhel-ubi9 287 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk17 288 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk21 289 | docker.io/jenkins/inbound-agent:rhel-ubi9-jdk25 290 | docker.io/jenkins/inbound-agent:trixie 291 | docker.io/jenkins/inbound-agent:trixie-jdk17 292 | docker.io/jenkins/inbound-agent:trixie-jdk21 293 | docker.io/jenkins/inbound-agent:trixie-jdk25 294 | ``` 295 | 296 |
297 | 298 | You can also call `make tags-windows` to show Windows image tags. 299 | 300 | `bats` is a dependency target. It will update the [`bats` submodule](https://github.com/bats-core/bats-core) and run the tests. 301 | 302 | ```bash 303 | make bats 304 | make: 'bats' is up to date. 305 | ``` 306 | 307 | `publish` allows the publication of all images targeted by 'linux' to a registry. 308 | 309 | `docker-init` is dedicated to Jenkins infrastructure for initializing docker and isn't required in other contexts. 310 | 311 | ### Building and testing on Windows 312 | 313 | #### Building all images 314 | 315 | Run `.\build.ps1` to launch the build of the images corresponding to the "windows" target of docker-bake.hcl. 316 | 317 | Internally, the first time you'll run this script and if there is no build-windows___.yaml file in your repository, it will use a combination of `docker buildx bake` and `yq` to generate a build-windows___.yaml docker compose file containing all Windows image definitions from docker-bake.hcl. Then it will run `docker compose` on this file to build these images. 318 | 319 | You can modify this docker compose file as you want, then rerun `.\build.ps1`. 320 | It won't regenerate the docker compose file from docker-bake.hcl unless you add the `-OverwriteDockerComposeFile` build.ps1 parameter: `.\build.ps1 -OverwriteDockerComposeFile`. 321 | 322 | Note: you can generate this docker compose file from docker-bake.hcl yourself with the following command (require `docker buildx` and `yq`): 323 | 324 | ```console 325 | # - Use docker buildx bake to output image definitions from the "windows" bake target 326 | # - Convert with yq to the format expected by docker compose 327 | # - Store the result in the docker compose file 328 | 329 | $ docker buildx bake --progress=plain --file=docker-bake.hcl windows --print ` 330 | | yq --prettyPrint '.target[] | del(.output) | {(. | key): {\"image\": .tags[0], \"build\": .}}' | yq '{\"services\": .}' ` 331 | | Out-File -FilePath build-windows_mybuild.yaml 332 | ``` 333 | 334 | Note that you don't need build.ps1 to build (or to publish) your images from this docker compose file, you can use `docker compose --file=build-windows_mybuild.yaml build`. 335 | 336 | #### Testing all images 337 | 338 | Run `.\build.ps1 test` if you also want to run the tests harness suit. 339 | 340 | Run `.\build.ps1 test -TestsDebug 'debug'` to also get commands & stderr of tests, displayed on top of them. 341 | You can set it to `'verbose'` to also get stdout of every test command. 342 | 343 | Note that instead of passing `-TestsDebug` parameter to build.ps1, you can set the $env:TESTS_DEBUG environment variable to the desired value. 344 | 345 | Also note that contrary to the Linux part, you have to build the images before testing them. 346 | 347 | #### Golden files 348 | 349 | A golden file (sometimes called a snapshot) is a file that contains the expected output of a program or function. 350 | Tests compare the current output of the code against this "golden" reference to detect regressions or unintended changes. 351 | They are treated as contract artifacts, not test fixtures. 352 | 353 | Golden files may be updated only when: 354 | - Output behavior is intentionally changed 355 | - A bug fix corrects previously incorrect output 356 | - A new test case is added 357 | 358 | Golden updates must be reviewed like code. 359 | 360 | If your work implies golden file changes, those changes must be committed: 361 | - In the same commit as the behavior change 362 | - With a commit message explaining why output changed 363 | 364 | Golden updates must never be automatic. 365 | 366 | ##### How to update a golden file 367 | - Reproduce output manually 368 | - Inspect output 369 | - Update the golden file explicitly 370 | - Run tests 371 | 372 | To update a golden file, you can use the dedicated ./tests/update-golden-file.sh helper script. 373 | 374 | Example: 375 | ``` 376 | ./tests/update-golden-file.sh expected_tags_linux make tags-linux 377 | ``` 378 | 379 | Then ensure corresponding tests are passing with: 380 | ``` 381 | bats ./tests/tags.bats 382 | ``` 383 | 384 | #### Dry run 385 | 386 | Add the `-DryRun` parameter to print out any build, publish or tests commands instead of executing them: `.\build.ps1 test -DryRun` 387 | 388 | #### Building and testing a specific image 389 | 390 | You can build (and test) only one image type by setting `-ImageType` to a combination of Windows flavors ("nanoserver" & "windowsservercore") and Windows versions ("1809", "ltsc2019", "ltsc2022"). 391 | 392 | Ex: `.\build.ps1 -ImageType 'nanoserver-ltsc2019'` 393 | 394 | Warning: trying to build `windowsservercore-1809` will fail as there is no corresponding image from Microsoft. 395 | 396 | You can also build (and test) only one agent type by setting `-AgentType` to either "agent" or "inbound-agent". 397 | 398 | Ex: `.\build.ps1 -AgentType 'agent'` 399 | 400 | Both parameters can be combined. 401 | --------------------------------------------------------------------------------