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