diff --git a/README.md b/README.md index 53bedb72a..19bcc3033 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ To run a Docker container with [Work Directory](https://github.com/jenkinsci/rem docker run --init jenkins/jnlp-slave -url http://jenkins-server:port -workDir=/home/jenkins/agent Windows agent: - + docker run jenkins/jnlp-agent-windows -Url http://jenkins-server:port -WorkDir=C:/Jenkins/agent -Secret -Name Optional environment variables: diff --git a/jenkins-agent.ps1 b/jenkins-agent.ps1 index 47d3e66ce..59548e55c 100644 --- a/jenkins-agent.ps1 +++ b/jenkins-agent.ps1 @@ -22,13 +22,16 @@ [CmdletBinding()] Param( - $Cmd = '', # this is only used when docker run has one arg positional arg - $Url = '', - $Secret = '', - $Name = '', + $Cmd = '', # this is only used when docker run has one arg positional arg + $Url = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_URL)) { throw ("Url is required") } else { '' } ), + $Secret = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_SECRET)) { throw ("Secret is required") } else { '' } ), + $Name = $( if([System.String]::IsNullOrWhiteSpace($Cmd) -and [System.String]::IsNullOrWhiteSpace($env:JENKINS_AGENT_NAME)) { throw ("Name is required") } else { '' } ), $Tunnel = '', - $WorkDir = 'C:/Users/jenkins/Agent', - $WebSocket = '', + $WorkDir = '', + [switch] $WebSocket = $false, + $DirectConnection = '', + $InstanceIdentity = '', + $Protocols = '', $JavaHome = $env:JAVA_HOME ) @@ -40,59 +43,81 @@ Param( # * JENKINS_AGENT_NAME : agent name, if not set as an argument # * JENKINS_AGENT_WORKDIR : agent work directory, if not set by optional parameter -workDir # * JENKINS_WEB_SOCKET : true if the connection should be made via WebSocket rather than TCP +# * JENKINS_DIRECT_CONNECTION: Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. +# Value: ":" +# * JENKINS_INSTANCE_IDENTITY: The base64 encoded InstanceIdentity byte array of the Jenkins master. When this is set, +# the agent skips connecting to an HTTP(S) port for connection info. +# * JENKINS_PROTOCOLS: Specify the remoting protocols to attempt when instanceIdentity is provided. if(![System.String]::IsNullOrWhiteSpace($Cmd)) { # if `docker run` only has one arguments, we assume user is running alternate command like `bash` to inspect the image Invoke-Expression "$Cmd" } else { - # if -Tunnel is not provided, try env vars - if(![System.String]::IsNullOrWhiteSpace($env:JENKINS_TUNNEL)) { - if(![System.String]::IsNullOrWhiteSpace($Tunnel)) { - Write-Warning "Tunnel is defined twice; in command-line arguments and the environment variable" - } - $Tunnel = $($env:JENKINS_TUNNEL).Trim() - } - $Tunnel = $Tunnel.Trim() - if(![System.String]::IsNullOrWhiteSpace($Tunnel)) { - $Tunnel = " -tunnel `"$Tunnel`"" - } + $AgentArguments = @("-cp", "C:/ProgramData/Jenkins/agent.jar", "hudson.remoting.jnlp.Main", "-headless") - # if -WorkDir is not provided, try env vars - if(![System.String]::IsNullOrWhiteSpace($env:JENKINS_AGENT_WORKDIR)) { - if(![System.String]::IsNullOrWhiteSpace($WorkDir)) { - Write-Warning "Work directory is defined twice; in command-line arguments and the environment variable" - } - $WorkDir = $env:JENKINS_AGENT_WORKDIR - } - $WorkDir = $WorkDir.Trim() - if(![System.String]::IsNullOrWhiteSpace($WorkDir)) { - $WorkDir = " -workDir `"$WorkDir`"" - } + # this maps the variable name from th CmdletBinding to environment variables + $ParamMap = @{ + 'Tunnel' = 'JENKINS_TUNNEL'; + 'Url' = 'JENKINS_URL'; + 'Secret' = 'JENKINS_SECRET'; + 'Name' = 'JENKINS_AGENT_NAME'; + 'WorkDir' = 'JENKINS_AGENT_WORKDIR'; + 'WebSocket' = 'JENKINS_WEB_SOCKET'; + 'DirectConnection' = 'JENKINS_DIRECT_CONNECTION'; + 'InstanceIdentity' = 'JENKINS_INSTANCE_IDENTITY'; + 'Protocols' = 'JENKINS_PROTOCOLS'; + } - if($Env:JENKINS_WEB_SOCKET -eq "true") { - $WebSocket = " -webSocket" - } + # this does some trickery to update the variable from the CmdletBinding + # with the value of the + foreach($p in $ParamMap.Keys) { + $var = Get-Variable $p + $envVar = Get-ChildItem -Path "env:$($ParamMap[$p])" -ErrorAction 'SilentlyContinue' - # if -Url is not provided, try env vars - if(![System.String]::IsNullOrWhiteSpace($env:JENKINS_URL)) { - if(![System.String]::IsNullOrWhiteSpace($Url)) { - Write-Warning "Url is defined twice; in command-line arguments and the environment variable" - } - $Url = $($env:JENKINS_URL).Trim() - } - $Url = $Url.Trim() - if(![System.String]::IsNullOrWhiteSpace($Url)) { - $Url = " -url `"$Url`"" - } + if(($null -ne $envVar) -and ((($envVar.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($envVar.Value))) -or ($null -ne $envVar.Value))) { + if(($null -ne $var) -and ((($var.Value -is [System.String]) -and (![System.String]::IsNullOrWhiteSpace($var.Value))))) { + Write-Warning "${p} is defined twice; in command-line arguments (-${p}) and in the environment variable ${envVar.Name}" + } + if($var.Value -is [System.String]) { + $var.Value = $envVar.Value + } elseif($var.Value -is [System.Management.Automation.SwitchParameter]) { + $var.Value = [bool]$envVar.Value + } + } + if($var.Value -is [System.String]) { + $var.Value = $var.Value.Trim() + } + } - # if -Name is not provided, try env vars - if(![System.String]::IsNullOrWhiteSpace($env:JENKINS_NAME)) { - if(![System.String]::IsNullOrWhiteSpace($Name)) { - Write-Warning "Name is defined twice; in command-line arguments and the environment variable" - } - $Name = $env:JENKINS_NAME - } - $Name = $Name.Trim() + if(![System.String]::IsNullOrWhiteSpace($Tunnel)) { + $AgentArguments += @("-tunnel", "`"$Tunnel`"") + } + + if(![System.String]::IsNullOrWhiteSpace($WorkDir)) { + $AgentArguments += @("-workDir", "`"$WorkDir`"") + } else { + $AgentArguments += @("-workDir", "`"C:/Users/jenkins/Work`"") + } + + if($WebSocket) { + $AgentArguments += @("-webSocket") + } + + if(![System.String]::IsNullOrWhiteSpace($Url)) { + $AgentArguments += @("-url", "`"$Url`"") + } + + if(![System.String]::IsNullOrWhiteSpace($DirectConnection)) { + $AgentArguments += @('-direct', $DirectConnection) + } + + if(![System.String]::IsNullOrWhiteSpace($InstanceIdentity)) { + $AgentArguments += @('-instanceIdentity', $InstanceIdentity) + } + + # these need to be the last things added since they are positional + # parameters to agent.jar + $AgentArguments += @($Secret, $Name) # if java home is defined, use it $JAVA_BIN="java.exe" @@ -100,19 +125,7 @@ if(![System.String]::IsNullOrWhiteSpace($Cmd)) { $JAVA_BIN="$JavaHome/bin/java.exe" } - # if -Url is not provided, try env vars - if(![System.String]::IsNullOrWhiteSpace($env:JENKINS_SECRET)) { - if(![System.String]::IsNullOrWhiteSpace($Secret)) { - Write-Warning "Secret is defined twice; in command-line arguments and the environment variable" - } - $Secret = $env:JENKINS_SECRET - } - $Secret = $Secret.Trim() - if(![System.String]::IsNullOrWhiteSpace($Secret)) { - $Secret = " $Secret" - } - #TODO: Handle the case when the command-line and Environment variable contain different values. #It is fine it blows up for now since it should lead to an error anyway. - Start-Process -FilePath $JAVA_BIN -Wait -NoNewWindow -ArgumentList $("-cp C:/ProgramData/Jenkins/agent.jar hudson.remoting.jnlp.Main -headless$Tunnel$Url$WorkDir$WebSocket$Secret $Name") + Start-Process -FilePath $JAVA_BIN -Wait -NoNewWindow -ArgumentList $AgentArguments } diff --git a/tests/netcat-helper/Dockerfile-windows b/tests/netcat-helper/Dockerfile-windows new file mode 100644 index 000000000..c0ac2f057 --- /dev/null +++ b/tests/netcat-helper/Dockerfile-windows @@ -0,0 +1,40 @@ +# escape=` + +# The MIT License +# +# Copyright (c) 2019-2020, Alex Earl and other Jenkins Contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +FROM mcr.microsoft.com/windows/servercore:1809 + +SHELL ["powershell.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +ARG NMAP_VERSION=7.80 +ENV NMAP_VERSION $NMAP_VERSION + +RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; ` + $url = $('https://nmap.org/dist/nmap-{0}-setup.exe' -f $env:NMAP_VERSION) ; ` + Write-Host "Retrieving $url..." ; ` + Invoke-WebRequest $url -OutFile 'nmap-install.exe' -UseBasicParsing ; ` + $proc = Start-Process "C:\nmap-install.exe" -PassThru -ArgumentList '/S' ; ` + $proc.WaitForExit() ; ` + Remove-Item -Path nmap-install.exe + + diff --git a/tests/tests.bats b/tests/tests.bats index cb07eccc9..b92970b47 100755 --- a/tests/tests.bats +++ b/tests/tests.bats @@ -1,24 +1,34 @@ #!/usr/bin/env bats -DOCKERFILE=Dockerfile -JDK=8 -SLAVE_IMAGE=jenkins-jnlp-slave -SLAVE_CONTAINER=bats-jenkins-jnlp-slave +AGENT_IMAGE=jenkins-jnlp-agent +AGENT_CONTAINER=bats-jenkins-jnlp-agent NETCAT_HELPER_CONTAINER=netcat-helper -if [[ -z "${FLAVOR}" ]] +REGEX='^([0-9]+)/(.+)$' + +REAL_FOLDER=$(realpath "${BATS_TEST_DIRNAME}/../${FOLDER}") + +if [[ ${FOLDER} =~ ${REGEX} ]] && [[ -d "${REAL_FOLDER}" ]] then - FLAVOR="debian" -elif [[ "${FLAVOR}" = "jdk11" ]] + JDK="${BASH_REMATCH[1]}" + FLAVOR="${BASH_REMATCH[2]}" +else + echo "Wrong folder format or folder does not exist: ${FOLDER}" + exit 1 +fi + +if [[ "${JDK}" = "11" ]] then - DOCKERFILE+="-jdk11" - JDK=11 - SLAVE_IMAGE+=":jdk11" - SLAVE_CONTAINER+="-jdk11" + AGENT_IMAGE+=":jdk11" + AGENT_CONTAINER+="-jdk11" else - DOCKERFILE+="-alpine" - SLAVE_IMAGE+=":alpine" - SLAVE_CONTAINER+="-alpine" + if [[ "${FLAVOR}" = "alpine*" ]] + then + AGENT_IMAGE+=":alpine" + AGENT_CONTAINER+="-alpine" + else + AGENT_IMAGE+=":latest" + fi fi load test_helpers @@ -31,29 +41,29 @@ function teardown () { clean_test_container } -@test "[${FLAVOR}] build image" { +@test "[${JDK} ${FLAVOR}] build image" { cd "${BATS_TEST_DIRNAME}"/.. || false - docker build -t "${SLAVE_IMAGE}" -f "${DOCKERFILE}" . + docker build -t "${AGENT_IMAGE}" ${FOLDER} } -@test "[${FLAVOR}] image has installed jenkins-agent in PATH" { - docker run -d -it --name "${SLAVE_CONTAINER}" -P "${SLAVE_IMAGE}" /bin/bash +@test "[${JDK} ${FLAVOR}] image has installed jenkins-agent in PATH" { + docker run -d -it --name "${AGENT_CONTAINER}" -P "${AGENT_IMAGE}" /bin/bash is_slave_container_running - run docker exec "${SLAVE_CONTAINER}" which jenkins-slave - [ "/usr/local/bin/jenkins-slave" = "${lines[0]}" ] + run docker exec "${AGENT_CONTAINER}" which jenkins-agent + [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] - run docker exec "${SLAVE_CONTAINER}" which jenkins-agent + run docker exec "${AGENT_CONTAINER}" which jenkins-agent [ "/usr/local/bin/jenkins-agent" = "${lines[0]}" ] } -@test "[${FLAVOR}] image starts jenkins-agent correctly (slow test)" { +@test "[${JDK} ${FLAVOR}] image starts jenkins-agent correctly (slow test)" { # Spin off a helper image which contains netcat docker run -d -it --name netcat-helper netcat-helper:latest /bin/sh # Run jenkins agent which tries to connect to the netcat-helper container at port 5000 - docker run -d --link netcat-helper --name "${SLAVE_CONTAINER}" "${SLAVE_IMAGE}" -url http://netcat-helper:5000 aaa bbb + docker run -d --link netcat-helper --name "${AGENT_CONTAINER}" "${AGENT_IMAGE}" -url http://netcat-helper:5000 aaa bbb # Launch the netcat utility, listening at port 5000 for 30 sec # bats will capture the output from netcat and compare the first line @@ -64,7 +74,7 @@ function teardown () { [ $'GET /tcpSlaveAgentListener/ HTTP/1.1\r' = "${lines[0]}" ] } -@test "[${FLAVOR}] use build args correctly" { +@test "[${JDK} ${FLAVOR}] use build args correctly" { cd "${BATS_TEST_DIRNAME}"/.. || false local ARG_TEST_VERSION @@ -84,16 +94,16 @@ function teardown () { docker build \ --build-arg "version=${ARG_TEST_VERSION}" \ --build-arg "user=${TEST_USER}" \ - -t "${SLAVE_IMAGE}" \ - -f "${DOCKERFILE}" . + -t "${AGENT_IMAGE}" \ + ${FOLDER} - docker run -d -it --name "${SLAVE_CONTAINER}" -P "${SLAVE_IMAGE}" /bin/sh + docker run -d -it --name "${AGENT_CONTAINER}" -P "${AGENT_IMAGE}" /bin/sh is_slave_container_running - run docker exec "${SLAVE_CONTAINER}" sh -c "java -cp /usr/share/jenkins/agent.jar hudson.remoting.jnlp.Main -version" + run docker exec "${AGENT_CONTAINER}" sh -c "java -cp /usr/share/jenkins/agent.jar hudson.remoting.jnlp.Main -version" [ "${TEST_VERSION}" = "${lines[0]}" ] - run docker exec "${SLAVE_CONTAINER}" sh -c "id -u -n ${TEST_USER}" + run docker exec "${AGENT_CONTAINER}" sh -c "id -u -n ${TEST_USER}" [ "${TEST_USER}" = "${lines[0]}" ] }