jenkins shared libraries workshop

117
Jenkins Shared Libraries Deep Dive Julien Pivotto (@roidelapluie) Day of Jenkins Oslo & Gothenburg June 2017

Upload: julien-pivotto

Post on 28-Jan-2018

5.659 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Jenkins Shared Libraries Workshop

Jenkins Shared Libraries Deep Dive

Julien Pivotto (@roidelapluie)

Day of Jenkins Oslo & Gothenburg

June 2017

Page 2: Jenkins Shared Libraries Workshop

whoamiJulien "roidelapluie" Pivotto

@roidelapluie

Sysadmin at inuits

Automation, monitoring, HA

Jenkins user for 5+ years

Page 3: Jenkins Shared Libraries Workshop

inuits

Page 4: Jenkins Shared Libraries Workshop

Jenkins Pipeline

Were you there this morning? :-)

One of the best things that happened toJenkins in 5 years

Page 5: Jenkins Shared Libraries Workshop

Pipeline limitsWhat when we build 10 projects who look thesame?

How to keep Jenkinsfiles in sync?

Split deployment from application code

What if we want to interact with the heart ofJenkins (advanced use cases)

Declarative Pipelines are limited by design

Page 6: Jenkins Shared Libraries Workshop

Shared Libraries ?

Why do we need that?

Page 7: Jenkins Shared Libraries Workshop

Why do we need that?

Put logic in Declarative Pipeline

DRY (e.g. microservices)

Scalability

Escape the Groovy Sandbox

Page 8: Jenkins Shared Libraries Workshop

What is a "Shared Library"?

Page 9: Jenkins Shared Libraries Workshop

What is a "Shared Library"?SCM repository (git, ...)

Groovy code

Pipeline steps

Resources (plain scripts, flat files, etc)

Easily reusable in your Pipeline jobs

Page 10: Jenkins Shared Libraries Workshop

The 4 featuresResources

Steps

Untrusted library

Trusted library

Page 11: Jenkins Shared Libraries Workshop

Now let's get prepared!!

Page 12: Jenkins Shared Libraries Workshop

Interrupt me at any time if youneed help or have questions!!

We will move forward when most of thepeople are at the same point as me

Page 13: Jenkins Shared Libraries Workshop

Our goals todayWe will not build real things (to have lowrequirements)

We will (maybe) not do the completeslidedeck... let's see!

Page 14: Jenkins Shared Libraries Workshop

RequirementsJenkins with the Git Plugin or Filesystem SCMplugin

2 repos (gitlab, github, filesystem...) + gitclient on your laptop (names: "trusted" and"untrusted")

Python 2 ("python" command)

Page 15: Jenkins Shared Libraries Workshop

Open your Jenkins instance

Page 16: Jenkins Shared Libraries Workshop

Make sure you have SCM reposyour Jenkins has access to

You can use the Git plugin or Filesystem SCMplugin

Install from:https://roidelapluie.be/uploads/filesystem_scm-1.21.hpi

Page 17: Jenkins Shared Libraries Workshop

Login to Jenkins (as admin)We login as admin because it is easier. Theworkshop will assume that.

Page 18: Jenkins Shared Libraries Workshop

Install the Pipeline Plugin

Page 19: Jenkins Shared Libraries Workshop

Download in Progress

Page 20: Jenkins Shared Libraries Workshop

Done!

Page 21: Jenkins Shared Libraries Workshop

Check plugins installation

Page 22: Jenkins Shared Libraries Workshop

Raise your hand if you haveproblems getting the plugins

installed

Page 23: Jenkins Shared Libraries Workshop

Create a folder

Page 24: Jenkins Shared Libraries Workshop
Page 25: Jenkins Shared Libraries Workshop

SaveDon't do anything with Shared Libraries at thatpoint, just save.

Page 26: Jenkins Shared Libraries Workshop

You should have the folder

Raise your hand if you don'thave the folder / have questions

Page 27: Jenkins Shared Libraries Workshop

Create a Pipeline Project in thefolder

Page 28: Jenkins Shared Libraries Workshop
Page 29: Jenkins Shared Libraries Workshop

Pipeline scriptpipeline { agent any stages {  stage('Clone') {   steps {       git url: \       'https://github.com/voxpupuli/puppetboard'   }  }  stage('Compile') {   steps {    sh """find . ­name '*.py' ­print0|    xargs ­0 ­L 1 python2 ­m py_compile"""   }  } }}

Page 30: Jenkins Shared Libraries Workshop

Run it!

Page 31: Jenkins Shared Libraries Workshop

It should run successfully

If not, raise your hand

Page 32: Jenkins Shared Libraries Workshop

Congratz!! We have a Pythonproject (vaguely) tested withinour Jenkins with a Declarative

Pipeline

Page 33: Jenkins Shared Libraries Workshop

Now let's see how resourceswork in shared libraries

Page 34: Jenkins Shared Libraries Workshop

Clone your (empty) sharedlibrary git repo on your laptop

$ git clone [email protected]:jenkinsws/untrusted01.git$ cd untrusted01

If you use Filesystem SCM, justcreate a empty directory

$ mkdir untrusted01$ cd untrusted01

Page 35: Jenkins Shared Libraries Workshop

Create a directory "resources"

This must be the exact name

$ mkdir resources

Then subdirecties

$ mkdir ­p resources/eu/inuits

Page 36: Jenkins Shared Libraries Workshop

What did we do?

Page 37: Jenkins Shared Libraries Workshop

What did we do?In the directories resources, we created a directoryfor the "inuits.eu" namespace: "eu/inuits".

Namespace for "example.com" would be"com/example".

You can nest that as you like:"com/example/web/design".

That's kind of a Java thing.

Page 38: Jenkins Shared Libraries Workshop

Let's create a file in resources/eu/inuits  called

 python.sh  with the following content:

find . ­name '*.py' ­print0|xargs ­0 ­L 1 python2 ­m py_compile

Page 39: Jenkins Shared Libraries Workshop

Let's commit and push the fileto the repo

$ git add resources/eu/inuits/python.sh$ git commit ­m 'add shared script python'$ git push

you should see

 * [new branch]      master ­> master

Raise your hand if you have problems

Page 40: Jenkins Shared Libraries Workshop

Wait, Shared Libraries need avars or src directory

I let you decide if it's a bug or a featureCreate an empty file  vars/noop.groovy 

$ mkdir vars$ touch vars/noop.groovy

(git only)

$ git add vars/noop.groovy$ git commit ­m 'add vars directory'$ git push

Page 41: Jenkins Shared Libraries Workshop

What did we do?

Page 42: Jenkins Shared Libraries Workshop

What did we do?We created our first shared pipeline!!!

Congratulations!!!!

Page 43: Jenkins Shared Libraries Workshop

Let's use it nowIn the folder

Page 44: Jenkins Shared Libraries Workshop

(git)

Page 45: Jenkins Shared Libraries Workshop

(filesystem)

Page 46: Jenkins Shared Libraries Workshop

Open your Pipeline config

Page 47: Jenkins Shared Libraries Workshop

Import the libraryAt the beginning of the pipeline script

@Library('pythonhelpers') _

Page 48: Jenkins Shared Libraries Workshop

Replace

    sh """find . ­name '*.py' ­print0|    xargs ­0 ­L 1 python2 ­m py_compile"""

with

    sh(libraryResource('eu/inuits/python.sh'))

Now, run the job

We should get the same result as before.

Page 49: Jenkins Shared Libraries Workshop

Job output

(filesystem SCM) (git)

Raise your hand if you don't have that

Page 50: Jenkins Shared Libraries Workshop

We have used our first sharedresource!! Congratz!!

Page 51: Jenkins Shared Libraries Workshop

Let's update our libraryChange  resources/eu/inuits/python.sh 

find . ­name '*.py' ­print0|xargs ­0 ­L 1 python2 ­m py_compile

by

find . ­name '*.py' ­print0|xargs ­0 ­t ­L 1 python2 ­m py_compile

(add -t in the second line)

Page 52: Jenkins Shared Libraries Workshop

Push that change$ git add resources/eu/inuits/python.sh$ git commit ­m 'make xargs more verbose'$ git push

Page 53: Jenkins Shared Libraries Workshop

Run the script againBefore:

Page 54: Jenkins Shared Libraries Workshop

Now you should see

Change in our script has been integrated in thebuild!! Imagine changing the script once applies toall the pipelines!

Raise your hand if youhave problems

Page 55: Jenkins Shared Libraries Workshop

let's go for something morepowerful

In the shared library git repo, create a file vars/compilePython.groovy  with

def call() {    sh """find . ­name '*.py' ­print0|    xargs ­0 ­t ­L 1 python2 ­m py_compile"""}

Note: you could reuse the libraryResource here.

Page 56: Jenkins Shared Libraries Workshop

Push the file$ git add vars/compilePython.groovy$ git commit ­m 'add a compilePython step'$ git push

Page 57: Jenkins Shared Libraries Workshop

In the Pipeline script

Replace

    sh(libraryResource('eu/inuits/python.sh'))

with

    compilePython()

Run, it should work!

Page 58: Jenkins Shared Libraries Workshop

Raise your hand if you needhelp

Congratz!! Our first shared step!

Page 59: Jenkins Shared Libraries Workshop

Let's add LOGICIn the shared library git repo, change vars/compilePython.groovy 

def call(){ if (fileExists("requirements.txt")){ sh "virtualenv venv" sh "venv/bin/pip install ­r requirements.txt" } sh """find . ­name '*.py' ­print0| xargs ­0 ­t ­L 1 python2 ­m py_compile"""}

Page 60: Jenkins Shared Libraries Workshop

Push the file$ git add vars/compilePython.groovy$ git commit ­m 'add vitualenv support'$ git push

Page 61: Jenkins Shared Libraries Workshop

Run the build ; should fail

Page 62: Jenkins Shared Libraries Workshop

Let's add a parameterIn the shared library git repo, change vars/compilePython.groovy 

def call(String directory = '.') { sh """find ${directory} ­name '*.py' ­print0| xargs ­0 ­t ­L 1 python2 ­m py_compile"""}

Push the file$ git add vars/compilePython.groovy$ git commit ­m 'add compilePython parameter'$ git push

Page 63: Jenkins Shared Libraries Workshop

Run the Pipeline again, itshould fail as before

Page 64: Jenkins Shared Libraries Workshop

In the Pipeline script

Replace

    compilePython()

with

    compilePython("puppetboard")    compilePython("test")

Run, it should work!

Page 65: Jenkins Shared Libraries Workshop

See the two steps

Page 66: Jenkins Shared Libraries Workshop

DocumentationIn the pipeline

Page 67: Jenkins Shared Libraries Workshop

In shared lib repo create vars/compilePython.txt 

This step compiles python files in the givendirectory

Push the file$ git add vars/compilePython.txt$ git commit ­m 'add compilePython doc'$ git push

Page 68: Jenkins Shared Libraries Workshop

Build once and check the docagain

\o/ Raise your hands if you have questions

Page 69: Jenkins Shared Libraries Workshop

NoteYou can remove our noop.groovy in the sharedlibrary

$ git rm vars/noop.groovy$ git commit ­m 'remove noop.groovy'$ git push

Page 70: Jenkins Shared Libraries Workshop

Let's do something awesomenow.

Remember that Declarative Pipeline?

We can do this as a "shared step".

Page 71: Jenkins Shared Libraries Workshop

Create  vars/pythonPipeline.groovy 

def call(String githubproject){ pipeline {  agent any  stages {   stage('Clone') {    steps {       git url: \       "https://github.com/${githubproject}"    }   }   stage('Compile') {    steps{     sh("ls")    }   }  } }}

Page 72: Jenkins Shared Libraries Workshop

Add, commit and push$ git add vars/pythonPipeline.groovy$ git commit ­m 'add a Python Pipeline step'$ git push

Page 73: Jenkins Shared Libraries Workshop

Make the library import implicitThat way you do not need to import it in yourPipeline script.

Page 74: Jenkins Shared Libraries Workshop

Change the Pipeline

pythonPipeline("voxpupuli/puppetboard")

Yes, that's allSave & run

Page 75: Jenkins Shared Libraries Workshop

What happened?We have put the COMPLETE declarativepipeline in a shared lib

The jenkins file is really small

It is parametrizable

Change to the shared lib can change a lot ofpipelines!

Page 76: Jenkins Shared Libraries Workshop

Improvement: groovy closure  vars/pythonPipeline.groovy 

def call(body) { def config = [:] body.resolveStrategy = Closure.DELEGATE_FIRST body.delegate = config body() pipeline {  agent any  stages {   stage('Clone') {    steps {      git url: \        "https://github.com/${config.githubproject}"    }   }   stage('Compile') {    steps{     sh("ls")    }   }  } }}

Page 77: Jenkins Shared Libraries Workshop

Change the Pipeline

pythonPipeline {  githubproject = "voxpupuli/puppetboard"}

Save & run

Page 78: Jenkins Shared Libraries Workshop

Let's get groovyWe will now recreate the same step but as "plaingroovy".The real power will come later with "trusted"library.

Page 79: Jenkins Shared Libraries Workshop

Reset the Pipeline scriptNow we will edit the job's Pipeline and revert ourlatest change. Here is the new script (next slide).

Page 80: Jenkins Shared Libraries Workshop

pipeline { agent any stages {  stage('Clone') {   steps {    git url: \     "https://github.com/voxpupuli/puppetboard"   }  }  stage('Compile') {   steps{    compilePython("puppetboard")    compilePython("test")   }  } }}

Save and run

Page 81: Jenkins Shared Libraries Workshop

In Pipeline dir, create src/eu/inuits 

$ mkdir ­p src/eu/inuits

once again, "eu/inuits" means "inuits.eu"

Page 82: Jenkins Shared Libraries Workshop

Create src/eu/inuits/PythonCompiler.groovy 

package eu.inuitsclass PythonCompiler { static def compileDirectory(script, directory) {  script.sh """find ${directory} ­name '*.py' \  ­print0| xargs ­0 ­t ­L 1 python2 ­m py_compile""" }}

Page 83: Jenkins Shared Libraries Workshop

Change  vars/compilePython.groovy 

import static eu.inuits.PythonCompiler.*

def call(String directory = '.') {    echo("Compiling ${directory}")    compileDirectory(this, directory)}

Page 84: Jenkins Shared Libraries Workshop

Push the files$ git add src/eu/inuits/PythonCompiler.groovy$ git add vars/compilePython.groovy$ git commit ­m 'add PythonCompiler class'$ git push

Page 85: Jenkins Shared Libraries Workshop

Run, it should work :-)

Raise your hand if it does not

Page 86: Jenkins Shared Libraries Workshop

What happened?We created a plain groovy library and we called itfrom within a (shared) step

If you do not use Declarative Pipeline you can do itin your Jenkinsfile directly

Page 87: Jenkins Shared Libraries Workshop

Keeping StateCreate a  vars/compileFile.groovy  in yourshared lib.

import eu.inuits.FileCompiler

def call(String project) {    fc = new FileCompiler(this, project)    fc.analyze('requirements.txt')    fc.analyze('setup.py')}

Page 88: Jenkins Shared Libraries Workshop

Implement it! src/eu/inuits/FileCompiler.groovy 

package eu.inuitsclass FileCompiler implements Serializable {  def script  def project  FileCompiler(script, String project){    this.script = script    this.project = project    this.script.echo("Compiling ${project}")  }  def analyze(String file) {    this.script.echo("${project}/${file}")    this.script.sh("cat ${file}")  }}

Page 89: Jenkins Shared Libraries Workshop

Add and push

(you know the drill)

$ git add src/eu/inuits/FileCompiler.groovy$ git add vars/compileFile.groovy$ git commit ­m "Add compileFile step"$ git push

Page 90: Jenkins Shared Libraries Workshop

In the Pipeline script

Replace

    compilePython("puppetboard")    compilePython("test")

with

    compileFile("puppetboard")

Run it

Page 91: Jenkins Shared Libraries Workshop

ooops

We hit the security sandbox

In the Pipeline script

& run again! Should work!

Page 92: Jenkins Shared Libraries Workshop

What happened? We have a "state" (variableproject). It can be changed on the fly in the class,etc...

Page 93: Jenkins Shared Libraries Workshop

Are you still following?

Page 94: Jenkins Shared Libraries Workshop

Global Trusted LibraryNow let's look into another kind of global library:the Trusted Libraries.

Those libraries are defined in the global config,and run unsandboxed.

Page 95: Jenkins Shared Libraries Workshop

Clone a new git repo for yourtrusted library

$ git clone [email protected]:jenkinsws/trusted01.git$ cd trusted01

Page 96: Jenkins Shared Libraries Workshop

Add the Library as a Global SharedLibrary

Same as we did before but in the global config

Page 97: Jenkins Shared Libraries Workshop
Page 98: Jenkins Shared Libraries Workshop

Global LibrariesUsed with caution: can do everything

Unsandboxed

Page 99: Jenkins Shared Libraries Workshop

Let's go!

create  src/eu/inuits/Admin.groovy 

package eu.inuitsimport com.cloudbees.groovy.cps.NonCPSclass Admin implements Serializable { def seedFullName = "seed" def script  Admin(script) {  this.currentJobValidation(script)  this.script = script }

 @NonCPS void currentJobValidation(script){  def name = \    script.currentBuild.rawBuild.project.fullName  assert name == this.seedFullName : "DENIED" }}

Page 100: Jenkins Shared Libraries Workshop

Add and commit file$ git add src/eu/inuits/Admin.groovy$ git commit ­m 'add admin lib'$ git push

Raise your hand if you need help!

Page 101: Jenkins Shared Libraries Workshop

Create a new job in the top

Page 102: Jenkins Shared Libraries Workshop

Set the following Pipeline Script@Library('globalhelp') _import eu.inuits.Admin

node {    adm = new Admin(this)}

And save.

Page 103: Jenkins Shared Libraries Workshop

Run the Pipeline

Page 104: Jenkins Shared Libraries Workshop

Rename the job

Page 105: Jenkins Shared Libraries Workshop

Run the renamed job

Our class can only be called from within a jobnamed seed.

Page 106: Jenkins Shared Libraries Workshop

Rename the job back

Page 107: Jenkins Shared Libraries Workshop

Who needs help at this point?

Page 108: Jenkins Shared Libraries Workshop

Add a function to the trusted lib src/eu/inuits/Admin.groovy 

after Package, before Class

import jenkins.model.Jenkins

then, in body

/**  * Set the description of a folder  *  * @param folder A jenkins project name  * @param description New description  */@NonCPSvoid setFolderDescription(folder, description) {    def f = Jenkins.instance.getItemByFullName(folder)    f.setDescription(description)}

Page 109: Jenkins Shared Libraries Workshop

Add, Commit and Push$ git add src/eu/inuits/Admin.groovy$ git commit ­m 'add setFolderDescription'$ git push

Page 110: Jenkins Shared Libraries Workshop

Change seed job Pipeline Script@Library('globalhelp') _import eu.inuits.Admin

node {  adm = new Admin(this)  adm.setFolderDescription("python­projects",  "Description set withing Global Pipeline")}

Save and Run

Page 111: Jenkins Shared Libraries Workshop

Check the result

Raise your hand if you have a different result

Page 112: Jenkins Shared Libraries Workshop

@GrabJust an example fromhttps://jenkins.io/doc/book/pipeline/shared-libraries/

@Grab('org.apache.commons:commons­math3:3.4.1')import org.apache.commons.math3.primes.Primesvoid parallelize(int count) {  if (!Primes.isPrime(count)) {    error "${count} was not prime"  }  // Ã¢â‚¬Â¦}

Page 113: Jenkins Shared Libraries Workshop

Global LibsDo not require sandbox escape at the job level

No sandbox exceptions needed

@Grab

Freedom!

Page 114: Jenkins Shared Libraries Workshop

Advice for safe global libCheck what you return: void, string.. do notreturn objects like Jenkins Job

Check the name of the job

No "implicit loading"

No "version overwrite"

Page 115: Jenkins Shared Libraries Workshop

ConclusionsShared Libraries keep your Jenkinsfiles small

They allow you to scale your Pipelines

Change the build process without comitting toall the repos

No need for tools like modulesync etc...

Page 116: Jenkins Shared Libraries Workshop

For this talk, thanks Jenkins, Praqma, Inuits.

Used for this talk: DigitalOcean, github, packer,consul, terraform, traefik, let's encrypt,gibby/letsencrypt-dns-digitalocean.

Page 117: Jenkins Shared Libraries Workshop

Julien Pivottoroidelapluie

[email protected]

Inuitshttps://[email protected]

Contact