sed replace with newline acting as space in Groovy Jenkinsfile - jenkins

We have a requirement to execute below in Jenkinsfile and it is working fine directly on the Server:
Server: echo $myval | sed 's/,/\n/g' | grep analysisId | cut -d":" -f2
However, when I am trying to execute it in Jenkinsfile, it is treating newline as spaces, even though when I tried using '\' to supress '\':
Jenkinsfile: echo $myval | sed 's/,/\\n/g' | grep analysisId | cut -d":" -f2
Any idea what I may be doing wrong ? Intent over here is that I am trying to parse JSON, and it's the only option I am left with
Value of myval
{"task":{"id":"AW1eTPbXXXXXXXX","type":"REPORT","componentId":"AWz2VsZM-CVpcXXXXXX","componentKey":"Sonar-Scanner-SFDX_CI-ProjectKey","componentName":"BV GitLAB Test Project","componentQualifier":"TRK","analysisId":"AW1eTP3AX12345","status":"SUCCESS","submittedAt":"2019-09-23T13:26:05+0000","submitterLogin":"vgulati","startedAt":"2019-09-23T13:26:07+0000","executedAt":"2019-09-23T13:26:08+0000","executionTimeMs":1216,"logs":false,"hasScannerContext":true,"organization":"default-organization"}}
Need to get Value of analysisId, which is AW1eTP3AX12345.
Section of Jenkinsfile:
def analysisId = sh script: "echo $sonarUrlContent | sed 's/,/\\n/g' | grep analysisId | cut -d':' -f2",returnStdout:true
echo "analysisId: ${analysisId}"

Since there is no way for you to install the Pipeline Utility Plugin, you can improvise some parsing of your own.
This is a hacky hacky solution! It only works since you only need one value:
def json = '{"task":{"id":"AW1eTPbXXXXXXXX","type":"REPORT","componentId":"AWz2VsZM-CVpcXXXXXX","componentKey":"Sonar-Scanner-SFDX_CI-ProjectKey","componentName":"BV GitLAB Test Project","componentQualifier":"TRK","analysisId":"AW1eTP3AX12345","status":"SUCCESS","submittedAt":"2019-09-23T13:26:05+0000","submitterLogin":"vgulati","startedAt":"2019-09-23T13:26:07+0000","executedAt":"2019-09-23T13:26:08+0000","executionTimeMs":1216,"logs":false,"hasScannerContext":true,"organization":"default-organization"}}'
def data = json.split(",")
data.each{ item ->
if(item.startsWith('"analysisId"')){
result = item.split(":")[1].replace('"', '')
}
}
echo "$result"
Using readJSON would be preferable in any case, but this should work.

I don't know why the sed command fails, but if you want to parse JSON like #daggett implied you should use the jenkins native readJSON method instead of the groovy jsonSlurper
You have to install the Pipeline Utility Plugin and can use it like this:
def json = '{"task":{"id":"AW1eTPbXXXXXXXX","type":"REPORT","componentId":"AWz2VsZM-CVpcXXXXXX","componentKey":"Sonar-Scanner-SFDX_CI-ProjectKey","componentName":"BV GitLAB Test Project","componentQualifier":"TRK","analysisId":"AW1eTP3AX12345","status":"SUCCESS","submittedAt":"2019-09-23T13:26:05+0000","submitterLogin":"vgulati","startedAt":"2019-09-23T13:26:07+0000","executedAt":"2019-09-23T13:26:08+0000","executionTimeMs":1216,"logs":false,"hasScannerContext":true,"organization":"default-organization"}}'
def data = readJSON text: json
echo "$data.task.analysisId"
readJSON returns a groovy map which can be used like one would expect.

Related

How to get owner of upstream build in downstream build without using parameter?

I have upstream and downstream pipelines and in downstream pipeline, I want to store user, who started upstream build, in downstream build variable.
I used following in downstream pipeline but it results into error - java.lang.NullPointerException: Cannot invoke method getUserId() on null object
Owner = currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId()
One way is to store it in a variable in upstream pipeline and pass it as param to downstream pipeline but that is additional handling and considering my parameters list, it is little tricky.
Is there any way I can get user who started upstream build in downstream build without passing parameter?
Similarly, JOB_NAME and BUILD_NUMBER of upstream to downstream.
Your current build would print the below as the build cause when triggered by a user.
env.current_BUILD_CAUSE = currentBuild.getBuildCauses().toString()
env.current_BUILD_CAUSE:
[
{
"_class": "hudson.model.Cause$UpstreamCause",
"shortDescription": "Started by upstream project \"ABC/XYZ/JobA\" build number 621",
"upstreamBuild": 621,
"upstreamProject": "ABC/XYZ/JobA",
"upstreamUrl": "job/ABC/job/XYZ/job/JobA/"
}
]
So now I'd take the necessary parameters from the above values. I'm not much familiar with the groovy way of doing it. So have used the sh directive.
env.upstream_BUILD_NUMBER = sh(script: 'echo \"${current_BUILD_CAUSE}\" | jq .[].upstreamBuild | tr -d \'"\'', returnStdout: true).trim()
env.upstream_JOB_NAME = sh(script: 'echo \"${current_BUILD_CAUSE}\" | jq .[].upstreamProject | tr -d \'"\'', returnStdout: true).trim()
env.upstream_JOB_URL = sh(script: 'echo \"${current_BUILD_CAUSE}\" | jq .[].upstreamUrl | tr -d \'"\'', returnStdout: true).trim()
Since, you don't want to pass parameters from one to another, I'd just curl the JSON api of the build and then use the same to get the values as required from upstream build. I'm using the credentials here since you may or may not need to authenticate to jenkins as per your requirement. You can remove that if not required along with -u arguments in curl.
withCredentials([usernamePassword(credentialsId: 'your-CredentialsID-for-Jenkins', usernameVariable: 'user', passwordVariable: 'password')]) {
env.upstream_OWNER_ID = sh(script: "curl -s -u $user:$password https://${JENKINS_DASHBOARD}/${upstream_JOB_URL}/{upstream_BUILD_NUMBER}/api/json | jq -r \'.actions[].causes[]?.userId | select(length>0)\'", returnStdout: true).trim()
}
Now I'll have everything that is needed from both current and upstream build causes without passing any parameters from or to.
Note: The code samples above also contains the escape sequences which I'm currently using in my jenkins job.

Jenkins pipeline - groovy.lang.MissingPropertyException

I am trying to write Pipeline script in Jenkins for that I want to store only image name from Docker Repo but IN Unix cmd the code is working but in pipeline script, I am getting an error like: groovy.lang.MissingPropertyException: No such property: x for class: groovy.lang.Binding
def Image_name="$(sudo docker images | grep -e 'hello-world.*latest' | awk -v x=1 '{print $x}')"
echo $Image_name
Double quoted strings are interpolated first in groovy. There is no $x groovy variable defined and so you are getting this error.
You can use single quotes instead of double quotes or escape the dollar sign as \$x in double quoted string.

What is "not serializable" in this Groovy method?

I wrote the following function which is loaded to my pipeline:
def userTrigger() {
[$class: 'UsernamePasswordMultiBinding', credentialsId: jenkins_creds, usernameVariable: 'J_USER', passwordVariable: 'J_PASS'],
]){
cmd = "curl -s -u \${J_USER}:\${J_PASS} \${env.BUILD_URL}api/json | python -mjson.tool | grep userId | awk '{print \$2}' | tr -d '"|,' "
def ut = sh(returnStdout: true, script: cmd)
return ut
}
}
The result should be the username of the one who triggered the build, before cleaning the output it looks like so:
"userId": "itaig",
Basically, this:
awk -F'"' '{print $4}'
would also give me the required output but i'm not sure how to escape chars in this statement too.
When I run the job, I get the following error:
java.io.NotSerializableException: org.codehaus.groovy.control.ErrorCollector
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1032)
at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:988)
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:854)
I'm pretty much sure it has something to do with my tries of escaping the relevant characters but I have a feeling that I haven't done it right, I've tried escaping the (") in the end of the cmd line but to no avail.
Can you try and find my issue?
What is that right parenthesis and right bracket closing on line #3?
]){
It might be more of the double-quote vs single-quote then the escaping of characters - make sure they are are pairing up nicely.

Jenkins pipeline: Running a shell command returns "Bad substitution", but why?

I'd like to populate the the groovy variable "committer" with the output of the command:
def committer = utils.sh("curl -s -u \${J_USER}:\${J_PASS} \${env.BUILD_URL}/api/json | python -mjson.tool | grep authorEmail | awk '{print \$2}' | tr -d '"|,' ")
Because of the known issue in Jenkins (JENKINS-26133) it is not possible to do that but only to populate the variable with the exit status of the command.
So I've go these 2 functions:
def gen_uuid(){
randomUUID() as String
}
def sh_out(cmd){ // As required by bug JENKINS-26133
String uuid = gen_uuid()
sh """( ${cmd} )> ${uuid}"""
String out = readFile(uuid).trim()
sh "set +x ; rm ${uuid}"
return out
}
These functions allow me to wrap my shell commands in sh_out(COMMAND) and in the background I'm using the workaround which is suggested in the mentioned above known issue link which means running the command while redirecting it's output to a file (in the case of my function it's a random filename) and then reading it into a variable.
So, In the beginning of my pipeline I load my functions file which ends with return this; like so:
fileLoader.withGit('git#bitbucket.org:company/pipeline_utils.git', 'master', git_creds, ''){
utils = fileLoader.load('functions.groovy');
}
And that's why the "utils.sh_out" that you see in the command, but when I use the shown above command in my Jenkins pipeline, I get the following error:
/home/ubuntu/workspace/-6870-bitbucket-integration-ECOPKSSBUJ6HCDNM4TOY77X7UTZ#tmp/durable-006d5c7e/script.sh: 2: /home/ubuntu/workspace/-6870-bitbucket-integration-ECOPKSSBUJ6HCDNM4TOY77X7UTZ#tmp/durable-006d5c7e/script.sh: Bad substitution
Running the command in a shell works properly:
$ curl -s -u user:password http://IPADDR:8080/job/COMPANY_BitBucket_Integration/job/research/job/COMPANY-6870-bitbucket-integration/3/api/json/api/json | python -mjson.tool | grep authorEmail | awk '{print $2}' | tr -d '"|,'
user#email.com
I suspect it has something to do with the tr command in the end and with the character escaping I did there but whatever I try fails, anyone got an idea?
according to the documentation now sh supports std output.
and i know i'm not answering your question directly, but i suggest to use groovy to parse json.
you are trying to get the value of authorEmail from json
if the response from /api/json looks like this (just an example):
{
"a":{
"b":{
"c":"ccc",
"authorEmail":"user#email.com"
}
}
}
then the groovy to take athorEmail:
def cmd = "curl -s -u \${J_USER}:\${J_PASS} \${env.BUILD_URL}/api/json"
def json = sh(returnStdout: true, script: cmd).trim()
//parse json and access it as an object (Map/Array)
json = new groovy.json.JsonSlurper().parseText(json)
def mail = json.a.b.athorEmail
you could receive java.io.NotSerializableException explained here
so i changed the code like this:
node {
def json = sh(
returnStdout: true,
script: "curl -s -u \${J_USER}:\${J_PASS} \${env.BUILD_URL}/api/json"
).trim()
def mail = evaluateJson(json, '${json.a.b.authorEmail}')
echo mail
}
#NonCPS
def evaluateJson(String json, String gpath){
//parse json
def ojson = new groovy.json.JsonSlurper().parseText(json)
//evaluate gpath as a gstring template where $json is a parsed json parameter
return new groovy.text.GStringTemplateEngine().createTemplate(gpath).make(json:ojson).toString()
}

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