title: How can I use `NODE_NAME` within an `options` block? - Jenkinsfile Declarative Pipeline
body: <h1>Question: How can I use <code>NODE_NAME</code> within an <code>options</code> block?</h1>
  <p>I've tried the naïve solution to simply try and use <code>env.NODE_NAME</code>, but this evaluates to <code>null</code> inside the <code>options</code> block:</p>
  <pre><code>options { lock(resource: &quot;${env.NODE_NAME}&quot;) }
  </code></pre>
  <p>The <a href="https://www.jenkins.io/doc/book/pipeline/syntax/#agent" rel="nofollow noreferrer">Jenkins Pipeline docs</a> explained why this did not work:</p>
  <blockquote>
  <p>Inside a <code>stage</code>, the steps in the <code>options</code> directive are invoked before entering the <code>agent</code> or checking any <code>when</code> conditions.</p>
  </blockquote>
  <p>So it's set to <code>null</code> in this context.</p>
  <p>Next, I tried various methods to get <code>env.NODE_NAME</code> at <code>stage { steps {}}</code> level, and pass it up via a global Groovy variable.  However, this doesn't appear to work.</p>
  <p>In the example <code>Jenkinsfile</code> below, the global Groovy variable <code>GLOBAL_NODE_NAME</code> acts as if it's a local variable within each <code>steps</code>, <code>step</code>, or <code>script</code> block.  Meanwhile, it acts as if it's a global variable within the <code>pipeline { stage { options {} }}</code> block.  The value set within the node's &quot;heavyweight executor&quot; context does not bubble up to the global Groovy script level.</p>
  <h2>What I'm trying to do:</h2>
  <p>I want to use the <code>lockable-resources</code> plugin's <code>lock</code> syntax inside an <code>options</code> block to limit concurrent jobs.  If more than one job runs the <code>parallel</code> &quot;<code>Run Tests</code>&quot; stage, all RAM is consumed on the Jenkins agent node.  Therefore, I'm trying to lock a per-node resource to limit concurrent jobs on this particular node.</p>
  <p>The minimal <code>Jenkinsfile</code> I'm using looks like this:</p>
  <pre><code>String GLOBAL_NODE_NAME = 'GLOBAL'
  pipeline {
      agent { label 'ec2-node' }
  
      stages {
          stage('Get Node Name') {
              steps {
                  script {
                      println(&quot;env.NODE_NAME='${env.NODE_NAME}'&quot;)
                      GLOBAL_NODE_NAME = NODE_NAME
                      println(&quot;GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'&quot;)
                  }
              }
          }
          stage('Build') {
              steps {
                  script {
                      println(&quot;DEBUG Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'&quot;)
                      println(&quot;DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='&quot; + GLOBAL_NODE_NAME + &quot;'&quot;)
                  }
                  sh &quot;echo Shell string Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'&quot;
                  // Do build steps here
              }
          }
  
          stage('Run Tests') {
              // Inverse order LIFO
              options {
                  // Get NODE_NAME from the currentBuild b/c lightweight executor returns 'null'
                  lock(inversePrecedence: true, resource: &quot;${GLOBAL_NODE_NAME}&quot;)
              }
              parallel {
                  stage('Unit Tests') {
                      steps {
                          echo &quot;Inside Steps block: GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'&quot;
                          script {
                              println(&quot;DEBUG Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'&quot;)
                              println(&quot;DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='&quot; + GLOBAL_NODE_NAME + &quot;'&quot;)
                          }
                          sh &quot;echo Shell string Interpolation GLOBAL_NODE_NAME='${GLOBAL_NODE_NAME}'&quot;
  
                          // Do Unit Tests
                      }
                  }
  
                  stage('Integration Tests') {
                      steps {
                          sh 'echo this so example Jenkinsfile is valid'
                          // Do Integration Tests
                      }
                  }
              } // end parallel
          }  // end Run Tests
      } // end stages
  } // end pipeline
  
  </code></pre>
  <h3>Output from above example job:</h3>
  <ul>
  <li><p>Stage: <code>Get Node Name</code>:</p>
  <ul>
  <li><code>env.NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'</code></li>
  <li><code>GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'</code></li>
  </ul>
  </li>
  <li><p>Stage: <code>Build</code>:</p>
  <ul>
  <li><code>DEBUG Interpolation GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'</code></li>
  <li><code>DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'</code></li>
  <li>
  <pre><code> echo Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) -
   Pipeline Builder (i-feeb1ec0de5caff01d)
  
   Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
  </code></pre>
  </li>
  </ul>
  </li>
  <li><p>Stage: <code>Run Tests</code>:</p>
  <ul>
  <li><strong>Note:</strong> Inside <code>options</code> block, <code>echo</code>, <code>println</code>, are not available... but we can see the output in the resource's name on the <code>https://&lt;jenkins-host-here&gt;/lockable-resources/</code> page:
  <ul>
  <li><code>GLOBAL_NODE_NAME</code> is set to &quot;<code>GLOBAL</code>&quot;, so the <code>stage</code> =&gt; <code>step</code> assignment did <strong>not</strong> work!</li>
  <li><a href="https://i.stack.imgur.com/a3ZDS.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/a3ZDS.png" alt="Lockable Resources shows the value is still set to GLOBAL" /></a></li>
  <li><strong>Lockable Resources shows that the value of <code>resource</code> is still set to <code>GLOBAL</code>!</strong></li>
  </ul>
  </li>
  <li>Parallel Stage: <code>Unit Tests</code>
  <ul>
  <li><code>Inside Steps block: GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'</code></li>
  <li><code>DEBUG Interpolation GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'</code></li>
  <li><code>DEBUG Raw Groovy Variable GLOBAL_NODE_NAME='EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)'</code></li>
  <li>
  <pre><code>   + echo Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
  
     Shell string Interpolation GLOBAL_NODE_NAME=EC2 (EC2-Jenkins) - Pipeline Builder (i-feeb1ec0de5caff01d)
  </code></pre>
  </li>
  </ul>
  </li>
  </ul>
  </li>
  </ul>
  <h1>EDIT: Here's the final working solution</h1>
  <p>Thanks to @AdamSmith's <a href="https://stackoverflow.com/a/72122586/645491">answer</a>, it got me started in the right direction.  Here is a skeleton of the final <code>Jenkinsfile</code> structure that worked:</p>
  <pre><code>/* groovylint-disable DuplicateStringLiteral */
  /* groovylint-disable-next-line CompileStatic */
  String debugBegin = '============================== DEBUG ENV =============================='
  String debugEnd = '============================== END DEBUG =============================='
  /* groovylint-disable NestedBlockDepth */
  /* groovylint-disable-next-line CompileStatic */
  pipeline {
      agent { label 'ec2-node' }
  
      stages {
          stage('Build') {
              steps {
                  script {
                     // Evaluates in 'heavyweight' executor context
                      println(&quot;Running on env.NODE_NAME='${env.NODE_NAME}'&quot;) // env.NODE_NAME works here
                  }
                  // Do build steps here
              }
          }
  
          stage('Run Tests') {
              options {
                  /*
                  Note: Cannot get NODE_NAME from this context!
                  options is evaluated inside 'lightweight' executor, so currentBuild returns 'null'
                  and any other method trying to pass env.NODE_NAME, or
                  NODE_NAME up from the node / 'heavyweight' executor context did not work!
  
                  lock(inversePrecedence: true, resource: &quot;cannot-get-node-name&quot;) // Anything I tried here did not work!
                  This context executes on jenkins master, and I could not find a way to pass the value back from a node
                  */
                  timeout(time: 15, unit: 'MINUTES')
              }
              steps {
                  // Inverse order LIFO
                  // lock NODE_NAME b/c parallel tests are RAM intensive
                  lock(inversePrecedence: true, resource: &quot;${NODE_NAME}&quot;) {
                      script { // hack so parallel syntax is made available here
                          parallel Test: { // map: {} syntax to pass to scripted-pipeline 'parallel'
                              stage('Test') {
                                  try { // Switched from post { always {}} to =&gt; try {} finally {} here
                                      // because syntax did not work otherwise
                                      echo &quot;Inside Steps block: NODE_NAME='${NODE_NAME}'&quot;
                                      println(&quot;DEBUG Interpolation NODE_NAME='${NODE_NAME}'&quot;)
                                      println(&quot;DEBUG Raw Groovy Variable NODE_NAME='&quot; + NODE_NAME + &quot;'&quot;)
                                      sh &quot;echo Shell string Interpolation GLOBAL_NODE_NAME='${NODE_NAME}'&quot;
                                      echo &quot;${debugBegin}&quot;
                                      sh 'env' // STAGE_NAME is now 'Test'
                                      echo &quot;${debugEnd}&quot;
                                      sh 'make test'
                                      echo 'Tests Succeeded!'
                                  }
                                  finally {
                                      junit '**/path/to/test-reports/*.xml'
                                      sh 'make cleanup'
                                  }
                              }
                          },
                          IntegrationTests: {
                              stage('Integration Tests') {
                                  try {
                                      echo &quot;${debugBegin}&quot;
                                      sh 'env' // STAGE_NAME is now 'Integration Tests'
                                      echo &quot;${debugEnd}&quot;
                                      sh 'make integration'
                                      echo 'Integration Tests Succeeded!'
                                  }
                                  finally {
                                      sh 'make cleanup'
                                  }
                              }
                          } // end parallel map
                      } // end script
                  } // end lock NODE_NAME
              } // end steps
          } // end stage Run Tests
      } // end stages
  } // end pipeline
  </code></pre>