Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Is there a way to set the agent label dynamically and not as plain string?

The job has 2 stages:

  • First stage - Runs on a "master" agent, always. At the end of this stage I will know on which agent should the 2nd stage run.
  • Second stage - should run on the agent decided in the first stage.
  • My (not working) attempt looks like this:

    pipeline {
        agent { label 'master' }
        stages {
            stage('Stage1') {
                steps {
                    script {
                        env.node_name = "my_node_label"
                    echo "node_name: ${env.node_name}"
            stage('Stage2') {
                agent { label "${env.node_name}" }
                steps {
                    echo "node_name: ${env.node_name}"
    

    The first echo works fine and "my_node_label" is printed. The second stage fails to run on an agent labeled "my_node_label" and the console prints:

    There are no nodes with the label ‘null’

    Maybe it can help - if I just put "${env}" in the label field I can see that this is a java class as it prints:

    There are no nodes with the label ‘org.jenkinsci.plugins.workflow.cps.EnvActionImpl@79c0ce06’

    Not sure this is true as the first "echo" command works fine and it's outside the script block. Anyway, in my actual scenario env.node_name is a result returned by a shell script. If you know any workaround - it would be helpful. – Gilad Shahrabani Oct 10, 2017 at 15:06

    Here is how I made it: mix scripted and declarative pipeline. First I've used scripted syntax to find, for example, the branch I'm on. Then define AGENT_LABEL variable. This var can be used anywhere along the declarative pipeline

    def AGENT_LABEL = null
    node('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"
                    Instead of doing string interpolation (label "${AGENT_LABEL}"), you can simply write label AGENT_LABEL.
    – aaronk6
                    Mar 30, 2020 at 16:02
                    Why use an agent at all in the first stanza? Just use a closure in the agent declaration: pipeline { agent { label "${env.BRANCH_NAME.equalsIgnoreCase('master') ? 'prod' : 'dev'}" }
    – Max Cascone
                    Jan 15, 2021 at 2:45
                    Could we define using your approach the content of the agent { "$CONTENT" } and not label "${AGENT_LABEL}" where CONTENT is calculated within the node/stage` ?
    – Charles Moulliard
                    Apr 23, 2021 at 11:50
    

    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] echo
    Right 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] echo
    Windows
    [Pipeline] echo
    Linux
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] stage
    [Pipeline] { (Final)
    [Pipeline] node
    Still waiting to schedule task
    There are no nodes with the label ‘Windows’
    Aborted by Admin
    [Pipeline] // node
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] End of Pipeline
    ERROR: Queue task was cancelled
    Finished: 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] echo
    Right Now the Agent Name is Linux
    [Pipeline] echo
    Linux
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] stage
    [Pipeline] { (Final)
    [Pipeline] echo
    Right Now the Agent Name is Linux
    [Pipeline] node
    Running on Slave 1 in /home/jenkinsslave/jenkins/workspace/test
    [Pipeline] {
    [Pipeline] echo
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    Right Now the Agent Name is Linux
    [Pipeline] echo
    Linux
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] End of Pipeline
    Finished: SUCCESS
    

    This would probably work without the lazy GString and type coercion later, but I didn't try that.

    kind of.. but I wonder if this solution has a downside: In the 'Final' stage the node agent is indeed the right one, but when I want to run a shell script on the agent, if I do it in the script block (the one just below the node block) it will run on the main agent and not the slave(!). Only if I run it inside the node block it will run on the right slave... Does this have any downside? different syntax / capabilities inside a "node" block..? – Gilad Shahrabani Oct 15, 2017 at 13:17 No, no downside at all. The "node" block is the syntax used before declarative pipeline. For your purposes here, this is the same as specifying the agent on the stage. I did just notice, though, that you should probably put an agent on the previous stages, and "agent none" at the pipeline level. Or you can specify an agent at the pipeline level and 'node' should still do what you want. But in my mind it is a little cleaner to specify an agent per stage if you are not using the same one on all stages. But either way, don't run the stage on the flyweight agent on the master. – Rob Hales Oct 15, 2017 at 16:43

    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 = null
    pipeline {
        agent { label 'master' }
        stages {
            stage('Stage1') {
                steps {
                    script {
                        hotLabel = "docker"
            stage('Stage2') {
                agent { label "${hotLabel}" }
                steps {
                    echo "node_name: ${hotLabel}"
                    The problem with this example is the agent { label 'master' } at the top of the pipeline overrides any other agent declarations. If you want to specify specific agents (or no agent) at any point in the pipe, you have to start with agent none and define an agent in every stage that needs one.
    – Max Cascone
                    Jan 15, 2021 at 2:43
    

    I used a ternary operator to have mine dynamically change.

    For below, if the Jenkins pipeline name ends in "prod" the label used is "myagent-prd". Otherwise, it's just "myagent".

    def ENVIRONMENT_NAME="${JOB_NAME}".tokenize('-').last().toLowerCase()
    pipeline {
        agent {
          label "myagent${ENVIRONMENT_NAME == "prod" ? "-prd" : "" }"
    [Pipeline] { (Finish)
    [Pipeline] node
    Running on 10.1agent in /home/build/jenkins/workspace/testlabel
    [Pipeline] {
    [Pipeline] echo
    rbreg6
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    rbreg6
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] End of Pipeline
    Finished: SUCCESS
    Changing the branch name to 9.2.0:
    Started by user build
    Running in Durability level: MAX_SURVIVABILITY
    [Pipeline] stage
    [Pipeline] { (Prep)
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    9.2.0
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] stage
    [Pipeline] { (Finish)
    [Pipeline] node
    Running on 9.2agent in /shared/build/workspace/testlabel
    [Pipeline] {
    [Pipeline] echo
    rbregistry
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] script
    [Pipeline] {
    [Pipeline] echo
    rbregistry
    [Pipeline] }
    [Pipeline] // script
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] End of Pipeline
    Finished: SUCCESS
                    While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
    – awh112
                    Jun 28, 2018 at 19:58
                    Are you saying you can define the agent inside the stage? I'll have to go try this, has anyone tried to do this inside a loop in a script directive?
    – Max Cascone
                    Jan 15, 2021 at 2:52
                    this is not dynamic enough for my needs - as written in the question: "At the end of this stage I will know on which agent should the 2nd stage run". So I don't yet know the node name when the job is starting.
    – Gilad Shahrabani
                    Oct 25, 2018 at 12:41
    

    For my work's pipeline jobs, I needed to solve a similar problem. I did only want to set an agent for the whole pipeline! I solved this by creating a function that returns the Nodename as String, which I can call in the pipeline directly.

    In my case the Nodename is part of a Jenkins environment variable $JOB_BASE_NAME. But you could use any logic that is allowed in the Jenkins Script Block, which is a big plus, I guess.

    // determine agent to run tests on
    def agent_selector() {
        if (env.JOB_BASE_NAME.contains('Nodename1')) {
            return "Nodename1"
        } else if (env.JOB_BASE_NAME.contains('Nodename2')) {
            return "Nodename2"
        } else {
            println("Could not get Agent from 'JOB_BASE_NAME' !")
            error('Aborting Build.')
    // start of pipeline
    pipeline {
        agent {label agent_selector()}
        stages {
            stage('Stagestuff') {
                steps {
                    echo "Hello World"
    

    Is there a way to set the agent label dynamically and not as plain string?

    So for those who wants to instantly use the relevant agent without run anything on the Jenkins controller, and still be able to pass dynamic agent label and keep it clean as possible:

    pipeline {
        agent {
            label env.JENKINS_AGENT_LABEL ?: 'pod-agent'
    

    In my case, I'm using the environment variable JENKINS_AGENT_LABEL (you can use whatever you want here), and if env.JENKINS_AGENT_LABEL is equal to null, then it will use my default agent label (in my case 'pod-agent'). But if env.JENKINS_AGENT_LABEL is not equal null, then the Jenkins will use the agent label from the value that it will get from env.JENKINS_AGENT_LABEL

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.