Once you’ve learned how easy it is to get started with Jenkins, you’ll want to start building pipelines right away. But as soon as it’s time to build more than one project, you have a problem: How do you avoid repeating yourself?
While simply copying and pasting code from one job to another seems like the fastest and easiest approach, that approach doesn’t scale. It’s only a matter of time before you need to do the same thing in more than one job. What happens when something needs to change? How many different jobs will you end up modifying? Can you find them all?
What you need are Jenkins Shared Libraries. They’re a simple mechanism for sharing code between Jenkins pipelines and jobs.
In this post, you’ll learn how to create a Shared Library. You’ll add it to a Jenkins build server and then use the library to share code between jobs. (You can also check out Darin Pope's video tutorial on this subject at the end of this post.)
What Are Jenkins Shared Libraries?
A Jenkins Shared Library is exactly what it says on the tin: a library with code you share between Jenkins jobs. Developers use libraries to share code and avoid duplication. Jenkins has libraries that let you do the same thing.
If you’ve written code, you’ve heard about the DRY principle of software development: Don’t repeat yourself. It’s one of the cardinal laws of software engineering, and it applies to Jenkins, too. Copying build steps between build jobs isn’t just inefficient—it’s potentially dangerous. If you need to change one of those steps, you’ll have to track it down to every place it’s been copied.
Jenkins loads Shared Libraries from SCM repositories. So, you write your code, push it to a repo, and then Jenkins loads it on demand. If you need to make a change, you update the code, push the new version to your source control and Jenkins picks it up the next time an associated build job runs.
If you have more than one build job, then you have common tasks or variables. It may be as simple as the email where you need to send build results. Or it might be as complicated as updating a Docker container before using it to run a build.
Creating a Shared Library
For this tutorial, you’ll be using git for source code control. I’ll be using a publicly available GitHub repo named github-api-global. But if your Jenkins can only access a private git server, you can download this repo from GitHub and add it to your source control. Alternatively, you can create a new one and add the code you need.
Library Directory Structure
Shared Libraries have a specific directory structure that tells Jenkins how to load your code and make it available to pipelines.
Here’s the layout, as defined by the Jenkins documentation:
(root)
+- src # Groovy source files
| +- org
| +- foo
| +- Bar.groovy # for org.foo.Bar class
+- vars
| +- fooBar.groovy # for global 'foo' variable
| +- foo.txt # help for 'foo' variable
+- resources # resource files (external libraries only)
| +- org
| +- foo
| +- bar.json # static helper data for org.foo.Bar
You structure the src directory like a Java project. Jenkins adds this directory to the classpath when it runs a pipeline. But Groovy handles the vars directory differently. Each Groovy file is exposed as a global variable. So, in the example above, fooBar.groovy creates a variable named fooBar. Global variable names must use camelCase.
Sample Jenkins Shared Library Variable
Let’s take a look at one of the sample vars in github-api-global.
After you’ve cloned or forked the repo, open vars/helloWorld.groovy in your favorite text editor.
def call(Map config = [:]) {
sh "echo Hello ${config.name}. Today is ${config.dayOfWeek}."
}
This simple variable demonstrates two important conventions.
First, as mentioned above, global variable names must be in camelCase, so the filename is helloWorld.groovy, not HelloWorld.groovy. Jenkins will not load the second filename as a variable.
Second, Jenkins loads global variables as singleton objects. Each one can have multiple methods. In this example, helloWorld() is a custom build step. So, it has a single method named call(). You’ll see how to use this below.
Add a Shared Library to Jenkins
Let’s add a library to Jenkins.
Start with the Manage Jenkins menu item.
This will take you to the System Configuration page. Select Configure System.
Configure System takes you to a very long page. Scroll down until you see Global Pipeline Libraries.
Click the Add button.
The Add button expands a form where you fill out information about the library.
First, use shared-library for the name. This is the name you’ll use to load the library in your pipeline project.
Next, specify main for the default version. A default version can be a branch name, a tag, or even a git commit hash. In this case, we’re using the main branch. You can override this version when you load the library in a pipeline. The Jenkins documentation has a detailed explanation on how to override the version.
The following three checkboxes cover advanced options. Leave them set as their default for now.
Note: Load Implicitly will load this library for any and all Jenkins jobs when it runs them. This is probably not what you want, since loading a library can impact how quickly a project runs.
Finally, select Modern SCM, then choose Git. Enter the URL for the GitHub repository we looked at above. Since it’s a public repo, you don’t need credentials.
There’s an option for GitHub below, and it would work for this repo, too. But since GitHub is git, you can use either configuration setting.
Select Save, and you’re ready to run a pipeline job with a Shared Library!
Add a Pipeline Job
Now it’s time to call a Shared Library from a pipeline job.
Start by creating a new Pipeline item.
Give it a memorable name, select Pipeline for the project type, and click OK.
First, you’ll enter a simple job without a Shared Library. Go to the pipeline section and enter this code:
Here it is as text.
pipeline {
agent any
stages {
stage('Example') {
steps {
sh 'echo Hello World'
}
}
}
}
Run this Jenkins project and view the console output:
Started by user
Administrator
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on
Jenkins
in /var/jenkins_home/workspace/Test-Pipeline
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Example)
[Pipeline] sh
+ echo Hello World
Hello World
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
Jenkins started the job and the agent echoed ‘Hello World’ to the console.
Now, let’s have a Shared Library do the work instead.
Use a Shared Library
Modify the pipeline script to load the Shared Library and call HelloWorld():
@Library('shared-library') _
def config = [name: 'Newman', dayOfWeek: 'Friday']
pipeline {
agent any
stages {
stage('Example') {
steps {
helloWorld(config)
}
}
}
}
On line #1, @Library loads the Shared Library. You need to follow the function call with an underscore (_) operator to tell Groovy to import the entire library namespace. Usually, your Shared Library would be smaller than the sample code, or you would only import a subset of the library namespace.
One line #2, the script defines a map with the two parameters helloWorld() expects—name and dayOfWeek.
Finally, replace the call to sh in the build step with helloWorld() with the config map.
Run the job, and the console input is very different:
Started by user
Administrator
Running in Durability level: MAX_SURVIVABILITY
Loading library shared-library@main
Attempting to resolve main from remote references...
> git --version # timeout=10
> git --version # 'git version 2.11.0'
> git ls-remote -h --
https://github.com/darinpope/github-api-global-lib.git
# timeout=10
Found match: refs/heads/main revision 5e80379a128e2a87f65a56f40f2bbbb3eebba703
Selected Git installation does not exist. Using Default
The recommended git tool is: NONE
No credentials specified
> git rev-parse --resolve-git-dir /var/jenkins_home/workspace/Test-Pipeline@libs/shared-library/.git # timeout=10
Fetching changes from the remote Git repository
> git config remote.origin.url
https://github.com/darinpope/github-api-global-lib.git
# timeout=10
Fetching without tags
Fetching upstream changes from
https://github.com/darinpope/github-api-global-lib.git
> git --version # timeout=10
> git --version # 'git version 2.11.0'
> git fetch --no-tags --progress --
https://github.com/darinpope/github-api-global-lib.git
+refs/heads/*:refs/remotes/origin/* # timeout=10
Checking out Revision 5e80379a128e2a87f65a56f40f2bbbb3eebba703 (main)
> git config core.sparsecheckout # timeout=10
> git checkout -f 5e80379a128e2a87f65a56f40f2bbbb3eebba703 # timeout=10
Commit message: "more"
> git rev-list --no-walk 5e80379a128e2a87f65a56f40f2bbbb3eebba703 # timeout=10
[Pipeline] Start of Pipeline
[Pipeline] node
Running on
Jenkins
in /var/jenkins_home/workspace/Test-Pipeline
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Example)
[Pipeline] sh
+ echo Hello Newman. Today is Friday.
Hello Newman. Today is Friday.
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
The call to @Library told Jenkins to load shared-library. It went to the git URL, looked for the default version and loaded it. This allowed the pipeline script to call a shared variable as its one and only build step.
Jenkins Shared Libraries
After learning how Shared Libraries are structured, you added one to a Jenkins server. Then you created a simple pipeline job. Finally, you modified your new job to use a Shared Library.
Jenkins Shared Libraries are a powerful tool for managing projects that share code and configuration. Now that you’ve seen how easy it is to get started with them, put them to work today! You can also learn how CloudBees can help you better manage Jenkins at scale.
This post was written by Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!).