Building Jenkins2 Pipelines as Groovy Code

Jenkins pipelines were revamped in Jenkins2 was exciting for me at the time.
This article was originally published during my DevOps time at Cake Solutions in 2016. There's probably a lot of developments since then, but I hope to continue to provide value by reposting it here.

The release of Jenkins2 put a huge emphasis on pipelines - a feature that Jenkins always had but wasn't the key highlight. This all changes with the default built in pipelines that help you write your pipelines in Groovy as code, check it into SCM and visualise your pipelines from a single pipeline job type. It provides huge improvements over the "workflow" view that used to exist, but the documentation doesn't come with a rich set of examples.

In this post I'm going to show you the key building blocks using Groovy, complete with code snippets and hopefully guide you on your way to true DevOps style pipelines - your pipeline as code.

Pre-requisites

Before we begin we need to make sure you have the following in terms of configuration and knowledge of your Jenkins installation:

  • Jenkins2: hopefully an obvious one, make sure you're using version 2 and above.
  • Stage view plugin: ensure that your installation has this plugin installed.
  • Jenkins jobs: you should have built or edited a couple of Jenkins jobs that take parameters, and your installation should have at least one. If not, you won't need this blog yet.
  • Can do attitude: not essential but certainly helps!

Basic constructs

Here are a few basic constructs that will help you dearly on your way to pipeline nirvana - if you've used a programming language or two before, especially Java or Javascript, then these will feel very at home. Groovy seems to not be very forgiving in terms of newlines so pay attention to those!

Tip: Use the script sandbox on your Jenkins at http://your.jenkins.net:8080/script

Node builder

To build anything to need to begin by specifying it to build on a node. Most of your code will live within one of these blocks.

node('optional tag of your slave'){
    stage "Stage 1: Echo stuff"
    echo "step one in my stage!"
    echo "now I'm doing step two of my stage"
}

Functions and variables

Now we're getting to some real pipelines as code; functions and variables are defined very similarly:

// Returns nothing
def my_first_function(param1){
    echo "I received this: " + param1
}

// Concat or add params and return them
def my_second_function(param1, param2){
    return param1 + param2
}

//Make a stage that echo's something
stage "My sandbox stage"
my_first_function("echo this, mum!")

def my_string_var = my_second_function("look at", " me")
def my_int_var = my_second_function(2, 2)

Tip: You can define functions outside of your node block, but I've had some difficulty defining variables outside. If you've had better luck, please please please drop me a comment!

Build a job

Probably one of the first things you'll want to do, build a job you've already specified on your Jenkins installation.

build job:'some-other-job', parameters: [
    string(name: 'parameter 1', value: "value1"),
    booleanParam(name: 'DO_SOMETHING', value: true),
    string(name: 'my choice parameter', value: some_function())
]

if/else

Now we're getting some flow control.

if (some_condition == true){
    echo "it was true!"
} else { // This HAS to be on the same line!
    echo "it wasn't true :disappointed:"
}

Advanced constructs

Once you're comfortable with the above you'll want to explore some more options to make your pipelines more robust and give some more sophisticated flow control options.

waitUntil

Keep running the body until it returns true; will only continue if the body throws no errors AND returns false.

// This is okay
waitUntil {
	def status = ""
	try {
		status = get_status_of_my_thing() // Could throw an error
	} catch {
		status = "NOT DONE"
	}

	if (status == "DONE"){
		return true
	} else {
		return false
	}
}

// This isn't okay
waitUntil {
	def status = get_status_of_my_thing() // Could throw an error
	if(status == "DONE"){
		return true
	} else {
		throw SomeError
	}
}

timeout

This is good to pair with the waitUntil so you're not getting pipelines that never finish.

timeout(1){
    echo "Should execute within approximately 1 minute"
}

// Override units
timeout(time: 300, unit: "SECONDS"){
		wait_until_environment_is_rebuilt("preprod")
    echo "Hurray, environment is rebuilt!"
}

/**
Available units:
    - NANOSECONDS
    - MICROSECONDS
    - MILLISECONDS
    - SECONDS
    - MINUTES
    - HOURS
    - DAYS

Ideally don't use hours, and definitely try not to use DAYS!
*/

Looping

I find the looping syntax strange, but it's easy enough to remember.

def mylist = [
    "item 1",
    "item 2",
    "item 3"
]

/**
Will output the following:
item 1
item 2
item 3
*/
for (String item : mylist){ // Would assume you can type cast here if desired
    echo item
}

Wrapper

This is more of a hint; you can wrap some interesting functionality to make your pipeline more readable as such.

def build_a_job(param1, param2, param3){
    build job:'some-other-job', parameters: [
        string(name: 'parameter 1', value: param1),
        booleanParam(name: 'DO_SOMETHING', value: param2),
        string(name: 'my choice parameter with spaces', value: param3)
    ]
}

// Now I can call the job in my stages
stage "Stage 1: build some-other-job with first set of params"
build_a_job("x", "y", "z")

stage "Stage 2: build some-other-job with second set of params"
build_a_job("a", "b", "c")

User input

I've only experimented with this briefly, but this is useful if you need to give the user some control or the option to abort the pipeline.

// To view input boxes in the UI, it must be in a stage
stage "stage 0: my sandbox"
//Yes or No input
echo "Waiting for input..."
input "Are you sure you want to echo random stuff?"
echo "echo random stuff!"

//Textual inputs
def userInput = input(
    id: 'userInput', message: 'What are you replicating again?', parameters: [
        [$class: 'TextParameterDefinition', defaultValue: 'staging', description: 'Source', name: 'source'],
        [$class: 'TextParameterDefinition', defaultValue: 'preproduction', description: 'Target', name: 'target']
    ]
)

stage "stage 1: other sandbox"
echo "For key [source] I got: [" + userInput['source'] + "]"
if (userInput['target'] == "preproduction"){
    echo "You used the default you lazy tomato!"
} else {
    echo "Wow, I didn't think you'd enter " + userInput['target'] + "!"
}

Your user will be able to see these options like so when they hover over the stage containing your input clause:

That wraps up all of the useful basic and advanced constructs that I've been using in my pipelines so far. There are a few others constructs that I've used here and there but the majority of the cases you'll have, at least in the early stages, should be covered if you use the ones listed above. Below is a short list of useful reading and tool(s) you may find useful on your journey onwards to pipeline nirvana.

  • Pipeline docs: the official Jenkins2 pipelines docs
  • Groovy docs: incorporate any other constructs I didn't mentioned like switch/case
  • Managing user inputs: a blog post from Cloudbees that has some more detail than offered by the docs
  • Best practices: another post on the Cloudbees blog, keep these in mind
  • Jenkins script: a shortcut to localhost:8080/script to access the Jenkins script sandbox if you're running it on your machine

Summary

In this blog post I highlighted a variety of techniques you can implement in your Jenkins pipelines written entirely in Groovy. A variety of basic constructs help you get started and running building your own basic pipelines, and the advanced constructs section helps to get you to the next level and beyond with more robust functionality.


Liked this? Forward to a friend, subscribe to my newsletter, Medium, YouTube, or become a Medium member to support my writing (and other writers, too!).