Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [CI] Record Github commit statuses outside of PRs (#69432) #69722

Merged
merged 1 commit into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .ci/pipeline-library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {
implementation 'org.jenkins-ci.plugins.workflow:workflow-step-api:2.19@jar'
testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.4'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.+'
testImplementation 'org.assertj:assertj-core:3.15+' // Temporary https://github.com/jenkinsci/JenkinsPipelineUnit/issues/209
}

Expand Down
7 changes: 5 additions & 2 deletions .ci/pipeline-library/src/test/KibanaBasePipelineTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class KibanaBasePipelineTest extends BasePipelineTest {
env.BUILD_DISPLAY_NAME = "#${env.BUILD_ID}"

env.JENKINS_URL = 'http://jenkins.localhost:8080'
env.BUILD_URL = "${env.JENKINS_URL}/job/elastic+kibana+${env.BRANCH_NAME}/${env.BUILD_ID}/"
env.BUILD_URL = "${env.JENKINS_URL}/job/elastic+kibana+${env.BRANCH_NAME}/${env.BUILD_ID}/".toString()

env.JOB_BASE_NAME = "elastic / kibana # ${env.BRANCH_NAME}"
env.JOB_BASE_NAME = "elastic / kibana # ${env.BRANCH_NAME}".toString()
env.JOB_NAME = env.JOB_BASE_NAME

env.WORKSPACE = 'WS'
Expand All @@ -31,6 +31,9 @@ class KibanaBasePipelineTest extends BasePipelineTest {
getBuildStatus: { 'SUCCESS' },
printStacktrace: { ex -> print ex },
],
githubPr: [
isPr: { false },
],
jenkinsApi: [ getFailedSteps: { [] } ],
testUtils: [ getFailures: { [] } ],
])
Expand Down
48 changes: 48 additions & 0 deletions .ci/pipeline-library/src/test/buildState.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import org.junit.*
import static groovy.test.GroovyAssert.*

class BuildStateTest extends KibanaBasePipelineTest {
def buildState

@Before
void setUp() {
super.setUp()

buildState = loadScript("vars/buildState.groovy")
}

@Test
void 'get() returns existing data'() {
buildState.add('test', 1)
def actual = buildState.get('test')
assertEquals(1, actual)
}

@Test
void 'get() returns null for missing data'() {
def actual = buildState.get('missing_key')
assertEquals(null, actual)
}

@Test
void 'add() does not overwrite existing keys'() {
assertTrue(buildState.add('test', 1))
assertFalse(buildState.add('test', 2))

def actual = buildState.get('test')

assertEquals(1, actual)
}

@Test
void 'set() overwrites existing keys'() {
assertFalse(buildState.has('test'))
buildState.set('test', 1)
assertTrue(buildState.has('test'))
buildState.set('test', 2)

def actual = buildState.get('test')

assertEquals(2, actual)
}
}
85 changes: 85 additions & 0 deletions .ci/pipeline-library/src/test/githubCommitStatus.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import org.junit.*
import static org.mockito.Mockito.*;

class GithubCommitStatusTest extends KibanaBasePipelineTest {
def githubCommitStatus
def githubApiMock
def buildStateMock

def EXPECTED_STATUS_URL = 'repos/elastic/kibana/statuses/COMMIT_HASH'
def EXPECTED_CONTEXT = 'kibana-ci'
def EXPECTED_BUILD_URL = 'http://jenkins.localhost:8080/job/elastic+kibana+master/1/'

interface BuildState {
Object get(String key)
}

interface GithubApi {
Object post(String url, Map data)
}

@Before
void setUp() {
super.setUp()

buildStateMock = mock(BuildState)
githubApiMock = mock(GithubApi)

when(buildStateMock.get('checkoutInfo')).thenReturn([ commit: 'COMMIT_HASH', ])
when(githubApiMock.post(any(), any())).thenReturn(null)

props([
buildState: buildStateMock,
githubApi: githubApiMock,
])

githubCommitStatus = loadScript("vars/githubCommitStatus.groovy")
}

void verifyStatusCreate(String state, String description) {
verify(githubApiMock).post(
EXPECTED_STATUS_URL,
[
'state': state,
'description': description,
'context': EXPECTED_CONTEXT,
'target_url': EXPECTED_BUILD_URL,
]
)
}

@Test
void 'onStart() should create a pending status'() {
githubCommitStatus.onStart()
verifyStatusCreate('pending', 'Build started.')
}

@Test
void 'onFinish() should create a success status'() {
githubCommitStatus.onFinish()
verifyStatusCreate('success', 'Build completed successfully.')
}

@Test
void 'onFinish() should create an error status for failed builds'() {
mockFailureBuild()
githubCommitStatus.onFinish()
verifyStatusCreate('error', 'Build failed.')
}

@Test
void 'onStart() should exit early for PRs'() {
prop('githubPr', [ isPr: { true } ])

githubCommitStatus.onStart()
verifyZeroInteractions(githubApiMock)
}

@Test
void 'onFinish() should exit early for PRs'() {
prop('githubPr', [ isPr: { true } ])

githubCommitStatus.onFinish()
verifyZeroInteractions(githubApiMock)
}
}
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
library 'kibana-pipeline-library'
kibanaLibrary.load()

kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true) {
kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true) {
githubPr.withDefaultPrComments {
ciStats.trackBuild {
catchError {
Expand Down
30 changes: 30 additions & 0 deletions vars/buildState.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import groovy.transform.Field

public static @Field JENKINS_BUILD_STATE = [:]

def add(key, value) {
if (!buildState.JENKINS_BUILD_STATE.containsKey(key)) {
buildState.JENKINS_BUILD_STATE[key] = value
return true
}

return false
}

def set(key, value) {
buildState.JENKINS_BUILD_STATE[key] = value
}

def get(key) {
return buildState.JENKINS_BUILD_STATE[key]
}

def has(key) {
return buildState.JENKINS_BUILD_STATE.containsKey(key)
}

def get() {
return buildState.JENKINS_BUILD_STATE
}

return this
42 changes: 42 additions & 0 deletions vars/githubCommitStatus.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
def shouldCreateStatuses() {
return !githubPr.isPr() && buildState.get('checkoutInfo')
}

def onStart() {
catchError {
if (!shouldCreateStatuses()) {
return
}

def checkoutInfo = buildState.get('checkoutInfo')
create(checkoutInfo.commit, 'pending', 'Build started.')
}
}

def onFinish() {
catchError {
if (!shouldCreateStatuses()) {
return
}

def checkoutInfo = buildState.get('checkoutInfo')
def status = buildUtils.getBuildStatus()

if (status == 'SUCCESS' || status == 'UNSTABLE') {
create(checkoutInfo.commit, 'success', 'Build completed successfully.')
} else if(status == 'ABORTED') {
create(checkoutInfo.commit, 'error', 'Build aborted or timed out.')
} else {
create(checkoutInfo.commit, 'error', 'Build failed.')
}
}
}

// state: error|failure|pending|success
def create(sha, state, description, context = 'kibana-ci') {
withGithubCredentials {
return githubApi.post("repos/elastic/kibana/statuses/${sha}", [ state: state, description: description, context: context, target_url: env.BUILD_URL ])
}
}

return this
13 changes: 11 additions & 2 deletions vars/kibanaPipeline.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,15 @@ def runErrorReporter() {
}

def call(Map params = [:], Closure closure) {
def config = [timeoutMinutes: 135, checkPrChanges: false] + params
def config = [timeoutMinutes: 135, checkPrChanges: false, setCommitStatus: false] + params

stage("Kibana Pipeline") {
timeout(time: config.timeoutMinutes, unit: 'MINUTES') {
timestamps {
ansiColor('xterm') {
if (config.setCommitStatus) {
buildState.set('shouldSetCommitStatus', true)
}
if (config.checkPrChanges && githubPr.isPr()) {
pipelineLibraryTests()

Expand All @@ -213,7 +216,13 @@ def call(Map params = [:], Closure closure) {
return
}
}
closure()
try {
closure()
} finally {
if (config.setCommitStatus) {
githubCommitStatus.onFinish()
}
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions vars/workers.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ def base(Map params, Closure closure) {

dir("kibana") {
checkoutInfo = getCheckoutInfo()

// use `checkoutInfo` as a flag to indicate that we've already reported the pending commit status
if (buildState.get('shouldSetCommitStatus') && !buildState.has('checkoutInfo')) {
buildState.set('checkoutInfo', checkoutInfo)
githubCommitStatus.onStart()
}
}

ciStats.reportGitInfo(
Expand Down