Skip to content

Instantly share code, notes, and snippets.

@angelworm
Created October 17, 2019 05:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save angelworm/a658d7972cc361ffd3b4ab6b45c5420f to your computer and use it in GitHub Desktop.
Save angelworm/a658d7972cc361ffd3b4ab6b45c5420f to your computer and use it in GitHub Desktop.
Review Boardに投げられたレビューをJenkinsでビルドする。
#!/bin/bash -xu
## Required Environment Variables
#
# REVIEWBOARD_TOKEN: Access tokens for Review Board user.
# REVIEW_DIFF_URL: Posted review request URL. (ex. https://example.org/reviews/api/review-requests/99999/diffs/1/)
#
# upgrade workspace(for unexpectedly subversion binary upgrade)
svn upgrade
# remove untracked files(clean check out)
svn status | grep '^?' | awk '{print $2}' | xargs rm -rfv
# fetch specified diff.
curl -sL \
-H "Authorization: token ${REVIEWBOARD_TOKEN}" \
-H "Accept: text/x-patch" \
${REVIEW_DIFF_URL} \
> review.patch
# fetch diff information
curl -sL \
-H "Authorization: token ${REVIEWBOARD_TOKEN}" \
${REVIEW_DIFF_URL} \
> review_info.json
BASEDIR=$(python << EOL
import sys, json, re
with open('review_info.json', 'r') as target:
basedir = json.load(target).get('diff', {}).get('basedir', '.')
print(basedir)
EOL
)
# patches diff
rm -f REJECT.diff
grep -E '^Index:' review.patch | sed "s|^Index: /\?|$BASEDIR/|g" | xargs sed -i 's/\r//g' || true
patch -p $STRIP_PATH_PREFIX -r REJECT.diff -l --binary -d $BASEDIR -i review.patch
# check patching is successful.
if [[ -s REJECT.diff ]]; then
echo "Patch Failed!"
exit 1
fi
import jenkins.model.*
import java.nio.file.*
def reviewBoardAPIURL = "https://example.org/reviews/api"
def jenkinsJobName = "jenkins_review_ci"
def requestId = build.envVars.REVIEW_REQUEST_ID
def diffUri = build.envVars.REVIEW_DIFF_URL
def token = "${REVIEWBOARD_TOKEN}"
def project = Jenkins.instance.projects.find {
it.name == jenkinsJobName
}
def build = project.builds.find {
it.id == "${TARGET_JOB_ID}"
}
def actions = build.actions
println "REVIEW_REQUEST_ID: ${requestId}"
println "TARGET_JOB_ID: ${TARGET_JOB_ID}"
/* *** *** collet new warnings *** *** */
def newWarnings = actions.findAll {
it instanceof hudson.plugins.analysis.core.ResultAction
}.collect {
it.result.newWarnings
}.flatten()
if (newWarnings.isEmpty()) {
println "Review: No Errors Found!"
return
}
def r
/* *** *** collect diff lines *** *** */
r = request(token, "GET", "${diffUri}files/", [
"max-results": "200"
])
def files = r.files
println "Files: ${files.collect {it.dest_file}}"
def updatedLines = r.files.collectEntries {
def fileUrl = it.links.self.href
def targetFileName = it.dest_file
def ri = request(token, "GET", fileUrl, new HashMap(), "application/vnd.reviewboard.org.diff.data+json")
def lines = ri.diff_data.chunks.findAll {
it.change != 'equal' && it.change != 'delete'
}.collect {
it.lines.collect { it[4] }
}.flatten()
[targetFileName, lines]
}
/* *** *** collect RR target annotations *** *** */
def annots = newWarnings.collect {
def annot = it
def found = files.find {
def rfp = Paths.get(it.dest_file)
def afp = Paths.get(annot.pathName, annot.shortFileName)
rfp.endsWith(afp)
}
new Tuple(annot, found)
}.findAll {
def (a, f) = it
if (f == null) {
println "RR has not updated: reported: ${a.pathName}/${a.shortFileName}"
}
return f != null
}.findAll {
def (a, f) = it
if (!(a instanceof hudson.plugins.checkstyle.parser.Warning)) {
return true
}
// if (a instanceof hudson.plugins.warnings.parser.Warning) {
// return true
// }
def updated = updatedLines.find {
def rfp = Paths.get(f.dest_file)
def afp = Paths.get(it.key)
rfp.endsWith(afp)
}
def linums = updated ?. value ?: []
def ret = a.lineRanges.any { al ->
linums.any { al.start <= it && it <= al.end }
}
if (!ret) {
println "Filtered: ${a.shortFileName}:${a.primaryLineNumber} ${a.message}"
}
ret
}
if (annots.isEmpty()) {
println "Review: No files matched."
return
}
/* *** *** DRY RUN MODE *** *** */
if ("true" == "${DRY_RUN_MODE}") {
annots.each {
def (annot, found) = it
println "Review File: ${annot.pathName}/${annot.shortFileName}: (${annot.origin}) [${annot.type}]${annot.message}"
}
return
}
/* *** *** Post RR *** *** */
r = request(token, "POST", "review-requests/${requestId}/reviews/", new HashMap())
def reviewId = r.review.id
println "Review: #${reviewId}"
try {
annots.each {
def (annot, found) = it
println "Review File: ${annot.pathName}/${annot.shortFileName}:${annot.primaryLineNumber} : (${annot.origin}) [${annot.type}]${annot.message}"
request(token, "POST", "review-requests/${requestId}/reviews/${reviewId}/diff-comments/", [
filediff_id: "${found.id}",
first_line: "${Math.max(0, annot.primaryLineNumber)}",
num_lines: "1",
issue_opened: "true",
text: "(${annot.origin}) [${annot.type}]${annot.message}"
])
}
request(token, "PUT", "review-requests/${requestId}/reviews/${reviewId}/", [
body_top: """
BUILD ${build.result}: ${build.absoluteUrl}
""",
public: "true"
])
} catch (Exception e) {
try {
request(token, "DELETE", "review-requests/${requestId}/reviews/${reviewId}/", new HashMap())
} catch (Exception ie) {
e.addSuppressed(ie)
}
throw e
}
def request(String token, String method, String action, Map<String, String> params = [], String acceptType = "application/json") {
def requestUri = action.startsWith("http") ? action : "${reviewBoardAPIURL}/${action}"
def paramStr = params.collect { "${it.key}=${java.net.URLEncoder.encode(it.value, 'UTF-8')}" } join '&'
if (method.equalsIgnoreCase('GET')) {
requestUri = "${requestUri}?${paramStr}"
paramStr = ""
}
println "Request: ${method} ${requestUri}"
URL url = new URL(requestUri)
HttpURLConnection conn = (HttpURLConnection)url.openConnection()
conn.doInput = true
conn.setRequestMethod(method)
conn.setRequestProperty("Connection", "Keep-Alive")
conn.setRequestProperty("Authorization", "token ${token}")
conn.setRequestProperty("Accept", acceptType)
def bytes = paramStr.getBytes "UTF-8"
if (bytes.length != 0) {
conn.doOutput = true
conn.setRequestProperty "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
conn.setRequestProperty "Content-Length", "${bytes.length}"
}
try {
if (bytes.length != 0) {
conn.outputStream.write(bytes)
conn.outputStream.flush()
}
conn.connect()
def r = new BufferedReader(new InputStreamReader(conn.inputStream))
try {
def json = new groovy.json.JsonSlurper()
return json.parse(r)
} catch (NullPointerException e) {
return null
} finally {
r.close()
}
} finally {
conn.disconnect()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment