I am trying to write groovy script which should send an email notification to the approver for deleting a pipeline job in Jenkins.
I am able to send email and get the approvers input, but I am confused on how to retrieve the input(Proceed or Abort).If input is Proceed, I should delete the job and if it is Abort, job shouldn't be deleted.
I looked at some reference and used "approveReceivedEvent" , but it is not working. Is there any way for retrieving user input?
Below is my code snippet
stage ('DELETE')
build job: 'JOBNAME', wait: true
mail to: 'xxx#xxx', subject: "Please approve #${env.JOB_NAME} to delete",
body: <p>Job '${env.JOB_NAME}" + environment + "[${env.BUILD_NUMBER}]' NEEDS APPROVAL</p><p>Please approve at "<a href='${env.JOB_URL}'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>"</p>"
try {
input id: 'Proceed', message: "\nDo you want to proceed to delete job?"
} catch (err) {
//approveReceivedEvent(id: id, approved: false)
throw err
}
//approveReceivedEvent(id: id, approved: true)
}
job(env.JOB_NAME) {
steps {
dsl {
removeAction('DELETE')
}
}
}
Modified the code.Below is the code snippet which worked.
def userInput = input (message: 'Approve Delete', submitterParameter: 'isApproved')
echo ("userInput was: " + userInput)
if(userInput.equals("Yes"))
{
job(env.JOB_NAME) {
steps {
dsl {
removeAction('DELETE')
}
}
}
}
else
{
echo("No Approval received to delete Job")
}
What you can try is , make a separate job (lets call it 'J') with the above mentioned code snippet and make it have the same parameters as input which are required by your code like :
JOB_NAME , BUILD_NUMBER , APPROVED etc.
Now in your original job when you send the mail you can give links for APPROVED = YES or 1 and APPROVED = NO or 0 . In those links you can remotely trigger your job 'J' with the parameters you sent in the mail and APPROVED parameter as 1 or 0 for the two links .
Related
I have Jenkins jobs that trigger twice a day and I would like to know if the current build is the first cron trigger of the day or not and do some action.
My cron job is as below
triggers {
// regression --> 3:00GMT, 14:00GMT
cron("00 3 * * 1-5 \n 00 14 * * 1-5")
}
Can I set some boolean param in my Jenkins file to check if it's the first trigger of the day?
The simplest option would be to check the build history. If the previous build was executed on the previous day, then the current build is the first build of the day. The logic must be defined in the executed job configurations.
The currentBuild object is an instance of the org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper class which provides all necessary information.
steps {
echo "The first build of the day started by trigger: ${isFirstBuildOfDayStartedByTrigger(currentBuild)}"
}
// ...
boolean isFirstBuildOfDayStartedByTrigger(currentBuild) {
if (isStartedByTrigger(currentBuild)) {
return false
}
def today = toLocalDate(currentBuild.startTimeInMillis)
def build = currentBuild.previousBuild
while(build != null) {
if (toLocalDate(build.startTimeInMillis).isBefore(today)) {
return true
}
if (isStartedByTrigger(build)) {
return false
}
build = build.previousBuild
}
return true
}
LocalDate toLocalDate(long millis) {
return Instant.ofEpochMilli(millis).atZone(ZoneId.systemDefault()).toLocalDate()
}
boolean isStartedByTrigger(build) {
// TODO: use build.buildCauses or build.getBuildCauses('cause.class.Name')
// to analyze if the job was started by trigger
return true // or false
}
You have to figure out which build cause is added when the job is started by trigger.
If you just want to find the first build of the day executed by anything or anyone, then the code is much simpler:
steps {
echo "The first build of the day: ${isFirstBuildOfDay(currentBuild)}"
}
boolean isFirstBuildOfDay(currentBuild) {
def today = toLocalDate(currentBuild.startTimeInMillis)
def previousBuild = currentBuild.previousBuild
return previousBuild == null || toLocalDate(previousBuild.startTimeInMillis).isBefore(today)
}
LocalDate toLocalDate(long millis) {
return Instant.ofEpochMilli(millis).atZone(ZoneId.systemDefault()).toLocalDate()
}
I used the new date API which I think is not whitelisted, so you have to put that code to the Jenkins library or approve the used method signatures.
Found the answer, it's simple but working fine for me.
First I am checking if this is a scheduled job or not and the current hour is less than 5 (scheduled job runs before 5)
def isItFirstScheduledJob = (params.JOB_IS_SCHEDULED && new Date().getHours() < 5) ? true : false
In the Jenkins output, I had the following assert error
but I need to get the String error from the error assert or any text. I'm using I'm My JenkinsFile:
def matcher = manager.getLogMatcher('.*Delete organization Account failed: *')
but generates the following error:
So I just want to check, that the log contains a specific string and if the texts exists make the build failed currentBuild.result = "FAILED", saving the text to send it by slack
You can put the condition in below way :
if (manager.logContains('.*Delete organization Account failed:*')) {
error("Build failed because of Delete organization Account..")
}
This is how it worked for me:
import hudson.model.*
node {
.....
if (Slack.toBoolean() ) {
def matcher = manager.getLogMatcher(".*Error.*")
if(matcher.matches()) {
pbn=matcher.group(0)
println pbn
slack_message = "`BUILD ERROR`: ${pbn} "
println slack_message
matcher = null // fix NotSerializableException
slackSend(channel: "#reports", message: slack_message, color: '#172530');
}
}
}
How I can list all culprits users related to the broken builds since the last successful build and the current one?
Besides that, how to compile all this information and then send by Slack?
The script below describes how to configure the post processing step to send culprits users from history of broken builds.
#!groovy​
pipeline {
agent { label 'pipeline-maven'}
post {
failure {
script {
def userDetailsService = load("get-users-details.groovy")
env.slack_msg = userDetailsService.getFailedBuildHistory()
}
slackSend baseUrl: 'https://xxx.slack.com/services/hooks/jenkins-ci/',
channel: '#xxxx',
color: 'bad',
token: 'aIPJis6V4P9VOpTFhUtCQRRL',
message: "Broken build ${currentBuild.fullDisplayName}\n${slack_msg}"
}
}
}
The script below (get-users-details.groovy) is responsible to enumerate all culprits based on the previous broken build history.
import jenkins.model.Jenkins
def String getFailedBuildHistory() {
def message = ""
// Iterate over previous broken builds to find culprits
def fullName = "pipeline-test"
def jobData = Jenkins.instance.getItemByFullName(fullName)
def lastStableBuild = jobData.getLastStableBuild()
def lastBuildNumber = jobData.getLastBuild().getNumber() - 1 // We subtract the current executing build from the list
if (lastStableBuild != null && lastStableBuild.getNumber() != lastBuildNumber) {
def culpritsSet = new HashSet();
message += "Responsibles:\n"
// From oldest to newest broken build, since the last sucessful build, find the culprits to notify them
// The list order represents who is more responsible to fix the build
for (int buildId = lastStableBuild.getNumber() + 1; buildId <= lastBuildNumber; buildId++) {
def lastBuildDetails = Jenkins.getInstance().getItemByFullName(fullName).getBuildByNumber(buildId)
if (lastBuildDetails != null) {
lastBuildDetails.getCulpritIds().each({ culprit ->
if (!culpritsSet.contains(culprit)) {
message += " ${culprit} (build ${lastBuildDetails.getNumber()})\n"
culpritsSet.add(culprit)
}
})
}
}
}
// Complement the message with information from the current executing build
if (currentBuild.getCurrentResult() != 'SUCCESS') {
def culprits = currentBuild.changeSets.collectMany({ it.toList().collect({ it.author }) }).unique()
if (culprits.isEmpty()) {
// If there is no change log, use the build executor user
def name = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause').userName
message += " ${name} (current build ${currentBuild.getId()})"
} else {
// If there is change log, use the committer user
culprits.each({ culprit ->
message += " ${culprit} (current build ${currentBuild.getId()})"
})
}
}
return message
}
return [
getFailedBuildHistory: this.&getFailedBuildHistory
]
I have the Build Failure Analyzer plugin installed in my Jenkins instance, and I have a number of different failures entered into the plugin. Does anyone know if it is possible to get the total number of failures across all jobs that have the same cause?
For example, I occasionally get "ChannelClosedException" failures if the build node goes offline during a build or test unexpectedly and I would like to determine how often this is happening across all my jobs. Is there some way to aggregate this value? I imagine it could be done through groovy if you can iterate over each build for each job and collect the Build Failure cause if one is detected.
Has anyone else done something like this before?
Not the exact answer... but should be able to modify to get what you are looking for:
Jenkins.instance.getAllItems(Job).each{
def jobBuilds=it.getBuilds()
//for each job get the things (you can limit at your convenience)
jobBuilds.each { build ->
def runningSince = groovy.time.TimeCategory.minus( new Date(), build.getTime() )
def currentStatus = build.buildStatusSummary.message
def cause = build.getCauses()[0] //we keep the first cause
def user = cause instanceof Cause.UserIdCause? cause.getUserId():""
println "Build: ${build} | Since: ${runningSince} | Status: ${currentStatus} | Cause: ${cause} | User: ${user}"
def parameters = build.getAction(ParametersAction)?.parameters
parameters.each {
println "Type: ${it.class} Name: ${it.name}, Value: ${it.dump()}"
}
}
}
Using some google-fu and some of the info from VinDev, I came up with this solution:
// get all jobs in Jenkins
Jenkins.instance.getAllItems(Job).each {
// get all builds for each job
def jobBuilds=it.getBuilds()
//for each build, get the name and status + and failure messages
jobBuilds.each { build ->
// get the build status
def currentStatus = build.buildStatusSummary.message
// we only care about the broken builds because we want failure messages
if (currentStatus.contains("broken")) {
println "Build: ${build} | Status: ${currentStatus}"
def BFA = build.actions.find{ it instanceof com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseBuildAction };
if (BFA != null) {
for (failureCause in BFA.getFoundFailureCauses()) {
println("name: " + failureCause.getName() + ", description: " + failureCause.getDescription())
}
}
}
}
}
I have Jenkins pipeline set up for Git branches with the last optional step of deploying to stage:
stage('Stage') {
if (gitBranch != "master") {
timeout(time: 1, unit: 'DAYS') {
input message: "Do you want to deploy ${shortCommit} from branch ${gitBranch} to STAGE?"
}
}
node {
stage('Deploy Stage') {
echo("Deploying to STAGE ${gitCommit}")
sh "NODE_ENV=stage yarn lerna-run --since ${sinceSha} deploy"
}
}
}
The problem is deploying a branch to stage is optional, but Jenkins doesn't return a success code to Github until it's done.
Is there any syntax to mark it as optional?
You can combine the timeout step with the input step, like we have it here:
/**
* Generates a pipeline {#code input} step that times out after a specified amount of time.
*
* The options for the timeout are supplied via {#code timeoutOptions}.
* The options for the input dialog are supplied via {#code inputOptions}.
*
* The returned Map contains the following keys:
*
* - proceed: true, if the Proceed button was clicked, false if aborted manually aborted or timed out
* - reason: 'user', if user hit Proceed or Abort; 'timeout' if input dialog timed out
* - submitter: name of the user that submitted or canceled the dialog
* - additional keys for every parameter submitted via {#code inputOptions.parameters}
*
* #param args Map containing inputOptions and timoutOptions, both passed to respective script
* #return Map containing above specified keys response/reason/submitter and those for parameters
*/
Map inputWithTimeout(Map args) {
def returnData = [:]
// see https://go.cloudbees.com/docs/support-kb-articles/CloudBees-Jenkins-Enterprise/Pipeline---How-to-add-an-input-step,-with-timeout,-that-continues-if-timeout-is-reached,-using-a-default-value.html
try {
timeout(args.timeoutOptions) {
def inputOptions = args.inputOptions
inputOptions.submitterParameter = "submitter"
// as we ask for the submitter, we get a Map back instead of a string
// besides the parameter supplied using args.inputOptions, this will include "submitter"
def responseValues = input inputOptions
echo "Response values: ${responseValues}"
// BlueOcean currently drops the submitterParameter
// https://issues.jenkins-ci.org/browse/JENKINS-41421
if (responseValues instanceof String) {
echo "Response is a String. BlueOcean? Mimicking the correct behavior."
String choiceValue = responseValues
String choiceKey = args.inputOptions.parameters.first().getName()
responseValues = [(choiceKey): choiceValue, submitter: null]
}
echo "Submitted by ${responseValues.submitter}"
returnData = [proceed: true, reason: 'user'] + responseValues
}
} catch (FlowInterruptedException err) { // error means we reached timeout
// err.getCauses() returns [org.jenkinsci.plugins.workflow.support.input.Rejection]
Rejection rejection = err.getCauses().first()
if ('SYSTEM' == rejection.getUser().toString()) { // user == SYSTEM means timeout.
returnData = [proceed: false, reason: 'timeout']
} else { // explicitly aborted
echo rejection.getShortDescription()
returnData = [proceed: false, reason: 'user', submitter: rejection.getUser().toString()]
}
} catch (err) {
// try to figure out, what's wrong when we manually abort the pipeline
returnData = [proceed: false, reason: err.getMessage()]
}
returnData
}
In addition to your requirements, this also returns, who submitted the dialog.