[CI] In-progress PR comments (#72211) (#72743)

This commit is contained in:
Brian Seeders 2020-07-21 21:39:41 -04:00 committed by GitHub
parent f93bc47374
commit 0fa62b0cff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 30 deletions

View file

@ -15,25 +15,43 @@
*/ */
def withDefaultPrComments(closure) { def withDefaultPrComments(closure) {
catchErrors { catchErrors {
// sendCommentOnError() needs to know if comments are enabled, so lets track it with a global
// isPr() just ensures this functionality is skipped for non-PR builds
buildState.set('PR_COMMENTS_ENABLED', isPr())
catchErrors { catchErrors {
closure() closure()
} }
sendComment(true)
}
}
if (!params.ENABLE_GITHUB_PR_COMMENTS || !isPr()) { def sendComment(isFinal = false) {
if (!buildState.get('PR_COMMENTS_ENABLED')) {
return return
} }
def status = buildUtils.getBuildStatus() def status = buildUtils.getBuildStatus()
if (status == "ABORTED") { if (status == "ABORTED") {
return; return
} }
def lastComment = getLatestBuildComment() def lastComment = getLatestBuildComment()
def info = getLatestBuildInfo(lastComment) ?: [:] def info = getLatestBuildInfo(lastComment) ?: [:]
info.builds = (info.builds ?: []).takeRight(5) // Rotate out old builds info.builds = (info.builds ?: []).takeRight(5) // Rotate out old builds
def message = getNextCommentMessage(info) // If two builds are running at the same time, the first one should not post a comment after the second one
postComment(message) if (info.number && info.number.toInteger() > env.BUILD_NUMBER.toInteger()) {
return
}
def shouldUpdateComment = !!info.builds.find { it.number == env.BUILD_NUMBER }
def message = getNextCommentMessage(info, isFinal)
if (shouldUpdateComment) {
updateComment(lastComment.id, message)
} else {
createComment(message)
if (lastComment && lastComment.user.login == 'kibanamachine') { if (lastComment && lastComment.user.login == 'kibanamachine') {
deleteComment(lastComment.id) deleteComment(lastComment.id)
@ -41,6 +59,19 @@ def withDefaultPrComments(closure) {
} }
} }
def sendCommentOnError(Closure closure) {
try {
closure()
} catch (ex) {
// If this is the first failed step, it's likely that the error hasn't propagated up far enough to mark the build as a failure
currentBuild.result = 'FAILURE'
catchErrors {
sendComment(false)
}
throw ex
}
}
// Checks whether or not this currently executing build was triggered via a PR in the elastic/kibana repo // Checks whether or not this currently executing build was triggered via a PR in the elastic/kibana repo
def isPr() { def isPr() {
return !!(env.ghprbPullId && env.ghprbPullLink && env.ghprbPullLink =~ /\/elastic\/kibana\//) return !!(env.ghprbPullId && env.ghprbPullLink && env.ghprbPullLink =~ /\/elastic\/kibana\//)
@ -66,7 +97,7 @@ def getLatestBuildInfo() {
} }
def getLatestBuildInfo(comment) { def getLatestBuildInfo(comment) {
return comment ? getBuildInfoFromComment(comment) : null return comment ? getBuildInfoFromComment(comment.body) : null
} }
def createBuildInfo() { def createBuildInfo() {
@ -137,14 +168,25 @@ def getTestFailuresMessage() {
return messages.join("\n") return messages.join("\n")
} }
def getNextCommentMessage(previousCommentInfo = [:]) { def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) {
def info = previousCommentInfo ?: [:] def info = previousCommentInfo ?: [:]
info.builds = previousCommentInfo.builds ?: [] info.builds = previousCommentInfo.builds ?: []
// When we update an in-progress comment, we need to remove the old version from the history
info.builds = info.builds.findAll { it.number != env.BUILD_NUMBER }
def messages = [] def messages = []
def status = buildUtils.getBuildStatus() def status = buildUtils.getBuildStatus()
if (status == 'SUCCESS') { if (!isFinal) {
def failuresPart = status != 'SUCCESS' ? ', with failures' : ''
messages << """
## :hourglass_flowing_sand: Build in-progress${failuresPart}
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
* Commit: ${getCommitHash()}
* This comment will update when the build is complete
"""
} else if (status == 'SUCCESS') {
messages << """ messages << """
## :green_heart: Build Succeeded ## :green_heart: Build Succeeded
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
@ -172,7 +214,9 @@ def getNextCommentMessage(previousCommentInfo = [:]) {
* [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps) * [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps)
* [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html) * [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html)
""" """
}
if (status != 'SUCCESS' && status != 'UNSTABLE') {
try { try {
def steps = getFailedSteps() def steps = getFailedSteps()
if (steps?.size() > 0) { if (steps?.size() > 0) {
@ -186,7 +230,10 @@ def getNextCommentMessage(previousCommentInfo = [:]) {
} }
messages << getTestFailuresMessage() messages << getTestFailuresMessage()
if (isFinal) {
messages << ciStats.getMetricsReport() messages << ciStats.getMetricsReport()
}
if (info.builds && info.builds.size() > 0) { if (info.builds && info.builds.size() > 0) {
messages << getHistoryText(info.builds) messages << getHistoryText(info.builds)
@ -208,7 +255,7 @@ def getNextCommentMessage(previousCommentInfo = [:]) {
.join("\n\n") .join("\n\n")
} }
def postComment(message) { def createComment(message) {
if (!isPr()) { if (!isPr()) {
error "Trying to post a GitHub PR comment on a non-PR or non-elastic PR build" error "Trying to post a GitHub PR comment on a non-PR or non-elastic PR build"
} }
@ -224,6 +271,20 @@ def getComments() {
} }
} }
def updateComment(commentId, message) {
if (!isPr()) {
error "Trying to post a GitHub PR comment on a non-PR or non-elastic PR build"
}
withGithubCredentials {
def path = "repos/elastic/kibana/issues/comments/${commentId}"
def json = toJSON([ body: message ]).toString()
def resp = githubApi([ path: path ], [ method: "POST", data: json, headers: [ "X-HTTP-Method-Override": "PATCH" ] ])
return toJSON(resp)
}
}
def deleteComment(commentId) { def deleteComment(commentId) {
withGithubCredentials { withGithubCredentials {
def path = "repos/elastic/kibana/issues/comments/${commentId}" def path = "repos/elastic/kibana/issues/comments/${commentId}"

View file

@ -10,7 +10,7 @@ def getSteps() {
def getFailedSteps() { def getFailedSteps() {
def steps = getSteps() def steps = getSteps()
def failedSteps = steps?.findAll { it.iconColor == "red" && it._class == "org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode" } def failedSteps = steps?.findAll { (it.iconColor == "red" || it.iconColor == "red_anime") && it._class == "org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode" }
failedSteps.each { step -> failedSteps.each { step ->
step.logs = "${env.BUILD_URL}execution/node/${step.id}/log".toString() step.logs = "${env.BUILD_URL}execution/node/${step.id}/log".toString()
} }

View file

@ -35,10 +35,12 @@ def functionalTestProcess(String name, Closure closure) {
"JOB=${name}", "JOB=${name}",
"KBN_NP_PLUGINS_BUILT=true", "KBN_NP_PLUGINS_BUILT=true",
]) { ]) {
githubPr.sendCommentOnError {
closure() closure()
} }
} }
} }
}
def functionalTestProcess(String name, String script) { def functionalTestProcess(String name, String script) {
return functionalTestProcess(name) { return functionalTestProcess(name) {
@ -163,6 +165,7 @@ def bash(script, label) {
} }
def doSetup() { def doSetup() {
githubPr.sendCommentOnError {
retryWithDelay(2, 15) { retryWithDelay(2, 15) {
try { try {
runbld("./test/scripts/jenkins_setup.sh", "Setup Build Environment and Dependencies") runbld("./test/scripts/jenkins_setup.sh", "Setup Build Environment and Dependencies")
@ -176,14 +179,19 @@ def doSetup() {
} }
} }
} }
}
def buildOss() { def buildOss() {
githubPr.sendCommentOnError {
runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana") runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana")
} }
}
def buildXpack() { def buildXpack() {
githubPr.sendCommentOnError {
runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana") runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana")
} }
}
def runErrorReporter() { def runErrorReporter() {
def status = buildUtils.getBuildStatus() def status = buildUtils.getBuildStatus()

View file

@ -126,11 +126,13 @@ def intake(jobName, String script) {
return { return {
ci(name: jobName, size: 's-highmem', ramDisk: true) { ci(name: jobName, size: 's-highmem', ramDisk: true) {
withEnv(["JOB=${jobName}"]) { withEnv(["JOB=${jobName}"]) {
githubPr.sendCommentOnError {
runbld(script, "Execute ${jobName}") runbld(script, "Execute ${jobName}")
} }
} }
} }
} }
}
// Worker for running functional tests. Runs a setup process (e.g. the kibana build) then executes a map of closures in parallel (e.g. one for each ciGroup) // Worker for running functional tests. Runs a setup process (e.g. the kibana build) then executes a map of closures in parallel (e.g. one for each ciGroup)
def functional(name, Closure setup, Map processes) { def functional(name, Closure setup, Map processes) {