31

I am considering to use Jenkins pipeline script recently, one question is that I don't figure out a smart to way to create internal reusable utils code, imagine, I have a common function helloworld which will be used by lots of pipeline jobs, so I hope to create a utils.jar can injected it into the job classpath.

I notice Jenkins have a similar concept with the global library, but my concern regarding this plugin:

Since it is a plugin, so we need to install/upgrade it through jenkins plugin manager, then it may require reboot to apply the change, this is not what I want to see since utils may change, add always, we hope it could be available immediately.

Secondly, it is official jenkins shared lib, I dont want to (Or they will not apply us) put private code into jenkins repo.

Any good idea?

StephenKing
  • 36,187
  • 11
  • 83
  • 112
Tim
  • 2,006
  • 2
  • 18
  • 25

4 Answers4

31

The Shared Libraries (docs) allows you to make your code accessible to all your pipeline scripts. You don't have to build a plugin for that and you don't have to restart Jenkins.

E.g. this is my library and this a Jenkinsfile that calls this common function.


EDIT (Feb 2017): The library can be accessed through Jenkins' internal Git server, or deployed through other means (e.g. via Chef) to the workflow-lib/ directory within the jenkins user's home directory. (still possible, but very unhandy).

The global library can be configured through the following means:

  • an @Library('github.com/...') annotation in the Jenkinsfile pointing to the URL of the shared library repo.
  • configured on the folder level of Jenkins jobs.
  • configured in Jenkins configuration as global library, with the advantage that the code is trusted, i.e., not subject to script security.

A mix of the first and last method would be a not explicitly loaded shared library that is then requested only using its name in the Jenkinsfile: @Library('mysharedlib').

StephenKing
  • 36,187
  • 11
  • 83
  • 112
10

Depending on how often you plan on reusing your code, you could also load a function (or a set of functions) as part of another pipeline.

{
  // ...your pipeline code...

  git 'http://urlToYourGit/projectContainingYourScript'
  pipeline = load 'global-functions.groovy'
  pipeline.helloworld()    // Call one of your defined function

  // ...some other pipeline code...
}

This solution might seems a little bit cumbersome compared to StephenKing's one but what I like about this solution is that my global functions are all commited to Git and anybody can easily modify them without (almost) any knowledge of Jenkins, just basics of Groovy.

In the Groovy script your are loading, make sure you add return this at the very end. This will allow you to make calls later. Otherwise when you set pipeline = load global-functions.groovy, the variable will be set to null.

lawnmowerlatte
  • 315
  • 3
  • 9
Pom12
  • 7,622
  • 5
  • 50
  • 69
  • We are updating the `$JENKINS_HOME/workflow-lib/` directory via Chef automatically from Github. You could do that also via Cron job. I also don't like the internal pseudo-git server of Jenkins. – StephenKing Aug 02 '16 at 15:18
  • 1
    remember to add `return this` at end of your shared groovy script. Look at this answer http://stackoverflow.com/questions/37800195/how-do-you-load-a-groovy-file-and-execute-it – Nayana Adassuriya Apr 30 '17 at 12:39
  • @Pom12 what's the URL for those using Github Enterprise? – Marcello DeSales Apr 25 '18 at 18:23
  • What do you mean ? My answer applies whether you are using Gitlab, Github or any Git repository management tool... – Pom12 Apr 26 '18 at 07:21
2

Here is the solution that we are currently using in order to re-use Jenkinsfile code:

node {

  curl_cmd = "curl -H 'Accept: application/vnd.github.v3.raw' -H 'Authorization: token ${env.GITHUB_TOKEN}' https://raw.githubusercontent.com/example/foobar/master/shared/Jenkinsfile > Jenkinsfile.t
  sh "${curl_cmd}"
  load 'Jenkinsfile.tmp'

}

I might be a bit ugly but it works realiably and in addition to that it also allows us to insert some repository specific code before or after the shared code.

sorin
  • 161,544
  • 178
  • 535
  • 806
  • As your `Jenkinsfile.tmp` script seems to be checked out to a Git repo, you could probably just use a basic git/scm checkout step to load your script instead of doing a manual curl. – Pom12 Aug 02 '16 at 11:50
  • If this would allow me to checkout a single file from the repository it would be great. But if this would checkout the entire repository it would clearly not be something to be desired as this repo can be really big. – sorin Aug 02 '16 at 15:47
  • In that case another possibility could be to checkout your `Jenkinsfile.tmp` Git repo as a freestyle Jenkins job that just does regular polling on your Git repo, and then load your Jenkins file from this other workspace. – Pom12 Aug 03 '16 at 07:52
0

I prefer creating a buildRepo() method that I invoke from repositories. Its signature is def call(givenConfig = [:]) so that it can also be invoked with parameters, like:

buildRepo([
  "npm": [
    "cypress": false
  ]
])

I keep parameters at an absolute minimum and try to rely on conventions rather than configuration.

I provide a default configuration which is also where I put documentation:

  def defaultConfig = [
    /**
      * The Jenkins node, or label, that will be allocated for this build.
      */
    "jenkinsNode": "BUILD",
    /**
      * All config specific to NPM repo type.
      */
    "npm": [
      /**
        * Whether or not to run Cypress tests, if there are any.
        */
      "cypress": true
    ]
  ]
  def effectiveConfig merge(defaultConfig, givenConfig)
  println "Configuration is documented here: https://whereverYouHos/getConfig.groovy"
  println "Default config: " + defaultConfig
  println "Given config: " + givenConfig
  println "Effective config: " + effectiveConfig

Using the effective configuration, and the content of the repository, I create a build plan.

...
derivedBuildPlan.npm.cypress = effectiveConfig.npm.cypress && packageJSON.devDependencies.cypress
...

The build plan is the input to some build methods like:

  node(buildPlan.jenkinsNode) {
    stage("Install") {
      sh "npm install"
    }
    stage("Build") {
      sh "npm run build"
    }
    if (buildPlan.npm.tslint) {
      stage("TSlint") {
        sh "npm run tslint"
      }
    }
    if (buildPlan.npm.eslint) {
      stage("ESlint") {
        sh "npm run eslint"
      }
    }
    if (buildPlan.npm.cypress) {
      stage("Cypress") {
        sh "npm run e2e:cypress"
      }
    }
  }

I wrote a blog post about this on Jenkins.io: https://www.jenkins.io/blog/2020/10/21/a-sustainable-pattern-with-shared-library/

Tomas Bjerre
  • 3,270
  • 22
  • 27