├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── release-drafter.yml │ ├── sync-dockerhub-readme.yaml │ └── updatecli.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Jenkinsfile ├── LICENSE ├── Makefile ├── README.md ├── alpine └── Dockerfile ├── build-windows.yaml ├── build.ps1 ├── debian ├── Dockerfile └── preview │ └── Dockerfile ├── docker-bake.hcl ├── docs └── windows-commandline.adoc ├── images ├── 0.jpg ├── screen-1.png ├── screen-2.png ├── screen-3.png ├── screen-4.png ├── screen-5.png └── screen-6.png ├── jenkins-agent ├── jenkins-agent.ps1 ├── tests ├── inboundAgent.Tests.ps1 ├── netcat-helper │ ├── Dockerfile │ └── Dockerfile-windows ├── test_helpers.bash ├── test_helpers.psm1 └── tests.bats ├── updatecli ├── updatecli.d │ ├── docker-agent.yaml │ └── nmap.yaml └── values.github-action.yaml └── windows ├── nanoserver └── Dockerfile └── windowsservercore └── Dockerfile /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/team-docker-packaging 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | community_bridge: jenkins 2 | custom: ["https://jenkins.io/donate/#why-donate"] 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Per https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | 5 | # GitHub actions 6 | 7 | - package-ecosystem: "github-actions" 8 | target-branch: master 9 | directory: "/" 10 | schedule: 11 | # Check for updates to GitHub Actions every week 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | 3 | name-template: 'next' 4 | tag-template: 'next' 5 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter (Changelog) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | # Allow to be run manually 8 | workflow_dispatch: 9 | # Prepare the new "next release" once a release is created 10 | release: 11 | 12 | # Only allow 1 release-drafter build at a time to avoid creating multiple "next" releases 13 | concurrency: "release-drafter" 14 | 15 | jobs: 16 | update_release_draft: 17 | uses: jenkinsci/.github/.github/workflows/release-drafter.yml@master 18 | -------------------------------------------------------------------------------- /.github/workflows/sync-dockerhub-readme.yaml: -------------------------------------------------------------------------------- 1 | name: Update Docker Hub Description 2 | on: 3 | release: 4 | types: [ published ] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | dockerHubDescription: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Update Docker Hub description 13 | uses: peter-evans/dockerhub-description@v3 14 | with: 15 | username: ${{ secrets.DOCKERHUB_USERNAME }} 16 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 17 | enable-url-completion: true 18 | short-description: ${{ github.event.repository.description }} 19 | repository: jenkins/inbound-agent 20 | -------------------------------------------------------------------------------- /.github/workflows/updatecli.yaml: -------------------------------------------------------------------------------- 1 | name: updatecli 2 | on: 3 | # Allow to be run manually 4 | workflow_dispatch: 5 | schedule: 6 | # Run once per week (to avoid alert fatigue) 7 | - cron: '0 2 * * 1' # Every Monday at 2am UTC 8 | push: 9 | pull_request: 10 | jobs: 11 | updatecli: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Install Updatecli in the runner 18 | uses: updatecli/updatecli-action@v2.52.0 19 | 20 | - name: Run Updatecli in Dry Run mode 21 | run: updatecli diff --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml 22 | env: 23 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - name: Run Updatecli in Apply mode 26 | if: github.ref == 'refs/heads/master' 27 | run: updatecli apply --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml 28 | env: 29 | UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | bats-core/ 3 | /.vscode/ 4 | 5 | bats/ 6 | target/ 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Jenkins Docker JNLP Agent 2 | 3 | ## Getting started 4 | 5 | 1. Fork the repository on GitHub 6 | 2. Clone the forked repository to your machine 7 | 3. Install Docker 8 | 4. Build the images with `make` (Linux utility or Windows Powershell `make.ps1`) 9 | 10 | ## Testing changes 11 | 12 | Follow the instructions in the [README](README.md#running) to run your image. 13 | 14 | ## Proposing Changes 15 | 16 | The Jenkins project source code repositories are hosted at GitHub. 17 | All proposed changes are submitted and code reviewed using the _GitHub Pull Request_ process. 18 | 19 | To submit a pull request: 20 | 21 | 1. Commit changes and push them to your fork on GitHub. 22 | It is a good practice is to create branches instead of pushing to master. 23 | 2. In GitHub Web UI click the _New Pull Request_ button 24 | 3. Select `jenkinsci` as _base fork_ and `master` as `base`, then click _Create Pull Request_ 25 | * We integrate all changes into the master branch towards continuous delivery releases 26 | 4. Fill in the Pull Request description 27 | 5. Click _Create Pull Request_ 28 | 6. Wait for CI results/reviews, process the feedback. 29 | * If you do not get feedback after 3 days, feel free to ping `@jenkinsci/code-reviewers` to CC. 30 | 31 | Once a Pull Request is merged, the continuous delivery process will update the `latest` images 32 | on [Dockerhub](https://hub.docker.com/r/jenkins/inbound-agent/tags?page=1&name=latest). 33 | 34 | ## Releasing the Docker Images 35 | 36 | Oleg Nenashev provided a tutorial of the docker image release process in 37 | [this video](https://youtu.be/rfUd-ymrXpo) 38 | 39 | [![Docker JNLP Agent Release Process Tutorial](images/0.jpg)](https://youtu.be/rfUd-ymrXpo) 40 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | def agentSelector(String imageType) { 2 | // Image type running on a Linux agent 3 | if (imageType == 'linux') { 4 | return 'linux' 5 | } 6 | // Image types running on a Windows Server Core 2022 agent 7 | if (imageType.contains('2022')) { 8 | return 'windows-2022' 9 | } 10 | // Remaining image types running on a Windows Server Core 2019 agent: (nanoserver|windowservercore)-(1809|2019) 11 | return 'windows-2019' 12 | } 13 | 14 | pipeline { 15 | agent none 16 | 17 | options { 18 | buildDiscarder(logRotator(daysToKeepStr: '10')) 19 | } 20 | 21 | stages { 22 | stage('docker-inbound-agent') { 23 | matrix { 24 | axes { 25 | axis { 26 | name 'IMAGE_TYPE' 27 | values 'linux', 'nanoserver-1809', 'nanoserver-ltsc2019', 'nanoserver-ltsc2022', 'windowsservercore-1809', 'windowsservercore-ltsc2019', 'windowsservercore-ltsc2022' 28 | } 29 | } 30 | stages { 31 | stage('Main') { 32 | agent { 33 | label agentSelector(env.IMAGE_TYPE) 34 | } 35 | options { 36 | timeout(time: 60, unit: 'MINUTES') 37 | } 38 | environment { 39 | DOCKERHUB_ORGANISATION = "${infra.isTrusted() ? 'jenkins' : 'jenkins4eval'}" 40 | } 41 | stages { 42 | stage('Prepare Docker') { 43 | when { 44 | environment name: 'IMAGE_TYPE', value: 'linux' 45 | } 46 | steps { 47 | sh ''' 48 | docker buildx create --use 49 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 50 | ''' 51 | } 52 | } 53 | stage('Build and Test') { 54 | // This stage is the "CI" and should be run on all code changes triggered by a code change 55 | when { 56 | not { buildingTag() } 57 | } 58 | steps { 59 | script { 60 | if(isUnix()) { 61 | sh 'make build' 62 | sh 'make test' 63 | // If the tests are passing for Linux AMD64, then we can build all the CPU architectures 64 | sh 'docker buildx bake --file docker-bake.hcl linux' 65 | } else { 66 | powershell '& ./build.ps1 test' 67 | } 68 | } 69 | } 70 | post { 71 | always { 72 | junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results.xml') 73 | } 74 | } 75 | } 76 | stage('Deploy to DockerHub') { 77 | // This stage is the "CD" and should only be run when a tag triggered the build 78 | when { 79 | buildingTag() 80 | } 81 | steps { 82 | script { 83 | infra.withDockerCredentials { 84 | if (isUnix()) { 85 | sh ''' 86 | export IMAGE_TAG="${TAG_NAME}" 87 | export ON_TAG=true 88 | docker buildx bake --push --file docker-bake.hcl linux 89 | ''' 90 | } else { 91 | powershell '& ./build.ps1 -PushVersions -VersionTag $env:TAG_NAME publish' 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | // vim: ft=groovy 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2019 CloudBees, Inc., Nicolas De loof, Carlos Sanchez, Oleg Nenashev, Alex Earl and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | ## For Docker <=20.04 4 | export DOCKER_BUILDKIT=1 5 | ## For Docker <=20.04 6 | export DOCKER_CLI_EXPERIMENTAL=enabled 7 | ## Required to have docker build output always printed on stdout 8 | export BUILDKIT_PROGRESS=plain 9 | 10 | current_arch := $(shell uname -m) 11 | export ARCH ?= $(shell case $(current_arch) in (x86_64) echo "amd64" ;; (i386) echo "386";; (aarch64|arm64) echo "arm64" ;; (armv6*) echo "arm/v6";; (armv7*) echo "arm/v7";; (s390*|riscv*|ppc64le) echo $(current_arch);; (*) echo "UNKNOWN-CPU";; esac) 12 | 13 | # Set to the path of a specific test suite to restrict execution only to this 14 | # default is "all test suites in the "tests/" directory 15 | TEST_SUITES ?= $(CURDIR)/tests 16 | 17 | ##### Macros 18 | ## Check the presence of a CLI in the current PATH 19 | check_cli = type "$(1)" >/dev/null 2>&1 || { echo "Error: command '$(1)' required but not found. Exiting." ; exit 1 ; } 20 | ## Check if a given image exists in the current manifest docker-bake.hcl 21 | check_image = make --silent list | grep -w '$(1)' >/dev/null 2>&1 || { echo "Error: the image '$(1)' does not exist in manifest for the platform 'linux/$(ARCH)'. Please check the output of 'make list'. Exiting." ; exit 1 ; } 22 | ## Base "docker buildx base" command to be reused everywhere 23 | bake_base_cli := docker buildx bake -f docker-bake.hcl --load 24 | 25 | .PHONY: build test test-alpine test-debian test-jdk17 test-jdk17-alpine 26 | 27 | check-reqs: 28 | ## Build requirements 29 | @$(call check_cli,bash) 30 | @$(call check_cli,git) 31 | @$(call check_cli,docker) 32 | @docker info | grep 'buildx:' >/dev/null 2>&1 || { echo "Error: Docker BuildX plugin required but not found. Exiting." ; exit 1 ; } 33 | ## Test requirements 34 | @$(call check_cli,curl) 35 | @$(call check_cli,jq) 36 | 37 | build: check-reqs 38 | @set -x; $(bake_base_cli) --set '*.platform=linux/$(ARCH)' $(shell make --silent list) 39 | 40 | build-%: 41 | @$(call check_image,$*) 42 | @set -x; $(bake_base_cli) --set '*.platform=linux/$(ARCH)' '$*' 43 | 44 | show: 45 | @$(bake_base_cli) linux --print 46 | 47 | list: check-reqs 48 | @set -x; make --silent show | jq -r '.target | path(.. | select(.platforms[] | contains("linux/$(ARCH)"))?) | add' 49 | 50 | 51 | bats: 52 | git clone https://github.com/bats-core/bats-core bats ;\ 53 | cd bats ;\ 54 | git checkout v1.8.2 55 | 56 | prepare-test: bats check-reqs 57 | git submodule update --init --recursive 58 | mkdir -p target 59 | 60 | ## Define bats options based on environment 61 | # common flags for all tests 62 | bats_flags := $(TEST_SUITES) 63 | # if DISABLE_PARALLEL_TESTS true, then disable parallel execution 64 | ifneq (true,$(DISABLE_PARALLEL_TESTS)) 65 | # If the GNU 'parallel' command line is absent, then disable parallel execution 66 | parallel_cli := $(shell command -v parallel 2>/dev/null) 67 | ifneq (,$(parallel_cli)) 68 | # If parallel execution is enabled, then set 2 tests per core available for the Docker Engine 69 | test-%: PARALLEL_JOBS ?= $(shell echo $$(( $(shell docker run --rm alpine grep -c processor /proc/cpuinfo) * 2))) 70 | test-%: bats_flags += --jobs $(PARALLEL_JOBS) 71 | endif 72 | endif 73 | test-%: prepare-test 74 | # Check that the image exists in the manifest 75 | @$(call check_image,$*) 76 | # Ensure that the image is built 77 | # @make --silent build-$* 78 | # Execute the test harness and write result to a TAP file 79 | set -x 80 | IMAGE=$* bats/bin/bats $(bats_flags) | tee target/results-$*.tap 81 | # convert TAP to JUNIT 82 | docker run --rm -v "$(CURDIR)":/usr/src/app -w /usr/src/app node:16-alpine \ 83 | sh -c "npm install tap-xunit -g && cat target/results-$*.tap | tap-xunit --package='jenkinsci.docker.$*' > target/junit-results-$*.xml" 84 | 85 | test: prepare-test 86 | @make --silent list | while read image; do make --silent "test-$${image}"; done 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker image for inbound Jenkins agents 2 | 3 | > [!IMPORTANT] 4 | > This repository is deprecated, its content is now included in https://github.com/jenkinsci/docker-agent/. 5 | > 6 | > Since https://github.com/jenkinsci/docker-agent/pull/570 both `agent` and `inbound-agent` images are built from https://github.com/jenkinsci/docker-agent/ repository, thanks to the use of [multi-stage Dockerfiles](https://docs.docker.com/build/building/multi-stage/) and to the use of targets. 7 | > 8 | > See https://github.com/jenkinsci/docker-agent/issues/569 for more details. 9 | > 10 | > Note that it doesn't change anything on the Docker hub side, these images still have their own Docker repositories `jenkins/agent` and `jenkins/inbound-agent`. 11 | 12 | [![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) 13 | [![GitHub stars](https://img.shields.io/github/stars/jenkinsci/docker-inbound-agent?label=GitHub%20stars)](https://github.com/jenkinsci/docker-inbound-agent) 14 | [![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/inbound-agent.svg)](https://hub.docker.com/r/jenkins/inbound-agent/) 15 | [![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-inbound-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-inbound-agent/releases/latest) 16 | 17 | :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/). 18 | These images are deprecated, use [jenkins/inbound-agent](https://hub.docker.com/r/jenkins/inbound-agent/). 19 | 20 | This is an image for [Jenkins](https://jenkins.io) agents using TCP or WebSockets to establish inbound connection to the Jenkins master. 21 | 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. 22 | 23 | See [Using Agents](https://www.jenkins.io/doc/book/using/using-agents/) for more info. 24 | 25 | ## Configuring agents with this container image 26 | 27 | ### Setup the agent on Jenkins 28 | 29 | 1. Go to your Jenkins dashboard 30 | 2. Go to `Manage Jenkins` option in main menu 31 | 3. Go to `Nodes` item in `System Configuration` 32 | ![image](images/screen-4.png) 33 | 4. Go to `New Node` option in side menu 34 | 5. Fill the Node(agent) name and select the type; (e.g. Name: agent1, Type: Permanent Agent) 35 | 6. Now fill the fields like remote root directory, labels, # of executors, etc. 36 | * **`Launch method` is `Launch agent by connecting it to the controller`** 37 | ![image](images/screen-1.png) 38 | 7. Press the `Save` button and the agent1 will be registered, but offline for the time being. Click on it. 39 | ![image](images/screen-2.png) 40 | 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. 41 | ![image](images/screen-3.png) 42 | 43 | ### Running this container 44 | 45 | To run a Docker container 46 | > **Note** 47 | > 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). 48 | > 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. 49 | 50 | Linux agent: 51 | 52 | docker run --init jenkins/inbound-agent -url http://jenkins-server:port 53 | Note: `--init` is necessary for correct subprocesses handling (zombie reaping) 54 | 55 | Windows agent: 56 | 57 | docker run jenkins/inbound-agent:windowsservercore-ltsc2019 -Url http://jenkins-server:port -Secret -Name 58 | 59 | To run a Docker container with [Work Directory](https://github.com/jenkinsci/remoting/blob/master/docs/workDir.md) 60 | 61 | Linux agent: 62 | 63 | docker run --init jenkins/inbound-agent -url http://jenkins-server:port -workDir=/home/jenkins/agent 64 | 65 | Windows agent: 66 | 67 | docker run jenkins/inbound-agent:windowsservercore-ltsc2019 -Url http://jenkins-server:port -WorkDir=C:/Jenkins/agent -Secret -Name 68 | 69 | Optional environment variables: 70 | 71 | * `JENKINS_JAVA_BIN`: Path to Java executable to use instead of the default in PATH or obtained from JAVA_HOME 72 | * `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). 73 | * `JENKINS_URL`: url for the Jenkins server, can be used as a replacement to `-url` option, or to set alternate jenkins URL 74 | * `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. 75 | * `JENKINS_SECRET`: (use only if not set as an argument) the secret as shown on the controller after creating the agent 76 | * `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 77 | * `JENKINS_AGENT_WORKDIR`: agent work directory, if not set by optional parameter `-workDir` 78 | * `JENKINS_WEB_SOCKET`: `true` if the connection should be made via WebSocket rather than TCP 79 | * `JENKINS_DIRECT_CONNECTION`: (`HOST:PORT`) Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. 80 | * `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. 81 | * `JENKINS_PROTOCOLS`: Specify the remoting protocols to attempt when `JENKINS_INSTANCE_IDENTITY` is provided. 82 | 83 | #### Example 84 | 85 | 1. Enter the command above. 86 | ![image](images/screen-5.png) 87 | 2. Check the Jenkins dashboard if the agent is connected well. 88 | ![image](images/screen-6.png) 89 | 90 | 91 | ## Windows Jenkins Java Opts 92 | 93 | 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. 94 | For example: 95 | 96 | -XX:+PrintCommandLineFlags --show-version 97 | 98 | This would need to be escaped with quotes like this: 99 | 100 | "-XX:+PrintCommandLineFlags" --show-version 101 | 102 | Or another example: 103 | -Dsome.property=some value --show-version 104 | 105 | This would need to be escaped like this: 106 | 107 | "-Dsome.property='some value'" --show-version 108 | 109 | 110 | ## Configuration specifics 111 | 112 | ### Enabled JNLP protocols 113 | 114 | 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. 115 | Earlier, long-unsupported protocols have been removed. 116 | As a result, Jenkins versions prior to 2.32 are no longer supported. 117 | 118 | ### Amazon ECS 119 | 120 | 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. 121 | -------------------------------------------------------------------------------- /alpine/Dockerfile: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2015-2020, CloudBees, Inc. and other Jenkins contributors 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | ARG version=3206.vb_15dcf73f6a_9-1 24 | ARG JAVA_MAJOR_VERSION=17 25 | FROM jenkins/agent:"${version}"-alpine-jdk"${JAVA_MAJOR_VERSION}" 26 | 27 | ARG user=jenkins 28 | 29 | USER root 30 | COPY ../../jenkins-agent /usr/local/bin/jenkins-agent 31 | RUN chmod +x /usr/local/bin/jenkins-agent &&\ 32 | ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave 33 | USER ${user} 34 | 35 | ENTRYPOINT ["/usr/local/bin/jenkins-agent"] 36 | -------------------------------------------------------------------------------- /build-windows.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | jdk11: 3 | image: jdk11-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG} 4 | build: 5 | context: ./ 6 | dockerfile: ./windows/${WINDOWS_FLAVOR}/Dockerfile 7 | args: 8 | JAVA_MAJOR_VERSION: 11 9 | version: ${PARENT_IMAGE_VERSION} 10 | WINDOWS_VERSION_TAG: ${WINDOWS_VERSION_TAG} 11 | jdk17: 12 | image: jdk17-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG} 13 | build: 14 | context: ./ 15 | dockerfile: ./windows/${WINDOWS_FLAVOR}/Dockerfile 16 | args: 17 | JAVA_MAJOR_VERSION: 17 18 | version: ${PARENT_IMAGE_VERSION} 19 | WINDOWS_VERSION_TAG: ${WINDOWS_VERSION_TAG} 20 | jdk21: 21 | image: jdk21-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG} 22 | build: 23 | context: ./ 24 | dockerfile: ./windows/${WINDOWS_FLAVOR}/Dockerfile 25 | args: 26 | JAVA_MAJOR_VERSION: 21 27 | version: ${PARENT_IMAGE_VERSION} 28 | WINDOWS_VERSION_TAG: ${WINDOWS_VERSION_TAG} 29 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=1)] 4 | [String] $Target = "build", 5 | [String] $Build = '', 6 | [String] $VersionTag = 'NEXT_TAG_VERSION', 7 | [String] $ParentImageVersion = '3206.vb_15dcf73f6a_9-1', 8 | 9 | [switch] $PushVersions = $false 10 | ) 11 | 12 | $ErrorActionPreference = 'Stop' 13 | $Repository = 'inbound-agent' 14 | $Organization = 'jenkins' 15 | $ImageType = 'windowsservercore-ltsc2019' 16 | 17 | if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO)) { 18 | $Repository = $env:DOCKERHUB_REPO 19 | } 20 | 21 | if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_ORGANISATION)) { 22 | $Organization = $env:DOCKERHUB_ORGANISATION 23 | } 24 | 25 | if(![String]::IsNullOrWhiteSpace($env:PARENT_IMAGE_VERSION)) { 26 | $ParentImageVersion = $env:PARENT_IMAGE_VERSION 27 | } 28 | 29 | if(![String]::IsNullOrWhiteSpace($env:IMAGE_TYPE)) { 30 | $ImageType = $env:IMAGE_TYPE 31 | } 32 | 33 | # Check for required commands 34 | Function Test-CommandExists { 35 | # From https://devblogs.microsoft.com/scripting/use-a-powershell-function-to-see-if-a-command-exists/ 36 | Param ( 37 | [String] $command 38 | ) 39 | 40 | $oldPreference = $ErrorActionPreference 41 | $ErrorActionPreference = 'stop' 42 | try { 43 | if(Get-Command $command){ 44 | Write-Debug "$command exists" 45 | } 46 | } 47 | Catch { 48 | "$command does not exist" 49 | } 50 | Finally { 51 | $ErrorActionPreference=$oldPreference 52 | } 53 | } 54 | 55 | # # this is the jdk version that will be used for the 'bare tag' images, e.g., jdk7-windowsservercore-1809 -> windowsserver-1809 56 | $defaultJdk = '17' 57 | $builds = @{} 58 | $env:PARENT_IMAGE_VERSION = "$ParentImageVersion" 59 | 60 | $items = $ImageType.Split("-") 61 | $env:WINDOWS_FLAVOR = $items[0] 62 | $env:WINDOWS_VERSION_TAG = $items[1] 63 | 64 | # # Uncomment to help debugging when working on this script 65 | # Write-Host "= DEBUG: env vars" 66 | # Get-ChildItem Env: | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } 67 | 68 | $ProgressPreference = 'SilentlyContinue' # Disable Progress bar for faster downloads 69 | 70 | Test-CommandExists "docker" 71 | Test-CommandExists "docker-compose" 72 | Test-CommandExists "yq" 73 | 74 | $baseDockerCmd = 'docker-compose --file=build-windows.yaml' 75 | $baseDockerBuildCmd = '{0} build --parallel --pull' -f $baseDockerCmd 76 | 77 | Invoke-Expression "$baseDockerCmd config --services" 2>$null | ForEach-Object { 78 | $image = '{0}-{1}-{2}' -f $_, $env:WINDOWS_FLAVOR, $env:WINDOWS_VERSION_TAG # Ex: "jdk17-windowsservercore-ltsc2019" 79 | 80 | # Remove the 'jdk' prefix (3 first characters) 81 | $jdkMajorVersion = $_.Remove(0,3) 82 | 83 | $baseImage = "${env:WINDOWS_FLAVOR}-${env:WINDOWS_VERSION_TAG}" 84 | $completeVersionTag = "${VersionTag}-${image}" 85 | $tags = @( $image, $completeVersionTag ) 86 | # Additional image tag without any 'jdk' prefix for the default JDK 87 | if($jdkMajorVersion -eq "$defaultJdk") { 88 | $tags += $baseImage 89 | } 90 | 91 | $builds[$image] = @{ 92 | 'Tags' = $tags; 93 | } 94 | } 95 | 96 | Write-Host "= PREPARE: List of ${Organization}/${Repository} images and tags to be processed for ${ImageType}:" 97 | ConvertTo-Json $builds 98 | 99 | if(![System.String]::IsNullOrWhiteSpace($Build) -and $builds.ContainsKey($Build)) { 100 | Write-Host "= BUILD: Building image ${Build}..." 101 | $dockerBuildCmd = '{0} {1}' -f $baseDockerBuildCmd, $Build 102 | Invoke-Expression $dockerBuildCmd 103 | Write-Host "= BUILD: Finished building image ${Build}" 104 | } else { 105 | Write-Host "= BUILD: Building all images..." 106 | Invoke-Expression $baseDockerBuildCmd 107 | Write-Host "= BUILD: Finished building all image" 108 | } 109 | 110 | if($lastExitCode -ne 0) { 111 | exit $lastExitCode 112 | } 113 | 114 | function Test-Image { 115 | param ( 116 | $ImageName 117 | ) 118 | 119 | Write-Host "= TEST: Testing image ${ImageName}:" # Ex: jdk17-windowsservercore-ltsc2019 120 | 121 | $env:AGENT_IMAGE = $ImageName 122 | $serviceName = $ImageName.SubString(0, $ImageName.IndexOf('-')) 123 | $env:BUILD_CONTEXT = Invoke-Expression "$baseDockerCmd config" 2>$null | yq -r ".services.${serviceName}.build.context" 124 | $env:version = $ParentImageVersion 125 | 126 | if(Test-Path ".\target\$ImageName") { 127 | Remove-Item -Recurse -Force ".\target\$ImageName" 128 | } 129 | New-Item -Path ".\target\$ImageName" -Type Directory | Out-Null 130 | $configuration.TestResult.OutputPath = ".\target\$ImageName\junit-results.xml" 131 | $TestResults = Invoke-Pester -Configuration $configuration 132 | if ($TestResults.FailedCount -gt 0) { 133 | Write-Host "There were $($TestResults.FailedCount) failed tests in $ImageName" 134 | $testFailed = $true 135 | } else { 136 | Write-Host "There were $($TestResults.PassedCount) passed tests out of $($TestResults.TotalCount) in $ImageName" 137 | } 138 | Remove-Item env:\AGENT_IMAGE 139 | Remove-Item env:\BUILD_CONTEXT 140 | Remove-Item env:\VERSION 141 | } 142 | 143 | if($target -eq "test") { 144 | Write-Host "= TEST: Starting test harness" 145 | 146 | # Only fail the run afterwards in case of any test failures 147 | $testFailed = $false 148 | $mod = Get-InstalledModule -Name Pester -MinimumVersion 5.3.0 -MaximumVersion 5.3.3 -ErrorAction SilentlyContinue 149 | if($null -eq $mod) { 150 | Write-Host "= TEST: Pester 5.3.x not found: installing..." 151 | $module = "c:\Program Files\WindowsPowerShell\Modules\Pester" 152 | if(Test-Path $module) { 153 | takeown /F $module /A /R 154 | icacls $module /reset 155 | icacls $module /grant Administrators:'F' /inheritance:d /T 156 | Remove-Item -Path $module -Recurse -Force -Confirm:$false 157 | } 158 | Install-Module -Force -Name Pester -MaximumVersion 5.3.3 159 | } 160 | 161 | Import-Module Pester 162 | Write-Host "= TEST: Setting up Pester environment..." 163 | $configuration = [PesterConfiguration]::Default 164 | $configuration.Run.PassThru = $true 165 | $configuration.Run.Path = '.\tests' 166 | $configuration.Run.Exit = $true 167 | $configuration.TestResult.Enabled = $true 168 | $configuration.TestResult.OutputFormat = 'JUnitXml' 169 | $configuration.Output.Verbosity = 'Diagnostic' 170 | $configuration.CodeCoverage.Enabled = $false 171 | 172 | if(![System.String]::IsNullOrWhiteSpace($Build) -and $builds.ContainsKey($Build)) { 173 | Test-Image $Build 174 | } else { 175 | Write-Host "= TEST: Testing all images..." 176 | foreach($image in $builds.Keys) { 177 | Test-Image $image 178 | } 179 | } 180 | 181 | # Fail if any test failures 182 | if($testFailed -ne $false) { 183 | Write-Error "Test stage failed!" 184 | exit 1 185 | } else { 186 | Write-Host "Test stage passed!" 187 | } 188 | } 189 | 190 | function Publish-Image { 191 | param ( 192 | [String] $Build, 193 | [String] $ImageName 194 | ) 195 | Write-Host "= PUBLISH: Tagging $Build => full name = $ImageName" 196 | docker tag "$Build" "$ImageName" 197 | 198 | Write-Host "= PUBLISH: Publishing $ImageName..." 199 | docker push "$ImageName" 200 | } 201 | 202 | 203 | if($target -eq "publish") { 204 | # Only fail the run afterwards in case of any issues when publishing the docker images 205 | $publishFailed = 0 206 | if(![System.String]::IsNullOrWhiteSpace($Build) -and $builds.ContainsKey($Build)) { 207 | foreach($tag in $Builds[$Build]['Tags']) { 208 | Publish-Image "$Build" "${Organization}/${Repository}:${tag}" 209 | if($lastExitCode -ne 0) { 210 | $publishFailed = 1 211 | } 212 | 213 | if($PushVersions) { 214 | $buildTag = "$tag" 215 | if($tag -eq 'latest') { 216 | $buildTag = "$VersionTag" 217 | } 218 | Publish-Image "$b" "${Organization}/${Repository}:${buildTag}" 219 | if($lastExitCode -ne 0) { 220 | $publishFailed = 1 221 | } 222 | } 223 | } 224 | } else { 225 | foreach($b in $builds.Keys) { 226 | foreach($tag in $Builds[$b]['Tags']) { 227 | Publish-Image "$b" "${Organization}/${Repository}:${tag}" 228 | if($lastExitCode -ne 0) { 229 | $publishFailed = 1 230 | } 231 | 232 | if($PushVersions) { 233 | $buildTag = "$tag" 234 | if($tag -eq 'latest') { 235 | $buildTag = "$VersionTag" 236 | } 237 | Publish-Image "$b" "${Organization}/${Repository}:${buildTag}" 238 | if($lastExitCode -ne 0) { 239 | $publishFailed = 1 240 | } 241 | } 242 | } 243 | } 244 | } 245 | 246 | # Fail if any issues when publising the docker images 247 | if($publishFailed -ne 0) { 248 | Write-Error "Publish failed!" 249 | exit 1 250 | } 251 | } 252 | 253 | if($lastExitCode -ne 0) { 254 | Write-Error "Build failed!" 255 | } else { 256 | Write-Host "Build finished successfully" 257 | } 258 | exit $lastExitCode 259 | -------------------------------------------------------------------------------- /debian/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG version=3206.vb_15dcf73f6a_9-1 2 | ARG JAVA_MAJOR_VERSION=17 3 | FROM jenkins/agent:"${version}"-jdk"${JAVA_MAJOR_VERSION}" 4 | 5 | ARG user=jenkins 6 | 7 | USER root 8 | COPY ../../jenkins-agent /usr/local/bin/jenkins-agent 9 | RUN chmod +x /usr/local/bin/jenkins-agent &&\ 10 | ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave 11 | USER ${user} 12 | 13 | ENTRYPOINT ["/usr/local/bin/jenkins-agent"] 14 | -------------------------------------------------------------------------------- /debian/preview/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG version=3206.vb_15dcf73f6a_9-1-preview 2 | ARG JAVA_MAJOR_VERSION=21 3 | FROM jenkins/agent:"${version}"-jdk"${JAVA_MAJOR_VERSION}-preview" 4 | 5 | ARG user=jenkins 6 | 7 | USER root 8 | COPY ../../jenkins-agent /usr/local/bin/jenkins-agent 9 | RUN chmod +x /usr/local/bin/jenkins-agent &&\ 10 | ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave 11 | USER ${user} 12 | 13 | ENTRYPOINT ["/usr/local/bin/jenkins-agent"] 14 | -------------------------------------------------------------------------------- /docker-bake.hcl: -------------------------------------------------------------------------------- 1 | group "linux" { 2 | targets = [ 3 | "alpine_jdk11", 4 | "alpine_jdk17", 5 | "alpine_jdk21", 6 | "debian_jdk11", 7 | "debian_jdk17", 8 | "debian_jdk21", 9 | "debian_jdk21_preview", 10 | ] 11 | } 12 | 13 | group "linux-arm64" { 14 | targets = [ 15 | "alpine_jdk21", 16 | "debian_jdk11", 17 | "debian_jdk17", 18 | "debian_jdk21", 19 | ] 20 | } 21 | 22 | group "linux-arm32" { 23 | targets = [ 24 | "debian_jdk11", 25 | "debian_jdk17", 26 | "debian_jdk21_preview", 27 | ] 28 | } 29 | 30 | group "linux-s390x" { 31 | targets = [ 32 | "debian_jdk11", 33 | "debian_jdk21_preview", 34 | ] 35 | } 36 | 37 | group "linux-ppc64le" { 38 | targets = [ 39 | "debian_jdk11", 40 | "debian_jdk17", 41 | "debian_jdk21_preview", 42 | ] 43 | } 44 | 45 | #### This is the current (e.g. jenkins/inbound-agent) version (including build number suffix). Overridden by release builds from GIT_TAG. 46 | variable "IMAGE_TAG" { 47 | default = "3071.v7e9b_0dc08466-1" 48 | } 49 | 50 | #### This is for the "parent" image version to use (jenkins/agent:-) 51 | variable "PARENT_IMAGE_VERSION" { 52 | default = "3206.vb_15dcf73f6a_9-1" 53 | } 54 | 55 | variable "REGISTRY" { 56 | default = "docker.io" 57 | } 58 | 59 | variable "JENKINS_REPO" { 60 | default = "jenkins/inbound-agent" 61 | } 62 | 63 | variable "ON_TAG" { 64 | default = "false" 65 | } 66 | 67 | target "alpine_jdk11" { 68 | dockerfile = "alpine/Dockerfile" 69 | context = "." 70 | args = { 71 | JAVA_MAJOR_VERSION = "11" 72 | version = "${PARENT_IMAGE_VERSION}" 73 | } 74 | tags = [ 75 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-alpine-jdk11" : "", 76 | "${REGISTRY}/${JENKINS_REPO}:alpine-jdk11", 77 | "${REGISTRY}/${JENKINS_REPO}:latest-alpine-jdk11", 78 | ] 79 | platforms = ["linux/amd64"] 80 | } 81 | 82 | target "alpine_jdk17" { 83 | dockerfile = "alpine/Dockerfile" 84 | context = "." 85 | args = { 86 | JAVA_MAJOR_VERSION = "17" 87 | version = "${PARENT_IMAGE_VERSION}" 88 | } 89 | tags = [ 90 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-alpine" : "", 91 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-alpine-jdk17" : "", 92 | "${REGISTRY}/${JENKINS_REPO}:alpine", 93 | "${REGISTRY}/${JENKINS_REPO}:alpine-jdk17", 94 | "${REGISTRY}/${JENKINS_REPO}:latest-alpine", 95 | "${REGISTRY}/${JENKINS_REPO}:latest-alpine-jdk17", 96 | ] 97 | platforms = ["linux/amd64"] 98 | } 99 | 100 | target "alpine_jdk21" { 101 | dockerfile = "alpine/Dockerfile" 102 | context = "." 103 | args = { 104 | JAVA_MAJOR_VERSION = "21" 105 | version = "${PARENT_IMAGE_VERSION}" 106 | } 107 | tags = [ 108 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-alpine-jdk21" : "", 109 | "${REGISTRY}/${JENKINS_REPO}:alpine-jdk21", 110 | "${REGISTRY}/${JENKINS_REPO}:latest-alpine-jdk21", 111 | ] 112 | platforms = ["linux/amd64", "linux/arm64"] 113 | } 114 | 115 | target "debian_jdk11" { 116 | dockerfile = "debian/Dockerfile" 117 | context = "." 118 | args = { 119 | JAVA_MAJOR_VERSION = "11" 120 | version = "${PARENT_IMAGE_VERSION}" 121 | } 122 | tags = [ 123 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-jdk11" : "", 124 | "${REGISTRY}/${JENKINS_REPO}:jdk11", 125 | "${REGISTRY}/${JENKINS_REPO}:latest-jdk11", 126 | ] 127 | platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/s390x", "linux/ppc64le"] 128 | } 129 | 130 | target "debian_jdk17" { 131 | dockerfile = "debian/Dockerfile" 132 | context = "." 133 | args = { 134 | JAVA_MAJOR_VERSION = "17" 135 | version = "${PARENT_IMAGE_VERSION}" 136 | } 137 | tags = [ 138 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}" : "", 139 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-jdk17" : "", 140 | "${REGISTRY}/${JENKINS_REPO}:jdk17", 141 | "${REGISTRY}/${JENKINS_REPO}:latest", 142 | "${REGISTRY}/${JENKINS_REPO}:latest-jdk17", 143 | ] 144 | platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/ppc64le"] 145 | } 146 | 147 | target "debian_jdk21" { 148 | dockerfile = "debian/Dockerfile" 149 | context = "." 150 | args = { 151 | JAVA_MAJOR_VERSION = "21" 152 | version = "${PARENT_IMAGE_VERSION}" 153 | } 154 | tags = [ 155 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-jdk21" : "", 156 | "${REGISTRY}/${JENKINS_REPO}:jdk21", 157 | "${REGISTRY}/${JENKINS_REPO}:latest-jdk21", 158 | ] 159 | platforms = ["linux/amd64", "linux/arm64"] 160 | } 161 | 162 | target "debian_jdk21_preview" { 163 | dockerfile = "debian/preview/Dockerfile" 164 | context = "." 165 | args = { 166 | JAVA_MAJOR_VERSION = "21" 167 | version = "${PARENT_IMAGE_VERSION}" 168 | } 169 | tags = [ 170 | equal(ON_TAG, "true") ? "${REGISTRY}/${JENKINS_REPO}:${PARENT_IMAGE_VERSION}-jdk21-preview" : "", 171 | "${REGISTRY}/${JENKINS_REPO}:jdk21-preview", 172 | "${REGISTRY}/${JENKINS_REPO}:latest-jdk21-preview", 173 | ] 174 | platforms = ["linux/ppc64le", "linux/s390x", "linux/arm/v7"] 175 | } 176 | -------------------------------------------------------------------------------- /docs/windows-commandline.adoc: -------------------------------------------------------------------------------- 1 | = Windows Command Line and Environment Variable Information 2 | 3 | Options can be passed to the docker image via command line parameters after the image name or via link:https://docs.docker.com/engine/reference/run/#env-environment-variables[environment variables]. 4 | 5 | * `-Url ` or `JENKINS_URL=` - the Jenkins controller URL (this should NOT include the `computer/NAME/slave-agent.jnlp` part which appears in the Jenkins UI after creating a new agent) 6 | * `-Secret ` or `JENKINS_SECRET=` - the secret as shown on the controller after creating the agent 7 | * `-Name ` or `JENKINS_AGENT_NAME=` - the name of the agent, it should match the name you specified when creating the agent on the controller 8 | * `-Tunnel ` or `JENKINS_TUNNEL=` - A tunnel host to route the TCP traffic through when connecting to the controller 9 | * `-WorkDir ` or `JENKINS_AGENT_WORKDIR=` - same as the -workDir parameter mentioned above in the jenkins/agent image information. 10 | * `-WebSocket` or `JENKINS_WEB_SOCKET=true` - when present the connection to the controller will be done via WebSocket through the Jenkins URL rather than using a separate network port. 11 | * `-DirectConnection ` or `JENKINS_DIRECT_CONNECTION=` - Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. 12 | * `-InstanceIdentity ` or `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 13 | * `-JavaHome ` or `JAVA_HOME=` - An override for the default JAVA_HOME baked into the image (different for JDK8 vs. JDK11) 14 | * `-Protocols ` or `JENKINS_PROTOCOLS=` - Specify the link:https://github.com/jenkinsci/remoting/blob/de7818885a5bf478760ba29f5ee216291437cb16/docs/protocols.md#active-protocols[remoting protocols] to attempt when instanceIdentity is provided. 15 | 16 | [NOTE] 17 | ==== 18 | There is currently an issue where the `-Protocols` parameter is not actually passed to agent.jar. The link:https://github.com/jenkinsci/docker-inbound-agent/pull/170[fix] has been merged, but a release has not been made. 19 | ==== 20 | -------------------------------------------------------------------------------- /images/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-inbound-agent/f72e0a199a9b27a2bbca701ce96b7aacfe13c104/images/0.jpg -------------------------------------------------------------------------------- /images/screen-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-inbound-agent/f72e0a199a9b27a2bbca701ce96b7aacfe13c104/images/screen-1.png -------------------------------------------------------------------------------- /images/screen-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-inbound-agent/f72e0a199a9b27a2bbca701ce96b7aacfe13c104/images/screen-2.png -------------------------------------------------------------------------------- /images/screen-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-inbound-agent/f72e0a199a9b27a2bbca701ce96b7aacfe13c104/images/screen-3.png -------------------------------------------------------------------------------- /images/screen-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-inbound-agent/f72e0a199a9b27a2bbca701ce96b7aacfe13c104/images/screen-4.png -------------------------------------------------------------------------------- /images/screen-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-inbound-agent/f72e0a199a9b27a2bbca701ce96b7aacfe13c104/images/screen-5.png -------------------------------------------------------------------------------- /images/screen-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/docker-inbound-agent/f72e0a199a9b27a2bbca701ce96b7aacfe13c104/images/screen-6.png -------------------------------------------------------------------------------- /jenkins-agent: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # The MIT License 4 | # 5 | # Copyright (c) 2015-2020, CloudBees, Inc. 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | # Usage jenkins-agent.sh [options] -url http://jenkins -secret [SECRET] -name [AGENT_NAME] 26 | # Optional environment variables : 27 | # * JENKINS_JAVA_BIN : Java executable to use instead of the default in PATH or obtained from JAVA_HOME 28 | # * JENKINS_JAVA_OPTS : Java Options to use for the remoting process, otherwise obtained from JAVA_OPTS 29 | # * JENKINS_TUNNEL : HOST:PORT for a tunnel to route TCP traffic to jenkins host, when jenkins can't be directly accessed over network 30 | # * JENKINS_URL : alternate jenkins URL 31 | # * JENKINS_SECRET : agent secret, if not set as an argument 32 | # * JENKINS_AGENT_NAME : agent name, if not set as an argument 33 | # * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir 34 | # * JENKINS_WEB_SOCKET: true if the connection should be made via WebSocket rather than TCP 35 | # * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. 36 | # Value: ":" 37 | # * JENKINS_INSTANCE_IDENTITY: The base64 encoded InstanceIdentity byte array of the Jenkins controller. When this is set, 38 | # the agent skips connecting to an HTTP(S) port for connection info. 39 | # * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. 40 | 41 | if [ $# -eq 1 ] && [ "${1#-}" = "$1" ] ; then 42 | 43 | # 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 44 | exec "$@" 45 | 46 | else 47 | 48 | # if -tunnel is not provided, try env vars 49 | case "$@" in 50 | *"-tunnel "*) ;; 51 | *) 52 | if [ ! -z "$JENKINS_TUNNEL" ]; then 53 | TUNNEL="-tunnel $JENKINS_TUNNEL" 54 | fi ;; 55 | esac 56 | 57 | # if -workDir is not provided, try env vars 58 | if [ ! -z "$JENKINS_AGENT_WORKDIR" ]; then 59 | case "$@" in 60 | *"-workDir"*) echo "Warning: Work directory is defined twice in command-line arguments and the environment variable" ;; 61 | *) 62 | WORKDIR="-workDir $JENKINS_AGENT_WORKDIR" ;; 63 | esac 64 | fi 65 | 66 | if [ -n "$JENKINS_URL" ]; then 67 | URL="-url $JENKINS_URL" 68 | fi 69 | 70 | if [ -n "$JENKINS_NAME" ]; then 71 | JENKINS_AGENT_NAME="$JENKINS_NAME" 72 | fi 73 | 74 | if [ "$JENKINS_WEB_SOCKET" = true ]; then 75 | WEB_SOCKET=-webSocket 76 | fi 77 | 78 | if [ -n "$JENKINS_PROTOCOLS" ]; then 79 | PROTOCOLS="-protocols $JENKINS_PROTOCOLS" 80 | fi 81 | 82 | if [ -n "$JENKINS_DIRECT_CONNECTION" ]; then 83 | DIRECT="-direct $JENKINS_DIRECT_CONNECTION" 84 | fi 85 | 86 | if [ -n "$JENKINS_INSTANCE_IDENTITY" ]; then 87 | INSTANCE_IDENTITY="-instanceIdentity $JENKINS_INSTANCE_IDENTITY" 88 | fi 89 | 90 | if [ "$JENKINS_JAVA_BIN" ]; then 91 | JAVA_BIN="$JENKINS_JAVA_BIN" 92 | else 93 | # if java home is defined, use it 94 | JAVA_BIN="java" 95 | if [ "$JAVA_HOME" ]; then 96 | JAVA_BIN="$JAVA_HOME/bin/java" 97 | fi 98 | fi 99 | 100 | if [ "$JENKINS_JAVA_OPTS" ]; then 101 | JAVA_OPTIONS="$JENKINS_JAVA_OPTS" 102 | else 103 | # if JAVA_OPTS is defined, use it 104 | if [ "$JAVA_OPTS" ]; then 105 | JAVA_OPTIONS="$JAVA_OPTS" 106 | fi 107 | fi 108 | 109 | # if both required options are defined, do not pass the parameters 110 | if [ -n "$JENKINS_SECRET" ]; then 111 | case "$@" in 112 | *"${JENKINS_SECRET}"*) echo "Warning: SECRET is defined twice in command-line arguments and the environment variable" ;; 113 | *) 114 | SECRET="-secret ${JENKINS_SECRET}" ;; 115 | esac 116 | fi 117 | 118 | if [ -n "$JENKINS_AGENT_NAME" ]; then 119 | case "$@" in 120 | *"${JENKINS_AGENT_NAME}"*) echo "Warning: AGENT_NAME is defined twice in command-line arguments and the environment variable" ;; 121 | *) 122 | AGENT_NAME="-name ${JENKINS_AGENT_NAME}" ;; 123 | esac 124 | fi 125 | 126 | #TODO: Handle the case when the command-line and Environment variable contain different values. 127 | #It is fine it blows up for now since it should lead to an error anyway. 128 | 129 | exec $JAVA_BIN $JAVA_OPTIONS -jar /usr/share/jenkins/agent.jar $SECRET $AGENT_NAME $TUNNEL $URL $WORKDIR $WEB_SOCKET $DIRECT $PROTOCOLS $INSTANCE_IDENTITY "$@" 130 | 131 | fi 132 | -------------------------------------------------------------------------------- /jenkins-agent.ps1: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2019-2020, Alex Earl 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | [CmdletBinding()] 24 | Param( 25 | $Cmd = '', # this must be specified explicitly 26 | $Url = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_URL) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_DIRECT_CONNECTION)) { throw ("Url is required") } else { '' } ), 27 | [Parameter(Position=0)]$Secret = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_SECRET)) { throw ("Secret is required") } else { '' } ), 28 | [Parameter(Position=1)]$Name = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_AGENT_NAME)) { throw ("Name is required") } else { '' } ), 29 | $Tunnel = '', 30 | $WorkDir = '', 31 | [switch] $WebSocket = $false, 32 | $DirectConnection = '', 33 | $InstanceIdentity = '', 34 | $Protocols = '', 35 | $JenkinsJavaBin = '', 36 | $JavaHome = $env:JAVA_HOME, 37 | $JenkinsJavaOpts = '' 38 | ) 39 | 40 | # Usage jenkins-agent.ps1 [options] -Url http://jenkins -Secret [SECRET] -Name [AGENT_NAME] 41 | # Optional environment variables : 42 | # * JENKINS_JAVA_BIN : Java executable to use instead of the default in PATH or obtained from JAVA_HOME 43 | # * JENKINS_JAVA_OPTS : Java Options to use for the remoting process, otherwise obtained from JAVA_OPTS 44 | # * JENKINS_TUNNEL : HOST:PORT for a tunnel to route TCP traffic to jenkins host, when jenkins can't be directly accessed over network 45 | # * JENKINS_URL : alternate jenkins URL 46 | # * JENKINS_SECRET : agent secret, if not set as an argument 47 | # * JENKINS_AGENT_NAME : agent name, if not set as an argument 48 | # * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir 49 | # * JENKINS_WEB_SOCKET : true if the connection should be made via WebSocket rather than TCP 50 | # * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. 51 | # Value: ":" 52 | # * JENKINS_INSTANCE_IDENTITY: The base64 encoded InstanceIdentity byte array of the Jenkins controller. When this is set, 53 | # the agent skips connecting to an HTTP(S) port for connection info. 54 | # * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. 55 | 56 | if(![System.String]::IsNullOrWhiteSpace($Cmd)) { 57 | Invoke-Expression "$Cmd" 58 | } else { 59 | 60 | # this maps the variable name from the CmdletBinding to environment variables 61 | $ParamMap = @{ 62 | 'JenkinsJavaBin' = 'JENKINS_JAVA_BIN'; 63 | 'JenkinsJavaOpts' = 'JENKINS_JAVA_OPTS'; 64 | 'Tunnel' = 'JENKINS_TUNNEL'; 65 | 'Url' = 'JENKINS_URL'; 66 | 'Secret' = 'JENKINS_SECRET'; 67 | 'Name' = 'JENKINS_AGENT_NAME'; 68 | 'WorkDir' = 'JENKINS_AGENT_WORKDIR'; 69 | 'WebSocket' = 'JENKINS_WEB_SOCKET'; 70 | 'DirectConnection' = 'JENKINS_DIRECT_CONNECTION'; 71 | 'InstanceIdentity' = 'JENKINS_INSTANCE_IDENTITY'; 72 | 'Protocols' = 'JENKINS_PROTOCOLS'; 73 | } 74 | 75 | # this does some trickery to update the variable from the CmdletBinding 76 | # with the value of the 77 | foreach($p in $ParamMap.Keys) { 78 | $var = Get-Variable $p 79 | $envVar = Get-ChildItem -Path "env:$($ParamMap[$p])" -ErrorAction 'SilentlyContinue' 80 | 81 | if(($null -ne $envVar) -and ((($envVar.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($envVar.Value))) -or ($null -ne $envVar.Value))) { 82 | if(($null -ne $var) -and ((($var.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($var.Value))))) { 83 | Write-Warning "${p} is defined twice; in command-line arguments (-${p}) and in the environment variable ${envVar.Name}" 84 | } 85 | if($var.Value -is [System.String]) { 86 | $var.Value = $envVar.Value 87 | } elseif($var.Value -is [System.Management.Automation.SwitchParameter]) { 88 | $var.Value = [bool]$envVar.Value 89 | } 90 | } 91 | if($var.Value -is [System.String]) { 92 | $var.Value = $var.Value.Trim() 93 | } 94 | } 95 | 96 | $AgentArguments = @() 97 | 98 | if(![System.String]::IsNullOrWhiteSpace($JenkinsJavaOpts)) { 99 | # this magic will basically process the $JenkinsJavaOpts like a command line 100 | # and split into an array, the command line processing follows the PowerShell 101 | # commnd line processing, which means for things like -Dsomething.something=something, 102 | # you need to quote the string like this: "-Dsomething.something=something" or else it 103 | # will get parsed incorrectly. 104 | $AgentArguments += Invoke-Expression "echo $JenkinsJavaOpts" 105 | } 106 | 107 | $AgentArguments += @("-jar", "C:/ProgramData/Jenkins/agent.jar") 108 | $AgentArguments += @("-secret", $Secret) 109 | $AgentArguments += @("-name", $Name) 110 | 111 | if(![System.String]::IsNullOrWhiteSpace($Tunnel)) { 112 | $AgentArguments += @("-tunnel", "`"$Tunnel`"") 113 | } 114 | 115 | if(![System.String]::IsNullOrWhiteSpace($WorkDir)) { 116 | $AgentArguments += @("-workDir", "`"$WorkDir`"") 117 | } else { 118 | $AgentArguments += @("-workDir", "`"C:/Users/jenkins/Work`"") 119 | } 120 | 121 | if($WebSocket) { 122 | $AgentArguments += @("-webSocket") 123 | } 124 | 125 | if(![System.String]::IsNullOrWhiteSpace($Url)) { 126 | $AgentArguments += @("-url", "`"$Url`"") 127 | } 128 | 129 | if(![System.String]::IsNullOrWhiteSpace($DirectConnection)) { 130 | $AgentArguments += @('-direct', $DirectConnection) 131 | } 132 | 133 | if(![System.String]::IsNullOrWhiteSpace($InstanceIdentity)) { 134 | $AgentArguments += @('-instanceIdentity', $InstanceIdentity) 135 | } 136 | 137 | if(![System.String]::IsNullOrWhiteSpace($Protocols)) { 138 | $AgentArguments += @('-protocols', $Protocols) 139 | } 140 | 141 | if(![System.String]::IsNullOrWhiteSpace($JenkinsJavaBin)) { 142 | $JAVA_BIN = $JenkinsJavaBin 143 | } else { 144 | # if java home is defined, use it 145 | $JAVA_BIN = "java.exe" 146 | if (![System.String]::IsNullOrWhiteSpace($JavaHome)) { 147 | $JAVA_BIN = "$JavaHome/bin/java.exe" 148 | } 149 | } 150 | 151 | #TODO: Handle the case when the command-line and Environment variable contain different values. 152 | #It is fine it blows up for now since it should lead to an error anyway. 153 | Start-Process -FilePath $JAVA_BIN -Wait -NoNewWindow -ArgumentList $AgentArguments 154 | } 155 | -------------------------------------------------------------------------------- /tests/inboundAgent.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 2 | 3 | $global:AGENT_IMAGE = Get-EnvOrDefault 'AGENT_IMAGE' '' 4 | $global:BUILD_CONTEXT = Get-EnvOrDefault 'BUILD_CONTEXT' '' 5 | $global:version = Get-EnvOrDefault 'VERSION' '' 6 | 7 | $items = $global:AGENT_IMAGE.Split("-") 8 | 9 | # Remove the 'jdk' prefix (3 first characters) 10 | $global:JAVAMAJORVERSION = $items[0].Remove(0,3) 11 | $global:WINDOWSFLAVOR = $items[1] 12 | $global:WINDOWSVERSIONTAG = $items[2] 13 | 14 | # TODO: make this name unique for concurency 15 | $global:CONTAINERNAME = 'pester-jenkins-inbound-agent-{0}' -f $global:AGENT_IMAGE 16 | 17 | $global:CONTAINERSHELL="powershell.exe" 18 | if($global:WINDOWSFLAVOR -eq 'nanoserver') { 19 | $global:CONTAINERSHELL = "pwsh.exe" 20 | } 21 | 22 | # # Uncomment to help debugging when working on this script 23 | # Write-Host "= DEBUG: global vars" 24 | # Get-Variable -Scope Global | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } 25 | # Write-Host "= DEBUG: env vars" 26 | # Get-ChildItem Env: | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } 27 | 28 | Cleanup($global:CONTAINERNAME) 29 | Cleanup("nmap") 30 | CleanupNetwork("jnlp-network") 31 | 32 | BuildNcatImage($global:WINDOWSVERSIONTAG) 33 | 34 | Describe "[$global:AGENT_IMAGE] build image" { 35 | It 'builds image' { 36 | $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg version=${global:version} --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg JAVA_MAJOR_VERSION=${global:JAVAMAJORVERSION} --tag=${global:AGENT_IMAGE} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile ${global:BUILD_CONTEXT}" 37 | $exitCode | Should -Be 0 38 | } 39 | } 40 | 41 | Describe "[$global:AGENT_IMAGE] check default user account" { 42 | BeforeAll { 43 | docker run --detach --tty --name "$global:CONTAINERNAME" "$global:AGENT_IMAGE" -Cmd "$global:CONTAINERSHELL" 44 | $LASTEXITCODE | Should -Be 0 45 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 46 | } 47 | 48 | It 'has a password that never expires' { 49 | $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 }`"" 50 | $exitCode | Should -Be 0 51 | } 52 | 53 | It 'has password policy of "not required"' { 54 | $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 }`"" 55 | $exitCode | Should -Be 0 56 | } 57 | 58 | AfterAll { 59 | Cleanup($global:CONTAINERNAME) 60 | } 61 | } 62 | 63 | Describe "[$global:AGENT_IMAGE] image has jenkins-agent.ps1 in the correct location" { 64 | BeforeAll { 65 | docker run --detach --tty --name "$global:CONTAINERNAME" "$global:AGENT_IMAGE" -Cmd "$global:CONTAINERSHELL" 66 | $LASTEXITCODE | Should -Be 0 67 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 68 | } 69 | 70 | It 'has jenkins-agent.ps1 in C:/ProgramData/Jenkins' { 71 | $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 }`"" 72 | $exitCode | Should -Be 0 73 | } 74 | 75 | AfterAll { 76 | Cleanup($global:CONTAINERNAME) 77 | } 78 | } 79 | 80 | Describe "[$global:AGENT_IMAGE] image starts jenkins-agent.ps1 correctly (slow test)" { 81 | It 'connects to the nmap container' { 82 | $exitCode, $stdout, $stderr = Run-Program 'docker' "network create --driver nat jnlp-network" 83 | # Launch the netcat utility, listening at port 5000 for 30 sec 84 | # bats will capture the output from netcat and compare the first line 85 | # of the header of the first HTTP request with the expected one 86 | $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000" 87 | $exitCode | Should -Be 0 88 | Is-ContainerRunning "nmap" | Should -BeTrue 89 | 90 | # get the ip address of the nmap container 91 | $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format `"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`" nmap" 92 | $exitCode | Should -Be 0 93 | $nmap_ip = $stdout.Trim() 94 | 95 | # run Jenkins agent which tries to connect to the nmap container at port 5000 96 | $secret = "aaa" 97 | $name = "bbb" 98 | $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" 99 | $exitCode | Should -Be 0 100 | Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 101 | 102 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'wait nmap' 103 | $exitCode, $stdout, $stderr = Run-Program 'docker' 'logs nmap' 104 | $exitCode | Should -Be 0 105 | $stdout | Should -Match "GET /tcpSlaveAgentListener/ HTTP/1.1`r" 106 | } 107 | 108 | AfterAll { 109 | Cleanup($global:CONTAINERNAME) 110 | Cleanup("nmap") 111 | CleanupNetwork("jnlp-network") 112 | } 113 | } 114 | 115 | Describe "[$global:AGENT_IMAGE] custom build args" { 116 | BeforeAll { 117 | Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." 118 | # Old version used to test overriding the build arguments. 119 | # 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.) 120 | $TEST_VERSION = "3148.v532a_7e715ee3" 121 | $PARENT_IMAGE_VERSION_SUFFIX = "12" 122 | $ARG_TEST_VERSION = "${TEST_VERSION}-${PARENT_IMAGE_VERSION_SUFFIX}" 123 | $customImageName = "custom-${global:AGENT_IMAGE}" 124 | } 125 | 126 | It 'builds image with arguments' { 127 | $exitCode, $stdout, $stderr = Run-Program 'docker' "build --build-arg version=${ARG_TEST_VERSION} --build-arg `"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`" --build-arg JAVA_MAJOR_VERSION=${global:JAVAMAJORVERSION} --build-arg WINDOWS_FLAVOR=${global:WINDOWSFLAVOR} --build-arg CONTAINER_SHELL=${global:CONTAINERSHELL} --tag=${customImageName} --file=./windows/${global:WINDOWSFLAVOR}/Dockerfile ${global:BUILD_CONTEXT}" 128 | $exitCode | Should -Be 0 129 | 130 | $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name $global:CONTAINERNAME $customImageName -Cmd $global:CONTAINERSHELL" 131 | $exitCode | Should -Be 0 132 | Is-ContainerRunning "$global:CONTAINERNAME" | Should -BeTrue 133 | } 134 | 135 | It "has the correct agent.jar version" { 136 | $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -c `"java -cp C:/ProgramData/Jenkins/agent.jar hudson.remoting.jnlp.Main -version`"" 137 | $exitCode | Should -Be 0 138 | $stdout | Should -Match $TEST_VERSION 139 | } 140 | 141 | AfterAll { 142 | Cleanup($global:CONTAINERNAME) 143 | Pop-Location -StackName 'agent' 144 | } 145 | } 146 | 147 | # === TODO: uncomment test later, see error log below 148 | # === this test passes on a Windows machine 149 | 150 | # Describe "[$global:AGENT_IMAGE] passing JVM options (slow test)" { 151 | # It "shows the java version ${global:JAVAMAJORVERSION} with --show-version" { 152 | # $exitCode, $stdout, $stderr = Run-Program 'docker' "network create --driver nat jnlp-network" 153 | # # Launch the netcat utility, listening at port 5000 for 30 sec 154 | # # bats will capture the output from netcat and compare the first line 155 | # # of the header of the first HTTP request with the expected one 156 | # $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000" 157 | # $exitCode | Should -Be 0 158 | # Is-ContainerRunning "nmap" | Should -BeTrue 159 | 160 | # # get the ip address of the nmap container 161 | # $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format `"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`" nmap" 162 | # $exitCode | Should -Be 0 163 | # $nmap_ip = $stdout.Trim() 164 | 165 | # # run Jenkins agent which tries to connect to the nmap container at port 5000 166 | # $secret = "aaa" 167 | # $name = "bbb" 168 | # $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --network=jnlp-network --name $global:CONTAINERNAME $global:AGENT_IMAGE -Url http://${nmap_ip}:5000 -JenkinsJavaOpts `"--show-version`" $secret $name" 169 | # $exitCode | Should -Be 0 170 | # Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue 171 | # $exitCode, $stdout, $stderr = Run-Program 'docker' "logs $global:CONTAINERNAME" 172 | # $exitCode | Should -Be 0 173 | # $stdout | Should -Match "OpenJDK Runtime Environment Temurin-${global:JAVAMAJORVERSION}" 174 | # } 175 | 176 | # AfterAll { 177 | # Cleanup($global:CONTAINERNAME) 178 | # Cleanup("nmap") 179 | # CleanupNetwork("jnlp-network") 180 | # } 181 | # } 182 | 183 | 184 | # === Corresponding error log: 185 | 186 | # Running tests from 'inboundAgent.Tests.ps1' 187 | # Describing [jdk17-windowsservercore-1809] build image 188 | # cmd = docker, params = build --build-arg version=3148.v532a_7e715ee3-3 --build-arg "WINDOWS_VERSION_TAG=1809" --build-arg JAVA_MAJOR_VERSION=17 --tag=jdk17-windowsservercore-1809 --file ./windows/windowsservercore/Dockerfile C:\Jenkins\agent\workspace\ging_docker-inbound-agent_PR-396 189 | # [+] builds image 572ms (378ms|195ms) 190 | # cmd = docker.exe, params = inspect -f "{{.State.Running}}" pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 191 | 192 | # Describing [jdk17-windowsservercore-1809] check default user account 193 | # cmd = docker, params = exec pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 powershell.exe -C "if((net user jenkins | Select-String -Pattern 'Password expires') -match 'Never') { exit 0 } else { net user jenkins ; exit -1 }" 194 | # [+] has a password that never expires 4.55s (4.55s|5ms) 195 | # cmd = docker, params = exec pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 powershell.exe -C "if((net user jenkins | Select-String -Pattern 'Password required') -match 'No') { exit 0 } else { net user jenkins ; exit -1 }" 196 | # [+] has password policy of "not required" 2.74s (2.73s|3ms) 197 | # cmd = docker.exe, params = inspect -f "{{.State.Running}}" pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 198 | 199 | # Describing [jdk17-windowsservercore-1809] image has jenkins-agent.ps1 in the correct location 200 | # cmd = docker, params = exec pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 powershell.exe -C "if(Test-Path 'C:/ProgramData/Jenkins/jenkins-agent.ps1') { exit 0 } else { exit 1 }" 201 | # [+] has jenkins-agent.ps1 in C:/ProgramData/Jenkins 4.35s (4.27s|85ms) 202 | 203 | # Describing [jdk17-windowsservercore-1809] image starts jenkins-agent.ps1 correctly (slow test) 204 | # cmd = docker, params = network create --driver nat jnlp-network 205 | # cmd = docker, params = run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000 206 | # cmd = docker.exe, params = inspect -f "{{.State.Running}}" nmap 207 | # cmd = docker, params = inspect --format "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" nmap 208 | # cmd = docker, params = run --detach --tty --network=jnlp-network --name pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 jdk17-windowsservercore-1809 -Url http://172.23.176.67:5000 aaa bbb 209 | # cmd = docker.exe, params = inspect -f "{{.State.Running}}" pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 210 | # cmd = docker, params = wait nmap 211 | # cmd = docker, params = logs nmap 212 | # [+] connects to the nmap container 89.43s (89.43s|8ms) 213 | 214 | # Describing [jdk17-windowsservercore-1809] custom build args 215 | # cmd = docker, params = build --build-arg version=3148.v532a_7e715ee3-3 --build-arg "WINDOWS_VERSION_TAG=1809" --build-arg JAVA_MAJOR_VERSION=17 --build-arg WINDOWS_FLAVOR=windowsservercore --build-arg CONTAINER_SHELL=powershell.exe --tag=custom-jdk17-windowsservercore-1809 --file=./windows/windowsservercore/Dockerfile C:\Jenkins\agent\workspace\ging_docker-inbound-agent_PR-396 216 | # cmd = docker, params = run --detach --tty --name pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 custom-jdk17-windowsservercore-1809 -Cmd powershell.exe 217 | # cmd = docker.exe, params = inspect -f "{{.State.Running}}" pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 218 | # [+] builds image with arguments 11.3s (11.3s|5ms) 219 | # cmd = docker, params = exec pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 powershell.exe -c "java -cp C:/ProgramData/Jenkins/agent.jar hudson.remoting.jnlp.Main -version" 220 | # [+] has the correct agent.jar version 2.25s (2.25s|5ms) 221 | 222 | # Describing [jdk17-windowsservercore-1809] passing JVM options (slow test) 223 | # cmd = docker, params = network create --driver nat jnlp-network 224 | # cmd = docker, params = run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000 225 | # cmd = docker.exe, params = inspect -f "{{.State.Running}}" nmap 226 | # cmd = docker, params = inspect --format "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" nmap 227 | # cmd = docker, params = run --detach --tty --network=jnlp-network --name pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 jdk17-windowsservercore-1809 -Url http://172.23.254.19:5000 -JenkinsJavaOpts "--show-version" aaa bbb 228 | # cmd = docker.exe, params = inspect -f "{{.State.Running}}" pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 229 | # cmd = docker, params = logs pester-jenkins-inbound-agent-jdk17-windowsservercore-1809 230 | # [-] shows the java version 17 with --show-version 24.36s (24.35s|11ms) 231 | # Expected regular expression 'OpenJDK Runtime Environment Temurin-17' to match ' 232 | # ', but it did not match. 233 | # at $stdout | Should -Match "OpenJDK Runtime Environment Temurin-${global:JAVAMAJORVERSION}", C:\Jenkins\agent\workspace\ging_docker-inbound-agent_PR-396\tests\inboundAgent.Tests.ps1:173 234 | # at , C:\Jenkins\agent\workspace\ging_docker-inbound-agent_PR-396\tests\inboundAgent.Tests.ps1:173 235 | # Tests completed in 219.22s 236 | # Tests Passed: 7, Failed: 1, Skipped: 0 NotRun: 0 237 | # System.Management.Automation.MethodInvocationException: Exception calling "WriteAttributeString" with "2" argument(s): "'exadecimal value 0x1B, is an invalid character." ---> System.ArgumentException: 'exadecimal value 0x1B, is an invalid character. 238 | # at System.Xml.XmlUtf8RawTextWriter.InvalidXmlChar(Int32 ch, Byte* pDst, Boolean entitize) 239 | # at System.Xml.XmlUtf8RawTextWriter.WriteAttributeTextBlock(Char* pSrc, Char* pSrcEnd) 240 | # at System.Xml.XmlUtf8RawTextWriter.WriteString(String text) 241 | # at System.Xml.XmlWellFormedWriter.WriteString(String text) 242 | # at System.Xml.XmlWriter.WriteAttributeString(String localName, String value) 243 | # at CallSite.Target(Closure , CallSite , XmlWriter , String , Object ) 244 | # --- End of inner exception stack trace --- 245 | # at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception) 246 | # at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame) 247 | # at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame) 248 | # at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame) 249 | # at System.Management.Automation.Interpreter.Interpreter.Run(InterpretedFrame frame) 250 | # at System.Management.Automation.Interpreter.LightLambda.RunVoid1[T0](T0 arg0) 251 | # at System.Management.Automation.PSScriptCmdlet.RunClause(Action`1 clause, Object dollarUnderbar, Object inputToProcess) 252 | # at System.Management.Automation.PSScriptCmdlet.DoEndProcessing() 253 | # at System.Management.Automation.CommandProcessorBase.Complete() 254 | # at Write-JUnitTestCaseMessageElements, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 16460 255 | # at Write-JUnitTestCaseAttributes, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 16452 256 | # at Write-JUnitTestCaseElements, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 16424 257 | # at Write-JUnitTestSuiteElements, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 16385 258 | # at Write-JUnitReport, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 16269 259 | # at Export-XmlReport, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 16005 260 | # at Export-PesterResults, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 15863 261 | # at Invoke-Pester, C:\Program Files\WindowsPowerShell\Modules\Pester\5.3.3\Pester.psm1: line 5263 262 | # at Test-Image, C:\Jenkins\agent\workspace\ging_docker-inbound-agent_PR-396\build.ps1: line 130 263 | # at , C:\Jenkins\agent\workspace\ging_docker-inbound-agent_PR-396\build.ps1: line 176 264 | # at , C:\Jenkins\agent\workspace\ging_docker-inbound-agent_PR-396@tmp\durable-513b70db\powershellScript.ps1: line 1 265 | # at , : line 1 266 | # at , : line 1 267 | 268 | # === end of error log 269 | -------------------------------------------------------------------------------- /tests/netcat-helper/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.11 2 | 3 | RUN apk update --no-cache \ 4 | && apk add --no-cache \ 5 | coreutils \ 6 | netcat-openbsd 7 | -------------------------------------------------------------------------------- /tests/netcat-helper/Dockerfile-windows: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | # The MIT License 4 | # 5 | # Copyright (c) 2019-2020, Alex Earl and other Jenkins Contributors 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | # Available tags: https://mcr.microsoft.com/v2/windows/servercore/tags/list 26 | ARG WINDOWS_VERSION_TAG=1809 27 | FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" 28 | 29 | SHELL ["powershell.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 30 | 31 | ARG NMAP_VERSION=7.80 32 | ENV NMAP_VERSION $NMAP_VERSION 33 | 34 | RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` 35 | $url = $('https://nmap.org/dist/nmap-{0}-setup.exe' -f $env:NMAP_VERSION) ; ` 36 | Write-Host "Retrieving $url..." ; ` 37 | Invoke-WebRequest $url -OutFile 'nmap-install.exe' -UseBasicParsing ; ` 38 | $proc = Start-Process "C:\nmap-install.exe" -PassThru -ArgumentList '/S' ; ` 39 | $proc.WaitForExit() ; ` 40 | Remove-Item -Path nmap-install.exe 41 | -------------------------------------------------------------------------------- /tests/test_helpers.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | # check dependencies 6 | ( 7 | type docker &>/dev/null || ( echo "docker is not available"; exit 1 ) 8 | )>&2 9 | 10 | function printMessage { 11 | echo "# ${*}" >&3 12 | } 13 | 14 | # Assert that $1 is the output of a command $2 15 | function assert { 16 | local expected_output 17 | local actual_output 18 | expected_output="${1}" 19 | shift 20 | actual_output=$("${@}") 21 | if ! [[ "${actual_output}" = "${expected_output}" ]]; then 22 | printMessage "Expected: '${expected_output}', actual: '${actual_output}'" 23 | false 24 | fi 25 | } 26 | 27 | # Retry a command $1 times until it succeeds. Wait $2 seconds between retries. 28 | function retry { 29 | local attempts 30 | local delay 31 | local i 32 | attempts="${1}" 33 | shift 34 | delay="${1}" 35 | shift 36 | 37 | for ((i=0; i < attempts; i++)); do 38 | run "${@}" 39 | if [[ "${status}" -eq 0 ]]; then 40 | return 0 41 | fi 42 | sleep "${delay}" 43 | done 44 | 45 | printMessage "Command '${*}' failed $attempts times. Status: ${status}. Output: ${output}" 46 | 47 | false 48 | } 49 | 50 | function get_sut_image { 51 | test -n "${IMAGE:?"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'."}" 52 | ## Retrieve the SUT image name from buildx 53 | # Option --print for 'docker buildx bake' prints the JSON configuration on the stdout 54 | # Option --silent for 'make' suppresses the echoing of command so the output is valid JSON 55 | # The image name is the 1st of the "tags" array, on the first "image" found 56 | make --silent show | jq -r ".target.${IMAGE}.tags[0]" 57 | } 58 | 59 | function clean_test_container { 60 | docker kill "${AGENT_CONTAINER}" "${NETCAT_HELPER_CONTAINER}" &>/dev/null || : 61 | docker rm -fv "${AGENT_CONTAINER}" "${NETCAT_HELPER_CONTAINER}" &>/dev/null || : 62 | } 63 | 64 | function is_agent_container_running { 65 | local cid="${1}" 66 | sleep 1 67 | retry 3 1 assert "true" docker inspect -f '{{.State.Running}}' "${cid}" 68 | } 69 | 70 | function buildNetcatImage() { 71 | if ! docker inspect --type=image netcat-helper:latest &>/dev/null; then 72 | docker build -t netcat-helper:latest tests/netcat-helper/ &>/dev/null 73 | fi 74 | } 75 | 76 | function cleanup { 77 | docker kill "$1" &>/dev/null ||: 78 | docker rm -fv "$1" &>/dev/null ||: 79 | } 80 | -------------------------------------------------------------------------------- /tests/test_helpers.psm1: -------------------------------------------------------------------------------- 1 | function Test-CommandExists($command) { 2 | $oldPreference = $ErrorActionPreference 3 | $ErrorActionPreference = 'stop' 4 | $res = $false 5 | try { 6 | if(Get-Command $command) { 7 | $res = $true 8 | } 9 | } catch { 10 | $res = $false 11 | } finally { 12 | $ErrorActionPreference=$oldPreference 13 | } 14 | return $res 15 | } 16 | 17 | # check dependencies 18 | if(-Not (Test-CommandExists docker)) { 19 | Write-Error "docker is not available" 20 | } 21 | 22 | function Get-EnvOrDefault($name, $def) { 23 | $entry = Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Select-Object -First 1 24 | if(($null -ne $entry) -and ![System.String]::IsNullOrWhiteSpace($entry.Value)) { 25 | return $entry.Value 26 | } 27 | return $def 28 | } 29 | 30 | function Retry-Command { 31 | [CmdletBinding()] 32 | param ( 33 | [parameter(Mandatory, ValueFromPipeline)] 34 | [ValidateNotNullOrEmpty()] 35 | [scriptblock] $ScriptBlock, 36 | [int] $RetryCount = 3, 37 | [int] $Delay = 30, 38 | [string] $SuccessMessage = "Command executed successfuly!", 39 | [string] $FailureMessage = "Failed to execute the command" 40 | ) 41 | 42 | process { 43 | $Attempt = 1 44 | $Flag = $true 45 | 46 | do { 47 | try { 48 | $PreviousPreference = $ErrorActionPreference 49 | $ErrorActionPreference = 'Stop' 50 | Invoke-Command -NoNewScope -ScriptBlock $ScriptBlock -OutVariable Result 4>&1 51 | $ErrorActionPreference = $PreviousPreference 52 | 53 | # flow control will execute the next line only if the command in the scriptblock executed without any errors 54 | # if an error is thrown, flow control will go to the 'catch' block 55 | Write-Verbose "$SuccessMessage `n" 56 | $Flag = $false 57 | } 58 | catch { 59 | if ($Attempt -gt $RetryCount) { 60 | Write-Verbose "$FailureMessage! Total retry attempts: $RetryCount" 61 | Write-Verbose "[Error Message] $($_.exception.message) `n" 62 | $Flag = $false 63 | } else { 64 | Write-Verbose "[$Attempt/$RetryCount] $FailureMessage. Retrying in $Delay seconds..." 65 | Start-Sleep -Seconds $Delay 66 | $Attempt = $Attempt + 1 67 | } 68 | } 69 | } 70 | While ($Flag) 71 | } 72 | } 73 | 74 | function Cleanup($name='') { 75 | try { 76 | docker kill "$name" 2>&1 | Out-Null 77 | docker rm -fv "$name" 2>&1 | Out-Null 78 | } catch { 79 | # do nothing.... 80 | } 81 | } 82 | 83 | function CleanupNetwork($name) { 84 | docker network rm $name 2>&1 | Out-Null 85 | } 86 | 87 | function Is-ContainerRunning($container) { 88 | Start-Sleep -Seconds 10 89 | return Retry-Command -RetryCount 10 -Delay 3 -ScriptBlock { 90 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{.State.Running}}`" $container" 91 | if(($exitCode -ne 0) -or (-not $stdout.Contains('true')) ) { 92 | throw('Exit code incorrect, or invalid value for running state') 93 | } 94 | return $true 95 | } 96 | } 97 | 98 | function Run-Program($cmd, $params, $quiet=$false, $debug=$false) { 99 | if($debug) { 100 | Write-Host "cmd = $cmd, params = $params" 101 | } 102 | $psi = New-Object System.Diagnostics.ProcessStartInfo 103 | $psi.CreateNoWindow = $true 104 | $psi.UseShellExecute = $false 105 | $psi.RedirectStandardOutput = $true 106 | $psi.RedirectStandardError = $true 107 | $psi.WorkingDirectory = (Get-Location) 108 | $psi.FileName = $cmd 109 | $psi.Arguments = $params 110 | $proc = New-Object System.Diagnostics.Process 111 | $proc.StartInfo = $psi 112 | [void]$proc.Start() 113 | $stdout = $proc.StandardOutput.ReadToEnd() 114 | $stderr = $proc.StandardError.ReadToEnd() 115 | $proc.WaitForExit() 116 | if(($proc.ExitCode -ne 0) -and (-not $quiet)) { 117 | Write-Host "`n`nstdout:`n$stdout`n`nstderr:`n$stderr`n`n`cmd:`n$cmd`n`nparams:`n$params`n`n" 118 | } 119 | 120 | return $proc.ExitCode, $stdout, $stderr 121 | } 122 | 123 | function BuildNcatImage($windowsVersionTag) { 124 | Write-Host "Building nmap image (Windows version '${windowsVersionTag}') for testing" 125 | $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect --type=image nmap" $true 126 | if($exitCode -ne 0) { 127 | Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." 128 | $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" 129 | $exitCode | Should -Be 0 130 | Pop-Location -StackName 'agent' 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tests/tests.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | AGENT_CONTAINER=bats-jenkins-jnlp-agent 4 | 5 | load test_helpers 6 | 7 | buildNetcatImage 8 | 9 | SUT_IMAGE="$(get_sut_image)" 10 | 11 | @test "[${SUT_IMAGE}] image has installed jenkins-agent in PATH" { 12 | local sut_cid 13 | sut_cid="$(docker run -d -it -P "${SUT_IMAGE}" /bin/bash)" 14 | 15 | is_agent_container_running "${sut_cid}" 16 | 17 | run docker exec "${sut_cid}" which jenkins-agent 18 | [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] 19 | 20 | run docker exec "${sut_cid}" which jenkins-agent 21 | [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] 22 | 23 | cleanup "${sut_cid}" 24 | } 25 | 26 | @test "[${SUT_IMAGE}] image starts jenkins-agent correctly (slow test)" { 27 | local netcat_cid sut_cid 28 | # Spin off a helper image which launches the netcat utility, listening at port 5000 for 30 sec 29 | netcat_cid="$(docker run -d -it netcat-helper:latest /bin/sh -c "timeout 30s nc -l 5000")" 30 | 31 | # Run jenkins agent which tries to connect to the netcat-helper container at port 5000 32 | sut_cid="$(docker run -d --link "${netcat_cid}" "${SUT_IMAGE}" -url "http://${netcat_cid}:5000" aaa bbb)" 33 | 34 | # Wait for the whole process to take place (in resource-constrained environments it can take 100s of milliseconds) 35 | sleep 5 36 | 37 | # Capture the logs output from netcat and check the header of the first HTTP request with the expected one 38 | run docker logs "${netcat_cid}" 39 | echo "${output}" | grep 'GET /tcpSlaveAgentListener/ HTTP/1.1' 40 | 41 | cleanup "${netcat_cid}" 42 | cleanup "${sut_cid}" 43 | } 44 | 45 | @test "[${SUT_IMAGE}] use build args correctly" { 46 | cd "${BATS_TEST_DIRNAME}"/.. || false 47 | 48 | local TEST_VERSION PARENT_IMAGE_VERSION_SUFFIX ARG_TEST_VERSION TEST_USER sut_image sut_cid 49 | 50 | # Old version used to test overriding the build arguments. 51 | # This old version must have the same tag suffixes as the ones defined in the docker-bake file (`-jdk17`, `jdk11`, etc.) 52 | TEST_VERSION="3180.v3dd999d24861" 53 | PARENT_IMAGE_VERSION_SUFFIX="2" 54 | 55 | ARG_TEST_VERSION="${TEST_VERSION}-${PARENT_IMAGE_VERSION_SUFFIX}" 56 | TEST_USER="root" 57 | 58 | sut_image="${SUT_IMAGE}-tests-${BATS_TEST_NUMBER}" 59 | 60 | docker buildx bake \ 61 | --set "${IMAGE}".args.version="${ARG_TEST_VERSION}" \ 62 | --set "${IMAGE}".args.user="${TEST_USER}" \ 63 | --set "${IMAGE}".platform=linux/"${ARCH}" \ 64 | --set "${IMAGE}".tags="${sut_image}" \ 65 | --load \ 66 | "${IMAGE}" 67 | 68 | sut_cid="$(docker run -d -it --name "${AGENT_CONTAINER}" -P "${sut_image}" /bin/sh)" 69 | 70 | is_agent_container_running "${sut_cid}" 71 | 72 | run docker exec "${sut_cid}" sh -c "java -cp /usr/share/jenkins/agent.jar hudson.remoting.jnlp.Main -version" 73 | [ "${TEST_VERSION}" = "${lines[0]}" ] 74 | 75 | run docker exec "${AGENT_CONTAINER}" sh -c "id -u -n ${TEST_USER}" 76 | [ "${TEST_USER}" = "${lines[0]}" ] 77 | 78 | cleanup "${sut_cid}" 79 | } 80 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/docker-agent.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump the parent image `jenkins/agent` version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | lastVersion: 18 | kind: githubrelease 19 | name: Get the latest version of the parent image `jenkins/agent` 20 | spec: 21 | owner: jenkinsci 22 | repository: docker-agent 23 | token: "{{ requiredEnv .github.token }}" 24 | username: "{{ .github.username }}" 25 | versionfilter: 26 | kind: latest 27 | 28 | conditions: 29 | checkJdk11AlpineDockerImage: 30 | kind: dockerimage 31 | name: Check if the container image "jenkins/agent:-alpine-jdk11" for linux/amd64 is available 32 | disablesourceinput: true 33 | spec: 34 | architecture: amd64 35 | image: jenkins/agent 36 | tag: '{{source "lastVersion" }}-alpine-jdk11' 37 | checkJdk17AlpineDockerImage: 38 | kind: dockerimage 39 | name: Check if the container image "jenkins/agent:-alpine-jdk17" for linux/amd64 is available 40 | disablesourceinput: true 41 | spec: 42 | architecture: amd64 43 | image: jenkins/agent 44 | tag: '{{source "lastVersion" }}-alpine-jdk17' 45 | checkJdk21AlpineDockerImage: 46 | kind: dockerimage 47 | name: Check if the container image "jenkins/agent:-alpine-jdk21" for linux/amd64 is available 48 | disablesourceinput: true 49 | spec: 50 | architectures: 51 | - amd64 52 | - arm64 53 | image: jenkins/agent 54 | tag: '{{source "lastVersion" }}-alpine-jdk21' 55 | checkJdk11DebianDockerImages: 56 | kind: dockerimage 57 | name: Check if the container image "jenkins/agent:-jdk11" for linux/amd64, linux/arm64, linux/arm/v7, s390x and ppc64le is available 58 | disablesourceinput: true 59 | spec: 60 | architectures: 61 | - amd64 62 | - arm64 63 | - linux/arm/v7 64 | - s390x 65 | - ppc64le 66 | image: jenkins/agent 67 | tag: '{{source "lastVersion" }}-jdk11' 68 | checkJdk17DebianDockerImages: 69 | kind: dockerimage 70 | name: Check if the container image "jenkins/agent:-jdk17" for linux/amd64, linux/arm64 & linux/arm/v7 is available 71 | disablesourceinput: true 72 | spec: 73 | architectures: 74 | - amd64 75 | - arm64 76 | - linux/arm/v7 77 | image: jenkins/agent 78 | tag: '{{source "lastVersion" }}-jdk17' 79 | checkJdk21DebianDockerImages: 80 | kind: dockerimage 81 | name: Check if the container image "jenkins/agent:-jdk21-preview" for linux/amd64 & linux/arm64 is available 82 | disablesourceinput: true 83 | spec: 84 | architectures: 85 | - amd64 86 | - arm64 87 | image: jenkins/agent 88 | tag: '{{source "lastVersion" }}-jdk21' 89 | checkJdk21DebianPreviewDockerImages: 90 | kind: dockerimage 91 | name: Check if the container image "jenkins/agent:-jdk21-preview" for ppc64le, linux/arm/v7 and s390x is available 92 | disablesourceinput: true 93 | spec: 94 | architectures: 95 | - ppc64le 96 | - linux/arm/v7 97 | - s390x 98 | image: jenkins/agent 99 | tag: '{{source "lastVersion" }}-jdk21-preview' 100 | checkJdk11WindowsNanoserver1809DockerImage: 101 | kind: dockerimage 102 | name: Check if the container image "jenkins/agent:-jdk11-nanoserver-1809" for windows/amd64 is available 103 | disablesourceinput: true 104 | spec: 105 | architecture: amd64 106 | image: jenkins/agent 107 | tag: '{{source "lastVersion" }}-jdk11-nanoserver-1809' 108 | checkJdk17WindowsNanoserver1809DockerImage: 109 | kind: dockerimage 110 | name: Check if the container image "jenkins/agent:-jdk17-nanoserver-1809" for windows/amd64 is available 111 | disablesourceinput: true 112 | spec: 113 | architecture: amd64 114 | image: jenkins/agent 115 | tag: '{{source "lastVersion" }}-jdk17-nanoserver-1809' 116 | checkJdk21WindowsNanoserver1809DockerImage: 117 | kind: dockerimage 118 | name: Check if the container image "jenkins/agent:-jdk21-nanoserver-1809" for windows/amd64 is available 119 | disablesourceinput: true 120 | spec: 121 | architecture: amd64 122 | image: jenkins/agent 123 | tag: '{{source "lastVersion" }}-jdk21-nanoserver-1809' 124 | checkJdk11WindowsServer2019DockerImage: 125 | kind: dockerimage 126 | name: Check if the container image "jenkins/agent:-jdk11-windowsservercore-ltsc2019" for windows/amd64 is available 127 | disablesourceinput: true 128 | spec: 129 | architecture: amd64 130 | image: jenkins/agent 131 | tag: '{{source "lastVersion" }}-jdk11-windowsservercore-ltsc2019' 132 | checkJdk17WindowsServer2019DockerImage: 133 | kind: dockerimage 134 | name: Check if the container image "jenkins/agent:-jdk17-windowsservercore-ltsc2019" for windows/amd64 is available 135 | disablesourceinput: true 136 | spec: 137 | architecture: amd64 138 | image: jenkins/agent 139 | tag: '{{source "lastVersion" }}-jdk17-windowsservercore-ltsc2019' 140 | checkJdk21WindowsServer2019DockerImage: 141 | kind: dockerimage 142 | name: Check if the container image "jenkins/agent:-jdk21-windowsservercore-ltsc2019" for windows/amd64 is available 143 | disablesourceinput: true 144 | spec: 145 | architecture: amd64 146 | image: jenkins/agent 147 | tag: '{{source "lastVersion" }}-jdk21-windowsservercore-ltsc2019' 148 | checkJdk21Nanoserver2019DockerImage: 149 | kind: dockerimage 150 | name: Check if the container image "jenkins/agent:-jdk21-nanoserver-ltsc2019" for windows/amd64 is available 151 | disablesourceinput: true 152 | spec: 153 | architecture: amd64 154 | image: jenkins/agent 155 | tag: '{{source "lastVersion" }}-jdk21-nanoserver-ltsc2019' 156 | 157 | targets: 158 | setAlpineDockerImage: 159 | name: Bump the parent image `jenkins/agent` version on Alpine 160 | kind: dockerfile 161 | spec: 162 | file: alpine/Dockerfile 163 | instruction: 164 | keyword: ARG 165 | matcher: version 166 | scmid: default 167 | setDebianDockerImage: 168 | name: Bump the parent image `jenkins/agent` version on Debian 169 | kind: dockerfile 170 | spec: 171 | file: debian/Dockerfile 172 | instruction: 173 | keyword: ARG 174 | matcher: version 175 | scmid: default 176 | setDebian21PreviewDockerImage: 177 | name: Bump the parent image `jenkins/agent` version on Debian 178 | kind: dockerfile 179 | spec: 180 | file: debian/preview/Dockerfile 181 | instruction: 182 | keyword: ARG 183 | matcher: version 184 | transformers: 185 | - addsuffix: "-preview" 186 | scmid: default 187 | setsNanoserverDockerImage: 188 | name: Bump the parent image `jenkins/agent` version on Windows Nanoserver 189 | kind: dockerfile 190 | spec: 191 | file: windows/nanoserver/Dockerfile 192 | instruction: 193 | keyword: ARG 194 | matcher: version 195 | scmid: default 196 | setWindowsServerCoreDockerImage: 197 | name: Bump the parent image `jenkins/agent` version on Windows Server Core 198 | kind: dockerfile 199 | spec: 200 | file: windows/windowsservercore/Dockerfile 201 | instruction: 202 | keyword: ARG 203 | matcher: version 204 | scmid: default 205 | setDockerBakeDefaultParentImage: 206 | name: Bump the parent image `jenkins/agent` version on the docker-bake.hcl file 207 | kind: hcl 208 | spec: 209 | file: docker-bake.hcl 210 | path: variable.PARENT_IMAGE_VERSION.default 211 | scmid: default 212 | setWindowsBuildPwshParentImage: 213 | name: Bump the parent image `jenkins/agent` version on the Windows build.ps1 powershell script 214 | kind: file 215 | spec: 216 | file: build.ps1 217 | matchpattern: >- 218 | ParentImageVersion(.*)=(.*), 219 | replacepattern: >- 220 | ParentImageVersion${1}= '{{ source "lastVersion" }}', 221 | scmid: default 222 | 223 | actions: 224 | default: 225 | kind: github/pullrequest 226 | scmid: default 227 | title: Bump the parent image `jenkins/agent` version to {{ source "lastVersion" }} 228 | spec: 229 | labels: 230 | - dependencies 231 | - jenkins/agent 232 | -------------------------------------------------------------------------------- /updatecli/updatecli.d/nmap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump `nmap` version 3 | 4 | scms: 5 | default: 6 | kind: github 7 | spec: 8 | user: "{{ .github.user }}" 9 | email: "{{ .github.email }}" 10 | owner: "{{ .github.owner }}" 11 | repository: "{{ .github.repository }}" 12 | token: "{{ requiredEnv .github.token }}" 13 | username: "{{ .github.username }}" 14 | branch: "{{ .github.branch }}" 15 | 16 | sources: 17 | lastVersion: 18 | kind: file 19 | name: Get the latest `nmap` version from https://nmap.org/dist/ HTML 20 | spec: 21 | file: https://nmap.org/dist/ 22 | matchpattern: 'The latest Nmap release is version (.*)\.' 23 | transformers: 24 | - findsubmatch: 25 | pattern: 'version (.*)\.' 26 | captureindex: 1 27 | 28 | targets: 29 | setNmapVersion: 30 | name: Bump `nmap` version in tests/netcat-helper/Dockerfile-windows used for Windows tests 31 | kind: dockerfile 32 | spec: 33 | file: tests/netcat-helper/Dockerfile-windows 34 | instruction: 35 | keyword: ARG 36 | matcher: NMAP_VERSION 37 | scmid: default 38 | 39 | actions: 40 | default: 41 | kind: github/pullrequest 42 | scmid: default 43 | title: Bump `nmap` version to {{ source "lastVersion" }} in Windows tests 44 | spec: 45 | labels: 46 | - dependencies 47 | - nmap 48 | -------------------------------------------------------------------------------- /updatecli/values.github-action.yaml: -------------------------------------------------------------------------------- 1 | github: 2 | user: "GitHub Actions" 3 | email: "41898282+github-actions[bot]@users.noreply.github.com" 4 | username: "github-actions" 5 | token: "UPDATECLI_GITHUB_TOKEN" 6 | branch: "master" 7 | owner: "jenkinsci" 8 | repository: "docker-inbound-agent" 9 | -------------------------------------------------------------------------------- /windows/nanoserver/Dockerfile: -------------------------------------------------------------------------------- 1 | # escape=` 2 | # The MIT License 3 | # 4 | # Copyright (c) 2019, Alex Earl 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | 24 | ARG version=3206.vb_15dcf73f6a_9-1 25 | ARG JAVA_MAJOR_VERSION=17 26 | ARG WINDOWS_VERSION_TAG=1809 27 | FROM jenkins/agent:"${version}"-jdk"${JAVA_MAJOR_VERSION}"-nanoserver-"${WINDOWS_VERSION_TAG}" 28 | 29 | COPY jenkins-agent.ps1 C:/ProgramData/Jenkins 30 | ENTRYPOINT ["pwsh.exe", "-f", "C:/ProgramData/Jenkins/jenkins-agent.ps1"] 31 | -------------------------------------------------------------------------------- /windows/windowsservercore/Dockerfile: -------------------------------------------------------------------------------- 1 | # escape=` 2 | # The MIT License 3 | # 4 | # Copyright (c) 2019, Alex Earl 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | 24 | ARG version=3206.vb_15dcf73f6a_9-1 25 | ARG JAVA_MAJOR_VERSION=17 26 | ARG WINDOWS_VERSION_TAG=ltsc2019 27 | FROM jenkins/agent:"${version}"-jdk"${JAVA_MAJOR_VERSION}"-windowsservercore-"${WINDOWS_VERSION_TAG}" 28 | 29 | COPY jenkins-agent.ps1 C:/ProgramData/Jenkins 30 | ENTRYPOINT ["powershell.exe", "-f", "C:/ProgramData/Jenkins/jenkins-agent.ps1"] 31 | --------------------------------------------------------------------------------