Monday, September 2, 2019

Jenkins pipeline for a monorepo git


What is a mono repo?

This means you have a single GIT repository for multiple software services.
For this example, lets assume that you have a folder for each service under the root folder of the git repository: service1, service2,  service3.
Each service build should create a docker image, and push it to a docker repository.


What is Jenkins pipeline?

Jenkins pipeline is a method to implement CI/CD using a groovy like script.
See Jenkins documents for more details.


So, what is our purpose?

Whenever a push is made to the GIT, we want a build job in Jenkins to run.
The job should build only the docker images whose source was changed.
Then it should push the docker images, using a tag name based on the GIT branch that was updated.
We want to build only the images whose source was updated since the last Jenkins successful build.

How to create this?

In Jenkins, create new job, and select pipeline.


The job can be triggered to start using a Jenkins webhook, which means that any push to the central GIT repository (such as github and bitbucket) notifies Jenkins of a change.
Alternatively, the job can use polling to check when a change was pushed.

Configure the job to

  • Use "Pipeline script from SCM"
  • Specify the GIT repo details
  • Specify "Jenkinsfile" as the "script path"
  • Use branch specifier of **/**



Now, add Jenkinsfile in the root of your GIT repo.

// no def keyword here to make this global variable
imagesNames = [
    "service1",
    "service2",
    "service3"
]

def BuildImage(imageName){
    stage ("${imageName}") {
        dir("images/${imageName}") {
            def branch = env.GIT_BRANCH
            // replace the IP here with your docker repository IP
            def tagPrefix = "10.1.2.3:5000/${imageName}/${branch}:"
            def tagBuildName = "${tagPrefix}${env.BUILD_NUMBER}"
            def tagLatestName = "${tagPrefix}latest"
            // we assume each service has a build.sh script that receives the docker tag name to build
            sh "./build.sh -tag ${tagBuildName}"
            sh "docker tag ${tagBuildName} ${tagLatestName}"
            sh "docker push ${tagBuildName}"
            sh "docker push ${tagLatestName}"
        }
    }
}

def BuildByChanges(){
    def buildImages = [:]
    def gitDiff = sh(script: "git diff --name-only ${env.GIT_COMMIT} ${env.GIT_PREVIOUS_SUCCESSFUL_COMMIT}", returnStdout: true)

    echo "git diff is:\n${gitDiff}"
    gitDiff.readLines().each { line ->
  imagesNames.each { imageName ->
   if (line.startsWith(imageName)) {
    buildImages[imageName] = true
   }
  }
    }
    echo "building images: ${buildImages}"
    buildImages.each { imageName, build ->
        BuildImage(imageName)
    }
}

def IsBuildAll(){
    if (env.GIT_PREVIOUS_SUCCESSFUL_COMMIT == null){
        echo "No previous successful build on this branch, performing full build"
        return true
    }
    if (env.GIT_BRANCH == 'dev'){
        echo "dev branch, performing full build"
        return true
    }
    if (env.GIT_BRANCH == 'master'){
        echo "The master branch, performing full build"
        return true
    }
    return false
}

pipeline {
    agent any

    stages{
        stage("prepare") {
            steps {
                script {
                    echo "Branch ${env.GIT_BRANCH}"
                    if (IsBuildAll()){
                        imagesNames.each { imageName ->
                            BuildImage(imageName)
                        }
                    } else {
                        BuildByChanges()
                    }
                 }
            }
        }
    }
}

That's it, your build is ready.
Push a change to one of the services, and see the results in Jenkins.


No comments:

Post a Comment