Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing parameters from Jenkinsfile to a shared library

I have several components(code projects with their own Bitbucket repositories) and each of them has a Jenkinsfile as follows:

properties([parameters([string(defaultValue: "", description: "List of components", name: 'componentsToUpdate'),
                        string(defaultValue: "refs%2Fheads%2Fproject%2Fintegration", description: "BuildInfo CommitID", name: 'commitId'),
                        string(defaultValue: "", description: "Tag to release, e.g. 1.1.0-integration", name: 'releaseTag'),
                        string(defaultValue: "", description: "Forked buildInfo repo. Be aware right commit ID!!!", name: 'fork')]),
                        [$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '7', numToKeepStr: '5']],
                        disableConcurrentBuilds()])

@Library('jenkins-shared-stages')

import mergePipeline
import releasePipeline
import ripplePipeline
import componentPipeline


def branchName = env.BRANCH_NAME
def rewriteDependencies = ""
def returnValue = null
def forkedRepo = params.fork
def buildInfoCommitId = params.commitId
def tagToRelease = params.releaseTag
println "buildInfoCommitId: " + buildInfoCommitId
if(params.componentsToUpdate) {
    rewriteDependencies = params.componentsToUpdate
}

if (branchName == "project/integration") {
    mergePipeline {
    }
} else if (branchName == 'master') {
    releasePipeline {
        releaseTag = tagToRelease
    }
} else {
    returnValue = componentPipeline {
        componentsToUpdate = rewriteDependencies
        commitId = buildInfoCommitId
        runOnForkedRepo = forkedRepo
    }

    rewriteDependencies = rewriteDependencies.isEmpty() ? returnValue : rewriteDependencies + "," + returnValue
    println "WHAT is rewriteDependencies? " + rewriteDependencies
    println "The return value: " + returnValue
    ripplePipeline {
        commitId = buildInfoCommitId
        componentName = returnValue
        runOnForkedRepo = forkedRepo
        componentsToUpdate = rewriteDependencies
    }
}

Need to use a 'wrapper' pipeline, say, wrapperPipeline.groovy:

import mergePipeline
import releasePipeline
import ripplePipeline
import componentPipeline
import org.slf4j.Logger
import org.slf4j.LoggerFactory

def call(body) {

    final Logger logger = LoggerFactory.getLogger(wrapperPipeline)

    def config = [:]
    body.resolveStrategy = Closure.DELEGATE_FIRST
    body.delegate = config
    body()

    // Assuming we have multibranch pipeline job or defined branch name in the env
    def branchName = env.BRANCH_NAME
    // There is a bug in the Jenkins it will pass a string "null" as a gradle build parameter instead of NULL object if there is
    // empty parameter has been passed!!!
    def rewriteDependencies = ""
    def returnValue = null
    def forkedRepo = config.runOnForkedRepo
    def buildInfoCommitId = config.commitId
    def tagToRelease = config.releaseTag

    def globalVars = new se.GlobalVars()
    def notifyHandler = new se.NotifyHandler()

    node(globalVars.getAgent('buildAgent')) {
        def PIPELINE_NAME = "wrapperPipeline"

        try {
            logger.info("The buildInfoCommitId is {}", buildInfoCommitId)
            logger.info("Branch name: {}", branchName)

            println "buildInfoCommitId:  "+buildInfoCommitId
            println"Branch name: "+branchName

            if (config.componentsToUpdate) {
                rewriteDependencies = config.componentsToUpdate
            }

    // keep the same integration pipeline for the master branch for now
            if (branchName == "project/integration") {
                logger.info("Invoking mergePipeline")
                println "Invoking mergePipeline"
                mergePipeline {
                }
            } else if (branchName == 'master') {
                logger.info("Invoking releasePipeline")
                println "Invoking releasePipeline"
                releasePipeline {
                    releaseTag = tagToRelease
                }
            } else {
                logger.info("Invoking componentPipeline")
                println "Invoking componentPipeline"

                returnValue = componentPipeline {
                    componentsToUpdate = rewriteDependencies
                    commitId = buildInfoCommitId
                    runOnForkedRepo = forkedRepo
                }
                logger.info("Component pipeline has returned {}", returnValue)
                println "Component pipeline has returned"+returnValue

                // We need to provide new version of the component to the Ripple builds
                rewriteDependencies = rewriteDependencies.isEmpty() ? returnValue : rewriteDependencies + "," + returnValue
                logger.info("rewriteDependencies: {}", rewriteDependencies)
                println "The return value: " + returnValue
                ripplePipeline {
                    commitId = buildInfoCommitId
                    componentName = returnValue
                    runOnForkedRepo = forkedRepo
                    componentsToUpdate = rewriteDependencies
                }
            }
        }
        catch (err) {
            def build_status = "Exception ${err.message} in build ${env.BUILD_ID}"
            logger.error(build_status,err)
            notifyHandler.NotifyFail(build_status, PIPELINE_NAME)

            throw err
        }

    }
}

The modified Jenkinsfile:

properties([parameters([string(defaultValue: "", description: "List of components", name: 'componentsToUpdate'),
                        string(defaultValue: "refs%2Fheads%2Fproject%2Fintegration", description: "BuildInfo CommitID", name: 'commitId'),
                        string(defaultValue: "", description: "Tag to release, e.g. 1.1.0-integration", name: 'releaseTag'),
                        string(defaultValue: "", description: "Forked buildInfo repo. Be aware right commit ID!!!", name: 'fork')]),
                        [$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '7', numToKeepStr: '5']],
                        disableConcurrentBuilds()])

@Library('jenkins-shared-stages@integration/CICD-959-wrapper-pipeline-for-the-jenkinsfile') _

import wrapperPipeline

wrapperPipeline{}

Now, I suspect that the params object(the properties from the Jenkinsfile) is not populated correctly. For example

def buildInfoCommitId = config.commitId
.
.
.
println "buildInfoCommitId:  "+buildInfoCommitId

prints null.

How do I invoke the wrapperPipeline correctly?

Note: I am new to both Jenkins pipelines and Groovy :)

like image 542
Kaliyug Antagonist Avatar asked Jan 10 '19 08:01

Kaliyug Antagonist


1 Answers

Because those are Jenkins Parameters, they are not in the config object.

You will access commitId as params.commitId

If you had something within the closure when you call wrapperPipeline(), then those would be in the config object. e.g.

wrapperPipeline({
    param="value"
})

then config.param would result in "value"

However, as a word of advice, I recommend avoiding using a closure when calling libs stored under vars/ in the shared library. See http://groovy-lang.org/closures.html for what closures are. The crux of it is, they are fairly complicated and can introduce some issues if you end up trying to pass in dynamic variables due to when the closure is instantiated. (They have their place but for simple things, I think avoiding is better)

I'd recommend instead, implementing a helper function that will allow you use maps OR closures for calling shared libs.

add a shared library called buildConfig under your src path:

package net.my.jenkins.workflow
import com.cloudbees.groovy.cps.NonCPS

class BuildConfig implements Serializable {
    static Map resolve(def body = [:]) {

        Map config = [:]
        config = body
        if (body in Map) {
            config = body
        } else if (body in Closure) {
            body.resolveStrategy = Closure.DELEGATE_FIRST
            body.delegate = config
            body()
        } else {
            throw  new Exception(sprintf("Unsupported build config type:%s", [config.getClass()]))
        }
        return config
    }
}

And then in your shared lib under vars/ start with

import net.my.jenkins.workflow.BuildConfig

def call(def body = [:]) {
    // evaluate the body block, and collect configuration into the object
    config = BuildConfig.resolve(body)

This then allows you to use Maps which removes the complexity, so you could for instance (not that you would since you would just use params.commitId) re-assign it.

wrapperPipeline ([
    "commitId": params.commitId,
])

Which means again config.commitId now has the value of params.commitId

Let me know if you need more detail.

TL;DR - You should be using params object, because you have parameters defined. If you did start passing in arguments via the shared lib call, I would use a map over a closure. (requires some minimal implementation)

like image 55
metalisticpain Avatar answered Oct 17 '22 18:10

metalisticpain