Skip to content
This repository has been archived by the owner on Oct 28, 2024. It is now read-only.

Support slack comments for builds #745

Merged
merged 25 commits into from
Oct 2, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .ci/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pipeline {
MAVEN_CONFIG = "${params.MAVEN_CONFIG}"
LANG = "C.UTF-8"
LC_ALL = "C.UTF-8"
SLACK_CHANNEL = '#beats-ci-builds'
}
options {
timeout(time: 1, unit: 'HOURS')
Expand Down Expand Up @@ -194,7 +195,7 @@ pipeline {
}
post {
cleanup {
notifyBuildResult(prComment: true)
notifyBuildResult(prComment: true, slackComment: true)
}
}
}
14 changes: 14 additions & 0 deletions resources/slack-markdown.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<% changes = changeSet?.find { true }%>
<% steps = stepsErrors?.findAll{it?.result == "FAILURE" && !it?.displayName?.contains('Notifies GitHub') && !it?.displayName?.contains('Archive JUnit')}%>
<% pipelineUrl = String.format("<%spipeline|build>", jobUrl)%>
<% commitUrl = String.format("<%s|%s>", changes?.url, changes?.msg)%>
*Status*: ${buildStatus} ${pipelineUrl}
*Build causes*: ${changes?.commitId} has been merged (see ${commitUrl}) (PR owner `${changes?.author?.id}`)
*Tests(Total/Passed/Failed/Skipped)*: `${(testsSummary?.total) ?: 0}/${(testsSummary?.passed) ?: 0}/${(testsSummary?.failed) ?: 0}/${(testsSummary?.skipped) ?: 0}`
<% if(testsErrors != null && testsErrors instanceof List && testsErrors?.any{item -> item?.status == "FAILED"}) {%>
<% testsErrors?.findAll{it?.status == "FAILED"}.each{ %>
* *Name*: `${it?.name}` for the last ${it?.age} builds<%}%>
<%}%>
*Steps failures*: ${(steps?.size()!= 0) ? steps?.size() : 0}
<% steps.each{ it -> %> <% stepUrl = (it?.url && it?.url != 'null') ? String.format("(<%s|see logs>)", it.url) : ''%>
* *Name*: `${(it?.displayDescription && it?.displayDescription != 'null') ? it?.displayDescription : it?.displayName}` ${stepUrl} <%}%>
52 changes: 52 additions & 0 deletions src/co/elastic/NotificationManager.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,58 @@ def notifyPR(Map params = [:]) {
}
}

/**
* This method sends a slack message with data from Jenkins
* @param build
* @param buildStatus String with job result
* @param changeSet list of change set, see src/test/resources/changeSet-info.json
* @param docsUrl URL with the preview docs
* @param log String that contains the log
* @param statsUrl URL to access to the stats
* @param stepsErrors list of steps failed, see src/test/resources/steps-errors.json
* @param testsErrors list of test failed, see src/test/resources/tests-errors.json
* @param testsSummary object with the test results summary, see src/test/resources/tests-summary.json
*/
def notifySlack(Map args = [:]) {
def build = args.containsKey('build') ? args.build : error('notifySlack: build parameter it is not valid')
def buildStatus = args.containsKey('buildStatus') ? args.buildStatus : error('notifySlack: buildStatus parameter is not valid')
def changeSet = args.containsKey('changeSet') ? args.changeSet : []
def docsUrl = args.get('docsUrl', null)
def log = args.containsKey('log') ? args.log : null
def statsUrl = args.containsKey('statsUrl') ? args.statsUrl : ''
def stepsErrors = args.containsKey('stepsErrors') ? args.stepsErrors : []
def testsErrors = args.containsKey('testsErrors') ? args.testsErrors : []
def testsSummary = args.containsKey('testsSummary') ? args.testsSummary : null
def enabled = args.get('enabled', false)
def channel = args.containsKey('channel') ? args.channel : error('notifySlack: channel parameter is not required')
def credentialId = args.containsKey('credentialId') ? args.credentialId : error('notifySlack: credentialId parameter is not required')

if (enabled) {
catchError(buildResult: 'SUCCESS', message: 'notifySlack: Error with the slack comment') {
def statusSuccess = (buildStatus == "SUCCESS")
def boURL = getBlueoceanDisplayURL()
def body = buildTemplate([
"template": 'slack-markdown.template',
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
"build": build,
"buildStatus": buildStatus,
"changeSet": changeSet,
"docsUrl": docsUrl,
"jenkinsText": env.JOB_NAME,
"jenkinsUrl": env.JENKINS_URL,
"jobUrl": boURL,
"log": log,
"statsUrl": statsUrl,
"statusSuccess": statusSuccess,
"stepsErrors": stepsErrors,
"testsErrors": testsErrors,
"testsSummary": testsSummary
])
def color = (buildStatus == "SUCCESS") ? 'good' : 'warning'
slackSend(channel: channel, color: color, message: "${body}", tokenCredentialId: credentialId)
}
}
}

/**
* This method generates the build report and archive it
* @param build
Expand Down
1 change: 1 addition & 0 deletions src/test/groovy/ApmBasePipelineTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ class ApmBasePipelineTest extends DeclarativePipelineTest {
throw lastError
}
})
helper.registerAllowedMethod('slackSend', [Map.class], { 'OK' })
helper.registerAllowedMethod('sleep', [Integer.class], null)
helper.registerAllowedMethod('sh', [Map.class], { 'OK' })
helper.registerAllowedMethod('sh', [String.class], { 'OK' })
Expand Down
34 changes: 34 additions & 0 deletions src/test/groovy/NotificationManagerStepTests.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,40 @@ class NotificationManagerStepTests extends ApmBasePipelineTest {
assertJobStatusSuccess()
}

@Test
void test_notify_slack_with_aborted_but_no_cancel_build() throws Exception {
v1v marked this conversation as resolved.
Show resolved Hide resolved
def script = loadScript(scriptName)
script.notifySlack(
build: readJSON(file: "build-info.json"),
buildStatus: "ABORTED",
changeSet: readJSON(file: "changeSet-info.json"),
stepsErrors: readJSON(file: "steps-errors.json"),
testsErrors: readJSON(file: "tests-errors.json"),
testsSummary: readJSON(file: "tests-summary.json"),
channel: 'test',
credentialId: 'test',
enabled: true
)
printCallStack()
assertTrue(assertMethodCallContainsPattern('slackSend', 'ABORTED'))
assertJobStatusSuccess()
}

@Test
void test_notify_slack_with_aborted_but_no_cancel_build_and_disabled() throws Exception {
def script = loadScript(scriptName)
script.notifySlack(
build: readJSON(file: "build-info.json"),
buildStatus: "ABORTED",
channel: 'test',
credentialId: 'test',
enabled: false
)
printCallStack()
assertFalse(assertMethodCallContainsPattern('slackSend', 'ABORTED'))
assertJobStatusSuccess()
}

@Test
void test_analyzeFlakey() throws Exception {
def script = loadScript(scriptName)
Expand Down
5 changes: 3 additions & 2 deletions src/test/resources/steps-errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"result": "FAILURE",
"startTime": "2019-05-29T12:08:57.040+0000",
"state": "FINISHED",
"type": "STEP"
"type": "STEP",
"url": "FOOOO"
},
{
"_class": "io.jenkins.blueocean.rest.impl.pipeline.PipelineStepImpl",
Expand Down Expand Up @@ -92,7 +93,7 @@
}
],
"displayDescription": "./mvnw clean test --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
"displayName": "Shell Script",
"displayName": "Notifies GitHub of the status of a Pull Request",
"durationInMillis": 101825,
"id": "58",
"input": null,
Expand Down
14 changes: 13 additions & 1 deletion vars/notifyBuildResult.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper

def call(Map args = [:]) {
def notifyPRComment = args.containsKey('prComment') ? args.prComment : true
def notifySlackComment = args.containsKey('slackComment') ? args.slackComment : false
def analyzeFlakey = args.containsKey('analyzeFlakey') ? args.analyzeFlakey : false
def newPRComment = args.containsKey('newPRComment') ? args.newPRComment : [:]
def flakyReportIdx = args.containsKey('flakyReportIdx') ? args.flakyReportIdx : ""
Expand All @@ -44,7 +45,9 @@ def call(Map args = [:]) {
def to = args.containsKey('to') ? args.to : customisedEmail(env.NOTIFY_TO)
def statsURL = args.containsKey('statsURL') ? args.statsURL : "https://ela.st/observabtl-ci-stats"
def shouldNotify = args.containsKey('shouldNotify') ? args.shouldNotify : !isPR() && currentBuild.currentResult != "SUCCESS"

def slackChannel = args.containsKey('slackChannel') ? args.slackChannel : env.SLACK_CHANNEL
def slackAlways = args.containsKey('slackAlways') ? args.slackAlways : (currentBuild.currentResult != "SUCCESS")
def slackCredentials = args.containsKey('slackCredentials') ? args.slackCredentials : 'jenkins-slack-integration-token'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we update the examples in the header comments too?

catchError(message: 'There were some failures with the notifications', buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
def data = getBuildInfoJsonFiles(jobURL: env.JOB_URL, buildNumber: env.BUILD_NUMBER, returnData: true)
data['docsUrl'] = "http://${env?.REPO_NAME}_${env?.CHANGE_ID}.docs-preview.app.elstc.co/diff"
Expand Down Expand Up @@ -79,6 +82,15 @@ def call(Map args = [:]) {
log(level: 'DEBUG', text: "notifyBuildResult: Notifying results in the PR.")
notificationManager.notifyPR(data)
}

// Should notify in slack if it's enabled
if(notifySlackComment) {
data['channel'] = slackChannel
data['credentialId'] = slackCredentials
data['enabled'] = slackAlways
log(level: 'DEBUG', text: "notifyBuildResult: Notifying results in slack.")
notificationManager.notifySlack(data)
}
log(level: 'DEBUG', text: 'notifyBuildResult: Generate build report.')
catchError(message: "There were some failures when generating the build report", buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
notificationManager.generateBuildReport(data)
Expand Down
4 changes: 4 additions & 0 deletions vars/notifyBuildResult.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Besides, if there are checkout environmental issues then it will rebuild the pip
* shouldNotify: boolean value to decide to send or not the email notifications, by default it send
emails on Failed builds that are not pull request.
* prComment: Whether to add a comment in the PR with the build summary as a comment. Default: `true`.
* slackComment: Whether to send a message in slack with the build summary as a comment. Default: `false`.
* slackChannel: What slack channel. Default value uses `env.SLACK_CHANNEL`.
* slackCredentials: What slack credentials to be used. Default value uses `jenkins-slack-integration-token`.
* slackAlways: Whether to send or not the slack notifications, by default it sends notifications on Failed builds.
* analyzeFlakey: Whether or not to add a comment in the PR with tests which have been detected as flakey. Default: `false`.
* flakyReportIdx: The flaky index to compare this jobs results to. e.g. reporter-apm-agent-java-apm-agent-java-master
* flakyThreshold: The threshold below which flaky tests will be ignored. Default: 0.0
Expand Down