Browse Source

Improve Deployment Pipeline/Gerrit feedback

Writes and posts easy-to-understand Gerrit comments from the Deployment
Pipeline.

This is the alternative to the current circuitous path of
links users of the pipeline are meant to follow to find basic
information about build status, Docker images, and Docker tags.

Adds two new classes that are meant to be invoked from inside a Jenkins
job defined as a Jenkins Pipeline script. By providing access to a few
build parameters from within a Jenkins job, this patch will post a
comment to Gerrit using an output format that will be styled by Gerrit
commentstyles implemented in I5b04aa10d54b6f2587da196c02ebff9bfe5ba166.

Example posted message:

    pipeline-dashboard: service-pipeline
    pipeline-build-result: SUCCESS (job: service-pipeline, build: 27)
    IMAGE:
     docker-registry.wikimedia.org/wikimedia/mediawiki-services-citoid

    TAGS:
     test, latest

Change-Id: I2fc0924996eb1a969fcbf41bac333d3c35cd34ea
master
Tyler Cipriani 2 years ago
parent
commit
43b64f2c0b
7 changed files with 360 additions and 0 deletions
  1. +21
    -0
      Makefile
  2. +13
    -0
      README.md
  3. +8
    -0
      src/org/wikimedia/integration/GerritComment.groovy
  4. +82
    -0
      src/org/wikimedia/integration/GerritPipelineComment.groovy
  5. +119
    -0
      src/org/wikimedia/integration/GerritReview.groovy
  6. +79
    -0
      test/org/wikimedia/integration/GerritPipelineCommentTest.groovy
  7. +38
    -0
      test/org/wikimedia/integration/GerritReviewTest.groovy

+ 21
- 0
Makefile View File

@ -0,0 +1,21 @@
SHELL := /bin/bash
GRADLE := $(shell command -v gradle)
BLUBBER := $(shell command -v blubber)
DOCKER := $(shell command -v docker)
DOCKER_TAG := piplinelib-tests-$(shell date -I)
.PHONY: test
test:
ifneq (,$(GRADLE))
gradle test
@exit 0
else ifneq (,$(and $(BLUBBER), $(DOCKER)))
blubber .pipeline/blubber.yaml test | docker build -t "$(DOCKER_TAG)" -f - .
docker run --rm -it "$(DOCKER_TAG)"
docker rmi "$(DOCKER_TAG)"
@exit 0
else
@echo "Can't find Gradle or Blubber/Docker. Install one to run tests."
@exit 1
endif

+ 13
- 0
README.md View File

@ -0,0 +1,13 @@
# README for pipelinelib
`pipelinelib` is a library built for use in the [Wikimedia Deployment
Pipeline][pipeline]. It is a library of Groovy code that is loaded by Jenkins
to execute tasks related to the Pipeline.
[pipeline]: https://wikitech.wikimedia.org/wiki/Deployment_pipeline
## Running Tests
To run tests use GNU Make:
make test

+ 8
- 0
src/org/wikimedia/integration/GerritComment.groovy View File

@ -0,0 +1,8 @@
package org.wikimedia.integration
/**
* Abstract class to represent the contract of a gerrit comment
*/
abstract class GerritComment {
String formatMessage
}

+ 82
- 0
src/org/wikimedia/integration/GerritPipelineComment.groovy View File

@ -0,0 +1,82 @@
package org.wikimedia.integration
/**
* Gerrit revision that can be used to comment on a gerrit patchset
*
* {@code
* import org.wikimedia.integration.GerritReview
* import org.wikimedia.integration.GerritPipelineComment
*
* stage('comment') {
* comment = new GerritPipelineComment(
* jobName: xx,
* buildNumber: xx,
* jobStatus: xx,
* image: xx,
* tags: xx
* )
* GerritReview.post(this, comment)
* }
*/
class GerritPipelineComment extends GerritComment implements Serializable {
/**
* Name of the job
*/
String jobName
/**
* Jenkins build number
*/
String buildNumber
/**
* Image in the docker registry
*/
String image
/**
* Build status
*/
String jobStatus
/**
* Image tags
*/
List<String> tags
String formatDashboard() {
"pipeline-dashboard: ${this.jobName}"
}
String formatResult() {
"pipeline-build-result: ${this.jobStatus} (job: ${this.jobName}, build: ${this.buildNumber})"
}
String formatImage() {
"IMAGE:\n ${this.image}"
}
String formatTags() {
"TAGS:\n ${this.tags.join(', ')}"
}
/**
* Format final message output
*/
String formatMessage() {
def msg = "${this.formatDashboard()}\n${this.formatResult()}\n"
if (this.image != null) {
msg = "${msg}\n${this.formatImage()}\n"
}
if (this.tags != null) {
msg = "${msg}\n${this.formatTags()}\n"
}
msg
}
GerritPipelineComment(Map settings = [:]) {
settings.each { prop, value -> this.@"${prop}" = value }
}
}

+ 119
- 0
src/org/wikimedia/integration/GerritReview.groovy View File

@ -0,0 +1,119 @@
package org.wikimedia.integration
import java.net.URLEncoder
import groovy.json.JsonOutput
/**
* Gerrit review that can be used to comment on a gerrit patchset
*
* {@code
* import org.wikimedia.integration.GerritReview
* import org.wikimedia.integration.GerritComment
*
* stage('comment') {
* comment = new GerritComment(
* jobName: xx,
* buildNumber: xx,
* jobStatus: xx,
* image: xx,
* tags: xx
* )
* GerritReview.post(this, comment)
* }
* }
*/
class GerritReview implements Serializable {
/**
* Jenkins pipeline workflow script context.
*/
final def workflowScript
/**
* Gerrit URL
*/
final def gerritURL = 'https://gerrit.wikimedia.org/r'
/**
* Name of the auth credentials in jenkins to use in gerrit
*/
final def gerritAuthCreds = 'gerrit.pipelinebot'
/**
* GerritComment with information for message body
*/
final GerritComment comment
GerritReview(workflowScript, GerritComment comment) {
this.workflowScript = workflowScript
this.comment = comment
}
/**
* URLEncoded ZUUL_PROJECT from the environment.
*
* This should be set for all patchsets.
*/
String getProject() {
URLEncoder.encode(this.workflowScript.env.ZUUL_PROJECT, 'UTF-8')
}
/**
* Return a full authorized url for a gerrit revision review.
*
* May return an empty string in the cases of an unexpected environment.
*/
String getRequestURL() {
def change = this.workflowScript.env.ZUUL_CHANGE
def revision = this.workflowScript.env.ZUUL_PATCHSET
if (! revision || ! change) { return "" }
def changeId = [this.getProject(), change].join('~')
[
this.gerritURL,
'a/changes',
changeId,
'revisions',
revision,
'review',
].join('/')
}
/**
* Format gerritcomment as a json message.
*/
String getBody() {
JsonOutput.toJson([message: this.comment.formatMessage()])
}
/**
* Static method to POST GerritComment to a particular change.
*
* Uses the Gerrit RESTAPI to POST a comment on a patchset revision.
*
* @param workflowScript Jenkins workflow script context.
* @param comment GerritComment
*/
static String post(workflowScript, GerritComment comment) {
def gr = new GerritReview(workflowScript, comment)
def url = gr.getRequestURL()
if (! url) {
gr.workflowScript.error "Could not determine Gerrit ChangeID from Environment. Aborting."
}
def response = gr.workflowScript.httpRequest(
url: url,
httpMode: 'POST',
customHeaders: [[name: "content-type", value: 'application/json']],
requestBody: gr.getBody(),
consoleLogResponseBody: true,
validResponseCodes: "200",
authentication: gr.gerritAuthCreds
)
response.content
}
}

+ 79
- 0
test/org/wikimedia/integration/GerritPipelineCommentTest.groovy View File

@ -0,0 +1,79 @@
import groovy.util.GroovyTestCase
import org.wikimedia.integration.GerritPipelineComment
class GerritCommentTestCase extends GroovyTestCase {
private GerritPipelineComment gerritComment
void testGetDashboardOutput() {
gerritComment = new GerritPipelineComment(
jobName: "service-pipeline-test-and-publish"
)
assert gerritComment.formatDashboard() == 'pipeline-dashboard: service-pipeline-test-and-publish'
}
void testGetResultOutput() {
gerritComment = new GerritPipelineComment(
jobName: 'service-pipeline-test-and-publish',
jobStatus: 'SUCCESS',
buildNumber: '25'
)
assert gerritComment.formatResult() == \
'pipeline-build-result: SUCCESS (job: service-pipeline-test-and-publish, build: 25)'
}
void testGetFormatImage() {
def imageName = 'docker-registry.wikimedia.org/wikimedia/mediawiki-services-citoid'
def expected = "IMAGE:\n ${imageName}"
gerritComment = new GerritPipelineComment(image: imageName)
assert gerritComment.formatImage() == expected
}
void testGetFormatTags() {
def tags = ['2019-02-11-214153-production', 'fc52e49b051872b282c6a66be6649c7d437bf066']
def expected = "TAGS:\n 2019-02-11-214153-production, fc52e49b051872b282c6a66be6649c7d437bf066"
gerritComment = new GerritPipelineComment(tags: tags)
assert gerritComment.formatTags() == expected
}
void testwithoutImage() {
def expected = '''\
pipeline-dashboard: service-pipeline-test-and-publish
pipeline-build-result: SUCCESS (job: service-pipeline-test-and-publish, build: 25)
'''.stripIndent()
gerritComment = new GerritPipelineComment(
jobName: 'service-pipeline-test-and-publish',
jobStatus: 'SUCCESS',
buildNumber: '25',
)
assert gerritComment.formatMessage() == expected
}
void testwithImage() {
def tags = ['2019-02-11-214153-production', 'fc52e49b051872b282c6a66be6649c7d437bf066']
def imageName = 'docker-registry.wikimedia.org/wikimedia/mediawiki-services-citoid'
def expected = '''\
pipeline-dashboard: service-pipeline-test-and-publish
pipeline-build-result: SUCCESS (job: service-pipeline-test-and-publish, build: 25)
IMAGE:
docker-registry.wikimedia.org/wikimedia/mediawiki-services-citoid
TAGS:
2019-02-11-214153-production, fc52e49b051872b282c6a66be6649c7d437bf066
'''.stripIndent()
gerritComment = new GerritPipelineComment(
jobName: 'service-pipeline-test-and-publish',
jobStatus: 'SUCCESS',
buildNumber: '25',
image: imageName,
tags: tags
)
assert gerritComment.formatMessage() == expected
}
}

+ 38
- 0
test/org/wikimedia/integration/GerritReviewTest.groovy View File

@ -0,0 +1,38 @@
import groovy.util.GroovyTestCase
import org.wikimedia.integration.GerritReview
import org.wikimedia.integration.GerritPipelineComment
class GerritReviewTestCase extends GroovyTestCase {
private class Env {
String ZUUL_PATCHSET = '8'
String ZUUL_CHANGE = '486851'
String ZUUL_PROJECT = 'mediawiki/services/citoid'
}
private class WorkflowScript {
Env env
WorkflowScript(env) { this.env = env }
}
void testReviewURL() {
def gr = new GerritReview(new WorkflowScript(new Env()), new GerritPipelineComment())
assert gr.getProject() == 'mediawiki%2Fservices%2Fcitoid'
assert gr.getRequestURL() == 'https://gerrit.wikimedia.org/r/a/changes/mediawiki%2Fservices%2Fcitoid~486851/revisions/8/review'
}
void testReviewBody() {
def expected = '{"message":"pipeline-dashboard: service-pipeline-test-and-publish\\n'
expected += 'pipeline-build-result: SUCCESS '
expected += '(job: service-pipeline-test-and-publish, build: 25)\\n"}'
def gerritComment = new GerritPipelineComment(
jobName: 'service-pipeline-test-and-publish',
jobStatus: 'SUCCESS',
buildNumber: '25',
)
def gr = new GerritReview(new WorkflowScript(new Env()), gerritComment)
assert gr.getBody() == expected
}
}

Loading…
Cancel
Save