Jenkins Shared Global Library

Github sends the full branch and committed information with the notifyCommit it sends to Jenkins. Jenkins passes just the repo url through to the git plugin. The plugin then does calculations to find the correct commit id to build. If Jenkins has state, and has built this job in the past, and there’s a commit history, this works. If Jenkins is stateless and jobs are configured on the fly, it fails. The default behavior when there is no build history and the Branch Specifier configuration is wide open (“**”), is to create a list of all the branches, take the alphabetically first branch, latest commit, and build that. That is an unusable default for CICD systems.

Jenkins does two checkouts. The first checkout grabs the Jenkinsfile, and the second checkout is the call in the Jenkinsfile to go get the code to build.

The CICD Discover plugin creates a brand new pipeline job from a template config.xml file and the git url sent through to the git plugin. It checks for the latest commit hash for that repo, and then restricts the first build call to that commit hash. It gets the Jenkinsfile from that commit hash, initiates the build, and returns the config.xml Branch Specifier to “**” (wide open). This guarantees we get a predictable Jenkinsfile.

The next piece needed was a replacement for the “checkout scm” internal module – I needed a piece of code for use in the Jenkinsfile, a checkout that grabbed the latest commit to the repo and built that. Ideally we’ll go find and capture the full GitHub notifyCommit message at some point, but right now the CICD Discover listens at the same point as the git plugin – it listens along side, and that plugin never actually sees the full git notifyCommit packet.

The first pieces are the Pipeline GitHub Library Plugin and the Pipeline Shared Groovy Libraries Plugin. Configured a library repo. Something like

 

 

 

The structure looks like Jenkins Shared Library Structure and Doc

Then in Jenkinsfile you have, at the top:

 

@Library('jenkins-nebula-core-lib') _
import org.nebula.Poc
import org.nebula.Whichawsregion
import org.nebula.Durationtime

 

I created a checkout library file called cicdcheckoutspecific.groovy, and then extensions of that library:

 

// src/org/nebula/Cicdcheckoutspecific.groovy
package org.nebula

// arg is a two-part string
// 'hash:' or 'branch:'
// default is null
def cicdCheckoutSpecific(String gittarget = null) {
  //  here's what jenkins checkout SCM does to work through the repo
  //  git init /var/lib/jenkins/jobs/example-cicd-directory_Test/workspace@script
  //  git fetch --tags --progress \
  //      ssh://git@git.ouroath.com/dmunsinger15/example-cicd-directory.git +refs/heads/*:refs/remotes/origin/*
  //  git config remote.origin.url ssh://git@git.ouroath.com/dmunsinger15/example-cicd-directory.git # timeout=10
  //  git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
  //  git config remote.origin.url ssh://git@git.ouroath.com/dmunsinger15/example-cicd-directory.git # timeout=10
  //  git fetch --tags --progress \
  //      ssh://git@git.ouroath.com/dmunsinger15/example-cicd-directory.git +refs/heads/*:refs/remotes/origin/*
  //  git show-ref --tags -d # timeout=10
  //  git config core.sparsecheckout # timeout=10
  //  git checkout -f 1f5dbd4f039e881763c789d4e5b206368de6a21

  // if this is FIRST BUILD, directly look for and checkout latest commithash regradless of branch
  // to accomodate CICD Discover plugin /build first build api call
  // set up tmp environs

  // create the tmp repo
  sh(script:"""
      rm -rf ${WORKSPACE}/ghe_tmp
      git init ${WORKSPACE}/ghe_tmp
      cd ${WORKSPACE}/ghe_tmp
      git fetch --tags --progress ${GIT_URL} +refs/heads/*:refs/remotes/origin/*
      git config remote.origin.url ${GIT_URL}
    """, returnStdout:true)

  if (gittarget == null) {
    // find in that tmp env latest commit hash
    def my_hash = sh(
      script:"""
        cd ${WORKSPACE}/ghe_tmp
        git log --all -n 1 | egrep ^commit | awk '{ print \$2 }'
        """, returnStdout:true).trim()

    // checkout that commit hash
    // this one works, so see if we can drop the credientials and have
    // it default back or if that breaks this again...
    def scmVars = checkout([$class:'GitSCM', branches:[[name:"${my_hash}"]],
                           doGenerateSubmoduleConfigurations:false,
                           extensions:[[$class:'LocalBranch', localBranch:'**']],
                           submoduleCfg:[],
                           userRemoteConfigs:[[credentialsId:'c0e21fda-697c-433c-8f1f-74e9f018b0aa',
                                               url:"${GIT_URL}"]]])
    env.GIT_COMMIT = scmVars.GIT_COMMIT
    env.GIT_BRANCH = scmVars.GIT_BRANCH
    def info = "found 1st Build:  ${my_hash}"
    return info
  } else {
    // is this a hash or branch?
    if (gittarget.contains("hash:")) {
      //split out hash value
      def myrawhash = gittarget.split(":")
      def myspecifichash = myrawhash[1]
      // do the checkout targetting the hash instead of latest
      def scmVars = checkout([$class:'GitSCM', branches:[[name:"${myspecifichash}"]],
                             doGenerateSubmoduleConfigurations:false,
                             extensions:[[$class:'LocalBranch', localBranch:'**']],
                             submoduleCfg:[],
                             userRemoteConfigs:[[credentialsId:'c0e21fda-697c-433c-8f1f-74e9f018b0aa',
                                                 url:"${GIT_URL}"]]])
      env.GIT_COMMIT = scmVars.GIT_COMMIT
      env.GIT_BRANCH = scmVars.GIT_BRANCH
      def info = "checked out ${myspecifichash}"
      return info
    } else if (gittarget.contains("branch:")) {
      // split out branch value
      def myrawbranch = gittarget.split(":")
      def myspecificbranch = myrawbranch[1]
      // do the checkout targetting the hash instead of latest
      def scmVars = checkout([$class:'GitSCM', branches:[[name:"${myspecificbranch}"]],
                             doGenerateSubmoduleConfigurations:false,
                             extensions:[[$class:'LocalBranch', localBranch:"${myspecificbranch}"]],
                             submoduleCfg:[],
                             userRemoteConfigs:[[credentialsId:'c0e21fda-697c-433c-8f1f-74e9f018b0aa',
                                                 url:"${GIT_URL}"]]])
      env.GIT_COMMIT = scmVars.GIT_COMMIT
      env.GIT_BRANCH = scmVars.GIT_BRANCH
      def info = "found branch in gittarget: ${myspecificbranch}"
      return info
    }
  }
}

 

This then goes in the checkout block of the Jenkinsfile something like:

 

        stage('Checkout') {
            steps {
                // working through checkout SCM rework
                script {
                    //checkout scm
                    echo "*** calling cicdCheckout, no args ***"
                    def b = new org.nebula.Cicdcheckoutspecific()
                    RESULT = b.cicdCheckoutSpecific()
                    echo "${RESULT}"
                }
            }
        }

 

Thus the “throw it up in the air and pick the alphabetical first branch” behavior gets bypassed.

— doug