diff --git a/.gitignore b/.gitignore index 069b0c8f1..43d42f3b8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ bats-core/ bats/ target/ -build-windows-current.yaml +build-windows_*.yaml diff --git a/Jenkinsfile b/Jenkinsfile index 5b68edfda..9cc03d3be 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,14 +1,14 @@ def agentSelector(String imageType) { - // Image type running on a Linux agent + // Linux agent if (imageType == 'linux') { // Need Docker and a LOT of memory for faster builds (due to multi archs) or fallback to linux (trusted.ci) return 'docker-highmem || linux' } - // Image types running on a Windows Server Core 2022 agent + // Windows Server Core 2022 agent if (imageType.contains('2022')) { return 'windows-2022' } - // Remaining image types running on a Windows Server Core 2019 agent: (nanoserver|windowservercore)-(1809|2019) + // Windows Server Core 2019 agent (for nanoserver 1809 & ltsc2019 and for windowservercore ltsc2019) return 'windows-2019' } @@ -37,7 +37,7 @@ pipeline { timeout(time: 60, unit: 'MINUTES') } environment { - DOCKERHUB_ORGANISATION = "${infra.isTrusted() ? 'jenkins' : 'jenkins4eval'}" + REGISTRY_ORG = "${infra.isTrusted() ? 'jenkins' : 'jenkins4eval'}" } stages { stage('Prepare Docker') { @@ -45,10 +45,7 @@ pipeline { environment name: 'IMAGE_TYPE', value: 'linux' } steps { - sh ''' - docker buildx create --use - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - ''' + sh 'make docker-init' } } stage('Build and Test') { @@ -58,11 +55,11 @@ pipeline { } steps { script { - if(isUnix()) { + if (isUnix()) { sh './build.sh' sh './build.sh test' // If the tests are passing for Linux AMD64, then we can build all the CPU architectures - sh 'docker buildx bake --file docker-bake.hcl linux' + sh 'make every-build' } else { powershell '& ./build.ps1 test' } @@ -70,6 +67,7 @@ pipeline { } post { always { + archiveArtifacts artifacts: 'build-windows_*.yaml', allowEmptyArchive: true junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results*.xml') } } @@ -83,18 +81,17 @@ pipeline { script { def tagItems = env.TAG_NAME.split('-') if(tagItems.length == 2) { - def remotingVersion = tagItems[0] - def buildNumber = tagItems[1] - // This function is defined in the jenkins-infra/pipeline-library - infra.withDockerCredentials { - if (isUnix()) { - sh """ - docker buildx create --use - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - ./build.sh -r ${remotingVersion} -b ${buildNumber} -d publish - """ - } else { - powershell "& ./build.ps1 -RemotingVersion $remotingVersion -BuildNumber $buildNumber -DisableEnvProps publish" + withEnv( + ["REMOTING_VERSION=${tagItems[0]}"], + ["BUILD_NUMBER=${tagItems[1]}"] + ) { + // This function is defined in the jenkins-infra/pipeline-library + infra.withDockerCredentials { + if (isUnix()) { + sh 'make publish' + } else { + powershell '& ./build.ps1 publish' + } } } } else { diff --git a/Makefile b/Makefile index a993d74d6..f2b32cd88 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ check_cli = type "$(1)" >/dev/null 2>&1 || { echo "Error: command '$(1)' require ## Check if a given image exists in the current manifest docker-bake.hcl 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 ; } ## Base "docker buildx base" command to be reused everywhere -bake_base_cli := docker buildx bake -f docker-bake.hcl --load +bake_base_cli := docker buildx bake --file docker-bake.hcl +bake_cli := $(bake_base_cli) --load .PHONY: build .PHONY: test test-alpine test-archlinux test-debian test-jdk11 test-jdk11-alpine @@ -31,16 +32,24 @@ check-reqs: @$(call check_cli,curl) @$(call check_cli,jq) +## This function is specific to Jenkins infrastructure and isn't required in other contexts +docker-init: check-reqs + @set -x; docker buildx create --use + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + build: check-reqs - @set -x; $(bake_base_cli) --set '*.platform=linux/$(ARCH)' $(shell make --silent list) + @set -x; $(bake_cli) $(shell make --silent list) --set '*.platform=linux/$(ARCH)' build-%: @$(call check_image,$*) @echo "== building $*" - @set -x; $(bake_base_cli) --set '*.platform=linux/$(ARCH)' '$*' + @set -x; $(bake_cli) '$*' --set '*.platform=linux/$(ARCH)' + +every-build: check-reqs + @set -x; $(bake_base_cli) linux show: - @$(bake_base_cli) linux --print + @$(bake_cli) linux --print list: check-reqs @set -x; make --silent show | jq -r '.target | path(.. | select(.platforms[] | contains("linux/$(ARCH)"))?) | add' @@ -52,6 +61,9 @@ prepare-test: bats check-reqs git submodule update --init --recursive mkdir -p target +publish: + @set -x; $(bake_base_cli) linux --push + ## Define bats options based on environment # common flags for all tests bats_flags := "" @@ -73,10 +85,7 @@ test-%: prepare-test @echo "== testing $*" set -x # Each type of image ("agent" or "inbound-agent") has its own tests suite - IMAGE=$* bats/bin/bats $(CURDIR)/tests/tests_$(shell echo $* | cut -d "_" -f 1).bats $(bats_flags) | tee target/results-$*.tap -# convert TAP to JUNIT - docker run --rm -v "$(CURDIR)":/usr/src/app -w /usr/src/app node:16-alpine \ - sh -c "npm install tap-xunit -g && cat target/results-$*.tap | tap-xunit --package='jenkinsci.docker.$*' > target/junit-results-$*.xml" + IMAGE=$* bats/bin/bats $(CURDIR)/tests/tests_$(shell echo $* | cut -d "_" -f 1).bats $(bats_flags) --formatter junit | tee target/junit-results-$*.xml test: prepare-test @make --silent list | while read image; do make --silent "test-$${image}"; done diff --git a/README.md b/README.md index 7433cabfc..6c219f36c 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,206 @@ See [the `agent` README](./README_agent.md) This is an image based on `agent` for [Jenkins](https://jenkins.io) agents using TCP or WebSockets to establish inbound connection to the Jenkins controller. See [the `inbound-agent` README](./README_inbound-agent.md) + +## Building + +### Building and testing on Linux + +#### Target images + +If you want to see the target images (matching your current architecture) that will be built, you can issue the following command: + +```bash +$ make list +agent_alpine_jdk21 +agent_debian_jdk11 +agent_debian_jdk17 +agent_debian_jdk21 +inbound-agent_alpine_jdk21 +inbound-agent_debian_jdk11 +inbound-agent_debian_jdk17 +inbound-agent_debian_jdk21 +``` + +#### Building a specific image + +If you want to build a specific image, you can issue the following command: + +```bash +make build-__ +``` + +That would give for an image of an inbound agent with JDK 17 on Debian: + +```bash +make build-inbound-agent_debian_jdk17 +``` + +#### Building images supported by your current architecture + +Then, you can build the images supported by your current architecture by running: + +```bash +make build +``` + +#### Testing all images + +If you want to test these images, you can run: + +```bash +make test +``` +#### Testing a specific image + +If you want to test a specific image, you can run: + +```bash +make test-__ +``` + +That would give for an image of an inbound agent with JDK 17 on Debian: + +```bash +make test-inbound-agent_debian_jdk17 +``` + +#### Building all images + +You can build all images (even those unsupported by your current architecture) by running: + +```bash +make every-build +``` + +#### Other `make` targets + +`show` gives us a detailed view of the images that will be built, with the tags, platforms, and Dockerfiles. + +```bash +$ make show +{ + "group": { + "alpine": { + "targets": [ + "agent_alpine_jdk11", + "agent_alpine_jdk17", + "agent_alpine_jdk21", + "inbound-agent_alpine_jdk11", + "inbound-agent_alpine_jdk17", + "inbound-agent_alpine_jdk21" + ] + }, + "debian": { + "targets": [ + "agent_debian_jdk11", + "agent_debian_jdk17", + "agent_debian_jdk21", + "inbound-agent_debian_jdk11", + "inbound-agent_debian_jdk17", + "inbound-agent_debian_jdk21" + ] + }, + "default": { + "targets": [ + "linux" + ] + }, + "linux": { + "targets": [ + "agent_archlinux_jdk11", + "alpine", + "debian" + ] + } + }, + "target": { + "agent_alpine_jdk11": { + "context": ".", + "dockerfile": "alpine/Dockerfile", + "args": { + "ALPINE_TAG": "3.20.1", + "JAVA_VERSION": "11.0.23_9", + "VERSION": "3256.v88a_f6e922152" + }, + "tags": [ + "docker.io/jenkins/agent:alpine-jdk11", + "docker.io/jenkins/agent:alpine3.20-jdk11", + "docker.io/jenkins/agent:latest-alpine-jdk11", + "docker.io/jenkins/agent:latest-alpine3.20-jdk11" + ], + "target": "agent", + "platforms": [ + "linux/amd64" + ], + "output": [ + "type=docker" + ] + }, + [...] +``` + +`bats` is a dependency target. It will update the [`bats` submodule](https://github.com/bats-core/bats-core) and run the tests. + +```bash +make bats +make: 'bats' is up to date. +``` + +`publish` allows the publication of all images targeted by 'linux' to a registry. + +`docker-init` is dedicated to Jenkins infrastructure for initializing docker and isn't required in other contexts. + +### Building and testing on Windows + +#### Building all images + +Run `.\build.ps1` to launch the build of the images corresponding to the "windows" target of docker-bake.hcl. + +Internally, the first time you'll run this script and if there is no build-windows___.yaml file in your repository, it will use a combination of `docker buildx bake` and `yq` to generate a build-windows___.yaml docker compose file containing all Windows image definitions from docker-bake.hcl. Then it will run `docker compose` on this file to build these images. + +You can modify this docker compose file as you want, then rerun `.\build.ps1`. +It won't regenerate the docker compose file from docker-bake.hcl unless you add the `-OverwriteDockerComposeFile` build.ps1 parameter: `.\build.ps1 -OverwriteDockerComposeFile`. + +Note: you can generate this docker compose file from docker-bake.hcl yourself with the following command (require `docker buildx` and `yq`): + +```console +# - Use docker buildx bake to output image definitions from the "windows" bake target +# - Convert with yq to the format expected by docker compose +# - Store the result in the docker compose file + +$ docker buildx bake --progress=plain --file=docker-bake.hcl windows --print ` + | yq --prettyPrint '.target[] | del(.output) | {(. | key): {\"image\": .tags[0], \"build\": .}}' | yq '{\"services\": .}' ` + | Out-File -FilePath build-windows_mybuild.yaml +``` + +Note that you don't need build.ps1 to build (or to publish) your images from this docker compose file, you can use `docker compose --file=build-windows_mybuild.yaml build`. + +#### Testing all images + +Run `.\build.ps1 test` if you also want to run the tests harness suit. + +Run `.\build.ps1 test -TestsDebug 'debug'` to also get commands & stderr of tests, displayed on top of them. +You can set it to `'verbose'` to also get stdout of every test command. + +Note that instead of passing `-TestsDebug` parameter to build.ps1, you can set the $env:TESTS_DEBUG environment variable to the desired value. + +Also note that contrary to the Linux part, you have to build the images before testing them. + +#### Dry run + +Add the `-DryRun` parameter to print out any build, publish or tests commands instead of executing them: `.\build.ps1 test -DryRun` + +#### Building and testing a specific image + +You can build (and test) only one image type by setting `-ImageType` to a combination of Windows flavors ("nanoserver" & "windowsservercore") and Windows versions ("1809", "ltsc2019", "ltsc2022"). + +Ex: `.\build.ps1 -ImageType 'nanoserver-ltsc2019'` + +Warning: trying to build `windowsservercore-1809` will fail as there is no corresponding image from Microsoft. + +You can also build (and test) only one agent type by setting `-AgentType` to either "agent" or "inbound-agent". + +Ex: `.\build.ps1 -AgentType 'agent'` + +Both parameters can be combined. diff --git a/build-windows.yaml b/build-windows.yaml deleted file mode 100644 index f6ed4404a..000000000 --- a/build-windows.yaml +++ /dev/null @@ -1,42 +0,0 @@ -services: - jdk11: - image: ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:jdk11-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG} - build: - context: ./ - dockerfile: ./windows/${WINDOWS_FLAVOR}/Dockerfile - args: - JAVA_HOME: "C:/openjdk-11" - JAVA_VERSION: 11.0.24+8 - VERSION: ${REMOTING_VERSION} - WINDOWS_VERSION_TAG: ${WINDOWS_VERSION_TAG} - TOOLS_WINDOWS_VERSION: ${TOOLS_WINDOWS_VERSION} - tags: - - "${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk11-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG}" - jdk17: - image: ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:jdk17-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG} - build: - context: ./ - dockerfile: ./windows/${WINDOWS_FLAVOR}/Dockerfile - args: - JAVA_HOME: "C:/openjdk-17" - JAVA_VERSION: 17.0.12+7 - VERSION: ${REMOTING_VERSION} - WINDOWS_VERSION_TAG: ${WINDOWS_VERSION_TAG} - TOOLS_WINDOWS_VERSION: ${TOOLS_WINDOWS_VERSION} - tags: - - "${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk17-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG}" - - "${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG}" - - "${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG}" - jdk21: - image: ${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:jdk21-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG} - build: - context: ./ - dockerfile: ./windows/${WINDOWS_FLAVOR}/Dockerfile - args: - JAVA_HOME: "C:/openjdk-21" - JAVA_VERSION: 21.0.4+7 - VERSION: ${REMOTING_VERSION} - WINDOWS_VERSION_TAG: ${WINDOWS_VERSION_TAG} - TOOLS_WINDOWS_VERSION: ${TOOLS_WINDOWS_VERSION} - tags: - - "${DOCKERHUB_ORGANISATION}/${DOCKERHUB_REPO}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk21-${WINDOWS_FLAVOR}-${WINDOWS_VERSION_TAG}" diff --git a/build.ps1 b/build.ps1 index c0b3cfed2..c3471bf3b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,63 +1,57 @@ [CmdletBinding()] Param( - [Parameter(Position=1)] - [String] $Target = "build", + [Parameter(Position = 1)] + # Default build.ps1 target + [String] $Target = 'build', + # Remoting version to include [String] $RemotingVersion = '3261.v9c670a_4748a_9', + # Type of agent ("agent" or "inbound-agent") [String] $AgentType = '', + # Windows flavor and windows version to build + [String] $ImageType = 'nanoserver-ltsc2019', + # Image build number [String] $BuildNumber = '1', - [switch] $DisableEnvProps = $false, - [switch] $DryRun = $false + # Generate a docker compose file even if it already exists + [switch] $OverwriteDockerComposeFile = $false, + # Print the build and publish command instead of executing them if set + [switch] $DryRun = $false, + # Output debug info for tests: 'empty' (no additional test output), 'debug' (test cmd & stderr outputed), 'verbose' (test cmd, stderr, stdout outputed) + [String] $TestsDebug = '' ) $ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' # Disable Progress bar for faster downloads -$originalDockerComposeFile = 'build-windows.yaml' -$finalDockerComposeFile = 'build-windows-current.yaml' -$baseDockerCmd = 'docker-compose --file={0}' -f $finalDockerComposeFile -$baseDockerBuildCmd = '{0} build --parallel --pull' -f $baseDockerCmd - -$AgentTypes = @('agent', 'inbound-agent') -if ($AgentType -ne '' -and $AgentType -in $AgentTypes) { - $AgentTypes = @($AgentType) -} -$ImageType = 'windowsservercore-ltsc2019' -$Organisation = 'jenkins4eval' -$Repository = @{ - 'agent' = 'agent' - 'inbound-agent' = 'inbound-agent' -} - -if(!$DisableEnvProps) { - Get-Content env.props | ForEach-Object { - $items = $_.Split("=") - if($items.Length -eq 2) { - $name = $items[0].Trim() - $value = $items[1].Trim() - Set-Item -Path "env:$($name)" -Value $value - } - } +if (![String]::IsNullOrWhiteSpace($env:TESTS_DEBUG)) { + $TestsDebug = $env:TESTS_DEBUG } +$env:TESTS_DEBUG = $TestsDebug -if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_ORGANISATION)) { - $Organisation = $env:DOCKERHUB_ORGANISATION +if (![String]::IsNullOrWhiteSpace($env:AGENT_TYPE)) { + $AgentType = $env:AGENT_TYPE } -if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO_AGENT)) { - $Repository['agent'] = $env:DOCKERHUB_REPO_AGENT +$AgentTypes = @('agent', 'inbound-agent') +if ($AgentType -ne '' -and $AgentType -in $AgentTypes) { + $AgentTypes = @($AgentType) } -if(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO_INBOUND_AGENT)) { - $Repository['inbound-agent'] = $env:DOCKERHUB_REPO_INBOUND_AGENT +if (![String]::IsNullOrWhiteSpace($env:REMOTING_VERSION)) { + $RemotingVersion = $env:REMOTING_VERSION } -if(![String]::IsNullOrWhiteSpace($env:REMOTING_VERSION)) { - $RemotingVersion = $env:REMOTING_VERSION +if (![String]::IsNullOrWhiteSpace($env:BUILD_NUMBER)) { + $BuildNumber = $env:BUILD_NUMBER } -if(![String]::IsNullOrWhiteSpace($env:IMAGE_TYPE)) { +if (![String]::IsNullOrWhiteSpace($env:IMAGE_TYPE)) { $ImageType = $env:IMAGE_TYPE } +# Ensure constant env vars used in docker-bake.hcl are defined +$env:REMOTING_VERSION = "$RemotingVersion" +$env:BUILD_NUMBER = $BuildNumber + # Check for required commands Function Test-CommandExists { # From https://devblogs.microsoft.com/scripting/use-a-powershell-function-to-see-if-a-command-exists/ @@ -68,48 +62,35 @@ Function Test-CommandExists { $oldPreference = $ErrorActionPreference $ErrorActionPreference = 'stop' try { - if(Get-Command $command){ + # Special case to test "docker buildx" + if ($command.Contains(' ')) { + Invoke-Expression $command | Out-Null Write-Debug "$command exists" + } else { + if(Get-Command $command){ + Write-Debug "$command exists" + } } } Catch { "$command does not exist" } Finally { - $ErrorActionPreference=$oldPreference + $ErrorActionPreference = $oldPreference } } -# Ensure constant env vars used in the docker compose file are defined -$env:DOCKERHUB_ORGANISATION = "$Organisation" -$env:REMOTING_VERSION = "$RemotingVersion" -$env:BUILD_NUMBER = $BuildNumber - -$items = $ImageType.Split("-") -$env:WINDOWS_FLAVOR = $items[0] -$env:WINDOWS_VERSION_TAG = $items[1] -$env:TOOLS_WINDOWS_VERSION = $items[1] -if ($items[1] -eq 'ltsc2019') { - # There are no eclipse-temurin:*-ltsc2019 or mcr.microsoft.com/powershell:*-ltsc2019 docker images unfortunately, only "1809" ones - $env:TOOLS_WINDOWS_VERSION = '1809' -} - -$ProgressPreference = 'SilentlyContinue' # Disable Progress bar for faster downloads - -Test-CommandExists "docker" -Test-CommandExists "docker-compose" -Test-CommandExists "yq" - function Test-Image { param ( - $AgentTypeAndImageName + [String] $AgentTypeAndImageName ) - $items = $AgentTypeAndImageName.Split("|") + # Ex: agent|docker.io/jenkins/agent:jdk21-windowsservercore-ltsc2019|21.0.3_9 + $items = $AgentTypeAndImageName.Split('|') $agentType = $items[0] - $imageName = $items[1] + $imageName = $items[1] -replace 'docker.io/', '' $javaVersion = $items[2] - $imageNameItems = $imageName.Split(":") + $imageNameItems = $imageName.Split(':') $imageTag = $imageNameItems[1] Write-Host "= TEST: Testing ${imageName} image:" @@ -119,7 +100,7 @@ function Test-Image { $env:JAVA_VERSION = "$javaVersion" $targetPath = '.\target\{0}\{1}' -f $agentType, $imageTag - if(Test-Path $targetPath) { + if (Test-Path $targetPath) { Remove-Item -Recurse -Force $targetPath } New-Item -Path $targetPath -Type Directory | Out-Null @@ -141,43 +122,95 @@ function Test-Image { return $failed } +function Initialize-DockerComposeFile { + param ( + [String] $AgentType, + [String] $ImageType, + [String] $DockerComposeFile + ) + + $baseDockerBakeCmd = 'docker buildx bake --progress=plain --file=docker-bake.hcl' + + $items = $ImageType.Split('-') + $windowsFlavor = $items[0] + $windowsVersion = $items[1] + + # Override the list of Windows versions taken defined in docker-bake.hcl by the version from image type + $env:WINDOWS_VERSION_OVERRIDE = $windowsVersion + + # Override the list of agent types defined in docker-bake.hcl by the specified agent type + $env:WINDOWS_AGENT_TYPE_OVERRIDE = $AgentType + + # Retrieve the targets from docker buildx bake --print output + # Remove the 'output' section (unsupported by docker compose) + # For each target name as service key, return a map consisting of: + # - 'image' set to the first tag value + # - 'build' set to the content of the bake target + $yqMainQuery = '''.target[]' + ` + ' | del(.output)' + ` + ' | {(. | key): {\"image\": .tags[0], \"build\": .}}''' + # Encapsulate under a top level 'services' map + $yqServicesQuery = '''{\"services\": .}''' + + # - Use docker buildx bake to output image definitions from the "" bake target + # - Convert with yq to the format expected by docker compose + # - Store the result in the docker compose file + $generateDockerComposeFileCmd = ' {0} {1} --print' -f $baseDockerBakeCmd, $windowsFlavor + ` + ' | yq --prettyPrint {0} | yq {1}' -f $yqMainQuery, $yqServicesQuery + ` + ' | Out-File -FilePath {0}' -f $DockerComposeFile + + Write-Host "= PREPARE: Docker compose file generation command`n$generateDockerComposeFileCmd" + + Invoke-Expression $generateDockerComposeFileCmd + + # Remove override + Remove-Item env:\WINDOWS_VERSION_OVERRIDE + Remove-Item env:\WINDOWS_AGENT_TYPE_OVERRIDE +} + +Test-CommandExists 'docker' +Test-CommandExists 'docker-compose' +Test-CommandExists 'docker buildx' +Test-CommandExists 'yq' + foreach($agentType in $AgentTypes) { - # Ensure remaining env vars used in the docker compose file are defined - $env:AGENT_TYPE = $agentType - $env:DOCKERHUB_REPO = $Repository[$agentType] - - # Temporary docker compose file (git ignored) - Copy-Item -Path $originalDockerComposeFile -Destination $finalDockerComposeFile - # If it's an "agent" type, add the corresponding target - if($agentType -eq 'agent') { - yq '.services.[].build.target = \"agent\"' $originalDockerComposeFile | Out-File -FilePath $finalDockerComposeFile + $dockerComposeFile = 'build-windows_{0}_{1}.yaml' -f $AgentType, $ImageType + $baseDockerCmd = 'docker-compose --file={0}' -f $dockerComposeFile + $baseDockerBuildCmd = '{0} build --parallel --pull' -f $baseDockerCmd + + # Generate the docker compose file if it doesn't exists or if the parameter OverwriteDockerComposeFile is set + if ((Test-Path $dockerComposeFile) -and -not $OverwriteDockerComposeFile) { + Write-Host "= PREPARE: The docker compose file '$dockerComposeFile' containing the image definitions already exists." + } else { + Write-Host "= PREPARE: Initialize the docker compose file '$dockerComposeFile' containing the image definitions." + Initialize-DockerComposeFile -AgentType $AgentType -ImageType $ImageType -DockerComposeFile $dockerComposeFile } - Write-Host "= PREPARE: List of $Organisation/$env:DOCKERHUB_REPO images and tags to be processed:" + Write-Host '= PREPARE: List of images and tags to be processed:' Invoke-Expression "$baseDockerCmd config" - Write-Host "= BUILD: Building all images..." + Write-Host '= BUILD: Building all images...' switch ($DryRun) { $true { Write-Host "(dry-run) $baseDockerBuildCmd" } $false { Invoke-Expression $baseDockerBuildCmd } } - Write-Host "= BUILD: Finished building all images." + Write-Host '= BUILD: Finished building all images.' - if($lastExitCode -ne 0) { + if ($lastExitCode -ne 0) { exit $lastExitCode } - if($target -eq "test") { + if ($target -eq 'test') { if ($DryRun) { - Write-Host "= TEST: (dry-run) test harness" + Write-Host '= TEST: (dry-run) test harness' } else { - Write-Host "= TEST: Starting test harness" + Write-Host '= TEST: Starting test harness' $mod = Get-InstalledModule -Name Pester -MinimumVersion 5.3.0 -MaximumVersion 5.3.3 -ErrorAction SilentlyContinue - if($null -eq $mod) { - Write-Host "= TEST: Pester 5.3.x not found: installing..." - $module = "c:\Program Files\WindowsPowerShell\Modules\Pester" - if(Test-Path $module) { + if ($null -eq $mod) { + Write-Host '= TEST: Pester 5.3.x not found: installing...' + $module = 'C:\Program Files\WindowsPowerShell\Modules\Pester' + if (Test-Path $module) { takeown /F $module /A /R icacls $module /reset icacls $module /grant Administrators:'F' /inheritance:d /T @@ -187,7 +220,7 @@ foreach($agentType in $AgentTypes) { } Import-Module Pester - Write-Host "= TEST: Setting up Pester environment..." + Write-Host '= TEST: Setting up Pester environment...' $configuration = [PesterConfiguration]::Default $configuration.Run.PassThru = $true $configuration.Run.Path = '.\tests' @@ -200,13 +233,13 @@ foreach($agentType in $AgentTypes) { Write-Host "= TEST: Testing all ${agentType} images..." # Only fail the run afterwards in case of any test failures $testFailed = $false - $jdks = Invoke-Expression "$baseDockerCmd config" | yq -r --output-format json '.services' | ConvertFrom-Json - foreach ($jdk in $jdks.PSObject.Properties) { - $testFailed = $testFailed -or (Test-Image ('{0}|{1}|{2}' -f $agentType, $jdk.Value.image, $jdk.Value.build.args.JAVA_VERSION)) + $imageDefinitions = Invoke-Expression "$baseDockerCmd config" | yq --unwrapScalar --output-format json '.services' | ConvertFrom-Json + foreach ($imageDefinition in $imageDefinitions.PSObject.Properties) { + $testFailed = $testFailed -or (Test-Image ('{0}|{1}|{2}' -f $agentType, $imageDefinition.Value.image, $imageDefinition.Value.build.args.JAVA_VERSION)) } # Fail if any test failures - if($testFailed -ne $false) { + if ($testFailed -ne $false) { Write-Error "Test stage failed for ${agentType}!" exit 1 } else { @@ -215,24 +248,24 @@ foreach($agentType in $AgentTypes) { } } - if($target -eq "publish") { - Write-Host "= PUBLISH: push all images and tags" + if ($target -eq 'publish') { + Write-Host '= PUBLISH: push all images and tags' switch($DryRun) { $true { Write-Host "(dry-run) $baseDockerCmd push" } $false { Invoke-Expression "$baseDockerCmd push" } } # Fail if any issues when publising the docker images - if($lastExitCode -ne 0) { - Write-Error "= PUBLISH: failed!" + if ($lastExitCode -ne 0) { + Write-Error '= PUBLISH: failed!' exit 1 } } } -if($lastExitCode -ne 0) { - Write-Error "Build failed!" +if ($lastExitCode -ne 0) { + Write-Error 'Build failed!' } else { - Write-Host "= Build finished successfully" + Write-Host '= Build finished successfully' } exit $lastExitCode diff --git a/docker-bake.hcl b/docker-bake.hcl index e5285ac9e..99c428ec1 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -6,6 +6,13 @@ group "linux" { ] } +group "windows" { + targets = [ + "nanoserver", + "windowsservercore" + ] +} + group "linux-agent-only" { targets = [ "agent_archlinux_jdk11", @@ -56,6 +63,30 @@ group "linux-ppc64le" { ] } +variable "agent_types_to_build" { + default = ["agent", "inbound-agent"] +} + +variable "jdks_to_build" { + default = [11, 17, 21] +} + +variable "default_jdk" { + default = 17 +} + +variable "JAVA11_VERSION" { + default = "11.0.24_8" +} + +variable "JAVA17_VERSION" { + default = "17.0.12_7" +} + +variable "JAVA21_VERSION" { + default = "21.0.4_7" +} + variable "REMOTING_VERSION" { default = "3261.v9c670a_4748a_9" } @@ -100,27 +131,23 @@ variable "UBI9_TAG" { default = "9.4-1214.1726694543" } -variable "JAVA11_VERSION" { - default = "11.0.24_8" +# Set this value to a specific Windows version to override Windows versions to build returned by windowsversions function +variable "WINDOWS_VERSION_OVERRIDE" { + default = "" } -variable "JAVA17_VERSION" { - default = "17.0.12_7" -} - -variable "JAVA21_VERSION" { - default = "21.0.4_7" +# Set this value to a specific agent type to override agent type to build returned by windowsagenttypes function +variable "WINDOWS_AGENT_TYPE_OVERRIDE" { + default = "" } +## Common functions +# Return the registry organization and repository depending on the agent type function "orgrepo" { params = [agentType] result = equal("agent", agentType) ? "${REGISTRY_ORG}/${REGISTRY_REPO_AGENT}" : "${REGISTRY_ORG}/${REGISTRY_REPO_INBOUND_AGENT}" } -variable "default_jdk" { - default = 17 -} - # Return "true" if the jdk passed as parameter is the same as the default jdk, "false" otherwise function "is_default_jdk" { params = [jdk] @@ -137,6 +164,7 @@ function "javaversion" { : "${JAVA21_VERSION}")) } +## Specific functions # Return an array of Alpine platforms to use depending on the jdk passed as parameter function "alpine_platforms" { params = [jdk] @@ -155,6 +183,37 @@ function "debian_platforms" { : ["linux/amd64", "linux/arm64", "linux/ppc64le", "linux/s390x"])) } +# Return array of Windows version(s) to build +# There is no mcr.microsoft.com/windows/servercore:1809 image +# Can be overriden by setting WINDOWS_VERSION_OVERRIDE to a specific Windows version +# Ex: WINDOWS_VERSION_OVERRIDE=1809 docker buildx bake windows +function "windowsversions" { + params = [flavor] + result = (notequal(WINDOWS_VERSION_OVERRIDE, "") + ? [WINDOWS_VERSION_OVERRIDE] + : (equal(flavor, "windowsservercore") + ? ["ltsc2019", "ltsc2022"] + : ["1809", "ltsc2019", "ltsc2022"])) +} + +# Return array of agent type(s) to build +# Can be overriden to a specific agent type +function "windowsagenttypes" { + params = [override] + result = (notequal(override, "") + ? [override] + : agent_types_to_build) +} + +# Return the Windows version to use as base image for the Windows version passed as parameter +# There is no mcr.microsoft.com/powershell ltsc2019 base image, using a "1809" instead +function "toolsversion" { + params = [version] + result = (equal("ltsc2019", version) + ? "1809" + : version) +} + # Return an array of RHEL UBI 9 platforms to use depending on the jdk passed as parameter # Note: Jenkins controller container image only supports jdk17 and jdk21 for ubi9 function "rhel_ubi9_platforms" { @@ -164,8 +223,8 @@ function "rhel_ubi9_platforms" { target "alpine" { matrix = { - type = ["agent", "inbound-agent"] - jdk = [11, 17, 21] + type = agent_types_to_build + jdk = jdks_to_build } name = "${type}_alpine_jdk${jdk}" target = type @@ -198,10 +257,10 @@ target "alpine" { target "debian" { matrix = { - type = ["agent", "inbound-agent"] - jdk = [11, 17, 21] + type = agent_types_to_build + jdk = jdks_to_build } - name = "${type}_debian_${jdk}" + name = "${type}_debian_jdk${jdk}" target = type dockerfile = "debian/Dockerfile" context = "." @@ -272,3 +331,59 @@ target "rhel_ubi9" { ] platforms = rhel_ubi9_platforms(jdk) } + +target "nanoserver" { + matrix = { + type = windowsagenttypes(WINDOWS_AGENT_TYPE_OVERRIDE) + jdk = jdks_to_build + windows_version = windowsversions("nanoserver") + } + name = "${type}_nanoserver-${windows_version}_jdk${jdk}" + dockerfile = "windows/nanoserver/Dockerfile" + context = "." + args = { + JAVA_HOME = "C:/openjdk-${jdk}" + JAVA_VERSION = "${replace(javaversion(jdk), "_", "+")}" + TOOLS_WINDOWS_VERSION = "${toolsversion(windows_version)}" + VERSION = REMOTING_VERSION + WINDOWS_VERSION_TAG = windows_version + } + target = type + tags = [ + # If there is a tag, add versioned tag containing the jdk + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk${jdk}-nanoserver-${windows_version}" : "", + # If there is a tag and if the jdk is the default one, add versioned and short tags + equal(ON_TAG, "true") ? (is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-nanoserver-${windows_version}" : "") : "", + equal(ON_TAG, "true") ? (is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:nanoserver-${windows_version}" : "") : "", + "${REGISTRY}/${orgrepo(type)}:jdk${jdk}-nanoserver-${windows_version}", + ] + platforms = ["windows/amd64"] +} + +target "windowsservercore" { + matrix = { + type = windowsagenttypes(WINDOWS_AGENT_TYPE_OVERRIDE) + jdk = jdks_to_build + windows_version = windowsversions("windowsservercore") + } + name = "${type}_windowsservercore-${windows_version}_jdk${jdk}" + dockerfile = "windows/windowsservercore/Dockerfile" + context = "." + args = { + JAVA_HOME = "C:/openjdk-${jdk}" + JAVA_VERSION = "${replace(javaversion(jdk), "_", "+")}" + TOOLS_WINDOWS_VERSION = "${toolsversion(windows_version)}" + VERSION = REMOTING_VERSION + WINDOWS_VERSION_TAG = windows_version + } + target = type + tags = [ + # If there is a tag, add versioned tag containing the jdk + equal(ON_TAG, "true") ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-jdk${jdk}-windowsservercore-${windows_version}" : "", + # If there is a tag and if the jdk is the default one, add versioned and short tags + equal(ON_TAG, "true") ? (is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:${REMOTING_VERSION}-${BUILD_NUMBER}-windowsservercore-${windows_version}" : "") : "", + equal(ON_TAG, "true") ? (is_default_jdk(jdk) ? "${REGISTRY}/${orgrepo(type)}:windowsservercore-${windows_version}" : "") : "", + "${REGISTRY}/${orgrepo(type)}:jdk${jdk}-windowsservercore-${windows_version}", + ] + platforms = ["windows/amd64"] +} diff --git a/env.props b/env.props deleted file mode 100644 index fa7e6367b..000000000 --- a/env.props +++ /dev/null @@ -1 +0,0 @@ -REMOTING_VERSION=3261.v9c670a_4748a_9 diff --git a/tests/agent.Tests.ps1 b/tests/agent.Tests.ps1 index 3239d8aa5..ed196dce1 100644 --- a/tests/agent.Tests.ps1 +++ b/tests/agent.Tests.ps1 @@ -1,13 +1,15 @@ Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 -$global:IMAGE_NAME = Get-EnvOrDefault 'IMAGE_NAME' '' +$global:IMAGE_NAME = Get-EnvOrDefault 'IMAGE_NAME' '' # Ex: jenkins/agent:jdk17-nanoserver-1809 $global:VERSION = Get-EnvOrDefault 'VERSION' '' $global:JAVA_VERSION = Get-EnvOrDefault 'JAVA_VERSION' '' -$imageItems = $global:IMAGE_NAME.Split(":") +Write-Host "= TESTS: Preparing $global:IMAGE_NAME with Remoting $global:VERSION and Java $global:JAVA_VERSION" + +$imageItems = $global:IMAGE_NAME.Split(':') $GLOBAL:IMAGE_TAG = $imageItems[1] -$items = $global:IMAGE_TAG.Split("-") +$items = $global:IMAGE_TAG.Split('-') # Remove the 'jdk' prefix $global:JAVAMAJORVERSION = $items[0].Remove(0,3) $global:WINDOWSFLAVOR = $items[1] @@ -17,12 +19,13 @@ if ($items[2] -eq 'ltsc2019') { $global:WINDOWSVERSIONFALLBACKTAG = '1809' } -# TODO: make this name unique for concurency -$global:CONTAINERNAME = 'pester-jenkins-agent-{0}' -f $global:IMAGE_TAG +$random = Get-Random +$global:CONTAINERNAME = 'pester-jenkins-agent_{0}_{1}' -f $global:IMAGE_TAG, $random +Write-Host "= TESTS: container name $global:CONTAINERNAME" -$global:CONTAINERSHELL="powershell.exe" -if($global:WINDOWSFLAVOR -eq 'nanoserver') { - $global:CONTAINERSHELL = "pwsh.exe" +$global:CONTAINERSHELL = 'powershell.exe' +if ($global:WINDOWSFLAVOR -eq 'nanoserver') { + $global:CONTAINERSHELL = 'pwsh.exe' } $global:GITLFSVERSION = '3.5.1' @@ -58,7 +61,7 @@ Describe "[$global:IMAGE_NAME] image has correct applications in the PATH" { } It 'has java installed and in the path' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if(`$null -eq (Get-Command java.exe -ErrorAction SilentlyContinue)) { exit -1 } else { exit 0 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if (`$null -eq (Get-Command java.exe -ErrorAction SilentlyContinue)) { exit -1 } else { exit 0 }`"" $exitCode | Should -Be 0 $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"`$global:VERSION = java -version 2>&1 ; Write-Host `$global:VERSION`"" $r = [regex] "^openjdk version `"(?\d+)" @@ -70,13 +73,13 @@ Describe "[$global:IMAGE_NAME] image has correct applications in the PATH" { It 'has AGENT_WORKDIR in the environment' { $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"Get-ChildItem env:`"" $exitCode | Should -Be 0 - $stdout.Trim() | Should -Match "AGENT_WORKDIR.*C:/Users/jenkins/Work" + $stdout.Trim() | Should -Match 'AGENT_WORKDIR.*C:/Users/jenkins/Work' } It 'has user in the environment' { $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"Get-ChildItem env:`"" $exitCode | Should -Be 0 - $stdout.Trim() | Should -Match "user.*jenkins" + $stdout.Trim() | Should -Match 'user.*jenkins' } It 'has git-lfs (and thus git) installed' { @@ -86,7 +89,7 @@ Describe "[$global:IMAGE_NAME] image has correct applications in the PATH" { } It 'does not include jenkins-agent.ps1 (inbound-agent)' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if(Test-Path C:/ProgramData/Jenkins/jenkins-agent.ps1) { exit -1 } else { exit 0 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if (Test-Path C:/ProgramData/Jenkins/jenkins-agent.ps1) { exit -1 } else { exit 0 }`"" $exitCode | Should -Be 0 } @@ -102,12 +105,12 @@ Describe "[$global:IMAGE_NAME] check user account" { } It 'Password never expires' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if((net user jenkins | Select-String -Pattern 'Password expires') -match 'Never') { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password expires') -match 'Never') { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } It 'Password not required' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if((net user jenkins | Select-String -Pattern 'Password required') -match 'No') { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password required') -match 'No') { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } @@ -123,17 +126,17 @@ Describe "[$global:IMAGE_NAME] check user access to directories" { } It 'can write to HOME' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/a.txt | Out-Null ; if(Test-Path C:/Users/jenkins/a.txt) { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/a.txt | Out-Null ; if (Test-Path C:/Users/jenkins/a.txt) { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } It 'can write to HOME/.jenkins' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/.jenkins/a.txt | Out-Null ; if(Test-Path C:/Users/jenkins/.jenkins/a.txt) { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/.jenkins/a.txt | Out-Null ; if (Test-Path C:/Users/jenkins/.jenkins/a.txt) { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } It 'can write to HOME/Work' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/Work/a.txt | Out-Null ; if(Test-Path C:/Users/jenkins/Work/a.txt) { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/jenkins/Work/a.txt | Out-Null ; if (Test-Path C:/Users/jenkins/Work/a.txt) { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } @@ -142,9 +145,9 @@ Describe "[$global:IMAGE_NAME] check user access to directories" { } } -$global:TEST_VERSION="4.0" -$global:TEST_USER="test-user" -$global:TEST_AGENT_WORKDIR="C:/test-user/something" +$global:TEST_VERSION = '4.0' +$global:TEST_USER = 'test-user' +$global:TEST_AGENT_WORKDIR = 'C:/test-user/something' Describe "[$global:IMAGE_NAME] can be built with custom build arguments" { BeforeAll { @@ -176,17 +179,17 @@ Describe "[$global:IMAGE_NAME] can be built with custom build arguments" { } It 'can write to HOME' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/${TEST_USER}/a.txt | Out-Null ; if(Test-Path C:/Users/${TEST_USER}/a.txt) { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/${TEST_USER}/a.txt | Out-Null ; if (Test-Path C:/Users/${TEST_USER}/a.txt) { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } It 'can write to HOME/.jenkins' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/${TEST_USER}/.jenkins/a.txt | Out-Null ; if(Test-Path C:/Users/${TEST_USER}/.jenkins/a.txt) { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path C:/Users/${TEST_USER}/.jenkins/a.txt | Out-Null ; if (Test-Path C:/Users/${TEST_USER}/.jenkins/a.txt) { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } It 'can write to HOME/Work' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path ${TEST_AGENT_WORKDIR}/a.txt | Out-Null ; if(Test-Path ${TEST_AGENT_WORKDIR}/a.txt) { exit 0 } else { exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"New-Item -ItemType File -Path ${TEST_AGENT_WORKDIR}/a.txt | Out-Null ; if (Test-Path ${TEST_AGENT_WORKDIR}/a.txt) { exit 0 } else { exit -1 }`"" $exitCode | Should -Be 0 } diff --git a/tests/inbound-agent.Tests.ps1 b/tests/inbound-agent.Tests.ps1 index 5c48ebd07..5d0cf9244 100644 --- a/tests/inbound-agent.Tests.ps1 +++ b/tests/inbound-agent.Tests.ps1 @@ -1,24 +1,27 @@ Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1 -$global:IMAGE_NAME = Get-EnvOrDefault 'IMAGE_NAME' '' +$global:IMAGE_NAME = Get-EnvOrDefault 'IMAGE_NAME' '' # Ex: jenkins/inbound-agent:jdk17-nanoserver-1809 $global:VERSION = Get-EnvOrDefault 'VERSION' '' $global:JAVA_VERSION = Get-EnvOrDefault 'JAVA_VERSION' '' -$imageItems = $global:IMAGE_NAME.Split(":") +Write-Host "= TESTS: Preparing $global:IMAGE_NAME with Remoting $global:VERSION and Java $global:JAVA_VERSION" + +$imageItems = $global:IMAGE_NAME.Split(':') $GLOBAL:IMAGE_TAG = $imageItems[1] -$items = $global:IMAGE_TAG.Split("-") +$items = $global:IMAGE_TAG.Split('-') # Remove the 'jdk' prefix (3 first characters) $global:JAVAMAJORVERSION = $items[0].Remove(0,3) $global:WINDOWSFLAVOR = $items[1] $global:WINDOWSVERSIONTAG = $items[2] -# TODO: make this name unique for concurency -$global:CONTAINERNAME = 'pester-jenkins-inbound-agent-{0}' -f $global:IMAGE_TAG +$random = Get-Random +$global:CONTAINERNAME = 'pester-jenkins-inbound-agent_{0}_{1}' -f $global:IMAGE_TAG, $random +Write-Host "= TESTS: container name $global:CONTAINERNAME" -$global:CONTAINERSHELL="powershell.exe" -if($global:WINDOWSFLAVOR -eq 'nanoserver') { - $global:CONTAINERSHELL = "pwsh.exe" +$global:CONTAINERSHELL = 'powershell.exe' +if ($global:WINDOWSFLAVOR -eq 'nanoserver') { + $global:CONTAINERSHELL = 'pwsh.exe' } # # Uncomment to help debugging when working on this script @@ -28,8 +31,8 @@ if($global:WINDOWSFLAVOR -eq 'nanoserver') { # Get-ChildItem Env: | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } Cleanup($global:CONTAINERNAME) -Cleanup("nmap") -CleanupNetwork("jnlp-network") +Cleanup('nmap') +CleanupNetwork('jnlp-network') BuildNcatImage($global:WINDOWSVERSIONTAG) @@ -48,12 +51,12 @@ Describe "[$global:IMAGE_NAME] check default user account" { } It 'has a password that never expires' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if((net user jenkins | Select-String -Pattern 'Password expires') -match 'Never') { exit 0 } else { net user jenkins ; exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password expires') -match 'Never') { exit 0 } else { net user jenkins ; exit -1 }`"" $exitCode | Should -Be 0 } It 'has password policy of "not required"' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if((net user jenkins | Select-String -Pattern 'Password required') -match 'No') { exit 0 } else { net user jenkins ; exit -1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if ((net user jenkins | Select-String -Pattern 'Password required') -match 'No') { exit 0 } else { net user jenkins ; exit -1 }`"" $exitCode | Should -Be 0 } @@ -70,7 +73,7 @@ Describe "[$global:IMAGE_NAME] image has jenkins-agent.ps1 in the correct locati } It 'has jenkins-agent.ps1 in C:/ProgramData/Jenkins' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if(Test-Path 'C:/ProgramData/Jenkins/jenkins-agent.ps1') { exit 0 } else { exit 1 }`"" + $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `"if (Test-Path 'C:/ProgramData/Jenkins/jenkins-agent.ps1') { exit 0 } else { exit 1 }`"" $exitCode | Should -Be 0 } @@ -81,11 +84,11 @@ Describe "[$global:IMAGE_NAME] image has jenkins-agent.ps1 in the correct locati Describe "[$global:IMAGE_NAME] image starts jenkins-agent.ps1 correctly (slow test)" { It 'connects to the nmap container' { - $exitCode, $stdout, $stderr = Run-Program 'docker' "network create --driver nat jnlp-network" + $exitCode, $stdout, $stderr = Run-Program 'docker' 'network create --driver nat jnlp-network' # Launch the netcat utility, listening at port 5000 for 30 sec # bats will capture the output from netcat and compare the first line # of the header of the first HTTP request with the expected one - $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000" + $exitCode, $stdout, $stderr = Run-Program 'docker' 'run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000' $exitCode | Should -Be 0 Is-ContainerRunning "nmap" | Should -BeTrue @@ -109,8 +112,8 @@ Describe "[$global:IMAGE_NAME] image starts jenkins-agent.ps1 correctly (slow te AfterAll { Cleanup($global:CONTAINERNAME) - Cleanup("nmap") - CleanupNetwork("jnlp-network") + Cleanup('nmap') + CleanupNetwork('jnlp-network') } } @@ -119,7 +122,7 @@ Describe "[$global:IMAGE_NAME] custom build args" { Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." # Old version used to test overriding the build arguments. # This old version must have the same tag suffixes as the current windows images (`-jdk17-nanoserver` etc.), and the same Windows version (2019, 2022, etc.) - $TEST_VERSION = "3206.vb_15dcf73f6a_9" + $TEST_VERSION = '3206.vb_15dcf73f6a_9' $customImageName = "custom-${global:IMAGE_NAME}" } @@ -132,7 +135,7 @@ Describe "[$global:IMAGE_NAME] custom build args" { Is-ContainerRunning "$global:CONTAINERNAME" | Should -BeTrue } - It "has the correct agent.jar version" { + It 'has the correct agent.jar version' { $exitCode, $stdout, $stderr = Run-Program 'docker' "exec $global:CONTAINERNAME $global:CONTAINERSHELL -c `"java -jar C:/ProgramData/Jenkins/agent.jar -version`"" $exitCode | Should -Be 0 $stdout | Should -Match $TEST_VERSION @@ -146,13 +149,13 @@ Describe "[$global:IMAGE_NAME] custom build args" { Describe "[$global:IMAGE_NAME] passing JVM options (slow test)" { It "shows the java version ${global:JAVAMAJORVERSION} with --show-version" { - $exitCode, $stdout, $stderr = Run-Program 'docker' "network create --driver nat jnlp-network" + $exitCode, $stdout, $stderr = Run-Program 'docker' 'network create --driver nat jnlp-network' # Launch the netcat utility, listening at port 5000 for 30 sec # bats will capture the output from netcat and compare the first line # of the header of the first HTTP request with the expected one - $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000" + $exitCode, $stdout, $stderr = Run-Program 'docker' 'run --detach --tty --name nmap --network=jnlp-network nmap:latest ncat.exe -w 30 -l 5000' $exitCode | Should -Be 0 - Is-ContainerRunning "nmap" | Should -BeTrue + Is-ContainerRunning 'nmap' | Should -BeTrue # get the ip address of the nmap container $exitCode, $stdout, $stderr = Run-Program 'docker' "inspect --format `"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`" nmap" @@ -160,8 +163,8 @@ Describe "[$global:IMAGE_NAME] passing JVM options (slow test)" { $nmap_ip = $stdout.Trim() # run Jenkins agent which tries to connect to the nmap container at port 5000 - $secret = "aaa" - $name = "bbb" + $secret = 'aaa' + $name = 'bbb' $exitCode, $stdout, $stderr = Run-Program 'docker' "run --detach --tty --network=jnlp-network --name $global:CONTAINERNAME $global:IMAGE_NAME -Url http://${nmap_ip}:5000 -JenkinsJavaOpts `"--show-version`" $secret $name" $exitCode | Should -Be 0 Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue @@ -173,7 +176,7 @@ Describe "[$global:IMAGE_NAME] passing JVM options (slow test)" { AfterAll { Cleanup($global:CONTAINERNAME) - Cleanup("nmap") - CleanupNetwork("jnlp-network") + Cleanup('nmap') + CleanupNetwork('jnlp-network') } } diff --git a/tests/test_helpers.psm1 b/tests/test_helpers.psm1 index 8a2e6e054..59243160f 100644 --- a/tests/test_helpers.psm1 +++ b/tests/test_helpers.psm1 @@ -3,7 +3,7 @@ function Test-CommandExists($command) { $ErrorActionPreference = 'stop' $res = $false try { - if(Get-Command $command) { + if (Get-Command $command) { $res = $true } } catch { @@ -15,13 +15,13 @@ function Test-CommandExists($command) { } # check dependencies -if(-Not (Test-CommandExists docker)) { - Write-Error "docker is not available" +if (-Not (Test-CommandExists docker)) { + Write-Error 'docker is not available' } function Get-EnvOrDefault($name, $def) { $entry = Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Select-Object -First 1 - if(($null -ne $entry) -and ![System.String]::IsNullOrWhiteSpace($entry.Value)) { + if (($null -ne $entry) -and ![System.String]::IsNullOrWhiteSpace($entry.Value)) { return $entry.Value } return $def @@ -35,9 +35,9 @@ function Retry-Command { [scriptblock] $ScriptBlock, [int] $RetryCount = 3, [int] $Delay = 30, - [string] $SuccessMessage = "Command executed successfuly!", - [string] $FailureMessage = "Failed to execute the command" - ) + [string] $SuccessMessage = 'Command executed successfuly!', + [string] $FailureMessage = 'Failed to execute the command' + ) process { $Attempt = 1 @@ -72,35 +72,32 @@ function Retry-Command { } function Cleanup($name='') { - if([System.String]::IsNullOrWhiteSpace($name)) { + if ([System.String]::IsNullOrWhiteSpace($name)) { $name = Get-EnvOrDefault 'AGENT_IMAGE' '' } - if(![System.String]::IsNullOrWhiteSpace($name)) { + if (![System.String]::IsNullOrWhiteSpace($name)) { docker kill "$name" 2>&1 | Out-Null docker rm -fv "$name" 2>&1 | Out-Null } } function Is-ContainerRunning($container='') { - if([System.String]::IsNullOrWhiteSpace($container)) { + if ([System.String]::IsNullOrWhiteSpace($container)) { $container = Get-EnvOrDefault 'AGENT_CONTAINER' '' } Start-Sleep -Seconds 5 Retry-Command -RetryCount 10 -Delay 3 -ScriptBlock { $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect -f `"{{.State.Running}}`" $container" - if(($exitCode -ne 0) -or (-not $stdout.Contains('true')) ) { + if (($exitCode -ne 0) -or (-not $stdout.Contains('true')) ) { throw('Exit code incorrect, or invalid value for running state') } return $true } } -function Run-Program($cmd, $params, $quiet=$true) { - if(-not $quiet) { - Write-Host "cmd & params: $cmd $params" - } +function Run-Program($cmd, $params) { $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.CreateNoWindow = $true $psi.UseShellExecute = $false @@ -115,11 +112,14 @@ function Run-Program($cmd, $params, $quiet=$true) { $stdout = $proc.StandardOutput.ReadToEnd() $stderr = $proc.StandardError.ReadToEnd() $proc.WaitForExit() - if(($proc.ExitCode -ne 0) -and (-not $quiet)) { - Write-Host "[err] stdout:`n$stdout" - Write-Host "[err] stderr:`n$stderr" - Write-Host "[err] cmd:`n$cmd" - Write-Host "[err] params:`n$param" + if (($env:TESTS_DEBUG -eq 'debug') -or ($env:TESTS_DEBUG -eq 'verbose')) { + Write-Host -ForegroundColor DarkBlue "[cmd] $cmd $params" + if ($env:TESTS_DEBUG -eq 'verbose') { + Write-Host -ForegroundColor DarkGray "[stdout] $stdout" + } + if ($proc.ExitCode -ne 0){ + Write-Host -ForegroundColor DarkRed "[stderr] $stderr" + } } return $proc.ExitCode, $stdout, $stderr @@ -127,8 +127,8 @@ function Run-Program($cmd, $params, $quiet=$true) { function BuildNcatImage($windowsVersionTag) { Write-Host "Building nmap image (Windows version '${windowsVersionTag}') for testing" - $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "inspect --type=image nmap" $true - if($exitCode -ne 0) { + $exitCode, $stdout, $stderr = Run-Program 'docker.exe' 'inspect --type=image nmap' $true + if ($exitCode -ne 0) { Push-Location -StackName 'agent' -Path "$PSScriptRoot/.." $exitCode, $stdout, $stderr = Run-Program 'docker.exe' "build -t nmap --build-arg `"WINDOWS_VERSION_TAG=${windowsVersionTag}`" -f ./tests/netcat-helper/Dockerfile-windows ./tests/netcat-helper" $exitCode | Should -Be 0 diff --git a/updatecli/updatecli.d/jdk11.yaml b/updatecli/updatecli.d/jdk11.yaml index 07c2fdafe..3baadee4c 100644 --- a/updatecli/updatecli.d/jdk11.yaml +++ b/updatecli/updatecli.d/jdk11.yaml @@ -95,17 +95,6 @@ targets: file: docker-bake.hcl path: variable.JAVA11_VERSION.default scmid: default - setJDK11VersionDockerCompose: - name: "Bump JDK11 version for Windows images in the build-windows.yaml file" - kind: yaml - spec: - file: build-windows.yaml - key: $.services.jdk11.build.args.JAVA_VERSION - scmid: default - transformers: - - replacer: - from: _ - to: + actions: default: diff --git a/updatecli/updatecli.d/jdk17.yaml b/updatecli/updatecli.d/jdk17.yaml index bf001835d..9b93a7bb4 100644 --- a/updatecli/updatecli.d/jdk17.yaml +++ b/updatecli/updatecli.d/jdk17.yaml @@ -88,7 +88,6 @@ conditions: image: eclipse-temurin targets: - ## Global config files setJDK17VersionDockerBake: name: "Bump JDK17 version for Linux images in the docker-bake.hcl file" kind: hcl @@ -96,18 +95,6 @@ targets: file: docker-bake.hcl path: variable.JAVA17_VERSION.default scmid: default - setJDK17VersionDockerCompose: - name: "Bump JDK17 version for Windows images in the build-windows.yaml file" - kind: yaml - spec: - file: build-windows.yaml - key: $.services.jdk17.build.args.JAVA_VERSION - scmid: default - transformers: - - replacer: - from: _ - to: + - ## Dockerfiles setJDK17VersionAlpine: name: "Bump JDK17 default ARG version on Alpine Dockerfile" kind: dockerfile diff --git a/updatecli/updatecli.d/jdk21.yaml b/updatecli/updatecli.d/jdk21.yaml index 7ce7e4109..16bb8b0d3 100644 --- a/updatecli/updatecli.d/jdk21.yaml +++ b/updatecli/updatecli.d/jdk21.yaml @@ -100,17 +100,6 @@ targets: file: docker-bake.hcl path: variable.JAVA21_VERSION.default scmid: default - setJDK21VersionDockerCompose: - name: "Bump JDK21 version for Windows images in the build-windows.yaml file" - kind: yaml - spec: - file: build-windows.yaml - key: $.services.jdk21.build.args.JAVA_VERSION - scmid: default - transformers: - - replacer: - from: _ - to: + actions: default: diff --git a/updatecli/updatecli.d/remoting.yaml b/updatecli/updatecli.d/remoting.yaml index 2277534fb..50cb6de23 100644 --- a/updatecli/updatecli.d/remoting.yaml +++ b/updatecli/updatecli.d/remoting.yaml @@ -106,16 +106,6 @@ targets: replacepattern: >- $$RemotingVersion${1}= '{{ source "lastVersion" }}', scmid: default - setEnvProps: - name: Bump the Jenkins remoting version on the env.props file - kind: file - spec: - file: env.props - matchpattern: >- - REMOTING_VERSION=(.*) - replacepattern: >- - REMOTING_VERSION={{ source "lastVersion" }} - scmid: default actions: default: diff --git a/windows/windowsservercore/Dockerfile b/windows/windowsservercore/Dockerfile index 7829205e2..8bdb355ca 100644 --- a/windows/windowsservercore/Dockerfile +++ b/windows/windowsservercore/Dockerfile @@ -23,7 +23,7 @@ # THE SOFTWARE. ARG WINDOWS_VERSION_TAG=ltsc2019 -ARG TOOLS_WINDOWS_VERSION=1809 +ARG TOOLS_WINDOWS_VERSION # Empty (only used on nanoserver) FROM mcr.microsoft.com/windows/servercore:"${WINDOWS_VERSION_TAG}" AS jdk-core # $ProgressPreference: https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251261324