I just simply want to pass the repo name cloud-nates in shared pipeline, so i've passed the parameter deployName from jenkinsfile to shared library. below is the Jenkinsfile
#Library("minePipelines#auto") _
if (env.BRANCH_NAME in ["auto", "stage"]) {
reloadDeploy {
deployName = "cloud-nates"
}
}
And below is the shared-pipeline reloadDeploy.groovy code :
def call(body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
properties([disableConcurrentBuilds()])
node("ops") {
timeout(unit: 'SECONDS', time: 60) {
stage("Reload Deployment") {
echo params.deployName
}
}
}
}
This prints null in console o/p. i've googled it but no luck :(
please feel free to ask any doubts.
I'm trying to define custom DSL refer https://www.jenkins.io/doc/book/pipeline/shared-libraries/#defining-custom-steps
it seems work if just define simple command in {}
but failed when use complicated command
(root)
+- vars
| +- shareL.groovy
| +- xxx.groovy
| +- monitorStep.groovy
shareL.groovy
def install(){
print "test install"
}
def checkout(){
print "test checkout"
}
monitorStep.groovy
def call(body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
// This is where the magic happens - put your pipeline snippets in here, get variables from config.
script {
def status
def failed_cause=''
try{
body()
} catch(e){
status = 'fail'
failed_cause = "${e}"
throw e
}finally {
def myDataMap = [:]
myDataMap['stage'] = STAGE_NAME
myDataMap['status'] = status
myDataMap['failed_cause'] = failed_cause
influxDbPublisher selectedTarget: 'myTest', measurementName: 'myTestTable',customData: myDataMap
}
}
}
Jenkinsfile
#!groovy
#Library('myShareLibray#') _
pipeline {
stages{
stage('Checkout') {
steps {
script {
monitorStep{
shareL.checkout()
}
}
}
}
stage('Install') {
steps {
script {
monitorStep{
docker.image("node").inside(){
shareL.install()
}
}
}
}
}
}
}
first stage failed with
java.lang.NullPointerException: Cannot invoke method checkout() on null object
second stage failed with
java.lang.NullPointerException: Cannot invoke method image() on null object
The problem is that the closure cannot find the name shareL which should be accessible in the closure's delegate, which is in our case the map config.
You need to redeclare the map to expose the name shareL and additionally a second name install which must be invokable.
The solution is to rewrite the map like this:
def config = [ shareL : [install: { println "Inside the map" }] ]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
Then when you call body() it will find shareL.install() but this will not point to the call() method in the shareL.groovy but to the property in the map.
I have a shared Jenkins library that has my pipeline for Jenkinsfile. The library is structured as follows:
myPipeline.groovy file
def call(body) {
def params= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = params
body()
pipeline {
// My entire pipeline is here
// Demo stage
stage("Something"){
steps{
script{
projectName = params.name
}
}
}
}
}
And my Jenkinsfile is as follows:
Jenkinsfile
#Library("some-shared-lib") _
myPipeline{
name = "Some name"
}
Now, I would like to replace "Some name" string with "env.JOB_NAME" command. Normally in Jenkinsfile, I would use name = "${env.JOB_NAME}" to get the info, but because I am using my shared library instead, it failed to work. Error message is as follows:
java.lang.NullPointerException: Cannot get property 'JOB_NAME' on null object
I tried to play around with brackets and other notation but never got it to work. I think that I incorrectly pass a parameter. I would like Jenkinsfile to assign "${env.JOB_NAME}" to projectName variable, once library runs the pipeline that I am calling (via myPipeline{} command)
You can do like this in myPipeline.groovy:
def call(body) {
def params= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = params
body()
pipeline {
// My entire pipeline is here
// Demo stage
stage("Something"){
steps{
script{
projectName = "${env.JOB_NAME}"
}
}
}
}
}
I have this template:
def call(body) {
def pipelineParams= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = pipelineParams
body()
pipeline {
agent any
....
stages {
stage('My stages') {
steps {
script {
pipelineParams.stagesParams.each { k, v ->
stage("$k") {
$v
}
}
}
}
}
}
post { ... }
}
}
Then I use the template in a pipeline:
#Library('pipeline-library') _
pipelineTemplateBasic {
stagesParams = [
'First stage': sh "do something...",
'Second stage': myCustomCommand("foo","bar")
]
}
In the stagesParams I pass the instances of my command (sh and myCustomCommand) and they land in the template as $v. How can I then execute them? Some sort of InvokeMethod($v)?
At the moment I am getting this error:
org.jenkinsci.plugins.workflow.steps.MissingContextVariableException: Required context class hudson.FilePath is missing
Perhaps you forgot to surround the code with a step that provides this, such as: node
The problem of using node is that it doesn't work in situations like parallel:
parallelStages = [:]
v.each { k2, v2 ->
parallelStages["$k2"] = {
// node {
stage("$k2") {
notifySlackStartStage()
$v2
checkLog()
}
// }
}
}
If you want to execute sh step provided with a map, you need to store map values as closures, e.g.
#Library('pipeline-library') _
pipelineTemplateBasic {
stagesParams = [
'First stage': {
sh "do something..."
}
'Second stage': {
myCustomCommand("foo","bar")
}
]
}
Then in the script part of your pipeline stage you will need to execute the closure, but also set the delegate and delegation strategy to the workflow script, e.g.
script {
pipelineParams.stagesParams.each { k, v ->
stage("$k") {
v.resolveStrategy = Closure.DELEGATE_FIRST
v.delegate = this
v.call()
}
}
}
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 :)
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)