64

I want to run multiple stages inside a lock within a declarative Jenkins pipeline:

pipeline {
    agent any
    stages {
        lock(resource: 'myResource') {
            stage('Stage 1') {
                steps {
                  echo "my first step"
                }
            }

            stage('Stage 2') {
                steps {
                  echo "my second step"
                }
            }

        }
    }
}

I get the following error:

Started by user anonymous
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
WorkflowScript: 10: Expected a stage @ line 10, column 9.
           lock(resource: 'myResource') {
           ^

WorkflowScript: 10: Stage does not have a name @ line 10, column 9.
           lock(resource: 'myResource') {
           ^

WorkflowScript: 10: Nothing to execute within stage "null" @ line 10, column 9.
           lock(resource: 'myResource') {
           ^

3 errors

    at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310)
    at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1085)
    at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:603)
    at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:581)
    at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:558)
    at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
    at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
    at groovy.lang.GroovyShell.parse(GroovyShell.java:700)
    at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.reparse(CpsGroovyShell.java:116)
    at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.parseScript(CpsFlowExecution.java:430)
    at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.start(CpsFlowExecution.java:393)
    at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:257)
    at hudson.model.ResourceController.execute(ResourceController.java:97)
    at hudson.model.Executor.run(Executor.java:405)
Finished: FAILURE

What's the problem here? The documentation explicitly states:

lock can be also used to wrap multiple stages into a single concurrency unit

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
John
  • 10,837
  • 17
  • 78
  • 141

6 Answers6

96

It should be noted that you can lock all stages in a pipeline by using the lock option:

pipeline {
    agent any
    options {
        lock resource: 'shared_resource_lock'
    }
    stages {
        stage('will_already_be_locked') {
            steps {
                echo "I am locked before I enter the stage!"
            }
        }
        stage('will_also_be_locked') {
            steps {
                echo "I am still locked!"
            }
        }
    }
}
dolphy
  • 6,218
  • 4
  • 24
  • 32
  • 2
    Nice! I tried this but unfortunately it won't prevent additional pipeline builds from taking up an executor slot, which is what I need. I've since hooked up a local build agent with a single executor slot to exclusively care of pipeline builds. – JohannSig Mar 21 '18 at 13:04
  • 5
    I've spent a lot of time on searching how to block concurrent builds in multibranch pipeline. And this simple one line of code was this i was looking for. – n1cr4m Oct 11 '18 at 15:06
  • 3
    This should be de approved answer! – Mircea-Andrei Albu Aug 13 '19 at 19:31
  • Awesome answer, used this 3 years later and it did exactly what I needed it to. – NickS Dec 07 '20 at 20:58
  • Perfect, this should be the accepted answer now. – CodeMonkey Jul 16 '21 at 12:07
  • 2
    This is a great suggestion except environment variables are not accessible from `options` blocks. Once again, Jenkins twists itself into inedible pretzels... – robross0606 Aug 27 '21 at 16:55
54

This has since been addressed.

You can lock multiples stages by grouping them in a parent stage, like this :

stage('Parent') {
  options {
    lock('something')
  }
  stages {
    stage('one') {
      ...
    }
    stage('two') {
      ...
    }
  }
}

(Don't forget you need the Lockable Resources Plugin)

KeatsPeeks
  • 19,126
  • 5
  • 52
  • 83
  • Hi Samuel, I cannot make this work with declarative pipelines plugin version 2.5 (https://wiki.jenkins.io/display/JENKINS/Pipeline+Plugin). What version do you have? Or maybe you are using scripted pipelines? Thanks! – gusgonnet Jul 26 '18 at 18:58
  • 2
    @gusgonnet I made it work in a declarative pipeline, on Jenkins 2.121.2, pipeline 2.5 and lockable-resources 2.3 – KeatsPeeks Jul 27 '18 at 08:27
  • Worked like a charm! I have a Parallel stage that I needed the entire set to be locked – Marcello DeSales Jul 12 '19 at 20:47
29

The problem is that, despite the fact that declarative pipelines were technically available in beta in September, 2016, the blog post you reference (from October) is documenting scripted pipelines, not declarative (it doesn't say as much, so I feel your pain). Lockable resources hasn't been baked in as a declarative pipeline step in a way that would enable the feature you're looking for yet.

You can do:

pipeline {
  agent { label 'docker' }
  stages {
    stage('one') {
      steps {
        lock('something') {
          echo 'stage one'
        }
      }
    }
  }
}

But you can't do:

pipeline {
  agent { label 'docker' }
  stages {
    lock('something') {
      stage('one') {
        steps {
          echo 'stage one'
        }
      }
      stage('two') {
        steps {
          echo 'stage two'
        }
      }
    }
  }
}

And you can't do:

pipeline {
  agent { label 'docker' }
  stages {
    stage('one') {
      lock('something') {
        steps {
          echo 'stage one'
        }
      }
    }
  }
}

You could use a scripted pipeline for this use case.

burnettk
  • 13,557
  • 4
  • 51
  • 52
  • The issue is still open: https://issues.jenkins-ci.org/browse/JENKINS-43336 – Der_Meister Jul 04 '17 at 10:58
  • 1
    Note that lock requires lockable-resources plugin: https://plugins.jenkins.io/lockable-resources – Dennis Hoer Dec 02 '17 at 03:40
  • 3
    This approved answer is out-of-date. The better answer for declarative pipelines in today's world is below – Jeff Bennett Sep 16 '20 at 23:30
  • 1
    @JeffBennett Yep, you're right, this answer was correct and is no longer the best answer. The above jira issue was closed as resolved in 2019. – burnettk Sep 17 '20 at 02:12
  • Do you know how this was resolved? I only found this PR [1] that was closed as duplicate to another PR which was never merged. I couldn't find the functionality yet. @JeffBennett Is there an answer that allows locking of some, not all pipeline steps? [1] https://github.com/jenkinsci/lockable-resources-plugin/pull/179 – David Schumann Mar 31 '21 at 08:20
2

If the resource is only used by this pipeline you could also disable concurrent builds:

pipeline {
    agent any
    options {
        disableConcurrentBuilds()
    }
    stages {
        stage('will_already_be_locked') {
            steps {
                echo "I am locked before I enter the stage!"
            }
        }
        stage('will_also_be_locked') {
            steps {
                echo "I am still locked!"
            }
        }
   }
}
2

Altho the options{} block offers this functionality it is not posible to use it in some use cases.

Lets say that you have to name your lock() with a specific name depending on a branch or an environment. You have a pipeline which you dont want to be block by disableConcurrentBuilds() and lock resources depending on a discriminator. You can not name your lock() inside the options{} block by using a environment variable or any other variable from the pipeline because the block is evaluated outside the agent.

The best solution in my opinion is the following:

pipeline {
 agent { label 'docker' }
  stages {
    stage('Wrapper') {
      steps {
        script {
          lock(env.BRANCH_NAME) {
             stage('Stage 1') {
               sh('echo "stage1"')
             }
             stage('Stage 2') {
               sh('echo "stage2"')
             }
           }
        }
      }
    }
  }
}

Keep in mind that the script {} block takes a block of Scripted Pipeline and executes that in the Declarative Pipeline so no steps{} are allowed inside.

nokse
  • 93
  • 7
1

I run multiple build and test containers on the same build nodes. The test containers must lock up the node name as db username for the tests.

lock(resource: "${env.NODE_NAME}" as String, variable: 'DBUSER')

Locks in options are computed at load time, but NODE_NAME is unknown that early. In order to lock multiple stages for visual effect, we can create stages inside script block, i.e. 'run test' stage in the snippet. The stage visualization is just as good as other stage blocks.

pipeline {
    agent any
    stages {
        stage('refresh') {
            steps {
                echo "freshing on $NODE_NAME"
                lock(resource: "${env.NODE_NAME}" as String, variable: 'DBUSER') {
                    sh '''
                        printenv | sort
                    '''
                    script {
                        stage('run test')
                        sh '''
                            printenv | sort
                        '''
                    }
                }
            }
        }
    }
}
wayne s
  • 11
  • 2