diff --git a/.github/workflows/update-dockerhub-description.yaml b/.github/workflows/update-dockerhub-description.yaml new file mode 100644 index 000000000..bba896fe4 --- /dev/null +++ b/.github/workflows/update-dockerhub-description.yaml @@ -0,0 +1,29 @@ +name: Update Docker Hub Description +on: + release: + types: [ published ] + workflow_dispatch: + +jobs: + dockerHubDescription: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Update Docker Hub description for agent + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + enable-url-completion: true + short-description: This is a base image, which provides the Jenkins agent executable (agent.jar) + repository: jenkins/agent + readme-filepath: ./README_agent.md + - name: Update Docker Hub description for inbound-agent + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + enable-url-completion: true + short-description: This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller + repository: jenkins/inbound-agent + readme-filepath: ./README_inbound-agent.md diff --git a/.gitignore b/.gitignore index 76ff725db..069b0c8f1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ bats-core/ bats/ target/ +build-windows-current.yaml diff --git a/Jenkinsfile b/Jenkinsfile index ea8a82ff3..557a5f002 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,7 +69,7 @@ pipeline { } post { always { - junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results.xml') + junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results*.xml') } } } diff --git a/Makefile b/Makefile index 3ad48865b..f55418549 100644 --- a/Makefile +++ b/Makefile @@ -10,12 +10,6 @@ export BUILDKIT_PROGRESS=plain current_arch := $(shell uname -m) 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) -IMAGE_NAME:=jenkins4eval/agent - -# Set to the path of a specific test suite to restrict execution only to this -# default is "all test suites in the "tests/" directory -TEST_SUITES ?= $(CURDIR)/tests - ##### Macros ## Check the presence of a CLI in the current PATH check_cli = type "$(1)" >/dev/null 2>&1 || { echo "Error: command '$(1)' required but not found. Exiting." ; exit 1 ; } @@ -42,6 +36,7 @@ build: check-reqs build-%: @$(call check_image,$*) + @echo "== building $*" @set -x; $(bake_base_cli) --set '*.platform=linux/$(ARCH)' '$*' show: @@ -59,7 +54,7 @@ prepare-test: bats check-reqs ## Define bats options based on environment # common flags for all tests -bats_flags := $(TEST_SUITES) +bats_flags := "" # if DISABLE_PARALLEL_TESTS true, then disable parallel execution ifneq (true,$(DISABLE_PARALLEL_TESTS)) # If the GNU 'parallel' command line is absent, then disable parallel execution @@ -75,9 +70,10 @@ test-%: prepare-test @$(call check_image,$*) # Ensure that the image is built @make --silent build-$* -# Execute the test harness and write result to a TAP file + @echo "== testing $*" set -x - IMAGE=$* bats/bin/bats $(bats_flags) | tee target/results-$*.tap +# Each type of image ("agent" or "inbound-agent") has its own tests suite + IMAGE=$* bats/bin/bats $(CURDIR)/tests/tests_$(shell echo $* | cut -d "_" -f 1).bats $(bats_flags) | tee target/results-$*.tap # convert TAP to JUNIT docker run --rm -v "$(CURDIR)":/usr/src/app -w /usr/src/app node:16-alpine \ sh -c "npm install tap-xunit -g && cat target/results-$*.tap | tap-xunit --package='jenkinsci.docker.$*' > target/junit-results-$*.xml" diff --git a/README.md b/README.md index 993f4595c..6b43ca1ce 100644 --- a/README.md +++ b/README.md @@ -1,135 +1,21 @@ -# Jenkins Agent Docker image +# Jenkins Agent and Inbound Agent Docker images [![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) [![GitHub stars](https://img.shields.io/github/stars/jenkinsci/docker-agent?label=GitHub%20stars)](https://github.com/jenkinsci/docker-agent) -[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/agent.svg)](https://hub.docker.com/r/jenkins/agent/) [![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-agent/releases/latest) -This is a base image for Docker, which includes JDK and the Jenkins agent executable (agent.jar). -This executable is an instance of the [Jenkins Remoting library](https://github.com/jenkinsci/remoting). -JDK version depends on the image and the platform, see the _Configurations_ section below. - -:exclamation: **Warning!** This image used to be published as [jenkinsci/slave](https://hub.docker.com/r/jenkinsci/slave/) and [jenkins/slave](https://hub.docker.com/r/jenkins/slave/). -These images are now deprecated, use [jenkins/agent](https://hub.docker.com/r/jenkins/agent/). - -## Changelog - -See [GitHub releases](https://github.com/jenkinsci/docker-agent/releases) for versions `3.35-1` and above. -There is no changelog for previous versions, see the commit history. - -Jenkins remoting changelogs are available [here](https://github.com/jenkinsci/remoting/releases). - -## Usage - -This image is used as the basis for the [Docker Inbound Agent](https://github.com/jenkinsci/docker-inbound-agent/) image. -In that image, the container is launched externally and attaches to Jenkins. - -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 - -```sh -docker run -i --rm --name agent --init jenkins/agent java -jar /usr/share/jenkins/agent.jar -``` - -after setting **Remote root directory** to `/home/jenkins/agent`. - -or if using Windows Containers - -```powershell -docker run -i --rm --name agent --init jenkins/agent:jdk17-windowsservercore-ltsc2019 java -jar C:/ProgramData/Jenkins/agent.jar -``` - -after setting **Remote root directory** to `C:\Users\jenkins\Agent`. - -### Agent Work Directories - -Starting from [Remoting 3.8](https://github.com/jenkinsci/remoting/blob/master/CHANGELOG.md#38) there is a support of Work directories, -which provides logging by default and change the JAR Caching behavior. +This repository contains the definition of two images: -Call example for Linux: - -```sh -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 -``` - -Call example for Windows Containers: - -```powershell -docker run -i --rm --name agent1 --init -v agent1-workdir:C:/Users/jenkins/Work jenkins/agent:jdk11-windowsservercore-ltsc2019 java -jar C:/ProgramData/Jenkins/agent.jar -workDir C:/Users/jenkins/Work -``` - -## Configurations - -The image has several supported configurations, which can be accessed via the following tags: - -* Linux Images: - * `latest` (`jdk17`, `bookworm-jdk17`, `latest-bookworm`, `latest-bookworm-jdk17`, `latest-jdk17`): Latest version with the newest remoting and JDK17 (based on `debian:bookworm-${builddate}`) - * `alpine` (`alpine-jdk17`, `latest-alpine`, `latest-alpine-jdk17`): Small image based on Alpine Linux with JDK17 (based on `alpine:${version}`) - * `archlinux` (`archlinux-jdk11`, `latest-archlinux`, `latest-archlinux-jdk11`): Image based on Arch Linux with JDK11 (based on `archlinux:latest`) - * `bookworm-jdk11` (`latest-bookworm-jdk11`, `latest-jdk11`): JDK11 version with the newest remoting (based on `debian:bookworm-${builddate}`) - * `alpine-jdk11` (`latest-alpine-jdk11`): Small image based on Alpine Linux with JDK11 (based on `alpine:${version}`) - -From version 4.11.2, the alpine images are tagged using the alpine OS version as well (i.e. `alpine` ==> `alpine3.16`, `alpine-jdk11` ==> `alpine3.16-jdk11`). - -* Windows Images: - * JDK11: - * `jdk11-nanoserver-1809`: Latest version with the newest remoting with Windows Nano Server and Java 11 (based on `mcr.microsoft.com/windows/nanoserver:1809` and `eclipse-temurin:11.xxx-jdk-nanoserver-1809`) - * `jdk11-nanoserver-ltsc2019`: Latest version with the newest remoting with Windows Nano Server and Java 11 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2019` and `eclipse-temurin:11.xxx-jdk-nanoserver-1809`) - * `jdk11-nanoserver-ltsc2022`: Latest version with the newest remoting with Windows Nano Server and Java 11 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2022` and `eclipse-temurin:11.xxx-jdk-nanoserver-ltsc2022`) - * `jdk11-windowsservercore-1809`: Latest version with the newest remoting and Java 11 (based on `mcr.microsoft.com/windows/servercore:1809` and `eclipse-temurin:11.xxx-jdk-windowsservercore-1809`) - * `jdk11-windowsservercore-ltsc2019`: Latest version with the newest remoting and Java 11 (based on `mcr.microsoft.com/windows/servercore:ltsc2019` and `eclipse-temurin:11.xxx-jdk-windowsservercore-1809`) - * `jdk11-windowsservercore-ltsc2022`: Latest version with the newest remoting and Java 11 (based on `mcr.microsoft.com/windows/servercore:ltsc2022` and `eclipse-temurin:11.xxx-jdk-windowsservercore-ltsc2022`) - * JDK17 (default): - * `jdk17-nanoserver-1809` (`nanoserver-1809`): Latest version with the newest remoting with Windows Nano Server and Java 17 (based on `mcr.microsoft.com/windows/nanoserver:1809` and `eclipse-temurin:17.xxx-jdk-nanoserver-1809`) - * `jdk17-nanoserver-ltsc2019` (`nanoserver-ltsc2019`): Latest version with the newest remoting with Windows Nano Server and Java 17 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2019` and `eclipse-temurin:17.xxx-jdk-nanoserver-1809`) - * `jdk17-nanoserver-ltsc2022` (`nanoserver-ltsc2022`): Latest version with the newest remoting with Windows Nano Server and Java 17 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2022` and `eclipse-temurin:17.xxx-jdk-nanoserver-ltsc2022`) - * `jdk17-windowsservercore-1809` (`windowsservercore-1809`): Latest version with the newest remoting and Java 17 (based on `mcr.microsoft.com/windows/servercore:1809` and `eclipse-temurin:17.xxx-jdk-windowsservercore-1809`) - * `jdk17-windowsservercore-ltsc2019` (`windowsservercore-ltsc2019`): Latest version with the newest remoting and Java 17 (based on `mcr.microsoft.com/windows/servercore:ltsc2019` and `eclipse-temurin:17.xxx-jdk-windowsservercore-1809`) - * `jdk17-windowsservercore-ltsc2022` (`windowsservercore-ltsc2022`): Latest version with the newest remoting and Java 17 (based on `mcr.microsoft.com/windows/servercore:ltsc2022` and `eclipse-temurin:17.xxx-jdk-windowsservercore-ltsc2022`) - * JDK21: - * `jdk21-nanoserver-1809`: Latest version with the newest remoting with Windows Nano Server and Java 21 (based on `mcr.microsoft.com/windows/nanoserver:1809` and `eclipse-temurin:21.xxx-jdk-nanoserver-1809`) - * `jdk21-nanoserver-ltsc2019`: Latest version with the newest remoting with Windows Nano Server and Java 21 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2019` and `eclipse-temurin:21.xxx-jdk-nanoserver-1809`) - * `jdk21-nanoserver-ltsc2022`: Latest version with the newest remoting with Windows Nano Server and Java 21 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2022` and `eclipse-temurin:21.xxx-jdk-nanoserver-ltsc2022`) - * `jdk21-windowsservercore-1809`: Latest version with the newest remoting and Java 21 (based on `mcr.microsoft.com/windows/servercore:1809` and `eclipse-temurin:21.xxx-jdk-windowsservercore-1809`) - * `jdk21-windowsservercore-ltsc2019`: Latest version with the newest remoting and Java 21 (based on `mcr.microsoft.com/windows/servercore:ltsc2019` and `eclipse-temurin:21.xxx-jdk-windowsservercore-1809`) - * `jdk21-windowsservercore-ltsc2022`: Latest version with the newest remoting and Java 21 (based on `mcr.microsoft.com/windows/servercore:ltsc2022` and `eclipse-temurin:21.xxx-jdk-windowsservercore-ltsc2022`) - -The file `docker-bake.hcl` defines all the configuration for Linux images and their associated tags. - -There are also versioned tags in DockerHub, and they are recommended for production use. -See the full list [here](https://hub.docker.com/r/jenkins/agent/tags) - -## Timezones - -### Using directly the `jenkins/agent` image - -By default, the image is using the `Etc/UTC` timezone. -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. -In this example, the machine is using the `Europe/Paris` timezone. - -```bash -docker run --rm --tty --interactive --entrypoint=date --volume=/etc/localtime:/etc/localtime:ro --volume=/etc/timezone:/etc/timezone:ro jenkins/agent -Fri Nov 25 18:27:22 CET 2022 -``` +## agent +[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/agent.svg)](https://hub.docker.com/r/jenkins/agent/) -You can also set the `TZ` environment variable to the desired timezone. -`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. -The next command is run on a machine using the `Europe/Paris` timezone a few seconds after the previous one. +This is a base image for Docker, which includes JDK and the Jenkins agent executable (agent.jar). -```bash -docker run --rm --tty --interactive --env TZ=Asia/Shanghai --entrypoint=date jenkins/agent -Sat Nov 26 01:27:58 CST 2022 -``` +See [the `agent` README](./README_agent.md) -### Using the `jenkins/agent` image as a base image +## inbound-agent +[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/inbound-agent.svg)](https://hub.docker.com/r/jenkins/inbound-agent/) -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)): +This is an image based on `agent` for [Jenkins](https://jenkins.io) agents using TCP or WebSockets to establish inbound connection to the Jenkins master. -```dockerfile -FROM jenkins/agent as agent - [...] -ENV TZ=Asia/Shanghai - [...] -RUN ln -snf /usr/share/zoneinfo/"${TZ}" /etc/localtime && echo "${TZ}" > /etc/timezone \ - && dpkg-reconfigure -f noninteractive tzdata \ - [...] -``` +See [the `inbound-agent` README](./README_inbound-agent.md) diff --git a/README_agent.md b/README_agent.md new file mode 100644 index 000000000..993f4595c --- /dev/null +++ b/README_agent.md @@ -0,0 +1,135 @@ +# Jenkins Agent Docker image + +[![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) +[![GitHub stars](https://img.shields.io/github/stars/jenkinsci/docker-agent?label=GitHub%20stars)](https://github.com/jenkinsci/docker-agent) +[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/agent.svg)](https://hub.docker.com/r/jenkins/agent/) +[![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-agent/releases/latest) + +This is a base image for Docker, which includes JDK and the Jenkins agent executable (agent.jar). +This executable is an instance of the [Jenkins Remoting library](https://github.com/jenkinsci/remoting). +JDK version depends on the image and the platform, see the _Configurations_ section below. + +:exclamation: **Warning!** This image used to be published as [jenkinsci/slave](https://hub.docker.com/r/jenkinsci/slave/) and [jenkins/slave](https://hub.docker.com/r/jenkins/slave/). +These images are now deprecated, use [jenkins/agent](https://hub.docker.com/r/jenkins/agent/). + +## Changelog + +See [GitHub releases](https://github.com/jenkinsci/docker-agent/releases) for versions `3.35-1` and above. +There is no changelog for previous versions, see the commit history. + +Jenkins remoting changelogs are available [here](https://github.com/jenkinsci/remoting/releases). + +## Usage + +This image is used as the basis for the [Docker Inbound Agent](https://github.com/jenkinsci/docker-inbound-agent/) image. +In that image, the container is launched externally and attaches to Jenkins. + +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 + +```sh +docker run -i --rm --name agent --init jenkins/agent java -jar /usr/share/jenkins/agent.jar +``` + +after setting **Remote root directory** to `/home/jenkins/agent`. + +or if using Windows Containers + +```powershell +docker run -i --rm --name agent --init jenkins/agent:jdk17-windowsservercore-ltsc2019 java -jar C:/ProgramData/Jenkins/agent.jar +``` + +after setting **Remote root directory** to `C:\Users\jenkins\Agent`. + +### Agent Work Directories + +Starting from [Remoting 3.8](https://github.com/jenkinsci/remoting/blob/master/CHANGELOG.md#38) there is a support of Work directories, +which provides logging by default and change the JAR Caching behavior. + +Call example for Linux: + +```sh +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 +``` + +Call example for Windows Containers: + +```powershell +docker run -i --rm --name agent1 --init -v agent1-workdir:C:/Users/jenkins/Work jenkins/agent:jdk11-windowsservercore-ltsc2019 java -jar C:/ProgramData/Jenkins/agent.jar -workDir C:/Users/jenkins/Work +``` + +## Configurations + +The image has several supported configurations, which can be accessed via the following tags: + +* Linux Images: + * `latest` (`jdk17`, `bookworm-jdk17`, `latest-bookworm`, `latest-bookworm-jdk17`, `latest-jdk17`): Latest version with the newest remoting and JDK17 (based on `debian:bookworm-${builddate}`) + * `alpine` (`alpine-jdk17`, `latest-alpine`, `latest-alpine-jdk17`): Small image based on Alpine Linux with JDK17 (based on `alpine:${version}`) + * `archlinux` (`archlinux-jdk11`, `latest-archlinux`, `latest-archlinux-jdk11`): Image based on Arch Linux with JDK11 (based on `archlinux:latest`) + * `bookworm-jdk11` (`latest-bookworm-jdk11`, `latest-jdk11`): JDK11 version with the newest remoting (based on `debian:bookworm-${builddate}`) + * `alpine-jdk11` (`latest-alpine-jdk11`): Small image based on Alpine Linux with JDK11 (based on `alpine:${version}`) + +From version 4.11.2, the alpine images are tagged using the alpine OS version as well (i.e. `alpine` ==> `alpine3.16`, `alpine-jdk11` ==> `alpine3.16-jdk11`). + +* Windows Images: + * JDK11: + * `jdk11-nanoserver-1809`: Latest version with the newest remoting with Windows Nano Server and Java 11 (based on `mcr.microsoft.com/windows/nanoserver:1809` and `eclipse-temurin:11.xxx-jdk-nanoserver-1809`) + * `jdk11-nanoserver-ltsc2019`: Latest version with the newest remoting with Windows Nano Server and Java 11 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2019` and `eclipse-temurin:11.xxx-jdk-nanoserver-1809`) + * `jdk11-nanoserver-ltsc2022`: Latest version with the newest remoting with Windows Nano Server and Java 11 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2022` and `eclipse-temurin:11.xxx-jdk-nanoserver-ltsc2022`) + * `jdk11-windowsservercore-1809`: Latest version with the newest remoting and Java 11 (based on `mcr.microsoft.com/windows/servercore:1809` and `eclipse-temurin:11.xxx-jdk-windowsservercore-1809`) + * `jdk11-windowsservercore-ltsc2019`: Latest version with the newest remoting and Java 11 (based on `mcr.microsoft.com/windows/servercore:ltsc2019` and `eclipse-temurin:11.xxx-jdk-windowsservercore-1809`) + * `jdk11-windowsservercore-ltsc2022`: Latest version with the newest remoting and Java 11 (based on `mcr.microsoft.com/windows/servercore:ltsc2022` and `eclipse-temurin:11.xxx-jdk-windowsservercore-ltsc2022`) + * JDK17 (default): + * `jdk17-nanoserver-1809` (`nanoserver-1809`): Latest version with the newest remoting with Windows Nano Server and Java 17 (based on `mcr.microsoft.com/windows/nanoserver:1809` and `eclipse-temurin:17.xxx-jdk-nanoserver-1809`) + * `jdk17-nanoserver-ltsc2019` (`nanoserver-ltsc2019`): Latest version with the newest remoting with Windows Nano Server and Java 17 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2019` and `eclipse-temurin:17.xxx-jdk-nanoserver-1809`) + * `jdk17-nanoserver-ltsc2022` (`nanoserver-ltsc2022`): Latest version with the newest remoting with Windows Nano Server and Java 17 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2022` and `eclipse-temurin:17.xxx-jdk-nanoserver-ltsc2022`) + * `jdk17-windowsservercore-1809` (`windowsservercore-1809`): Latest version with the newest remoting and Java 17 (based on `mcr.microsoft.com/windows/servercore:1809` and `eclipse-temurin:17.xxx-jdk-windowsservercore-1809`) + * `jdk17-windowsservercore-ltsc2019` (`windowsservercore-ltsc2019`): Latest version with the newest remoting and Java 17 (based on `mcr.microsoft.com/windows/servercore:ltsc2019` and `eclipse-temurin:17.xxx-jdk-windowsservercore-1809`) + * `jdk17-windowsservercore-ltsc2022` (`windowsservercore-ltsc2022`): Latest version with the newest remoting and Java 17 (based on `mcr.microsoft.com/windows/servercore:ltsc2022` and `eclipse-temurin:17.xxx-jdk-windowsservercore-ltsc2022`) + * JDK21: + * `jdk21-nanoserver-1809`: Latest version with the newest remoting with Windows Nano Server and Java 21 (based on `mcr.microsoft.com/windows/nanoserver:1809` and `eclipse-temurin:21.xxx-jdk-nanoserver-1809`) + * `jdk21-nanoserver-ltsc2019`: Latest version with the newest remoting with Windows Nano Server and Java 21 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2019` and `eclipse-temurin:21.xxx-jdk-nanoserver-1809`) + * `jdk21-nanoserver-ltsc2022`: Latest version with the newest remoting with Windows Nano Server and Java 21 (based on `mcr.microsoft.com/windows/nanoserver:ltsc2022` and `eclipse-temurin:21.xxx-jdk-nanoserver-ltsc2022`) + * `jdk21-windowsservercore-1809`: Latest version with the newest remoting and Java 21 (based on `mcr.microsoft.com/windows/servercore:1809` and `eclipse-temurin:21.xxx-jdk-windowsservercore-1809`) + * `jdk21-windowsservercore-ltsc2019`: Latest version with the newest remoting and Java 21 (based on `mcr.microsoft.com/windows/servercore:ltsc2019` and `eclipse-temurin:21.xxx-jdk-windowsservercore-1809`) + * `jdk21-windowsservercore-ltsc2022`: Latest version with the newest remoting and Java 21 (based on `mcr.microsoft.com/windows/servercore:ltsc2022` and `eclipse-temurin:21.xxx-jdk-windowsservercore-ltsc2022`) + +The file `docker-bake.hcl` defines all the configuration for Linux images and their associated tags. + +There are also versioned tags in DockerHub, and they are recommended for production use. +See the full list [here](https://hub.docker.com/r/jenkins/agent/tags) + +## Timezones + +### Using directly the `jenkins/agent` image + +By default, the image is using the `Etc/UTC` timezone. +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. +In this example, the machine is using the `Europe/Paris` timezone. + +```bash +docker run --rm --tty --interactive --entrypoint=date --volume=/etc/localtime:/etc/localtime:ro --volume=/etc/timezone:/etc/timezone:ro jenkins/agent +Fri Nov 25 18:27:22 CET 2022 +``` + +You can also set the `TZ` environment variable to the desired timezone. +`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. +The next command is run on a machine using the `Europe/Paris` timezone a few seconds after the previous one. + +```bash +docker run --rm --tty --interactive --env TZ=Asia/Shanghai --entrypoint=date jenkins/agent +Sat Nov 26 01:27:58 CST 2022 +``` + +### Using the `jenkins/agent` image as a base image + +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)): + +```dockerfile +FROM jenkins/agent as agent + [...] +ENV TZ=Asia/Shanghai + [...] +RUN ln -snf /usr/share/zoneinfo/"${TZ}" /etc/localtime && echo "${TZ}" > /etc/timezone \ + && dpkg-reconfigure -f noninteractive tzdata \ + [...] +``` diff --git a/README_inbound-agent.md b/README_inbound-agent.md new file mode 100644 index 000000000..d6621f0d4 --- /dev/null +++ b/README_inbound-agent.md @@ -0,0 +1,111 @@ +# Docker image for inbound Jenkins agents + +[![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) +[![GitHub stars](https://img.shields.io/github/stars/jenkinsci/docker-inbound-agent?label=GitHub%20stars)](https://github.com/jenkinsci/docker-inbound-agent) +[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/inbound-agent.svg)](https://hub.docker.com/r/jenkins/inbound-agent/) +[![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-inbound-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-inbound-agent/releases/latest) + +: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/). +These images are deprecated, use [jenkins/inbound-agent](https://hub.docker.com/r/jenkins/inbound-agent/). + +This is an image for [Jenkins](https://jenkins.io) agents using TCP or WebSockets to establish inbound connection to the Jenkins master. +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/) image. + +See [Using Agents](https://www.jenkins.io/doc/book/using/using-agents/) for more info. + +## Configuring agents with this container image + +### Setup the agent on Jenkins + +1. Go to your Jenkins dashboard +2. Go to `Manage Jenkins` option in main menu +3. Go to `Nodes` item in `System Configuration` + ![image](images/screen-4.png) +4. Go to `New Node` option in side menu +5. Fill the Node(agent) name and select the type; (e.g. Name: agent1, Type: Permanent Agent) +6. Now fill the fields like remote root directory, labels, # of executors, etc. + * **`Launch method` is `Launch agent by connecting it to the controller`** + ![image](images/screen-1.png) +7. Press the `Save` button and the agent1 will be registered, but offline for the time being. Click on it. + ![image](images/screen-2.png) +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. + ![image](images/screen-3.png) + +### Running this container + +To run a Docker container + > **Note** + > 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). + > 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. + + Linux agent: + + docker run --init jenkins/inbound-agent -url http://jenkins-server:port + Note: `--init` is necessary for correct subprocesses handling (zombie reaping) + + Windows agent: + + docker run jenkins/inbound-agent:windowsservercore-ltsc2019 -Url http://jenkins-server:port -Secret -Name + +To run a Docker container with [Work Directory](https://github.com/jenkinsci/remoting/blob/master/docs/workDir.md) + + Linux agent: + + docker run --init jenkins/inbound-agent -url http://jenkins-server:port -workDir=/home/jenkins/agent + + Windows agent: + + docker run jenkins/inbound-agent:windowsservercore-ltsc2019 -Url http://jenkins-server:port -WorkDir=C:/Jenkins/agent -Secret -Name + +Optional environment variables: + +* `JENKINS_JAVA_BIN`: Path to Java executable to use instead of the default in PATH or obtained from JAVA_HOME +* `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). +* `JENKINS_URL`: url for the Jenkins server, can be used as a replacement to `-url` option, or to set alternate jenkins URL +* `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. +* `JENKINS_SECRET`: (use only if not set as an argument) the secret as shown on the controller after creating the agent +* `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 +* `JENKINS_AGENT_WORKDIR`: agent work directory, if not set by optional parameter `-workDir` +* `JENKINS_WEB_SOCKET`: `true` if the connection should be made via WebSocket rather than TCP +* `JENKINS_DIRECT_CONNECTION`: (`HOST:PORT`) Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. +* `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. +* `JENKINS_PROTOCOLS`: Specify the remoting protocols to attempt when `JENKINS_INSTANCE_IDENTITY` is provided. + +#### Example + +1. Enter the command above. + ![image](images/screen-5.png) +2. Check the Jenkins dashboard if the agent is connected well. + ![image](images/screen-6.png) + + +## Windows Jenkins Java Opts + +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. +For example: + +-XX:+PrintCommandLineFlags --show-version + +This would need to be escaped with quotes like this: + +"-XX:+PrintCommandLineFlags" --show-version + +Or another example: +-Dsome.property=some value --show-version + +This would need to be escaped like this: + +"-Dsome.property='some value'" --show-version + + +## Configuration specifics + +### Enabled JNLP protocols + +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. +Earlier, long-unsupported protocols have been removed. +As a result, Jenkins versions prior to 2.32 are no longer supported. + +### Amazon ECS + +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. diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 0e9c4e4c1..0fa14a319 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -40,7 +40,8 @@ RUN if [ "$TARGETPLATFORM" != 'linux/arm/v7' ]; then \ cp -r /opt/java/openjdk /javaruntime; \ fi -FROM alpine:"${ALPINE_TAG}" AS build +## Agent image target +FROM alpine:"${ALPINE_TAG}" AS agent ARG user=jenkins ARG group=jenkins @@ -48,7 +49,7 @@ ARG uid=1000 ARG gid=1000 RUN addgroup -g "${gid}" "${group}" \ - && adduser -h /home/"${user}" -u "${uid}" -G "${group}" -D "${user}" + && adduser -h /home/"${user}" -u "${uid}" -G "${group}" -D "${user}" || echo "user ${user} already exists." ARG AGENT_WORKDIR=/home/"${user}"/agent @@ -82,7 +83,7 @@ ENV PATH="${JAVA_HOME}/bin:${PATH}" USER "${user}" ENV AGENT_WORKDIR="${AGENT_WORKDIR}" -RUN mkdir /home/"${user}"/.jenkins && mkdir -p "${AGENT_WORKDIR}" +RUN mkdir -p /home/"${user}"/.jenkins && mkdir -p "${AGENT_WORKDIR}" VOLUME /home/"${user}"/.jenkins VOLUME "${AGENT_WORKDIR}" @@ -96,3 +97,25 @@ LABEL \ org.opencontainers.image.url="https://www.jenkins.io/" \ org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ org.opencontainers.image.licenses="MIT" + +## Inbound Agent image target +FROM agent AS inbound-agent + +ARG user=jenkins + +USER root +COPY ../../jenkins-agent /usr/local/bin/jenkins-agent +RUN chmod +x /usr/local/bin/jenkins-agent &&\ + ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave +USER ${user} + +LABEL \ + org.opencontainers.image.vendor="Jenkins project" \ + org.opencontainers.image.title="Official Jenkins Agent Base Docker image" \ + org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.url="https://www.jenkins.io/" \ + org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ + org.opencontainers.image.licenses="MIT" + +ENTRYPOINT ["/usr/local/bin/jenkins-agent"] diff --git a/build.ps1 b/build.ps1 index 508b61d26..8bb257791 100644 --- a/build.ps1 +++ b/build.ps1 @@ -3,15 +3,21 @@ Param( [Parameter(Position=1)] [String] $Target = "build", [String] $RemotingVersion = '3206.vb_15dcf73f6a_9', + [String] $AgentType = '', [String] $BuildNumber = '1', [switch] $DisableEnvProps = $false, [switch] $DryRun = $false ) $ErrorActionPreference = 'Stop' -$Repository = 'agent' -$Organization = 'jenkins' -$ImageType = 'windows-ltsc2019' +$AgentTypes = @('agent', 'inbound-agent') +if ($AgentType -ne '' -and $AgentType -in $AgentTypes) { + $AgentTypes = @($AgentType) +} +$ImageType = 'windowsservercore-ltsc2019' +$Organisation = 'jenkins4eval' +$AgentRepository = 'agent' +$InboundAgentRepository = 'inbound-agent' if(!$DisableEnvProps) { Get-Content env.props | ForEach-Object { @@ -24,12 +30,16 @@ if(!$DisableEnvProps) { } } -if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO)) { - $Repository = $env:DOCKERHUB_REPO +if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_ORGANISATION)) { + $Organisation = $env:DOCKERHUB_ORGANISATION } -if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_ORGANISATION)) { - $Organization = $env:DOCKERHUB_ORGANISATION +if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO_AGENT)) { + $AgentRepository = $env:DOCKERHUB_REPO_AGENT +} + +if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO_INBOUND_AGENT)) { + $InboundAgentRepository = $env:DOCKERHUB_REPO_INBOUND_AGENT } if(![String]::IsNullOrWhiteSpace($env:REMOTING_VERSION)) { @@ -64,7 +74,6 @@ Function Test-CommandExists { # this is the jdk version that will be used for the 'bare tag' images, e.g., jdk17-windowsservercore-1809 -> windowsserver-1809 $defaultJdk = '17' -$builds = @{} $env:REMOTING_VERSION = "$RemotingVersion" $items = $ImageType.Split("-") @@ -82,121 +91,41 @@ Test-CommandExists "docker" Test-CommandExists "docker-compose" Test-CommandExists "yq" -$baseDockerCmd = 'docker-compose --file=build-windows.yaml' -$baseDockerBuildCmd = '{0} build --parallel --pull' -f $baseDockerCmd - -Invoke-Expression "$baseDockerCmd config --services" 2>$null | ForEach-Object { - $image = '{0}-{1}-{2}' -f $_, $env:WINDOWS_FLAVOR, $env:WINDOWS_VERSION_TAG # Ex: "jdk17-nanoserver-1809" - - # Remove the 'jdk' prefix - $jdkMajorVersion = $_.Remove(0,3) - - $versionTag = "${RemotingVersion}-${BuildNumber}-${image}" - $tags = @( $image, $versionTag ) - - # Additional image tag without any 'jdk' prefix for the default JDK - $baseImage = "${env:WINDOWS_FLAVOR}-${env:WINDOWS_VERSION_TAG}" - if($jdkMajorVersion -eq "$defaultJdk") { - $tags += $baseImage - $tags += "${RemotingVersion}-${BuildNumber}-${baseImage}" - } - - $builds[$image] = @{ - 'Tags' = $tags; - } -} - -Write-Host "= PREPARE: List of $Organization/$Repository images and tags to be processed:" -ConvertTo-Json $builds - -Write-Host "= BUILD: Building all images..." -if ($DryRun) { - Write-Host "(dry-run) $baseDockerBuildCmd" -} else { - Invoke-Expression $baseDockerBuildCmd -} -Write-Host "= BUILD: Finished building all images." - -if($lastExitCode -ne 0) { - exit $lastExitCode -} - function Test-Image { param ( - $ImageName + $AgentTypeAndImageName ) - Write-Host "= TEST: Testing image ${ImageName}:" + $items = $AgentTypeAndImageName.Split("|") + $agentType = $items[0] + $imageName = $items[1] - $env:AGENT_IMAGE = $ImageName - $serviceName = $ImageName.SubString(0, $ImageName.IndexOf('-')) - $env:BUILD_CONTEXT = Invoke-Expression "$baseDockerCmd config" 2>$null | yq -r ".services.${serviceName}.build.context" + Write-Host "= TEST: Testing ${agentType} image ${imageName}:" + + $env:AGENT_TYPE = $agentType + $env:AGENT_IMAGE = $imageName $env:VERSION = "$RemotingVersion-$BuildNumber" - if(Test-Path ".\target\$ImageName") { - Remove-Item -Recurse -Force ".\target\$ImageName" + $targetPath = '.\target\{0}\{1}' -f $agentType, $imageName + if(Test-Path $targetPath) { + Remove-Item -Recurse -Force $targetPath } - New-Item -Path ".\target\$ImageName" -Type Directory | Out-Null - $configuration.TestResult.OutputPath = ".\target\$ImageName\junit-results.xml" + New-Item -Path $targetPath -Type Directory | Out-Null + $configuration.Run.Path = 'tests\{0}.Tests.ps1' -f $agentType + $configuration.TestResult.OutputPath = '{0}\junit-results.xml' -f $targetPath $TestResults = Invoke-Pester -Configuration $configuration if ($TestResults.FailedCount -gt 0) { - Write-Host "There were $($TestResults.FailedCount) failed tests in $ImageName" + Write-Host "There were $($TestResults.FailedCount) failed tests in ${agentType} $imageName" $testFailed = $true } else { - Write-Host "There were $($TestResults.PassedCount) passed tests out of $($TestResults.TotalCount) in $ImageName" + Write-Host "There were $($TestResults.PassedCount) passed tests out of $($TestResults.TotalCount) in ${agentType} $imageName" } + + Remove-Item env:\AGENT_TYPE Remove-Item env:\AGENT_IMAGE - Remove-Item env:\BUILD_CONTEXT Remove-Item env:\VERSION } -if($target -eq "test") { - if ($DryRun) { - Write-Host "= TEST: (dry-run) test harness" - } else { - Write-Host "= TEST: Starting test harness" - - # Only fail the run afterwards in case of any test failures - $testFailed = $false - $mod = Get-InstalledModule -Name Pester -MinimumVersion 5.3.0 -MaximumVersion 5.3.3 -ErrorAction SilentlyContinue - if($null -eq $mod) { - Write-Host "= TEST: Pester 5.3.x not found: installing..." - $module = "c:\Program Files\WindowsPowerShell\Modules\Pester" - if(Test-Path $module) { - takeown /F $module /A /R - icacls $module /reset - icacls $module /grant Administrators:'F' /inheritance:d /T - Remove-Item -Path $module -Recurse -Force -Confirm:$false - } - Install-Module -Force -Name Pester -MaximumVersion 5.3.3 - } - - Import-Module Pester - Write-Host "= TEST: Setting up Pester environment..." - $configuration = [PesterConfiguration]::Default - $configuration.Run.PassThru = $true - $configuration.Run.Path = '.\tests' - $configuration.Run.Exit = $true - $configuration.TestResult.Enabled = $true - $configuration.TestResult.OutputFormat = 'JUnitXml' - $configuration.Output.Verbosity = 'Diagnostic' - $configuration.CodeCoverage.Enabled = $false - - Write-Host "= TEST: Testing all images..." - foreach($image in $builds.Keys) { - Test-Image $image - } - - # Fail if any test failures - if($testFailed -ne $false) { - Write-Error "Test stage failed!" - exit 1 - } else { - Write-Host "= TEST: stage passed!" - } - } -} - function Publish-Image { param ( [String] $Build, @@ -214,22 +143,126 @@ function Publish-Image { } -if($target -eq "publish") { - # Only fail the run afterwards in case of any issues when publishing the docker images - $publishFailed = 0 - foreach($b in $builds.Keys) { - foreach($tag in $Builds[$b]['Tags']) { - Publish-Image "$b" "${Organization}/${Repository}:${tag}" - if($lastExitCode -ne 0) { - $publishFailed = 1 +$originalDockerComposeFile = 'build-windows.yaml' +$finalDockerComposeFile = 'build-windows-current.yaml' +$baseDockerCmd = 'docker-compose --file={0}' -f $finalDockerComposeFile +$baseDockerBuildCmd = '{0} build --parallel --pull' -f $baseDockerCmd + +foreach($agentType in $AgentTypes) { + $env:AGENT_TYPE = $agentType + + # Temporary docker compose file (git ignored) + Copy-Item -Path $originalDockerComposeFile -Destination $finalDockerComposeFile + $repository = $InboundAgentRepository + # If it's a type "agent", set corresponding target and repository + if($agentType -eq 'agent') { + yq '.services.[].build.target = \"agent\"' $originalDockerComposeFile | Out-File -FilePath $finalDockerComposeFile + $repository = $AgentRepository + } + + $builds = @{} + + Invoke-Expression "$baseDockerCmd config --services" 2>$null | ForEach-Object { + $image = '{0}-{1}-{2}' -f $_, $env:WINDOWS_FLAVOR, $env:WINDOWS_VERSION_TAG # Ex: "jdk17-nanoserver-1809" + + # Remove the 'jdk' prefix + $jdkMajorVersion = $_.Remove(0,3) + + $versionTag = "${RemotingVersion}-${BuildNumber}-${image}" + $tags = @( $image, $versionTag ) + + # Additional image tag without any 'jdk' prefix for the default JDK + $baseImage = "${env:WINDOWS_FLAVOR}-${env:WINDOWS_VERSION_TAG}" + if($jdkMajorVersion -eq "$defaultJdk") { + $tags += $baseImage + $tags += "${RemotingVersion}-${BuildNumber}-${baseImage}" + } + + $builds[$image] = @{ + 'Tags' = $tags; + } + } + + Write-Host "= PREPARE: List of $Organisation/$repository images and tags to be processed:" + ConvertTo-Json $builds + + Write-Host "= BUILD: Building all images..." + if ($DryRun) { + Write-Host "(dry-run) $baseDockerBuildCmd" + } else { + Invoke-Expression $baseDockerBuildCmd + } + Write-Host "= BUILD: Finished building all images." + + if($lastExitCode -ne 0) { + exit $lastExitCode + } + + if($target -eq "test") { + if ($DryRun) { + Write-Host "= TEST: (dry-run) test harness" + } else { + Write-Host "= TEST: Starting test harness" + + # Only fail the run afterwards in case of any test failures + $testFailed = $false + $mod = Get-InstalledModule -Name Pester -MinimumVersion 5.3.0 -MaximumVersion 5.3.3 -ErrorAction SilentlyContinue + if($null -eq $mod) { + Write-Host "= TEST: Pester 5.3.x not found: installing..." + $module = "c:\Program Files\WindowsPowerShell\Modules\Pester" + if(Test-Path $module) { + takeown /F $module /A /R + icacls $module /reset + icacls $module /grant Administrators:'F' /inheritance:d /T + Remove-Item -Path $module -Recurse -Force -Confirm:$false + } + Install-Module -Force -Name Pester -MaximumVersion 5.3.3 + } + + Import-Module Pester + Write-Host "= TEST: Setting up Pester environment..." + $configuration = [PesterConfiguration]::Default + $configuration.Run.PassThru = $true + $configuration.Run.Path = '.\tests' + $configuration.Run.Exit = $true + $configuration.TestResult.Enabled = $true + $configuration.TestResult.OutputFormat = 'JUnitXml' + $configuration.Output.Verbosity = 'Diagnostic' + $configuration.CodeCoverage.Enabled = $false + + Write-Host "= TEST: Testing all images..." + Write-Host "= TEST: Testing all ${agentType} images..." + foreach($image in $builds.Keys) { + Test-Image ('{0}|{1}' -f $agentType, $image) + } + + # Fail if any test failures + if($testFailed -ne $false) { + Write-Error "Test stage failed for ${agentType}!" + exit 1 + } else { + Write-Host "= TEST: stage passed for ${agentType}!" } } } - # Fail if any issues when publising the docker images - if($publishFailed -ne 0) { - Write-Error "Publish failed!" - exit 1 + if($target -eq "publish") { + # Only fail the run afterwards in case of any issues when publishing the docker images + $publishFailed = 0 + foreach($b in $builds.Keys) { + foreach($tag in $Builds[$b]['Tags']) { + Publish-Image "$b" "${Organisation}/${Repository}:${tag}" + if($lastExitCode -ne 0) { + $publishFailed = 1 + } + } + } + + # Fail if any issues when publising the docker images + if($publishFailed -ne 0) { + Write-Error "Publish failed for ${Organisation}/${repository}!" + exit 1 + } } } diff --git a/build.sh b/build.sh index f8963a3c7..1c3da8bcd 100755 --- a/build.sh +++ b/build.sh @@ -51,8 +51,9 @@ if [[ "${disable_env_props}" = "0" ]] ; then export "$(cut -d= -f1 env.props)" fi -REPOSITORY=${DOCKERHUB_REPO:-agent} -ORGANIZATION=${DOCKERHUB_ORGANISATION:-jenkins} +export REGISTRY_ORG=${DOCKERHUB_ORGANISATION:-jenkins4eval} +export REGISTRY_REPO_AGENT=${DOCKERHUB_REPO_AGENT:-agent} +export REGISTRY_REPO_INBOUND_AGENT=${DOCKERHUB_REPO_INBOUND_AGENT:-inbound-agent} remoting_version=${REMOTING_VERSION:-${remoting_version}} if [[ "${target}" = "build" ]] ; then @@ -72,7 +73,6 @@ fi if [[ "${target}" = "publish" ]] ; then set -x - export JENKINS_REPO="${ORGANIZATION}/${REPOSITORY}" export REMOTING_VERSION="${remoting_version}" if [[ -n "${build_number}" ]] ; then export ON_TAG=true diff --git a/debian/Dockerfile b/debian/Dockerfile index 26a5ee249..3333b261e 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -50,8 +50,8 @@ RUN if test "${TARGETPLATFORM}" != 'linux/arm/v7'; then \ else cp -r /opt/java/openjdk /javaruntime; \ fi - -FROM debian:"${DEBIAN_RELEASE}"-slim AS build +## Agent image target +FROM debian:"${DEBIAN_RELEASE}"-slim AS agent ARG user=jenkins ARG group=jenkins @@ -59,7 +59,7 @@ ARG uid=1000 ARG gid=1000 RUN groupadd -g "${gid}" "${group}" \ - && useradd -l -c "Jenkins user" -d /home/"${user}" -u "${uid}" -g "${gid}" -m "${user}" + && useradd -l -c "Jenkins user" -d /home/"${user}" -u "${uid}" -g "${gid}" -m "${user}" || echo "user ${user} already exists." ARG AGENT_WORKDIR=/home/"${user}"/agent ENV TZ=Etc/UTC @@ -94,7 +94,7 @@ ENV PATH="${JAVA_HOME}/bin:${PATH}" USER "${user}" ENV AGENT_WORKDIR=${AGENT_WORKDIR} -RUN mkdir /home/${user}/.jenkins && mkdir -p "${AGENT_WORKDIR}" +RUN mkdir -p /home/"${user}"/.jenkins && mkdir -p "${AGENT_WORKDIR}" VOLUME /home/"${user}"/.jenkins VOLUME "${AGENT_WORKDIR}" @@ -108,3 +108,25 @@ LABEL \ org.opencontainers.image.url="https://www.jenkins.io/" \ org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ org.opencontainers.image.licenses="MIT" + +## Inbound Agent image target +FROM agent AS inbound-agent + +ARG user=jenkins + +USER root +COPY ../../jenkins-agent /usr/local/bin/jenkins-agent +RUN chmod +x /usr/local/bin/jenkins-agent &&\ + ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave +USER ${user} + +LABEL \ + org.opencontainers.image.vendor="Jenkins project" \ + org.opencontainers.image.title="Official Jenkins Inbound Agent Base Docker image" \ + org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.url="https://www.jenkins.io/" \ + org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent-inbound" \ + org.opencontainers.image.licenses="MIT" + +ENTRYPOINT ["/usr/local/bin/jenkins-agent"] diff --git a/debian/preview/Dockerfile b/debian/preview/Dockerfile index b727cea5c..32a2caea1 100644 --- a/debian/preview/Dockerfile +++ b/debian/preview/Dockerfile @@ -40,7 +40,8 @@ RUN if test "${TARGETPLATFORM}" != 'linux/arm/v7'; then \ cp -r "/opt/jdk-${JAVA_VERSION}" /javaruntime; \ fi -FROM debian:"${DEBIAN_RELEASE}" AS build +## Agent image target +FROM debian:"${DEBIAN_RELEASE}" AS agent ARG user=jenkins ARG group=jenkins @@ -48,7 +49,7 @@ ARG uid=1000 ARG gid=1000 RUN groupadd -g "${gid}" "${group}" \ - && useradd -l -c "Jenkins user" -d /home/"${user}" -u "${uid}" -g "${gid}" -m "${user}" + && useradd -l -c "Jenkins user" -d /home/"${user}" -u "${uid}" -g "${gid}" -m "${user}" || echo "user ${user} already exists." ARG AGENT_WORKDIR=/home/"${user}"/agent ENV TZ=Etc/UTC @@ -83,7 +84,7 @@ ENV PATH="${JAVA_HOME}/bin:${PATH}" USER "${user}" ENV AGENT_WORKDIR=${AGENT_WORKDIR} -RUN mkdir /home/${user}/.jenkins && mkdir -p "${AGENT_WORKDIR}" +RUN mkdir -p /home/${user}/.jenkins && mkdir -p "${AGENT_WORKDIR}" VOLUME /home/"${user}"/.jenkins VOLUME "${AGENT_WORKDIR}" @@ -97,3 +98,25 @@ LABEL \ org.opencontainers.image.url="https://www.jenkins.io/" \ org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" \ org.opencontainers.image.licenses="MIT" + +## Inbound Agent image target +FROM agent AS inbound-agent + +ARG user=jenkins + +USER root +COPY ../../jenkins-agent /usr/local/bin/jenkins-agent +RUN chmod +x /usr/local/bin/jenkins-agent &&\ + ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave +USER ${user} + +LABEL \ + org.opencontainers.image.vendor="Jenkins project" \ + org.opencontainers.image.title="Official Jenkins Inbound Agent Base Docker image" \ + org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.url="https://www.jenkins.io/" \ + org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent-inbound" \ + org.opencontainers.image.licenses="MIT" + +ENTRYPOINT ["/usr/local/bin/jenkins-agent"] diff --git a/docker-bake.hcl b/docker-bake.hcl index b7b8eea36..79149924c 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -3,7 +3,7 @@ group "linux" { "alpine_jdk11", "alpine_jdk17", "alpine_jdk21", - "archlinux_jdk11", + "agent_archlinux_jdk11", "debian_jdk11", "debian_jdk17", "debian_jdk21", @@ -11,6 +11,31 @@ group "linux" { ] } +group "linux-agent-only" { + targets = [ + "agent_alpine_jdk11", + "agent_alpine_jdk17", + "agent_alpine_jdk21", + "agent_archlinux_jdk11", + "agent_debian_jdk11", + "agent_debian_jdk17", + "agent_debian_jdk21", + "agent_debian_jdk21_preview" + ] +} + +group "linux-inbound-agent-only" { + targets = [ + "inbound-agent_alpine_jdk11", + "inbound-agent_alpine_jdk17", + "inbound-agent_alpine_jdk21", + "inbound-agent_debian_jdk11", + "inbound-agent_debian_jdk17", + "inbound-agent_debian_jdk21", + "inbound-agent_debian_jdk21_preview" + ] +} + group "linux-arm64" { targets = [ "debian_jdk11", @@ -51,8 +76,16 @@ variable "REGISTRY" { default = "docker.io" } -variable "JENKINS_REPO" { - default = "jenkins/agent" +variable "REGISTRY_ORG" { + default = "jenkins" +} + +variable "REGISTRY_REPO_AGENT" { + default = "agent" +} + +variable "REGISTRY_REPO_INBOUND_AGENT" { + default = "inbound-agent" } variable "BUILD_NUMBER" { @@ -91,7 +124,12 @@ variable "JAVA21_PREVIEW_VERSION" { default = "21.0.1+12" } -target "archlinux_jdk11" { +function "orgrepo" { + params = [agentType] + result = equal("agent", agentType) ? "${REGISTRY_ORG}/${REGISTRY_REPO_AGENT}" : "${REGISTRY_ORG}/${REGISTRY_REPO_INBOUND_AGENT}" +} + +target "agent_archlinux_jdk11" { dockerfile = "archlinux/Dockerfile" context = "." args = { @@ -99,17 +137,22 @@ target "archlinux_jdk11" { VERSION = REMOTING_VERSION } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-archlinux" : "", - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-archlinux-jdk11" : "", - "${REGISTRY}/${JENKINS_REPO}:archlinux", - "${REGISTRY}/${JENKINS_REPO}:latest-archlinux", - "${REGISTRY}/${JENKINS_REPO}:archlinux-jdk11", - "${REGISTRY}/${JENKINS_REPO}:latest-archlinux-jdk11", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo("agent")}:${REMOTING_VERSION}-${BUILD_NUMBER}-archlinux" : "", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo("agent")}:${REMOTING_VERSION}-${BUILD_NUMBER}-archlinux-jdk11" : "", + "${REGISTRY}/${orgrepo("agent")}:archlinux", + "${REGISTRY}/${orgrepo("agent")}:latest-archlinux", + "${REGISTRY}/${orgrepo("agent")}:archlinux-jdk11", + "${REGISTRY}/${orgrepo("agent")}:latest-archlinux-jdk11", ] platforms = ["linux/amd64"] } target "alpine_jdk11" { + matrix = { + type = ["agent", "inbound-agent"] + } + name = "${type}_alpine_jdk11" + target = type dockerfile = "alpine/Dockerfile" context = "." args = { @@ -118,17 +161,22 @@ target "alpine_jdk11" { VERSION = REMOTING_VERSION } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine-jdk11" : "", - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}-jdk11" : "", - "${REGISTRY}/${JENKINS_REPO}:alpine-jdk11", - "${REGISTRY}/${JENKINS_REPO}:alpine${ALPINE_SHORT_TAG}-jdk11", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine-jdk11", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine${ALPINE_SHORT_TAG}-jdk11", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine-jdk11" : "", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}-jdk11" : "", + "${REGISTRY}/${orgrepo(type)}:alpine-jdk11", + "${REGISTRY}/${orgrepo(type)}:alpine${ALPINE_SHORT_TAG}-jdk11", + "${REGISTRY}/${orgrepo(type)}:latest-alpine-jdk11", + "${REGISTRY}/${orgrepo(type)}:latest-alpine${ALPINE_SHORT_TAG}-jdk11", ] platforms = ["linux/amd64"] } target "alpine_jdk17" { + matrix = { + type = ["agent", "inbound-agent"] + } + name = "${type}_alpine_jdk17" + target = type dockerfile = "alpine/Dockerfile" context = "." args = { @@ -137,23 +185,28 @@ target "alpine_jdk17" { VERSION = REMOTING_VERSION } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine" : "", - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}" : "", - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine-jdk17" : "", - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}-jdk17" : "", - "${REGISTRY}/${JENKINS_REPO}:alpine", - "${REGISTRY}/${JENKINS_REPO}:alpine${ALPINE_SHORT_TAG}", - "${REGISTRY}/${JENKINS_REPO}:alpine-jdk17", - "${REGISTRY}/${JENKINS_REPO}:alpine${ALPINE_SHORT_TAG}-jdk17", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine${ALPINE_SHORT_TAG}", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine-jdk17", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine${ALPINE_SHORT_TAG}-jdk17", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine" : "", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}" : "", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine-jdk17" : "", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}-jdk17" : "", + "${REGISTRY}/${orgrepo(type)}:alpine", + "${REGISTRY}/${orgrepo(type)}:alpine${ALPINE_SHORT_TAG}", + "${REGISTRY}/${orgrepo(type)}:alpine-jdk17", + "${REGISTRY}/${orgrepo(type)}:alpine${ALPINE_SHORT_TAG}-jdk17", + "${REGISTRY}/${orgrepo(type)}:latest-alpine", + "${REGISTRY}/${orgrepo(type)}:latest-alpine${ALPINE_SHORT_TAG}", + "${REGISTRY}/${orgrepo(type)}:latest-alpine-jdk17", + "${REGISTRY}/${orgrepo(type)}:latest-alpine${ALPINE_SHORT_TAG}-jdk17", ] platforms = ["linux/amd64"] } target "alpine_jdk21" { + matrix = { + type = ["agent", "inbound-agent"] + } + name = "${type}_alpine_jdk21" + target = type dockerfile = "alpine/Dockerfile" context = "." args = { @@ -162,17 +215,22 @@ target "alpine_jdk21" { VERSION = REMOTING_VERSION } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine-jdk21" : "", - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}-jdk21" : "", - "${REGISTRY}/${JENKINS_REPO}:alpine-jdk21", - "${REGISTRY}/${JENKINS_REPO}:alpine${ALPINE_SHORT_TAG}-jdk21", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine-jdk21", - "${REGISTRY}/${JENKINS_REPO}:latest-alpine${ALPINE_SHORT_TAG}-jdk21", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine-jdk21" : "", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-alpine${ALPINE_SHORT_TAG}-jdk21" : "", + "${REGISTRY}/${orgrepo(type)}:alpine-jdk21", + "${REGISTRY}/${orgrepo(type)}:alpine${ALPINE_SHORT_TAG}-jdk21", + "${REGISTRY}/${orgrepo(type)}:latest-alpine-jdk21", + "${REGISTRY}/${orgrepo(type)}:latest-alpine${ALPINE_SHORT_TAG}-jdk21", ] platforms = ["linux/amd64", "linux/arm64"] } target "debian_jdk11" { + matrix = { + type = ["agent", "inbound-agent"] + } + name = "${type}_debian_jdk11" + target = type dockerfile = "debian/Dockerfile" context = "." args = { @@ -181,16 +239,21 @@ target "debian_jdk11" { DEBIAN_RELEASE = DEBIAN_RELEASE } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk11" : "", - "${REGISTRY}/${JENKINS_REPO}:bookworm-jdk11", - "${REGISTRY}/${JENKINS_REPO}:jdk11", - "${REGISTRY}/${JENKINS_REPO}:latest-bookworm-jdk11", - "${REGISTRY}/${JENKINS_REPO}:latest-jdk11", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk11" : "", + "${REGISTRY}/${orgrepo(type)}:bookworm-jdk11", + "${REGISTRY}/${orgrepo(type)}:jdk11", + "${REGISTRY}/${orgrepo(type)}:latest-bookworm-jdk11", + "${REGISTRY}/${orgrepo(type)}:latest-jdk11", ] platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/s390x", "linux/ppc64le"] } target "debian_jdk17" { + matrix = { + type = ["agent", "inbound-agent"] + } + name = "${type}_debian_jdk17" + target = type dockerfile = "debian/Dockerfile" context = "." args = { @@ -199,19 +262,24 @@ target "debian_jdk17" { DEBIAN_RELEASE = DEBIAN_RELEASE } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}" : "", - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk17" : "", - "${REGISTRY}/${JENKINS_REPO}:bookworm-jdk17", - "${REGISTRY}/${JENKINS_REPO}:jdk17", - "${REGISTRY}/${JENKINS_REPO}:latest", - "${REGISTRY}/${JENKINS_REPO}:latest-bookworm", - "${REGISTRY}/${JENKINS_REPO}:latest-bookworm-jdk17", - "${REGISTRY}/${JENKINS_REPO}:latest-jdk17", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}" : "", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk17" : "", + "${REGISTRY}/${orgrepo(type)}:bookworm-jdk17", + "${REGISTRY}/${orgrepo(type)}:jdk17", + "${REGISTRY}/${orgrepo(type)}:latest", + "${REGISTRY}/${orgrepo(type)}:latest-bookworm", + "${REGISTRY}/${orgrepo(type)}:latest-bookworm-jdk17", + "${REGISTRY}/${orgrepo(type)}:latest-jdk17", ] platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/ppc64le"] } target "debian_jdk21" { + matrix = { + type = ["agent", "inbound-agent"] + } + name = "${type}_debian_jdk21" + target = type dockerfile = "debian/Dockerfile" context = "." args = { @@ -220,16 +288,21 @@ target "debian_jdk21" { DEBIAN_RELEASE = DEBIAN_RELEASE } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk21" : "", - "${REGISTRY}/${JENKINS_REPO}:bookworm-jdk21", - "${REGISTRY}/${JENKINS_REPO}:jdk21", - "${REGISTRY}/${JENKINS_REPO}:latest-bookworm-jdk21", - "${REGISTRY}/${JENKINS_REPO}:latest-jdk21", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk21" : "", + "${REGISTRY}/${orgrepo(type)}:bookworm-jdk21", + "${REGISTRY}/${orgrepo(type)}:jdk21", + "${REGISTRY}/${orgrepo(type)}:latest-bookworm-jdk21", + "${REGISTRY}/${orgrepo(type)}:latest-jdk21", ] platforms = ["linux/amd64", "linux/arm64"] } target "debian_jdk21_preview" { + matrix = { + type = ["agent", "inbound-agent"] + } + name = "${type}_debian_jdk21_preview" + target = type dockerfile = "debian/preview/Dockerfile" context = "." args = { @@ -238,11 +311,11 @@ target "debian_jdk21_preview" { DEBIAN_RELEASE = DEBIAN_RELEASE } tags = [ - equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk21-preview" : "", - "${REGISTRY}/${JENKINS_REPO}:bookworm-jdk21-preview", - "${REGISTRY}/${JENKINS_REPO}:jdk21-preview", - "${REGISTRY}/${JENKINS_REPO}:latest-bookworm-jdk21-preview", - "${REGISTRY}/${JENKINS_REPO}:latest-jdk21-preview", + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk21-preview" : "", + "${REGISTRY}/${orgrepo(type)}:bookworm-jdk21-preview", + "${REGISTRY}/${orgrepo(type)}:jdk21-preview", + "${REGISTRY}/${orgrepo(type)}:latest-bookworm-jdk21-preview", + "${REGISTRY}/${orgrepo(type)}:latest-jdk21-preview", ] platforms = ["linux/ppc64le", "linux/s390x", "linux/arm/v7"] } diff --git a/jenkins-agent b/jenkins-agent new file mode 100755 index 000000000..22025cd1b --- /dev/null +++ b/jenkins-agent @@ -0,0 +1,131 @@ +#!/usr/bin/env sh + +# The MIT License +# +# Copyright (c) 2015-2020, CloudBees, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Usage jenkins-agent.sh [options] -url http://jenkins -secret [SECRET] -name [AGENT_NAME] +# Optional environment variables : +# * JENKINS_JAVA_BIN : Java executable to use instead of the default in PATH or obtained from JAVA_HOME +# * JENKINS_JAVA_OPTS : Java Options to use for the remoting process, otherwise obtained from JAVA_OPTS +# * JENKINS_TUNNEL : HOST:PORT for a tunnel to route TCP traffic to jenkins host, when jenkins can't be directly accessed over network +# * JENKINS_URL : alternate jenkins URL +# * JENKINS_SECRET : agent secret, if not set as an argument +# * JENKINS_AGENT_NAME : agent name, if not set as an argument +# * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir +# * JENKINS_WEB_SOCKET: true if the connection should be made via WebSocket rather than TCP +# * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. +# Value: ":" +# * 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. +# * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. + +if [ $# -eq 1 ] && [ "${1#-}" = "$1" ] ; then + + # 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 + exec "$@" + +else + + # if -tunnel is not provided, try env vars + case "$@" in + *"-tunnel "*) ;; + *) + if [ ! -z "$JENKINS_TUNNEL" ]; then + TUNNEL="-tunnel $JENKINS_TUNNEL" + fi ;; + esac + + # if -workDir is not provided, try env vars + if [ ! -z "$JENKINS_AGENT_WORKDIR" ]; then + case "$@" in + *"-workDir"*) echo "Warning: Work directory is defined twice in command-line arguments and the environment variable" ;; + *) + WORKDIR="-workDir $JENKINS_AGENT_WORKDIR" ;; + esac + fi + + if [ -n "$JENKINS_URL" ]; then + URL="-url $JENKINS_URL" + fi + + if [ -n "$JENKINS_NAME" ]; then + JENKINS_AGENT_NAME="$JENKINS_NAME" + fi + + if [ "$JENKINS_WEB_SOCKET" = true ]; then + WEB_SOCKET=-webSocket + fi + + if [ -n "$JENKINS_PROTOCOLS" ]; then + PROTOCOLS="-protocols $JENKINS_PROTOCOLS" + fi + + if [ -n "$JENKINS_DIRECT_CONNECTION" ]; then + DIRECT="-direct $JENKINS_DIRECT_CONNECTION" + fi + + if [ -n "$JENKINS_INSTANCE_IDENTITY" ]; then + INSTANCE_IDENTITY="-instanceIdentity $JENKINS_INSTANCE_IDENTITY" + fi + + if [ "$JENKINS_JAVA_BIN" ]; then + JAVA_BIN="$JENKINS_JAVA_BIN" + else + # if java home is defined, use it + JAVA_BIN="java" + if [ "$JAVA_HOME" ]; then + JAVA_BIN="$JAVA_HOME/bin/java" + fi + fi + + if [ "$JENKINS_JAVA_OPTS" ]; then + JAVA_OPTIONS="$JENKINS_JAVA_OPTS" + else + # if JAVA_OPTS is defined, use it + if [ "$JAVA_OPTS" ]; then + JAVA_OPTIONS="$JAVA_OPTS" + fi + fi + + # if both required options are defined, do not pass the parameters + if [ -n "$JENKINS_SECRET" ]; then + case "$@" in + *"${JENKINS_SECRET}"*) echo "Warning: SECRET is defined twice in command-line arguments and the environment variable" ;; + *) + SECRET="-secret ${JENKINS_SECRET}" ;; + esac + fi + + if [ -n "$JENKINS_AGENT_NAME" ]; then + case "$@" in + *"${JENKINS_AGENT_NAME}"*) echo "Warning: AGENT_NAME is defined twice in command-line arguments and the environment variable" ;; + *) + AGENT_NAME="-name ${JENKINS_AGENT_NAME}" ;; + esac + fi + + #TODO: Handle the case when the command-line and Environment variable contain different values. + #It is fine it blows up for now since it should lead to an error anyway. + + exec $JAVA_BIN $JAVA_OPTIONS -jar /usr/share/jenkins/agent.jar $SECRET $AGENT_NAME $TUNNEL $URL $WORKDIR $WEB_SOCKET $DIRECT $PROTOCOLS $INSTANCE_IDENTITY "$@" + +fi diff --git a/jenkins-agent.ps1 b/jenkins-agent.ps1 new file mode 100644 index 000000000..6dbf62f8b --- /dev/null +++ b/jenkins-agent.ps1 @@ -0,0 +1,154 @@ +# The MIT License +# +# Copyright (c) 2019-2020, Alex Earl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +[CmdletBinding()] +Param( + $Cmd = '', # this must be specified explicitly + $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 { '' } ), + [Parameter(Position=0)]$Secret = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_SECRET)) { throw ("Secret is required") } else { '' } ), + [Parameter(Position=1)]$Name = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_AGENT_NAME)) { throw ("Name is required") } else { '' } ), + $Tunnel = '', + $WorkDir = '', + [switch] $WebSocket = $false, + $DirectConnection = '', + $InstanceIdentity = '', + $Protocols = '', + $JenkinsJavaBin = '', + $JavaHome = $env:JAVA_HOME, + $JenkinsJavaOpts = '' +) + +# Usage jenkins-agent.ps1 [options] -Url http://jenkins -Secret [SECRET] -Name [AGENT_NAME] +# Optional environment variables : +# * JENKINS_JAVA_BIN : Java executable to use instead of the default in PATH or obtained from JAVA_HOME +# * JENKINS_JAVA_OPTS : Java Options to use for the remoting process, otherwise obtained from JAVA_OPTS +# * JENKINS_TUNNEL : HOST:PORT for a tunnel to route TCP traffic to jenkins host, when jenkins can't be directly accessed over network +# * JENKINS_URL : alternate jenkins URL +# * JENKINS_SECRET : agent secret, if not set as an argument +# * JENKINS_AGENT_NAME : agent name, if not set as an argument +# * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir +# * JENKINS_WEB_SOCKET : true if the connection should be made via WebSocket rather than TCP +# * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. +# Value: ":" +# * 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. +# * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. + +if(![System.String]::IsNullOrWhiteSpace($Cmd)) { + Invoke-Expression "$Cmd" +} else { + + # this maps the variable name from the CmdletBinding to environment variables + $ParamMap = @{ + 'JenkinsJavaBin' = 'JENKINS_JAVA_BIN'; + 'JenkinsJavaOpts' = 'JENKINS_JAVA_OPTS'; + 'Tunnel' = 'JENKINS_TUNNEL'; + 'Url' = 'JENKINS_URL'; + 'Secret' = 'JENKINS_SECRET'; + 'Name' = 'JENKINS_AGENT_NAME'; + 'WorkDir' = 'JENKINS_AGENT_WORKDIR'; + 'WebSocket' = 'JENKINS_WEB_SOCKET'; + 'DirectConnection' = 'JENKINS_DIRECT_CONNECTION'; + 'InstanceIdentity' = 'JENKINS_INSTANCE_IDENTITY'; + 'Protocols' = 'JENKINS_PROTOCOLS'; + } + + # this does some trickery to update the variable from the CmdletBinding + # with the value of the + foreach($p in $ParamMap.Keys) { + $var = Get-Variable $p + $envVar = Get-ChildItem -Path "env:$($ParamMap[$p])" -ErrorAction 'SilentlyContinue' + + if(($null -ne $envVar) -and ((($envVar.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($envVar.Value))) -or ($null -ne $envVar.Value))) { + if(($null -ne $var) -and ((($var.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($var.Value))))) { + Write-Warning "${p} is defined twice; in command-line arguments (-${p}) and in the environment variable ${envVar.Name}" + } + if($var.Value -is [System.String]) { + $var.Value = $envVar.Value + } elseif($var.Value -is [System.Management.Automation.SwitchParameter]) { + $var.Value = [bool]$envVar.Value + } + } + if($var.Value -is [System.String]) { + $var.Value = $var.Value.Trim() + } + } + + $AgentArguments = @() + + if(![System.String]::IsNullOrWhiteSpace($JenkinsJavaOpts)) { + # this magic will basically process the $JenkinsJavaOpts like a command line + # and split into an array, the command line processing follows the PowerShell + # commnd line processing, which means for things like -Dsomething.something=something, + # you need to quote the string like this: "-Dsomething.something=something" or else it + # will get parsed incorrectly. + $AgentArguments += Invoke-Expression "echo $JenkinsJavaOpts" + } + + $AgentArguments += @("-jar", "C:/ProgramData/Jenkins/agent.jar") + $AgentArguments += @("-secret", $Secret) + $AgentArguments += @("-name", $Name) + + if(![System.String]::IsNullOrWhiteSpace($Tunnel)) { + $AgentArguments += @("-tunnel", "`"$Tunnel`"") + } + + if(![System.String]::IsNullOrWhiteSpace($WorkDir)) { + $AgentArguments += @("-workDir", "`"$WorkDir`"") + } else { + $AgentArguments += @("-workDir", "`"C:/Users/jenkins/Work`"") + } + + if($WebSocket) { + $AgentArguments += @("-webSocket") + } + + if(![System.String]::IsNullOrWhiteSpace($Url)) { + $AgentArguments += @("-url", "`"$Url`"") + } + + if(![System.String]::IsNullOrWhiteSpace($DirectConnection)) { + $AgentArguments += @('-direct', $DirectConnection) + } + + if(![System.String]::IsNullOrWhiteSpace($InstanceIdentity)) { + $AgentArguments += @('-instanceIdentity', $InstanceIdentity) + } + + if(![System.String]::IsNullOrWhiteSpace($Protocols)) { + $AgentArguments += @('-protocols', $Protocols) + } + + if(![System.String]::IsNullOrWhiteSpace($JenkinsJavaBin)) { + $JAVA_BIN = $JenkinsJavaBin + } else { + # if java home is defined, use it + $JAVA_BIN = "java.exe" + if (![System.String]::IsNullOrWhiteSpace($JavaHome)) { + $JAVA_BIN = "$JavaHome/bin/java.exe" + } + } + + #TODO: Handle the case when the command-line and Environment variable contain different values. + #It is fine it blows up for now since it should lead to an error anyway. + Start-Process -FilePath $JAVA_BIN -Wait -NoNewWindow -ArgumentList $AgentArguments +} diff --git a/tests/agent.Tests.ps1 b/tests/agent.Tests.ps1 index 745dcc1ff..88b22b395 100644 --- a/tests/agent.Tests.ps1 +++ b/tests/agent.Tests.ps1 @@ -1,7 +1,7 @@ Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 +$global:AGENT_TYPE = Get-EnvOrDefault 'AGENT_TYPE' '' $global:AGENT_IMAGE = Get-EnvOrDefault 'AGENT_IMAGE' '' -$global:BUILD_CONTEXT = Get-EnvOrDefault 'BUILD_CONTEXT' '' $global:VERSION = Get-EnvOrDefault 'VERSION' '' $items = $global:AGENT_IMAGE.Split("-") @@ -27,14 +27,14 @@ $global:GITLFSVERSION = '3.4.1' Cleanup($global:CONTAINERNAME) -Describe "[$global:AGENT_IMAGE] image is present" { +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] image is present" { It 'builds image' { $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect $global:AGENT_IMAGE" $exitCode | Should -Be 0 } } -Describe "[$global:AGENT_IMAGE] correct image metadata" { +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] correct image metadata" { It 'has correct volumes' { $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format='{{.Config.Volumes}}' $global:AGENT_IMAGE" $stdout = $stdout.Trim() @@ -43,10 +43,10 @@ Describe "[$global:AGENT_IMAGE] correct image metadata" { } } -Describe "[$global:AGENT_IMAGE] image has correct applications in the PATH" { +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] image has correct applications in the PATH" { BeforeAll { docker run --detach --interactive --tty --name "$global:CONTAINERNAME" "$global:AGENT_IMAGE" "$global:CONTAINERSHELL" - Is-AgentContainerRunning $global:CONTAINERNAME + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue } It 'has java installed and in the path' { @@ -78,15 +78,20 @@ Describe "[$global:AGENT_IMAGE] image has correct applications in the PATH" { $stdout.Trim() | Should -Match "git-lfs/${global:GITLFSVERSION}" } + It 'does not include jenkins-agent.ps1 (inbound-agent)' { + $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 }`"" + $exitCode | Should -Be 0 + } + AfterAll { Cleanup($global:CONTAINERNAME) } } -Describe "[$global:AGENT_IMAGE] check user account" { +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] check user account" { BeforeAll { docker run -d -it --name "$global:CONTAINERNAME" -P "$global:AGENT_IMAGE" "$global:CONTAINERSHELL" - Is-AgentContainerRunning $global:CONTAINERNAME + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue } It 'Password never expires' { @@ -104,10 +109,10 @@ Describe "[$global:AGENT_IMAGE] check user account" { } } -Describe "[$global:AGENT_IMAGE] check user access to directories" { +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] check user access to directories" { BeforeAll { docker run -d -it --name "$global:CONTAINERNAME" -P "$global:AGENT_IMAGE" "$global:CONTAINERSHELL" - Is-AgentContainerRunning $global:CONTAINERNAME + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue } It 'can write to HOME' { @@ -134,33 +139,33 @@ $global:TEST_VERSION="4.0" $global:TEST_USER="test-user" $global:TEST_AGENT_WORKDIR="C:/test-user/something" -Describe "[$global:AGENT_IMAGE] can be built with custom build arguments" { +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] can be built with custom build arguments" { BeforeAll { Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." - $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg `"VERSION=${global:TEST_VERSION}`" --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:AGENT_IMAGE} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ${global:BUILD_CONTEXT}" + $exitCode, $stdout, $stderr = Run-Program 'docker' "build --target agent --build-arg `"VERSION=${global:TEST_VERSION}`" --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:AGENT_IMAGE} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." $exitCode | Should -Be 0 - docker run -d -it --name "$global:CONTAINERNAME" -P "$global:AGENT_IMAGE" "$global:CONTAINERSHELL" - Is-AgentContainerRunning $global:CONTAINERNAME + $exitCode, $stdout, $stderr = Run-Program 'docker' "run -d -it --name $global:CONTAINERNAME -P $global:AGENT_IMAGE $global:CONTAINERSHELL" + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue } It 'has the correct version of remoting' { $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`"" $exitCode | Should -Be 0 - $stdout.Trim() | Should -Match $TEST_VERSION + $stdout.Trim() | Should -Match $global:TEST_VERSION } It 'has correct user' { $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"(Get-ChildItem env:\ | Where-Object { `$_.Name -eq 'USERNAME' }).Value`"" $exitCode | Should -Be 0 - $stdout.Trim() | Should -Match $TEST_USER + $stdout.Trim() | Should -Match $global:TEST_USER } It 'has correct AGENT_WORKDIR' { $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"Get-ChildItem env:`"" $exitCode | Should -Be 0 - $stdout | Should -Match "AGENT_WORKDIR.*${TEST_AGENT_WORKDIR}" + $stdout | Should -Match "AGENT_WORKDIR.*${global:TEST_AGENT_WORKDIR}" } It 'can write to HOME' { @@ -181,7 +186,7 @@ Describe "[$global:AGENT_IMAGE] can be built with custom build arguments" { It 'version in docker metadata' { $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format=`"{{index .Config.Labels \`"org.opencontainers.image.version\`"}}`" $global:AGENT_IMAGE" $exitCode | Should -Be 0 - $stdout.Trim() | Should -Match $TEST_VERSION + $stdout.Trim() | Should -Match $global:sTEST_VERSION } AfterAll { diff --git a/tests/inbound-agent.Tests.ps1 b/tests/inbound-agent.Tests.ps1 new file mode 100644 index 000000000..a10ab6b8f --- /dev/null +++ b/tests/inbound-agent.Tests.ps1 @@ -0,0 +1,177 @@ +Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 + +$global:AGENT_TYPE = Get-EnvOrDefault 'AGENT_TYPE' '' +$global:AGENT_IMAGE = Get-EnvOrDefault 'AGENT_IMAGE' '' +$global:version = Get-EnvOrDefault 'VERSION' '' + +$items = $global:AGENT_IMAGE.Split("-") + +# Remove the 'jdk' prefix (3 first characters) +$global:JAVAMAJORVERSION = $items[0].Remove(0,3) +$global:WINDOWSFLAVOR = $items[1] +$global:WINDOWSVERSIONTAG = $items[2] + +# TODO: make this name unique for concurency +$global:CONTAINERNAME = 'pester-jenkins-inbound-agent-{0}' -f $global:AGENT_IMAGE + +$global:CONTAINERSHELL="powershell.exe" +if($global:WINDOWSFLAVOR -eq 'nanoserver') { + $global:CONTAINERSHELL = "pwsh.exe" +} + +# # Uncomment to help debugging when working on this script +# Write-Host "= DEBUG: global vars" +# Get-Variable -Scope Global | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } +# Write-Host "= DEBUG: env vars" +# Get-ChildItem Env: | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } + +Cleanup($global:CONTAINERNAME) +Cleanup("nmap") +CleanupNetwork("jnlp-network") + +BuildNcatImage($global:WINDOWSVERSIONTAG) + +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] build image" { + It 'builds image' { + $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg version=${global:version} --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg `"JAVA_VERSION=${global:JAVAMAJORVERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --tag=${global:AGENT_IMAGE} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ." + $exitCode | Should -Be 0 + } +} + +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] check default user account" { + BeforeAll { + docker run --detach --tty --name "$global:CONTAINERNAME" "$global:AGENT_IMAGE" -Cmd "$global:CONTAINERSHELL" + $LASTEXITCODE | Should -Be 0 + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue + } + + It 'has a password that never expires' { + $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 }`"" + $exitCode | Should -Be 0 + } + + It 'has password policy of "not required"' { + $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 }`"" + $exitCode | Should -Be 0 + } + + AfterAll { + Cleanup($global:CONTAINERNAME) + } +} + +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] image has jenkins-agent.ps1 in the correct location" { + BeforeAll { + docker run --detach --tty --name "$global:CONTAINERNAME" "$global:AGENT_IMAGE" -Cmd "$global:CONTAINERSHELL" + $LASTEXITCODE | Should -Be 0 + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue + } + + It 'has jenkins-agent.ps1 in C:/ProgramData/Jenkins' { + $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 }`"" + $exitCode | Should -Be 0 + } + + AfterAll { + Cleanup($global:CONTAINERNAME) + } +} + +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] image starts jenkins-agent.ps1 correctly (slow test)" { + It 'connects to the nmap container' { + $exitCode, $stdout, $stderr = Run-Program 'docker' "network create --driver nat jnlp-network" + # Launch the netcat utility, listening at port 5000 for 30 sec + # bats will capture the output from netcat and compare the first line + # of the header of the first HTTP request with the expected one + $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000" + $exitCode | Should -Be 0 + Is-ContainerRunning "nmap" | Should -BeTrue + + # get the ip address of the nmap container + $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format `"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`" nmap" + $exitCode | Should -Be 0 + $nmap_ip = $stdout.Trim() + + # run Jenkins agent which tries to connect to the nmap container at port 5000 + $secret = "aaa" + $name = "bbb" + $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --network=jnlp-network --name $global:CONTAINERNAME $global:AGENT_IMAGE -Url http://${nmap_ip}:5000 $secret $name" + $exitCode | Should -Be 0 + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue + + $exitCode, $stdout, $stderr = Run-Program 'docker' 'wait nmap' + $exitCode, $stdout, $stderr = Run-Program 'docker' 'logs nmap' + $exitCode | Should -Be 0 + $stdout | Should -Match "GET /tcpSlaveAgentListener/ HTTP/1.1`r" + } + + AfterAll { + Cleanup($global:CONTAINERNAME) + Cleanup("nmap") + CleanupNetwork("jnlp-network") + } +} + +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] custom build args" { + BeforeAll { + Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." + # Old version used to test overriding the build arguments. + # 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.) + $TEST_VERSION = "3206.vb_15dcf73f6a_9" + $customImageName = "custom-${global:AGENT_IMAGE}" + } + + It 'builds image with arguments' { + $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg version=${TEST_VERSION} --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg `"JAVA_VERSION=${global:JAVAMAJORVERSION}`" --build-arg `"JAVA_HOME=C:\openjdk-${global:JAVAMAJORVERSION}`" --build-arg WINDOWS_FLAVOR=${global:WINDOWSFLAVOR} --build-arg CONTAINER_SHELL=${global:CONTAINERSHELL} --tag=${customImageName} --file=./windows/${global:WINDOWSFLAVOR}/Dockerfile ." + $exitCode | Should -Be 0 + + $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name $global:CONTAINERNAME $customImageName -Cmd $global:CONTAINERSHELL" + $exitCode | Should -Be 0 + Is-ContainerRunning "$global:CONTAINERNAME" | Should -BeTrue + } + + It "has the correct agent.jar version" { + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -c `"java -jar C:/ProgramData/Jenkins/agent.jar -version`"" + $exitCode | Should -Be 0 + $stdout | Should -Match $TEST_VERSION + } + + AfterAll { + Cleanup($global:CONTAINERNAME) + Pop-Location -StackName 'agent' + } +} + +Describe "[$global:AGENT_TYPE > $global:AGENT_IMAGE] passing JVM options (slow test)" { + It "shows the java version ${global:JAVAMAJORVERSION} with --show-version" { + $exitCode, $stdout, $stderr = Run-Program 'docker' "network create --driver nat jnlp-network" + # Launch the netcat utility, listening at port 5000 for 30 sec + # bats will capture the output from netcat and compare the first line + # of the header of the first HTTP request with the expected one + $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000" + $exitCode | Should -Be 0 + Is-ContainerRunning "nmap" | Should -BeTrue + + # get the ip address of the nmap container + $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format `"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`" nmap" + $exitCode | Should -Be 0 + $nmap_ip = $stdout.Trim() + + # run Jenkins agent which tries to connect to the nmap container at port 5000 + $secret = "aaa" + $name = "bbb" + $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --network=jnlp-network --name $global:CONTAINERNAME $global:AGENT_IMAGE -Url http://${nmap_ip}:5000 -JenkinsJavaOpts `"--show-version`" $secret $name" + $exitCode | Should -Be 0 + Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue + Start-Sleep -Seconds 20 + $exitCode, $stdout, $stderr = Run-Program 'docker' "logs $global:CONTAINERNAME" + $exitCode | Should -Be 0 + $stdout | Should -Match "OpenJDK Runtime Environment Temurin-${global:JAVAMAJORVERSION}" + } + + AfterAll { + Cleanup($global:CONTAINERNAME) + Cleanup("nmap") + CleanupNetwork("jnlp-network") + } +} diff --git a/tests/netcat-helper/Dockerfile b/tests/netcat-helper/Dockerfile new file mode 100644 index 000000000..6313e4a4e --- /dev/null +++ b/tests/netcat-helper/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:3.11 + +RUN apk update --no-cache \ + && apk add --no-cache \ + coreutils \ + netcat-openbsd diff --git a/tests/netcat-helper/Dockerfile-windows b/tests/netcat-helper/Dockerfile-windows new file mode 100644 index 000000000..38e3a09d6 --- /dev/null +++ b/tests/netcat-helper/Dockerfile-windows @@ -0,0 +1,40 @@ +# escape=` + +# The MIT License +# +# Copyright (c) 2019-2020, Alex Earl and other Jenkins Contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Available tags: https://mcr.microsoft.com/v2/windows/servercore/tags/list +ARG WINDOWS_VERSION_TAG=1809 +FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" + +SHELL ["powershell.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +ARG NMAP_VERSION=7.80 +ENV NMAP_VERSION $NMAP_VERSION + +RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` + $url = $('https://nmap.org/dist/nmap-{0}-setup.exe' -f $env:NMAP_VERSION) ; ` + Write-Host "Retrieving $url..." ; ` + Invoke-WebRequest $url -OutFile 'nmap-install.exe' -UseBasicParsing ; ` + $proc = Start-Process "C:\nmap-install.exe" -PassThru -ArgumentList '/S' ; ` + $proc.WaitForExit() ; ` + Remove-Item -Path nmap-install.exe diff --git a/tests/test_helpers.bash b/tests/test_helpers.bash index 5f99823e6..1b61dba95 100755 --- a/tests/test_helpers.bash +++ b/tests/test_helpers.bash @@ -30,13 +30,13 @@ function get_sut_image { # Option --print for 'docker buildx bake' prints the JSON configuration on the stdout # Option --silent for 'make' suppresses the echoing of command so the output is valid JSON # The image name is the 1st of the "tags" array, on the first "image" found - make --silent show | jq -r ".target.${IMAGE}.tags[0]" + make --silent show | jq -r ".target.\"${IMAGE}\".tags[0]" } function get_remoting_version() { test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" - make --silent show | jq -r ".target.${IMAGE}.args.VERSION" + make --silent show | jq -r ".target.\"${IMAGE}\".args.VERSION" } function cleanup { @@ -47,7 +47,7 @@ function cleanup { function get_dockerfile_directory() { test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" - DOCKERFILE=$(make --silent show | jq -r ".target.${IMAGE}.dockerfile") + DOCKERFILE=$(make --silent show | jq -r ".target.\"${IMAGE}\".dockerfile") echo "${DOCKERFILE%"/Dockerfile"}" } @@ -79,3 +79,14 @@ function is_agent_container_running { sleep 1 retry 3 1 assert "true" docker inspect -f '{{.State.Running}}' "${1}" } + +function buildNetcatImage() { + if ! docker inspect --type=image netcat-helper:latest &>/dev/null; then + docker build -t netcat-helper:latest tests/netcat-helper/ &>/dev/null + fi +} + +function clean_test_container { + docker kill "${AGENT_CONTAINER}" "${NETCAT_HELPER_CONTAINER}" &>/dev/null || : + docker rm -fv "${AGENT_CONTAINER}" "${NETCAT_HELPER_CONTAINER}" &>/dev/null || : +} diff --git a/tests/test_helpers.psm1 b/tests/test_helpers.psm1 index 985d485de..8a2e6e054 100644 --- a/tests/test_helpers.psm1 +++ b/tests/test_helpers.psm1 @@ -77,29 +77,30 @@ function Cleanup($name='') { } if(![System.String]::IsNullOrWhiteSpace($name)) { - #Write-Host "Cleaning up $name" docker kill "$name" 2>&1 | Out-Null docker rm -fv "$name" 2>&1 | Out-Null } } -function Is-AgentContainerRunning($container='') { +function Is-ContainerRunning($container='') { if([System.String]::IsNullOrWhiteSpace($container)) { $container = Get-EnvOrDefault 'AGENT_CONTAINER' '' } Start-Sleep -Seconds 5 - Retry-Command -RetryCount 3 -Delay 1 -ScriptBlock { + Retry-Command -RetryCount 10 -Delay 3 -ScriptBlock { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{.State.Running}}`" $container" if(($exitCode -ne 0) -or (-not $stdout.Contains('true')) ) { throw('Exit code incorrect, or invalid value for running state') } return $true - } | Should -BeTrue + } } -function Run-Program($cmd, $params) { - #Write-Host "cmd = $cmd, params = $params" +function Run-Program($cmd, $params, $quiet=$true) { + if(-not $quiet) { + Write-Host "cmd & params: $cmd $params" + } $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.CreateNoWindow = $true $psi.UseShellExecute = $false @@ -114,7 +115,7 @@ function Run-Program($cmd, $params) { $stdout = $proc.StandardOutput.ReadToEnd() $stderr = $proc.StandardError.ReadToEnd() $proc.WaitForExit() - if($proc.ExitCode -ne 0) { + if(($proc.ExitCode -ne 0) -and (-not $quiet)) { Write-Host "[err] stdout:`n$stdout" Write-Host "[err] stderr:`n$stderr" Write-Host "[err] cmd:`n$cmd" @@ -123,3 +124,18 @@ function Run-Program($cmd, $params) { return $proc.ExitCode, $stdout, $stderr } + +function BuildNcatImage($windowsVersionTag) { + Write-Host "Building nmap image (Windows version '${windowsVersionTag}') for testing" + $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect --type=image nmap" $true + if($exitCode -ne 0) { + Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." + $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" + $exitCode | Should -Be 0 + Pop-Location -StackName 'agent' + } +} + +function CleanupNetwork($name) { + docker network rm $name 2>&1 | Out-Null +} diff --git a/tests/tests.bats b/tests/tests_agent.bats similarity index 88% rename from tests/tests.bats rename to tests/tests_agent.bats index 451a41788..def0c679c 100755 --- a/tests/tests.bats +++ b/tests/tests_agent.bats @@ -4,7 +4,7 @@ load test_helpers load 'test_helper/bats-support/load' # this is required by bats-assert! load 'test_helper/bats-assert/load' -IMAGE=${IMAGE:-debian_jdk11} +IMAGE=${IMAGE:-debian_jdk17} SUT_IMAGE=$(get_sut_image) ARCH=${ARCH:-x86_64} @@ -99,19 +99,19 @@ ARCH=${ARCH:-x86_64} local TEST_AGENT_WORKDIR="/home/test-user/something" local sut_image="${SUT_IMAGE}-tests-${BATS_TEST_NUMBER}" -# false positive detecting platform -# shellcheck disable=SC2140 -docker buildx bake \ - --set "${IMAGE}".args.VERSION="${TEST_VERSION}" \ - --set "${IMAGE}".args.user="${TEST_USER}" \ - --set "${IMAGE}".args.group="${TEST_GROUP}" \ - --set "${IMAGE}".args.uid="${TEST_UID}" \ - --set "${IMAGE}".args.gid="${TEST_GID}" \ - --set "${IMAGE}".args.AGENT_WORKDIR="${TEST_AGENT_WORKDIR}" \ - --set "${IMAGE}".platform="linux/${ARCH}" \ - --set "${IMAGE}".tags="${sut_image}" \ - --load `# Image should be loaded on the Docker engine`\ - "${IMAGE}" + # false positive detecting platform + # shellcheck disable=SC2140 + docker buildx bake \ + --set "${IMAGE}".args.VERSION="${TEST_VERSION}" \ + --set "${IMAGE}".args.user="${TEST_USER}" \ + --set "${IMAGE}".args.group="${TEST_GROUP}" \ + --set "${IMAGE}".args.uid="${TEST_UID}" \ + --set "${IMAGE}".args.gid="${TEST_GID}" \ + --set "${IMAGE}".args.AGENT_WORKDIR="${TEST_AGENT_WORKDIR}" \ + --set "${IMAGE}".platform="linux/${ARCH}" \ + --set "${IMAGE}".tags="${sut_image}" \ + --load `# Image should be loaded on the Docker engine`\ + "${IMAGE}" local cid cid="$(docker run -d -it -P "${sut_image}" /bin/sh)" diff --git a/tests/tests_inbound-agent.bats b/tests/tests_inbound-agent.bats new file mode 100755 index 000000000..c07ccf588 --- /dev/null +++ b/tests/tests_inbound-agent.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bats + +AGENT_CONTAINER=bats-jenkins-jnlp-agent + +load test_helpers + +buildNetcatImage + +SUT_IMAGE="$(get_sut_image)" + +@test "[${SUT_IMAGE}] image has installed jenkins-agent in PATH" { + local sut_cid + sut_cid="$(docker run -d -it -P "${SUT_IMAGE}" /bin/bash)" + + is_agent_container_running "${sut_cid}" + + run docker exec "${sut_cid}" which jenkins-agent + [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] + + run docker exec "${sut_cid}" which jenkins-agent + [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] + + cleanup "${sut_cid}" +} + +@test "[${SUT_IMAGE}] image starts jenkins-agent correctly (slow test)" { + local netcat_cid sut_cid + # Spin off a helper image which launches the netcat utility, listening at port 5000 for 30 sec + netcat_cid="$(docker run -d -it netcat-helper:latest /bin/sh -c "timeout 30s nc -l 5000")" + + # Run jenkins agent which tries to connect to the netcat-helper container at port 5000 + sut_cid="$(docker run -d --link "${netcat_cid}" "${SUT_IMAGE}" -url "http://${netcat_cid}:5000" aaa bbb)" + + # Wait for the whole process to take place (in resource-constrained environments it can take 100s of milliseconds) + sleep 5 + + # Capture the logs output from netcat and check the header of the first HTTP request with the expected one + run docker logs "${netcat_cid}" + echo "${output}" | grep 'GET /tcpSlaveAgentListener/ HTTP/1.1' + + cleanup "${netcat_cid}" + cleanup "${sut_cid}" +} + +@test "[${SUT_IMAGE}] use build args correctly" { + cd "${BATS_TEST_DIRNAME}"/.. || false + + local TEST_VERSION TEST_USER sut_image sut_cid + + # Old version used to test overriding the build arguments. + TEST_VERSION="3180.v3dd999d24861" + TEST_USER="root" + + sut_image="${SUT_IMAGE}-tests-${BATS_TEST_NUMBER}" + + docker buildx bake \ + --set "${IMAGE}.args.VERSION=${TEST_VERSION}" \ + --set "${IMAGE}.args.user=${TEST_USER}" \ + --set "${IMAGE}.platform=linux/${ARCH}" \ + --set "${IMAGE}.tags=${sut_image}" \ + --load \ + "${IMAGE}" + + sut_cid="$(docker run -d -it --name "${AGENT_CONTAINER}" -P "${sut_image}" /bin/sh)" + + is_agent_container_running "${sut_cid}" + + run docker exec "${sut_cid}" sh -c "java -jar /usr/share/jenkins/agent.jar -version" + [ "${TEST_VERSION}" = "${lines[0]}" ] + + run docker exec "${AGENT_CONTAINER}" sh -c "id -u -n ${TEST_USER}" + [ "${TEST_USER}" = "${lines[0]}" ] + + cleanup "${sut_cid}" +} diff --git a/windows/nanoserver/Dockerfile b/windows/nanoserver/Dockerfile index 8dd900c28..ecd95f994 100644 --- a/windows/nanoserver/Dockerfile +++ b/windows/nanoserver/Dockerfile @@ -29,7 +29,8 @@ FROM eclipse-temurin:"${JAVA_VERSION}"-jdk-windowsservercore-"${TOOLS_WINDOWS_VE FROM mcr.microsoft.com/powershell:nanoserver-"${TOOLS_WINDOWS_VERSION}" AS pwsh-source -FROM mcr.microsoft.com/windows/nanoserver:"${WINDOWS_VERSION_TAG}" +## Agent image target +FROM mcr.microsoft.com/windows/nanoserver:"${WINDOWS_VERSION_TAG}" AS agent ARG JAVA_HOME="C:\openjdk-17" ENV PSHOME="C:\Program Files\PowerShell" @@ -114,3 +115,19 @@ LABEL ` org.opencontainers.image.url="https://www.jenkins.io/" ` org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" ` org.opencontainers.image.licenses="MIT" + +## Inbound Agent image target +FROM agent AS inbound-agent + +COPY jenkins-agent.ps1 C:/ProgramData/Jenkins + +LABEL ` + org.opencontainers.image.vendor="Jenkins project" ` + org.opencontainers.image.title="Official Jenkins Inbound Agent Base Docker image" ` + org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" ` + org.opencontainers.image.version="${VERSION}" ` + org.opencontainers.image.url="https://www.jenkins.io/" ` + org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent-inbound" ` + org.opencontainers.image.licenses="MIT" + +ENTRYPOINT ["pwsh.exe", "-f", "C:/ProgramData/Jenkins/jenkins-agent.ps1"] diff --git a/windows/windowsservercore/Dockerfile b/windows/windowsservercore/Dockerfile index 8f4b71903..622a605db 100644 --- a/windows/windowsservercore/Dockerfile +++ b/windows/windowsservercore/Dockerfile @@ -27,7 +27,8 @@ ARG WINDOWS_VERSION_TAG=ltsc2019 ARG TOOLS_WINDOWS_VERSION=1809 FROM eclipse-temurin:"${JAVA_VERSION}"-jdk-windowsservercore-"${TOOLS_WINDOWS_VERSION}" AS jdk-core -FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" +## Agent image target +FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" AS agent ARG JAVA_HOME="C:\openjdk-17" ENV JAVA_HOME=${JAVA_HOME} @@ -107,3 +108,19 @@ LABEL ` org.opencontainers.image.url="https://www.jenkins.io/" ` org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent" ` org.opencontainers.image.licenses="MIT" + +## Inbound Agent image target +FROM agent AS inbound-agent + +COPY jenkins-agent.ps1 C:/ProgramData/Jenkins + +LABEL ` + org.opencontainers.image.vendor="Jenkins project" ` + org.opencontainers.image.title="Official Jenkins Inbound Agent Base Docker image" ` + org.opencontainers.image.description="This is an image for Jenkins agents using TCP or WebSockets to establish inbound connection to the Jenkins controller" ` + org.opencontainers.image.version="${VERSION}" ` + org.opencontainers.image.url="https://www.jenkins.io/" ` + org.opencontainers.image.source="https://github.com/jenkinsci/docker-agent-inbound" ` + org.opencontainers.image.licenses="MIT" + +ENTRYPOINT ["powershell.exe", "-f", "C:/ProgramData/Jenkins/jenkins-agent.ps1"]