Understanding groovy code in jenkins file - jenkins

I'm absolutely new to Groovy and Jenskins, please ignore if question sounds noob. Following is a code snippet from a jenkins file containing groovy code.
def boolean hasChanged(String searchText) {
return sh(
returnStatus: true,
script: "git diff --name-only ${GIT_PREVIOUS_COMMIT} ${GIT_COMMIT} | grep \"${searchText}\""
) == 0
}
Questions:
Is the above snippet is function/method written in groovy?
what does return sh do?
Per my understanding script: "git diff --name-only ${GIT_PREVIOUS_COMMIT} ${GIT_COMMIT} | grep \"${searchText}\"" the output of grep \"${searchText}\"" is fed into it diff --name-only ${GIT_PREVIOUS_COMMIT} ${GIT_COMMIT}, is the understanding correct?
Please assist.

It is looks like a Groovy with Jenkins plugins
(sh)
Here I Added comments to explain this code.
// hasChanged method return boolean value
def boolean hasChanged(String searchText) {
// Insted of
// def shResult = sh(...); return shResult
// the sh results is returned
return sh(
// Preform the sh script and return the script exist code
returnStatus: true,
script: "git diff --name-only ${GIT_PREVIOUS_COMMIT} ${GIT_COMMIT} | grep \"${searchText}\""
) == 0 // check script exist code status
}
The output of git diff is piped to grep command that searches for given text in the git diff output

Yes
In this case, the entire Groovy function returns True if grep finds ${searchText} in the output of command git diff --name-only ${GIT_PREVIOUS_COMMIT} ${GIT_COMMIT}, or else False.

Related

Script in Jenkins file giving exit code 1

I want to get a line from a file in my workspace. I am using this script :
stage('Test') {
steps {
script {
outputJenkins = 'output-jenkins.log'
sh "cd invoker && mvn clean install && mvn exec:java -Dexec.mainClass=\"com.JenkinsRunner\" -Dexec.args=\"qal ${GIT_COMMIT_HASH}\" > ../${outputJenkins}"
logFile = readFile(outputJenkins)
echo logFile
adminRepoLogLine = sh "echo logFile | grep \"Admin repo url is :::\""
echo adminRepoLogLine
}
}
}
But I am getting this error:
+ echo logFile
+ grep Admin repo url is :::
script returned exit code 1
The script works fine in my shell when I try it locally. Are there any contains around doing it in a JenkinsFile?
If we apply various fixes and improvements to the code in the question to achieve the desired functionality, then it will succeed:
stage('Test') {
steps {
script {
dir('invoker') {
sh(label: 'Maven Clean Install', script: 'mvn clean install')
// assign maven output to variable
String output = sh(label: 'Maven Git Log', script: "mvn exec:java -Dexec.mainClass=\"com.JenkinsRunner\" -Dexec.args=\"qal ${GIT_COMMIT_HASH}\"", returnStdout: true)
}
// assign regex return to variable
def adminRepoLogLine = output =~ /(.*Admin repo url is :::.*)/
// print extracted string from return
print adminRepoLogLine[0][1]
}
}
}
Note that GIT_COMMIT_HASH is neither an intrinsic Jenkins environment variable, nor defined in the pipeline code in the question, so it will need to be defined at Pipeline scope elsewhere in your code.
This is because the string literal logFile does not contain the string Admin repo url is :::. If there's no such match, then grep will exit with status 1.
You probably want to use
cat logFile | grep \"Admin repo url is :::\"
instead, or, even simpler:
grep \"Admin repo url is :::\" logFile
Append || true (or ||:) to the command if you want to avoid the errors when the log line does not appear.

How do I use sh in Jenkins Global library

I am creating my own global library for Jenkins, which I have hosted on github, and to simplify some run-of-the-mill tasks, I wanted to add a function that returns the GIT tag.
Therefore I created something like this:
class Myclass{
static String getGitTag() {
return "${sh(returnStdout: true, script: 'git tag --sort version:refname | tail -1').trim()}"
}
}
... which results in this error:
No signature of method: static com.stevnsvig.jenkins.release.ReleaseUtil.sh()
So I'm left with two questions:
Is the solution to import the sh() library that Jenkins' groovy flavor obviously already has imported? (and if so how)
What is the best practice here? I am wondering why there isn't a GIT_TAG global variable when you use declarative pipelines, and something like this should (in my opinion) be easy as pie.
EDIT #1:
static String getGitTag() {
stdout = script.sh(script: "git tag --sort version:refname | tail -1", returnStdout: true)
return stdout.trim()
}
produces a similar error:
No signature of method: static com.stevnsvig.jenkins.release.ReleaseUtil.sh() is applicable for argument types: (java.util.LinkedHashMap) values: [[returnStdout:true, script:git tag --sort version:refname | tail -1]]
EDIT #2:
static String getGitTag() {
def stdout = "git tag --sort version:refname | tail -1".execute()
return stdout.in.text
}
completes, but the output is blank. Running the same command with pwd returns / which indicaes that the environment is not set, which makes sense, since all the commands running under Jenkins are designed to rununder pipelines
EDIT #3:
I went hunting for the import. Stumbled across the Jenkins CI project on github and started searching the many repositories. Found a promising one... and put a file called pwd.groovy in /vars with this content:
import org.jenkinsci.plugins.workflow.steps.durable_task.ShellStep
static String getPWD() {
def ret = ShellStep.sh(returnStdout: true, script: "git tag --sort version:refname | tail -1").trim()
echo "currently in ${ret}"
}
The error I got is a variation of the same. I guess since itsa plugin, the definition is different...
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: static org.jenkinsci.plugins.workflow.steps.durable_task.ShellStep.sh() is applicable ...
Option 1) Use Groovy execute to run cmd and get its output as below
tag = "git tag --sort version:refname | tail -1".execute().text
Option 2) Use Jenkins pipeline step sh.
One concept need to get clear: the context of sh is global function is when sh used directly inside Jenkinsfile.
In your case, sh is used outside the Jenkinsfile. To make better understand I give an example Jenkinsfile.
pipeline {
stages('foo') {
steps {
sh 'pwd'
// In above sh step, there is an implicit `this` which represents the
// global object for Jenkinsfile, you can image sh 'pwd' to this.sh 'pwd'
//
// Thus if you want to use `sh` outside Jenkinsfile, you must pass down the
// implicit `this` into the file where you used `sh`
}
}
}
To address your issue
// ReleaseUtil.groovy
static String getGitTag(steps) {
// here `steps` is the global object for Jenkinsfile
// you can use other pipeline step here by `steps`
steps.echo 'test use pipeline echo outside Jenkinsfile'
steps.withCredentials([steps.string(credentialsId: 'git_hub_auth', variable: 'GIT_AUTH_TOKEN')]) {
steps.echo '....'
steps.sh '....'
}
return steps.sh(returnStdout: true, script:"git tag --sort version:refname | tail -1").trim()
}
// Jenkinsfile
import com.stevnsvig.jenkins.release.ReleaseUtil
pipeline {
stages('foo') {
steps {
ReleaseUtil.getGitTag(this)
}
}
}

How to use shell regular expression in jenkinsfile for jenkins pipeline?

I am trying to replace the '/' from Git branch name with '_' in my jenkinsfile so that I can tag my docker image with the branch name. In bash the below command works fine
echo "${git_branch_name//\//_}"
But when use the above command in jenkinsfile as below it throws an error.
#!/usr/bin/env groovy
def commit_id
def imagetag
def branch_name
def git_branch_name
node('Nodename') {
stage('checkout') {
checkout (scm).$Branch_Param
sh "git rev-parse --short HEAD > .git/commit-id"
commit_id = readFile('.git/commit-id').trim()
sh "git rev-parse --abbrev-ref HEAD > .git/branch-name"
git_branch_name = readFile('.git/branch-name').trim()
branch_name= sh "echo ${git_branch_name//\//_}"
sh "echo ${commit_id}"
sh "echo ${branch_name}"
sh "echo Current branch is ${branch_name}"
}
}
WorkflowScript: 15: end of line reached within a simple string 'x' or "x" or /x/;
solution: for multi-line literals, use triple quotes '''x''' or """x""" or /x/ or $/x/$ # line 15, column 28.
sh "branch_name = echo ${git_branch_name//\//_}"
What am I doing wrong here? Should I use Groovy regular expression instead of shell? why is shell not being interpreted correctly?
Thank you
The issue is that you're asking Groovy itself to interpret the expression ${git_branch_name//\//_}, not the shell. Using double-quotes around the string you pass to the sh step is what causes that. So if you instead write the following, this first error will go away:
sh 'echo ${git_branch_name//\\//_}' // <- Note the single-quotes
Basically, always use single-quotes unless you specifically need to use groovy's string interpolation (see the very last echo at the bottom of this answer).
Interestingly, it seems when I tested I didn't need the shebang (#!/bin/bash) to specify bash as some comments suggest; this ${variable//x/y} replace syntax worked in an sh step as-is. I guess the shell spawned was bash. I don't know if that's always the case, or if our Jenkins box has been specifically setup that way.
Also note you need to escape the escape sequence ('\\/') because what you're passing to the sh step is a string literal in groovy code. If you don't add that extra backslash, the line passed to the shell to be interpreted by it will be echo ${git_branch_name////_}, which it won't understand.
But there are other issues as well. First, assigning the output of the sh step to branch_name as you do means branch_name will always equal null. To get the stdout from a line of shell code you need to pass the extra parameter returnStdout: true to sh:
branch_name = sh (
script: 'echo ${git_branch_name//\\//_}',
returnStdout: true
).trim () // You basically always need to use trim, because the
// stdout will have a newline at the end
For bonus points, we could wrap that sh call in a closure. I find myself using it often enough to make this a good idea.
// Get it? `sh` out, "shout!"
def shout = { cmd -> sh (script: cmd, returnStdout: true).trim () }
//...
branch_name = shout 'echo ${git_branch_name//\\//_}'
But finally, the major problem is that bash (or whatever shell is actually spawned) doesn't have access to groovy variables. As far as it knows, echo ${git_branch_name} outputs an empty string, and therefore so does echo ${git_branch_name//\//_}.
You have a couple of choices. You could skip the creation of .git/branch-name and just immediately output the string-replaced result of git rev-parse:
branch_name = shout 'name=$(git rev-parse --abbrev-ref HEAD) && echo ${name//\\//_}'
Or to simplify that further you could use groovy's string replace function rather than the bash syntax:
branch_name = shout ('git rev-parse --abbrev-ref HEAD').replace ('/', '_')
Personally, I find the latter quite a bit more readable. YMMV. So bringing it all together at last:
#!groovy
def shout = { cmd -> sh (script: cmd, returnStdout: true).trim () }
// Note that I'm not declaring any variables up here. They're not needed.
// But you can if you want, just to clearly declare the environment for
// future maintainers.
node ('Nodename') {
stage ('checkout') {
checkout (scm).$Branch_Param
commit_id = shout 'git rev-parse --short HEAD'
branch_name = shout ('git rev-parse --abbrev-ref HEAD').replace ('/', '_')
echo commit_id
echo branch_name
echo "The most recent commit on branch ${branch_name} was ${commit_id}"
}
}

Is it possible to capture the stdout from the sh DSL command in the pipeline

For example:
var output=sh "echo foo";
echo "output=$output";
I will get:
output=0
So, apparently I get the exit code rather than the stdout. Is it possible to capture the stdout into a pipeline variable, such that I could get:
output=foo
as my result?
Now, the sh step supports returning stdout by supplying the parameter returnStdout.
// These should all be performed at the point where you've
// checked out your sources on the slave. A 'git' executable
// must be available.
// Most typical, if you're not cloning into a sub directory
gitCommit = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
// short SHA, possibly better for chat notifications, etc.
shortCommit = gitCommit.take(6)
See this example.
Note: The linked Jenkins issue has since been solved.
As mention in JENKINS-26133 it was not possible to get shell output as a variable. As a workaround suggested using of writ-read from temporary file. So, your example would have looked like:
sh "echo foo > result";
def output=readFile('result').trim()
echo "output=$output";
Try this:
def get_git_sha(git_dir='') {
dir(git_dir) {
return sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
}
}
node(BUILD_NODE) {
...
repo_SHA = get_git_sha('src/FooBar.git')
echo repo_SHA
...
}
Tested on:
Jenkins ver. 2.19.1
Pipeline 2.4
You can try to use as well this functions to capture StdErr StdOut and return code.
def runShell(String command){
def responseCode = sh returnStatus: true, script: "${command} &> tmp.txt"
def output = readFile(file: "tmp.txt")
if (responseCode != 0){
println "[ERROR] ${output}"
throw new Exception("${output}")
}else{
return "${output}"
}
}
Notice:
&>name means 1>name 2>name -- redirect stdout and stderr to the file name
I had the same issue and tried almost everything then found after I came to know I was trying it in the wrong block. I was trying it in steps block whereas it needs to be in the environment block.
stage('Release') {
environment {
my_var = sh(script: "/bin/bash ${assign_version} || ls ", , returnStdout: true).trim()
}
steps {
println my_var
}
}
A short version would be:
echo sh(script: 'ls -al', returnStdout: true).result
def listing = sh script: 'ls -la /', returnStdout:true
Reference : http://shop.oreilly.com/product/0636920064602.do Page 433

How to get the BUILD_USER in Jenkins when job triggered by timer?

I wanted to show the user who triggered a Jenkins job in the post job email. This is possible by using the plugin Build User Vars Plugin and the env variable BUILD_USER.
But this variable do not get initialized when the job is triggered by a scheduler.
How can we achieve this? I know we have a plugin called - EnvInject Plugin, and that can be used...
But I just want to know how we can use this and achieve the solution...
Build user vars plugin wasn't working for me so I did a quick-and-dirty hack:
BUILD_CAUSE_JSON=$(curl --silent ${BUILD_URL}/api/json | tr "{}" "\n" | grep "Started by")
BUILD_USER_ID=$(echo $BUILD_CAUSE_JSON | tr "," "\n" | grep "userId" | awk -F\" '{print $4}')
BUILD_USER_NAME=$(echo $BUILD_CAUSE_JSON | tr "," "\n" | grep "userName" | awk -F\" '{print $4}')
SIMPLE SOLUTIONS (NO PLUGINS) !!
METHOD 1: Via Shell
BUILD_TRIGGER_BY=$(curl -k --silent ${BUILD_URL}/api/xml | tr '<' '\n' | egrep '^userId>|^userName>' | sed 's/.*>//g' | sed -e '1s/$/ \//g' | tr '\n' ' ')
echo "BUILD_TRIGGER_BY: ${BUILD_TRIGGER_BY}"
METHOD 2: Via Groovy
node('master') {
BUILD_TRIGGER_BY = sh ( script: "BUILD_BY=\$(curl -k --silent ${BUILD_URL}/api/xml | tr '<' '\n' | egrep '^userId>|^userName>' | sed 's/.*>//g' | sed -e '1s/\$/ \\/ /g'); if [[ -z \${BUILD_BY} ]]; then BUILD_BY=\$(curl -k --silent ${BUILD_URL}/api/xml | tr '<' '\n' | grep '^shortDescription>' | sed 's/.*user //g;s/.*by //g'); fi; echo \${BUILD_BY}", returnStdout: true ).trim()
echo "BUILD_TRIGGER_BY: ${BUILD_TRIGGER_BY}"
}
METHOD 3: Via Groovy
BUILD_TRIGGER_BY = "${currentBuild.getBuildCauses()[0].shortDescription} / ${currentBuild.getBuildCauses()[0].userId}"
echo "BUILD_TRIGGER_BY: ${BUILD_TRIGGER_BY}"
OUTPUT:
Started by user Admin / user#example.com
Note: Output will be both User ID and User Name
This can be done using the Jenkins Build User Vars Plugin which exposes a set of environment variables, including the user who started the build.
It gives environment variables like BUILD_USER_ID, EMAIL, etc.
When the build is triggered manually by a logged-in user, that user's userid is available in the BUILD_USER_ID environment variable.
However, this environment variable won't be replaced / initialized when the build is automatically triggered by a Jenkins timer / scheduler.
Attached a screenshot for details
This can be resolved by injecting a condition to the Job by using Conditional Build Step Plugin / Run Condition Plugin,where in to each job we can add a condition to initialize the variable BUILD_USER_ID only when the build is caused or triggered by the Timer or scheduler, by setting a condition using the regular expression..
Without Plugin ->
def cause = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')
echo "userName: ${cause.userName}"
Install 'Build User Vars Plugin' and use like below:- [ See https://plugins.jenkins.io/build-user-vars-plugin ]
Be sure to check mark the Set jenkins user build variables checkbox under Build Environment for your Jenkins job's configuration.
I found similar but really working on Jenkins 2.1.x and easy for my understanding way.
And it works without any plugins.
if (currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')['userId']){
// Will be run only if someone user triggers build
// Because in other cases this contructions returns null
}
You can use in this construction any classes described here.
They will be returns maps with usable values.
This gets the username who clicked "Build Now" in a Jenkins pipeline job.
#NonCPS
def getBuildUser() {
return currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId()
}
I'm using a combination of the 'Execute Shell' and 'Env Inject' plugin as follows:
Create an 'Execute Shell' build step that uses shell parameter substitution to write default the value and echo that value into a file. Example highlighted in screen shot below.
Use the 'Env Inject' file to read that file as properties to set.
The token $BUILD_CAUSE from the email-ext plugin is what you are looking for.
You can see the full content token reference when you click the ? just after the Attach build log combobox at the email content configuration.
Some tokens get added by plugins, but this one should be aviable by default.
Edit: As pointed out by bishop in the comments, when using the EnvInject plugin, the $BUILD_CAUSE token gets changed to behave differently.
I have written a groovy script to extract the started by which would correctly get the source, regardless if user, scm or timer (could add more). It would recursively navigate the build tree to get the "original" 'started by' cause https://github.com/Me-ion/jenkins_build_trigger_cause_extractor
I wanted to trigger build initiator info to one of my slack/flock group so I used following way to get build initiator email and name by writing in Declarative fashion .
I am just printing here, you can use to store in some environment variable or write in one file giving file path according to your own convenience..
pipeline {
environment {
BRANCH_NAME = "${env.BRANCH_NAME}"
}
agent any
stages{
stage('Build-Initiator-Info'){
sh 'echo $(git show -s --pretty=%ae)'
sh 'echo $(git show -s --pretty=%an)'
}
}
}
Just to elaborate on Musaffir Lp's answer. The Conditional Build Step plugin now supports the Build Cause directly - it requires the Run Condition Plugin also.
If you wanted to detect when the build was started by a timer you can select a Run? value of Build Cause, with Build Cause of: TimerTrigger
This is a little simpler and more robust than using a regex. There are also other triggers you can detect, for example when the build was a result of Source Control Management commit, you can select: SCMTrigger.
This below is working for me.
Install "user build vars plugin"
Build Name = ${BUILD_NUMBER}_${TICKET}_${ENV,var="BUILD_USER_ID"}
I created a function that return the Triggered Job Name:
String getTriggeredJob(CURRENT_BUILD) {
if (CURRENT_BUILD.upstreamBuilds.size() > 0) {
TRIGGERED_JOB = CURRENT_BUILD.upstreamBuilds[0].projectName
if (!TRIGGERED_JOB.isEmpty()) {
return TRIGGERED_JOB
}
}
return "Self"
}
CURRENT_BUILD is env var currentBuild
How to return Username & UserId:
UserName: currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserName()
UserId: currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId()
There is other way to get user_id, where you don't need to install anything.
BUILD_USER_ID = sh (
script: 'id -u',
returnStdout: true
).trim()
echo "bUILD USER: ${BUILD_USER_ID }"
For declarative pipeline syntax, here is a quick hack, base on #Kevin answer.
For declarative pipeline you need to enclose them in a node, else you will get an error/ build failure
node {
def BUILD_FULL = sh (
script: 'curl --silent '+buildURL+' | tr "{}" "\\n" | grep -Po \'"shortDescription":.*?[^\\\\]"\' | cut -d ":" -f2',
returnStdout: true
)
slackSend channel: '#ci-cd',
color: '#000000',
message: "The pipeline was ${BUILD_FULL} ${GIT_COMMIT_MSG} "
}
The output will be slack notification sent to your slack channel with the git short description

Resources