Skip to content

Commit 448c5e0

Browse files
authored
Added option to include a secondary source for jira issue attestation (#428)
* Added jira-secondary-source argument to jira attest command * Added test of duplicate keys found * Updated order in test result * Sort jira issues
1 parent 1f44920 commit 448c5e0

File tree

6 files changed

+116
-29
lines changed

6 files changed

+116
-29
lines changed

cmd/kosli/attestJira.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,21 @@ type JiraAttestationPayload struct {
2020

2121
type attestJiraOptions struct {
2222
*CommonAttestationOptions
23-
baseURL string
24-
username string
25-
apiToken string
26-
pat string
27-
issueFields string
28-
assert bool
29-
payload JiraAttestationPayload
23+
baseURL string
24+
username string
25+
apiToken string
26+
pat string
27+
issueFields string
28+
secondarySource string
29+
assert bool
30+
payload JiraAttestationPayload
3031
}
3132

3233
const attestJiraShortDesc = `Report a jira attestation to an artifact or a trail in a Kosli flow. `
3334

3435
const attestJiraLongDesc = attestJiraShortDesc + `
35-
Parses the given commit's message or current branch name for Jira issue references of the
36-
form:
36+
Parses the given commit's message, current branch name or the content of the ^--jira-secondary-source^
37+
argument for Jira issue references of the form:
3738
'at least 2 characters long, starting with an uppercase letter project key followed by
3839
dash and one or more digits'.
3940
@@ -134,6 +135,18 @@ kosli attest jira \
134135
--api-token yourAPIToken \
135136
--org yourOrgName \
136137
--assert
138+
139+
# get jira reference from original branch name in a GitHub Pull Request merge job
140+
kosli attest jira \
141+
--name yourAttestationName \
142+
--flow yourFlowName \
143+
--trail yourTrailName \
144+
--jira-secondary-source ${{ github.head_ref }} \
145+
--jira-base-url https://kosli.atlassian.net \
146+
--jira-username user@domain.com \
147+
--jira-api-token yourJiraAPIToken \
148+
--api-token yourAPIToken \
149+
--org yourOrgName
137150
`
138151

139152
func newAttestJiraCmd(out io.Writer) *cobra.Command {
@@ -203,6 +216,7 @@ func newAttestJiraCmd(out io.Writer) *cobra.Command {
203216
cmd.Flags().StringVar(&o.apiToken, "jira-api-token", "", jiraAPITokenFlag)
204217
cmd.Flags().StringVar(&o.pat, "jira-pat", "", jiraPATFlag)
205218
cmd.Flags().StringVar(&o.issueFields, "jira-issue-fields", "", jiraIssueFieldFlag)
219+
cmd.Flags().StringVar(&o.secondarySource, "jira-secondary-source", "", jiraSecondarySourceFlag)
206220
cmd.Flags().BoolVar(&o.assert, "assert", false, attestationAssertFlag)
207221

208222
err := RequireFlags(cmd, []string{"flow", "trail", "name", "commit", "jira-base-url"})
@@ -235,7 +249,7 @@ func (o *attestJiraOptions) run(args []string) error {
235249
// more info: https://support.atlassian.com/jira-software-cloud/docs/what-is-an-issue/#Workingwithissues-Projectandissuekeys
236250
jiraIssueKeyPattern := `[A-Z][A-Z0-9]{1,9}-[0-9]+`
237251

238-
issueIDs, commitInfo, err := gv.MatchPatternInCommitMessageORBranchName(jiraIssueKeyPattern, o.payload.Commit.Sha1)
252+
issueIDs, commitInfo, err := gv.MatchPatternInCommitMessageORBranchName(jiraIssueKeyPattern, o.payload.Commit.Sha1, o.secondarySource)
239253
if err != nil {
240254
return err
241255
}

cmd/kosli/reportEvidenceCommitJira.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func (o *reportEvidenceCommitJiraOptions) run(args []string) error {
175175
// more info: https://support.atlassian.com/jira-software-cloud/docs/what-is-an-issue/#Workingwithissues-Projectandissuekeys
176176
jiraIssueKeyPattern := `[A-Z][A-Z0-9]{1,9}-[0-9]+`
177177

178-
issueIDs, commitInfo, err := gv.MatchPatternInCommitMessageORBranchName(jiraIssueKeyPattern, o.payload.CommitSHA)
178+
issueIDs, commitInfo, err := gv.MatchPatternInCommitMessageORBranchName(jiraIssueKeyPattern, o.payload.CommitSHA, "")
179179
if err != nil {
180180
return err
181181
}

cmd/kosli/reportEvidenceCommitJira_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func (suite *CommitEvidenceJiraCommandTestSuite) TestCommitEvidenceJiraCommandCm
9090
},
9191
},
9292
{
93-
name: "report existing and non existing Jira commit evidence with reference in midle of line works",
93+
name: "report existing and non existing Jira commit evidence with reference in middle of line works",
9494
cmd: fmt.Sprintf(`report evidence commit jira --name jira-validation
9595
--jira-base-url https://kosli-test.atlassian.net --jira-username tore@kosli.com
9696
--repo-root %s

cmd/kosli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file,
115115
jiraAPITokenFlag = "Jira API token (for Jira Cloud)"
116116
jiraPATFlag = "Jira personal access token (for self-hosted Jira)"
117117
jiraIssueFieldFlag = "[optional] The comma separated list of fields to include from the Jira issue. Default no fields are included. '*all' will give all fields."
118+
jiraSecondarySourceFlag = "[optional] An optional string to search for Jira ticket reference, e.g. '--jira-secondary-source ${{ github.head_ref }}'"
118119
envDescriptionFlag = "[optional] The environment description."
119120
flowDescriptionFlag = "[optional] The Kosli flow description."
120121
trailDescriptionFlag = "[optional] The Kosli trail description."

internal/gitview/gitView.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"net/url"
66
"regexp"
7+
"sort"
78
"strings"
89

910
"github.com/go-git/go-git/v5"
@@ -271,23 +272,37 @@ func getCommitURL(repoURL, commitHash string) string {
271272
// MatchPatternInCommitMessageORBranchName returns a slice of strings matching a pattern in a commit message or branch name
272273
// matches lookup happens in the commit message first, and if none is found, matching against the branch name is done
273274
// if no matches are found in both the commit message and the branch name, an empty slice is returned
274-
func (gv *GitView) MatchPatternInCommitMessageORBranchName(pattern, commitSHA string) ([]string, *CommitInfo, error) {
275+
func (gv *GitView) MatchPatternInCommitMessageORBranchName(pattern, commitSHA, secondarySource string) ([]string, *CommitInfo, error) {
275276
commitInfo, err := gv.GetCommitInfoFromCommitSHA(commitSHA, true, []string{})
276277
if err != nil {
277278
return []string{}, nil, err
278279
}
279280

280281
re := regexp.MustCompile(pattern)
281-
matches := re.FindAllString(commitInfo.Message, -1)
282-
if matches != nil {
283-
return matches, commitInfo, nil
284-
} else {
285-
matches := re.FindAllString(commitInfo.Branch, -1)
286-
if matches != nil {
287-
return matches, commitInfo, nil
288-
}
282+
commitMatches := re.FindAllString(commitInfo.Message, -1)
283+
branchMatches := re.FindAllString(commitInfo.Branch, -1)
284+
secondaryMatches := re.FindAllString(secondarySource, -1)
285+
286+
// Use a map to remove duplicates
287+
uniqueMatches := make(map[string]struct{})
288+
for _, match := range commitMatches {
289+
uniqueMatches[match] = struct{}{}
290+
}
291+
for _, match := range branchMatches {
292+
uniqueMatches[match] = struct{}{}
293+
}
294+
for _, match := range secondaryMatches {
295+
uniqueMatches[match] = struct{}{}
289296
}
290-
return []string{}, commitInfo, nil
297+
298+
// Convert map keys back to a slice
299+
matches := make([]string, 0, len(uniqueMatches))
300+
for match := range uniqueMatches {
301+
matches = append(matches, match)
302+
}
303+
sort.Strings(matches)
304+
305+
return matches, commitInfo, nil
291306
}
292307

293308
// ResolveRevision returns an explicit commit SHA1 from commit SHA or ref (e.g. HEAD~2)

internal/gitview/gitView_test.go

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,14 @@ func (suite *GitViewTestSuite) TestMatchPatternInCommitMessageORBranchName() {
361361
require.NoError(suite.T(), err)
362362

363363
for _, t := range []struct {
364-
name string
365-
pattern string
366-
commitMessage string
367-
wantError bool
368-
want []string
369-
commitSha string
364+
name string
365+
pattern string
366+
commitMessage string
367+
secondarySource string
368+
wantError bool
369+
want []string
370+
commitSha string
371+
branchName string
370372
}{
371373
{
372374
name: "One Jira reference found",
@@ -389,6 +391,55 @@ func (suite *GitViewTestSuite) TestMatchPatternInCommitMessageORBranchName() {
389391
want: []string{},
390392
wantError: false,
391393
},
394+
{
395+
name: "Jira references found in branch name",
396+
pattern: "[A-Z][A-Z0-9]{1,9}-[0-9]+",
397+
commitMessage: "some test commit",
398+
branchName: "EX-5-cool-branch",
399+
want: []string{"EX-5"},
400+
wantError: false,
401+
},
402+
{
403+
name: "Jira references found in secondary source",
404+
pattern: "[A-Z][A-Z0-9]{1,9}-[0-9]+",
405+
commitMessage: "some test commit",
406+
secondarySource: "EX-1-test-commit",
407+
want: []string{"EX-1"},
408+
wantError: false,
409+
},
410+
{
411+
name: "Jira references found in commit and secondary source",
412+
pattern: "[A-Z][A-Z0-9]{1,9}-[0-9]+",
413+
commitMessage: "EX-1 some test commit",
414+
secondarySource: "EX-2-test-commit",
415+
want: []string{"EX-1", "EX-2"},
416+
wantError: false,
417+
},
418+
{
419+
name: "Jira references found in commit and branch name",
420+
pattern: "[A-Z][A-Z0-9]{1,9}-[0-9]+",
421+
commitMessage: "EX-1 some test commit",
422+
branchName: "EX-2-test-commit",
423+
want: []string{"EX-1", "EX-2"},
424+
wantError: false,
425+
},
426+
{
427+
name: "Same Jira references found in commit and branch name is not duplicated",
428+
pattern: "[A-Z][A-Z0-9]{1,9}-[0-9]+",
429+
commitMessage: "DUP-1 some test commit",
430+
branchName: "DUP-1-test-commit",
431+
want: []string{"DUP-1"},
432+
wantError: false,
433+
},
434+
{
435+
name: "Jira references found in commit, branch name and secondary source",
436+
pattern: "[A-Z][A-Z0-9]{1,9}-[0-9]+",
437+
commitMessage: "ALL-1 some test commit",
438+
branchName: "ALL-2-test-commit",
439+
secondarySource: "ALL-3-some-things",
440+
want: []string{"ALL-1", "ALL-2", "ALL-3"},
441+
wantError: false,
442+
},
392443
{
393444
name: "No Jira references found, despite something that looks similar to Jira reference",
394445
pattern: "[A-Z][A-Z0-9]{1,9}-[0-9]+",
@@ -417,10 +468,16 @@ func (suite *GitViewTestSuite) TestMatchPatternInCommitMessageORBranchName() {
417468
require.NoError(suite.T(), err)
418469
}
419470

471+
if t.branchName != "" {
472+
err := testHelpers.CheckoutNewBranch(workTree, t.branchName)
473+
require.NoError(suite.T(), err)
474+
defer testHelpers.CheckoutMaster(workTree, suite.T())
475+
}
476+
420477
gitView, err := New(suite.tmpDir)
421478
require.NoError(suite.T(), err)
422479

423-
actual, _, err := gitView.MatchPatternInCommitMessageORBranchName(t.pattern, t.commitSha)
480+
actual, _, err := gitView.MatchPatternInCommitMessageORBranchName(t.pattern, t.commitSha, t.secondarySource)
424481
require.True(suite.T(), (err != nil) == t.wantError)
425482
require.ElementsMatch(suite.T(), t.want, actual)
426483

0 commit comments

Comments
 (0)