Issue
I use the multibranch plugin to scan new pull requests, and I need to limit my builds to approved pull requests only. I filter the approved pull requests after they are scanned, however the repository scanner can only tell whether a pull request has new commits.
I tried the the PR comment build plugin and added the Trigger build on pull request review
property to the github branch source to no avail - it seems that adding this property doesn't have any effect on how the scanner processes pull requests.
Can I tell the repository scanner to trigger a build on new reviews? Is there any other way to build pull requests only after approval?
Thanks!
Solution
I had to accept that there was no way to make the branch scan consider non-git objects (such as github reviews).
I ended up making a pipeline that kept a list of all the yet-to-be-approved pull requests (using the github API), and once a pull request was approved it would trigger a build on it.
It feels hacky, but unfortunately that was the only way I could think of to build only on approval... Important note: this solution requires an existing multibranch job to work with. So this is what I did:
First query for the existing pull requests and their status (install the httpRequest plugin):
// Send a query to github and get the response JSON
def githubQuery(Map args = [:]) {
def formattedQuery = args.query.replaceAll('\n', ' ').replaceAll('"', '\\\\"')
def response = httpRequest(
authentication: args.auth,
httpMode: 'POST',
requestBody: """{ "query": "${formattedQuery}" }""",
url: "https://api.github.com/graphql"
)
(response.status == 200) ? readJSON(text: response.content) : [:]
}
def getPRStatus(Map args = [:]) {
// Build the query to get all open pull requests and their status
def query = """query {
organization(login: "${args.organization}") {
repositories(first: 30) {
nodes {
name
pullRequests(first: 100, states: [OPEN]) {
nodes {
number
state
reviews(first: 10, states: [APPROVED]) {
totalCount
}
}
}
}
}
}
}"""
def response = githubQuery(args + [query: query])
def repositories = response?.data.organization.repositories.nodes
// Organize the pull requests into approved and unapproved groups
repositories?.collectEntries { repo ->
// Take out draft pull requests
def prs = repo.pullRequests.nodes.findAll { it.state != "DRAFT" }
def repoPrs = [
unapproved: prs.findAll { it.reviews.totalCount == 0 },
approved: prs.findAll { it.reviews.totalCount > 0 }
].collectEntries { category, categoryPrs ->
[ category, categoryPrs.collect { it.number } ]
}
[ repo.name, repoPrs ]
}
}
Then compare each pull request's status to its status from the previous poll, and build only those that changed their status to approved:
def monitorRecentlyApprovedPRs(Map args = [:]) {
def prMap = getPRStatus(args)
// Build recently approved pull requests on each repository
prMap.each { repoName, repoPrs ->
// Get previously unapproved pull requests
def previouslyUnapproved = currentBuild.previousBuild?.buildVariables?."${repoName}"?.tokenize(",").collect { it.toInteger() } ?: []
// Build recently approved pull requests
repoPrs.approved.intersect(previouslyUnapproved).each { prNumber ->
build job: "/${args.multibranch}/PR-${prNumber}", wait: false
}
env."${repoName}" = repoPrs.unapproved.join(",")
}
}
When calling monitorRecentlyApprovedPRs
you'll have to provide these arguments:
monitorRecentlyApprovedPRs organization: "YOUR-ORGANIZATION", auth: "GITHUB-CREDENTIALS", multibranch: "PATH-TO-THE-MULTIBRANCH-JOB-IN-JENKINS"
Finally, update the multibranch's Jenkinsfile
to skip unapproved PRs:
def shouldBuildPR(Map args = [:]) {
// Get pull request info
def query = """query {
organization(login: "${args.organization}") {
repository(name: "${args.repo}") {
pullRequest(number: ${args.pr}) {
state
reviews(first: 10, states: [APPROVED]) {
totalCount
}
}
}
}
}"""
def response = githubQuery(args + [query: query])
def prInfo = response?.data.organization.repository.pullRequest
def shouldBuild = (
// Skip merged pull requests
prInfo.state != "MERGED" &&
// Check for draft state
(prInfo.state != "DRAFT") &&
// Check for approval
(prInfo.reviews.totalCount > 0)
)
shouldBuild
}
To call shouldBuildPR
you'll provide these arguments:
shouldBuildPR(organization: "YOUR-ORGANIZATION", repo: "PR-REPO", auth: "GITHUB-CREDENTIALS", pr: env.CHANGE_ID)
If the returned value is false
you should stop the rest of the pipeline's execution. Things would have been a lot simpler if the multibranch pipeline plugin provided a PR status environment variable :)
Answered By - towel