In a declarative jenkins pipeline - can I set the agent label dynamically?
Here is how I made it: mix scripted and declarative pipeline. First I've used scripted syntax to find, for example, branch I'm on. Then define AGENT_LABEL variable. This var can be used anywhere along the declarative pipeline
def AGENT_LABEL = nullnode('master') { stage('Checkout and set agent'){ checkout scm ### Or just use any other approach to figure out agent label: read file, etc if (env.BRANCH_NAME == 'master') { AGENT_LABEL = "prod" } else { AGENT_LABEL = "dev" } }}pipeline { agent { label "${AGENT_LABEL}" } stages { stage('Normal build') { steps { echo "Running in ${AGENT_LABEL}" sh "hostname" } } stage ("Docker build") { agent{ dockerfile { dir 'Dockerfiles' label "${AGENT_LABEL}" } } steps{ sh "hostname" } } }}
To see how this works, use a GString
object to do a println
and return the variable for the agentName at the same time. You can see from the output that this line evaluates well before any of the other pipeline code.
agentName = "Windows"agentLabel = "${println 'Right Now the Agent Name is ' + agentName; return agentName}"pipeline { agent none stages { stage('Prep') { steps { script { agentName = "Linux" } } } stage('Checking') { steps { script { println agentLabel println agentName } } } stage('Final') { agent { label agentLabel } steps { script { println agentLabel println agentName } } } }}
Console output (note that I don't actually have node on this instance labeled Windows, so I aborted after it couldn't find it):
Started by user Admin[Pipeline] echoRight Now the Agent Name is Windows[Pipeline] stage[Pipeline] { (Prep)[Pipeline] script[Pipeline] {[Pipeline] }[Pipeline] // script[Pipeline] }[Pipeline] // stage[Pipeline] stage[Pipeline] { (Checking)[Pipeline] script[Pipeline] {[Pipeline] echoWindows[Pipeline] echoLinux[Pipeline] }[Pipeline] // script[Pipeline] }[Pipeline] // stage[Pipeline] stage[Pipeline] { (Final)[Pipeline] nodeStill waiting to schedule taskThere are no nodes with the label ‘Windows’Aborted by Admin[Pipeline] // node[Pipeline] }[Pipeline] // stage[Pipeline] End of PipelineERROR: Queue task was cancelledFinished: ABORTED
Notice how the line Right Now the Agent Name is Windows
appears very early in the output. This explains why your value is null. That statement is evaluated long before your script modifies the variable.
I might try to use a lazy GString
to get the variable later.
agentLabel = "${-> println 'Right Now the Agent Name is ' + agentName; return agentName}"
Unfortunately, this throws an error because it is expecting a type of String. Apparently it can coerce the non-lazy GString to a String on its own, but not the lazy version. So when I force coercion to a String, of course, it evaluates the variable at that time (which is again, before the pipeline code actually runs).
agent { label agentLabel as String }
You can solve the problem by falling back to the old node allocation method:
agentName = "Windows"agentLabel = "${-> println 'Right Now the Agent Name is ' + agentName; return agentName}"pipeline { agent none stages { stage('Prep') { steps { script { agentName = "Linux" } } } stage('Checking') { steps { script { println agentLabel println agentName } } } stage('Final') { steps { node( agentLabel as String ) { // Evaluate the node label later echo "TEST" } script { println agentLabel println agentName } } } }}
You can see from this console output that it now properly finds the Linux node and finishes the pipeline. The early evaluation while agentName == Windows never happens:
Started by user Admin[Pipeline] stage[Pipeline] { (Prep)[Pipeline] script[Pipeline] {[Pipeline] }[Pipeline] // script[Pipeline] }[Pipeline] // stage[Pipeline] stage[Pipeline] { (Checking)[Pipeline] script[Pipeline] {[Pipeline] echoRight Now the Agent Name is Linux[Pipeline] echoLinux[Pipeline] }[Pipeline] // script[Pipeline] }[Pipeline] // stage[Pipeline] stage[Pipeline] { (Final)[Pipeline] echoRight Now the Agent Name is Linux[Pipeline] nodeRunning on Slave 1 in /home/jenkinsslave/jenkins/workspace/test[Pipeline] {[Pipeline] echoTEST[Pipeline] }[Pipeline] // node[Pipeline] script[Pipeline] {[Pipeline] echoRight Now the Agent Name is Linux[Pipeline] echoLinux[Pipeline] }[Pipeline] // script[Pipeline] }[Pipeline] // stage[Pipeline] End of PipelineFinished: SUCCESS
This would probably work without the lazy GString
and type coercion later, but I didn't try that.
it might be something about the context of the script block.
this works, using a label of 'docker' in second stage:
def hotLabel = 'docker'pipeline { agent { label 'master' } stages { stage('Stage1') { steps { echo "node_name: ${hotLabel}" } } stage('Stage2') { agent { label "${hotLabel}" } steps { echo "node_name: ${hotLabel}" } } }}
this does not (gets the same There are no nodes with the label ‘null’ error):
def hotLabel = nullpipeline { agent { label 'master' } stages { stage('Stage1') { steps { script { hotLabel = "docker" } } } stage('Stage2') { agent { label "${hotLabel}" } steps { echo "node_name: ${hotLabel}" } } }}