Hello,
I am working on making our pull requests merge automatically if a certain set of conditions are true.
However, I have not been successful yet and therefore kindly ask for your help!
I am using the adaptavist ScriptRunner for Bitbucket version 4.5.2 (Self-hosted Bitbucket).
By testing I have figured out that a merge, triggered from adaptavist ScriptRunner, can only happen if the pull request "conditions" are met.
In my case:
✓ Requires 1 approvers
✓ Requires all tasks to be resolved
✓ Requires a minimum of 1 successful builds
It seems like build status and issue state is not part of the pull request? I do not know if this is true? But I can not find any information about e.g. build statuses in the documentation here: https://developer.atlassian.com/static/javadoc/bitbucket-server/4.5.2/api/reference/com/atlassian/bitbucket/event/pull/package-summary.html
I can make the adaptavist ScriptRunner fire events e.g. in case of approval with the following code:
event.pullRequest.reviewers.count {it.approved} >= 1
However if the tasks are not resolved or there are no successful builds the merge will not happen (This is as it should be!)
But when tasks do get resolved or a successful build comes in I cannot get the adaptavist ScriptRunner to trigger, I have tried with the following code:
event.pullRequest
If my suspicions is true and Bitbucket only handles pull requests events which are defined in git this means that I have no way to check for these events with the adaptavist ScriptRunner right?
Some elegant solution for me would be to make Bitbucket and adaptavist ScriptRunner able to trigger on all events on a pull request, i.e. issue state change in jira, build status from CI, approvals.
Or make a scheduler which checks every 10 seconds if something is ready to be merged.
I have heard about a workaround where the CI server tries to merge the pull requests if the conditions are met, but I think this is a far from optimal solution.
If anymore information is needed feel free to ask!
Anyone have any information or helpful thoughts about how I can get this to work?
This question took a long time to find. In the future could you add the tag "com.onresolve.jira.stash.groovyrunner" so we can answer more quickly. I have added it for you now.
Your correct the pull request does not hold all this information, even if it did it wouldn't trigger with the auto merge built-in event handler, thats only triggered on a pull request approval. You will need to do a custom event listener.
This is not trivial to do for a number of reasons. The first being that not all events are triggered in Bitbucket, some are in JIRA also.
Although I have got a partial solution for you which comprises of an event listener which is triggered on a pull request approval which then uses an application link between JIRA and Bitbucket to get issue statuses and the BuildStatusService to get the build status
First you need a custom event handler script, adding this an the inline script for events, PullRequestApprovedEvent:
import com.atlassian.bitbucket.build.BuildStatusService import com.atlassian.bitbucket.event.pull.PullRequestApprovedEvent import com.atlassian.bitbucket.integration.jira.JiraIssueService import com.atlassian.bitbucket.pull.PullRequest import com.atlassian.bitbucket.pull.PullRequestMergeRequest import com.atlassian.bitbucket.pull.PullRequestService import com.atlassian.bitbucket.user.SecurityService import com.atlassian.bitbucket.util.Operation import com.atlassian.sal.api.component.ComponentLocator import com.atlassian.sal.api.net.Request import com.atlassian.sal.api.net.Response import com.atlassian.sal.api.net.ResponseException import com.atlassian.sal.api.net.ReturningResponseHandler import com.onresolve.scriptrunner.canned.bitbucket.util.BitbucketBaseScript import com.onresolve.scriptrunner.runner.customisers.WithPlugin import groovy.transform.BaseScript //✓ Requires 1 approvers //✓ Requires all tasks to be resolved //✓ Requires a minimum of 1 successful build @BaseScript BitbucketBaseScript baseScript def buildStatusService = ComponentLocator.getComponent(BuildStatusService) def jiraIssueService = ComponentLocator.getComponent(JiraIssueService) def securityService = ComponentLocator.getComponent(SecurityService) def pullRequestService = ComponentLocator.getComponent(PullRequestService) def pullRequests = [] def repository = null if (event instanceof PullRequestApprovedEvent) { def event = event as PullRequestApprovedEvent pullRequests.add(event.pullRequest) } // we need to gather the pull requests for each event using above approach // go through each pull request and check conditions pullRequests.each { pullRequest -> // require 1 approver if (pullRequest.participants.find { it.approved }) { def summaries = buildStatusService.getSummaries(pullRequest.getCommits()*.id) def successfulBuild = summaries.values().find { it.successfulCount > 0 } // require 1 successful build if (successfulBuild) { def issueKeys = jiraIssueService.getIssuesForPullRequest(repository, pullRequest.id)*.key def jiraLink = getJiraAppLink() def authenticatedRequestFactory = jiraLink.createImpersonatingAuthenticatedRequestFactory() def allIssuesResolved = issueKeys.every { String key -> def response = authenticatedRequestFactory .createRequest(Request.MethodType.GET, "/rest/api/2/issue/$key?fields=status") .addHeader("Content-Type", "application/json") .executeAndReturn(new ReturningResponseHandler<Response, Map>() { @Override Map handle(Response response) throws ResponseException { if (response.statusCode != HttpURLConnection.HTTP_OK) { throw new Exception(response.getResponseBodyAsString()) } return response.getEntity(Map) } }) response.fields.status.name == "Done" } // require all issues to be resolved - this doesn't handle subtasks or linked issues if (allIssuesResolved) { mergePullRequest(securityService, pullRequest, pullRequestService) } } } } private Object mergePullRequest(SecurityService securityService, PullRequest pullRequest, pullRequestService) { securityService.impersonating(pullRequest.author.user, "Merging pull-request on behalf of author").call(new Operation<Object, RuntimeException>() { @SuppressWarnings("ConstantConditions") @Override public Object perform() { pullRequestService.merge(new PullRequestMergeRequest.Builder(pullRequest).message("Automatically merged").build()) } }) }
This would be more optimal than having a service poll every few minutes.Unfortunately this event handler won't fire when all the subtasks are resolved in JIRA or when the build status changes for the pull request. I haven't quite got a solution for that currently. I will update this when I have, but this should get you going.
At a high level, maybe consider adding a REST endpoint in Bitbucket and calling it from your build server once the build is complete and successful, then publishing an event from that endpoint inside Bitbucket which will fire the above listener. Also could be the same if you add a scripted post-function on a JIRA workflow transition to signal when an issue is done and call an endpoint in Bitbucket.
You can publish events using the code below:
def eventPublisher = ComponentLocator.getComponent(EventPublisher) // change for your event eventPublisher.publish(new RepositoryPushEvent("", repository, []))
Its just mapping the build back to the actual pull request which is tricky.
Hope this helps,
Adam
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.