Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Require valid Jira ticket type for branch creation

Sascha SK March 25, 2024

Hello,

my company uses

  • Atlassian Jira Server 9.12.4 and
  • Atlassian Bitbucket Server 8.9.10 with
  • Adaptavist ScriptRunner for Bitbucket 8.19.0.

With the help of the ScriptRunner pre-hook "Require commits to be associated with a valid Jira issue", I want to ensure that very certain branches (feature/, bugfix/ or hotfix/ with -bbb- in the following name) can only be created in our Git repository in case the corresponding Jira ticket is of the type Defect or Story. It is fine to check this only for new branches and not for any legacy ones which are already part of the repository.

The hook that I have created works like a charm in case the branches to be checked are created locally and then pushed either via CLI or any local Git client.
However, when creating branches right from a Jira ticket, the hook does not seem to work.

Other ScriptRunner pre-hooks, like "Branch and tag naming standards enforcement", that we use to generally ensure proper branch names, also work when branches are created from a Jira ticket (veto/error message is shown in Jira UI). My assumption is, that while the "Branch and tag naming standards enforcement" pre-hook simply checks against a regex, the "Require commits to be associated with a valid Jira issue" pre-hook additionally establishes the connection to Jira what might end up in a race condition-like behavior (but again, that's just a guess).

Does anybody have a clue why the "Require commits to be associated with a valid Jira issue" pre-hook does not work when the branches are created from a Jira ticket (and how to solve it)?

Please see the config of the affected hook below:

Condition:

// block git push of new branches if they are proper BBB source branches
// (short-living and "-bbb-" in the name) but the corresponding ticket is not
// of the expected type (as per the JQL clause below)

import com.atlassian.bitbucket.repository.RefChangeType

refChanges.any {
it.type == RefChangeType.ADD && it.ref.displayId.matches(/^(feature|bugfix|hotfix)\/\S+?-bbb-\S+$/)
}

 JQL clause template:

type in (Story, Defect)

 

Thanks and best regards
Sascha

2 answers

0 votes
Max Lim _Adaptavist_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 3, 2024

The “Require commits to be associated with a valid Jira issue” built-in pre-hook actually works as expected.

As the name of the pre-hook suggests, the pre-hook validation is based on commits. A push is allowed even the branch name doesn’t contain issue key as long as the commits contain a valid one.

I can confirm that an empty branch without associated Jira issue key can be successfully pushed from client machine.

Having said this, if you want to validate branch name on push and UI. This library script does exactly that. So, I have modified it to validate issue type instead of status name:

import com.atlassian.applinks.api.ApplicationLinkResponseHandler
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.jira.JiraApplicationType
import com.atlassian.bitbucket.hook.repository.RepositoryHookResult
import com.atlassian.bitbucket.repository.MinimalRef
import com.atlassian.bitbucket.repository.RefChangeType
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 groovy.json.JsonSlurper
 
private RepositoryHookResult rejectedResult(String message) {
    RepositoryHookResult.rejected(message, message)
}
 
private RepositoryHookResult getIssueKeyValidationResult(String issueKey, List<String> requiredIssueTypeNames, MinimalRef branchRef) {
    def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService)
 
    def primaryJiraApplicationLink = applicationLinkService.getPrimaryApplicationLink(JiraApplicationType)
 
    def authenticatedRequestFactory = primaryJiraApplicationLink.createImpersonatingAuthenticatedRequestFactory() ?:
            primaryJiraApplicationLink.createAuthenticatedRequestFactory()
 
    authenticatedRequestFactory.createRequest(Request.MethodType.GET, "rest/api/2/issue/$issueKey")
            .addHeader('Content-Type', 'application/json')
            .execute(new ApplicationLinkResponseHandler<RepositoryHookResult>() {
                @Override
                RepositoryHookResult credentialsRequired(Response response) throws ResponseException {
                    rejectedResult(
                            "Please authenticate with Jira: ${authenticatedRequestFactory.authorisationURI}"
                    )
                }
 
                @Override
                RepositoryHookResult handle(Response response) throws ResponseException {
                    if (!response.successful) {
                        if (response.statusCode == 404) {
                            return rejectedResult(
                                    "Branch with name: ${branchRef.displayId} matches Jira issue key: $issueKey but no issue with this key exists, branch creation cancelled."
                            )
                        }
 
                        log.warn("Jira call failed, status: ${response.statusCode}.")

                        return rejectedResult(
                                'Validation of issue key in branch name failed, please contact your system administrator.'
                        )
                    }
 
                    def issueJson = new JsonSlurper().parseText(response.responseBodyAsString) as Map<String, Object>
                    def issueTypeName = issueJson['fields']['issuetype']['name'] as String
 
                    if (!(issueTypeName in requiredIssueTypeNames)) {
                        return rejectedResult(
                                "Issue with key: $issueKey was found from branch name, but its issue type name: ($issueTypeName) does not match the required issue type names: ($requiredIssueTypeNames)."
                        )
                    }
 
                    RepositoryHookResult.accepted()
                }
            })
}
 
def builder = new RepositoryHookResult.Builder()
 
def requiredIssueTypeNames = ['Story', 'Defect']

// A regular expression for extracting Jira issue keys from branch names, this is the default regular expression used by Bitbucket.
// If your Jira issue keys do not match the default pattern, alterations will need to be made here.
def jiraIssueKeyPattern = ~'(?<=^|[a-z]\\-|[\\s\\p{Punct}&&[^\\-]])([A-Z][A-Z0-9_]*-\\d+)(?![^\\W_])'
 
// Find all created refs which relate to a branch creation
def createdBranchRefs = refChanges.find { it.type == RefChangeType.ADD && it.refId.startsWith('refs/heads') }*.ref
 
// For each created branch ref, check the branch name for an issue key, and then verify the status of that issue via a linked Jira instance.
createdBranchRefs.each { branchRef ->
    def matcher = branchRef.id =~ jiraIssueKeyPattern
 
    if (matcher.find()) {
        def issueKey = matcher.group(1)
 
        builder.add(getIssueKeyValidationResult(issueKey, requiredIssueTypeNames, branchRef))
    } else {
        def message = "Branch with name: ${branchRef.displayId} does not contain a reference to a Jira issue."
 
        builder.add(rejectedResult(message))
    }
}
 
builder.build()

 Please also remember to set triggers to both “branch-create” and “push”.

0 votes
Sean Chua _Adaptavist_ April 1, 2024

Hey @Sascha SK ,

I unfortunately do not have an answer for you. But seeing that no one has responded in awhile, you may want to try creating a Support Ticket with us for one of our Support Engineers to investigate with you.

Regards,
Sean

Sascha SK April 3, 2024

Hi @Sean Chua _Adaptavist_ 

thanks for your response. That's exactly what I did this week. Waiting for feedback from your team now.

For the others: I will share the outcome of the support ticket here whenever there is something to share.

Best regards
Sascha

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events