13

I'm trying to do something like this:

def makeStage = {
  stage('a') {
    steps {
      echo 'Hello World'
    }
  }
} 
pipeline {
  agent none
  stages {
    makeStage()
  }
}

But it gives me this exception:

WorkflowScript: 11: Expected a stage @ line 11, column 5.
   makeStage()
   ^

Is it possible to define a stage as a external closure and if so - how?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Will Brode
  • 1,026
  • 2
  • 10
  • 27
  • Probably not advisable but you *could* use the jobDSL plugin and generate a declarative pipeline via string manipulation that you would stick directly in the generated pipeline job. Its not too pretty but then you can use functions to generate stages. – Will Brode Jun 01 '18 at 23:00

2 Answers2

14

Super late, but in case anyone runs into this issue, a possible solution would be to wrap your generated stage around a script declarative and invoke .call on the generated stage.

So for you:

def makeStage = {
  return {
    stage('a') {
      echo 'Hello World'
    }
  }
}

pipeline {
  agent none
  stages {
    stage ('hello world') {
      steps {
        script {
          makeStage().call()
        }      
      }
    }
  }
}

Whoops. edited, I had "steps" inside on my stage('a') in the makeStage declaration. "steps" is a declarative pipeline directive so it was throwing an error inside the script block.

John Huynh
  • 369
  • 3
  • 6
  • Are you sure this works? The accepted answer says that this can't be done. – Will Brode Apr 11 '19 at 18:58
  • 1
    Yea, I have this in one of my jobs right now with actual production steps – John Huynh Apr 12 '19 at 16:46
  • I just tried a pipeline with this exact code and it didn't work. I think it decided it was scripted pipeline because it didn't start with "pipeline". Can you verify this exact code works for you? – Will Brode Apr 15 '19 at 22:53
  • edited and ran the job successfully on jenkins, thanks @WillBrode – John Huynh Apr 19 '19 at 16:30
  • It works great once I realized that the part in the function must follow scripted pipeline syntax and not declarative pipeline syntax. Typically, no "stages", "steps", "script", "options", "when", ... – A. Richard Sep 27 '19 at 10:44
  • 6
    One note folks - adding stage this way is mixing declarative stages (pipeline model definition) with scripted stages. Those two things are not equal, even though they may look the same. You can even inject multiple stages to a single declarative stage, but this is abuse or anti-pattern. Check Blue Ocean UI and see that the stage injected that way does not provide the same features as a regular declarative stage. You can't e.g. restart your pipeline from that stage, or set stage options, just to name those two things. I would not recommend mixing those two different things. – Szymon Stepniak Feb 20 '20 at 11:45
9

You can't define stages outside the declarative pipeline. The main purpose of declarative pipeline is to provide simplified and opinionated syntax so you can focus on what should be done (by using some of the available steps) and not how to do it.

If you are interested in more flexible way of implementing pipeline, you may choose Scripted Pipeline approach which is not that strict if it comes to the syntax - it's only limited by Groovy and CPS execution module.

Working (scripted) pipeline from your example would look like this:

#!groovy

def makeStage = {
  stage('a') {
    echo 'Hello World'
  }
} 

node {
    makeStage()
}

Attention: There is no steps method inside stage in a scripted pipeline. If you leave it there you will get

java.lang.NoSuchMethodError: No such DSL method 'steps' found among 
    steps [archive, bat, build, catchError, checkout, deleteDir, dir, 
    dockerFingerprintFrom, ...

Scripts in declarative pipeline

Declarative pipeline defines a script step that allows you to put a block of scripted pipeline. However it still does not allow you to define stage dynamically or/and extract stage definition to a function or closure. script step gets executed inside the stage so you can't control inside this block if stage is executed or not. In some cases however this step might be very useful if you want to do something more complex than just calling pre-defined step of a declarative pipeline.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • don't agree, pls check answers from John below. – star Feb 20 '20 at 11:21
  • 2
    @star, keep in mind that stage added this way is not the declarative's pipeline stage. In this case, you are mixing the declarative stage that comes from the Pipeline Model Definition plugin with a scripted stage. Those two things are not equal. Check Blue Ocean UI and see if you can restart such a stage. You can't. So if you want to define a declarative pipeline stage outside the pipeline block, it is not possible. Adding scripted stage equivalent is possible (you can even inject multiple stages to a single declarative stage), but this is actually an anti-pattern. No need to downvote. – Szymon Stepniak Feb 20 '20 at 11:42
  • 1
    thanks for the explanation, will try put the upvote back. :) – star Feb 21 '20 at 02:37