Skip to content

Commit 5dcdf9f

Browse files
committed
feat: First version
1 parent ecaba7b commit 5dcdf9f

11 files changed

Lines changed: 624 additions & 2 deletions

File tree

Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM golang:1.24 as Builder
2+
3+
WORKDIR /app
4+
COPY . .
5+
RUN go build -o action cmd/action.go
6+
7+
FROM alpine:latest as Runner
8+
9+
RUN apk add gcompat
10+
11+
COPY --from=Builder /app/action /action
12+
RUN chmod +x /action
13+
14+
ENTRYPOINT ["/action"]

README.md

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,75 @@
22

33
## Introduction
44

5-
This Github action does things in Jira triggered from Github Actions.
5+
A Github action does things in Jira Cloud triggered from Github Actions.
66

7-
Currently under heavy development.
7+
Currently supported:
8+
9+
* Adding a comment on a push or a pull request
10+
11+
## Usage
12+
13+
Use a workflow like this:
14+
15+
```yaml
16+
on:
17+
- push
18+
- pull_request
19+
20+
jobs:
21+
jiracomment:
22+
name: "Comment on Jira"
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: "0"
29+
- uses: dodevops/jira-cloud-github-action@feature/dpr/first-version
30+
with:
31+
url: "https://mycompany.atlassian.net"
32+
username: "serviceuser@company.com"
33+
token: ${{ secrets.JIRA_SERVICEUSER_TOKEN }}
34+
```
35+
36+
This will automatically send comments to Jira tickets identified by commits that contain a specific format.
37+
38+
For example, these three commits in a pull request created by "user4 <user4@company.com>":
39+
40+
* `(SUP-1234): Fixed problem` by user1 <user1@company.com>
41+
* `(SUP-1234): Added feature` by user2 <user2@company.com>
42+
* `(SUP-2345): Removed feature` by user3 <user3@company.com>
43+
44+
will result in these two comments (the markdown format is converted to Jira markdown):
45+
46+
Comment on SUP-1234
47+
```
48+
*Pull Request*: [user4](mailto:user4@company.com) sent 2 commit(s) to [Pull request title]({{ https://linktopullrequest }})
49+
50+
* (SUP-1234): Fixed problem ([user1](mailto:user1@company.com))
51+
* (SUP-1234): Added feature ([user2](mailto:user2@company.com))
52+
```
53+
54+
Comment on SUP-2345
55+
```
56+
*Pull Request*: [user4](mailto:user4@company.com) sent 1 commit(s) to [Pull request title]({{ https://linktopullrequest }})
57+
58+
* (SUP-2345): Removed feature ([user3](mailto:user3@company.com))
59+
```
60+
61+
## Options
62+
63+
* *url*: The URL to your Jira Cloud instance (required)
64+
* *username*: The user name for API requests (required)
65+
* *token*: The user's token for API requests (required)
66+
* *loglevel*: The loglevel to use [INFO]
67+
* *commitParseRegExp*: The Regexp string to use to parse Jira issue ids from commit messages. Requires a named group called id that catches the whole issue id and a group project that catches only the project key [\\((?P<id>(?P<project>[A-Za-z]+)-[0-9]+)\\)"]
68+
* *commentTemplate*: The go template for the issue comment. Takes the CommitsInfo struct as an input [default see below]
69+
```yaml
70+
*{{ .CommitsInfo.Type }}*: [{{ .CommitsInfo.AuthorName }}](mailto:{{ .CommitsInfo.AuthorEmail }}) sent {{ .Commits | len }} commit(s) to [{{ .CommitsInfo.Target }}]({{ .CommitsInfo.Target }})
71+
72+
{{ range .Commits }}
73+
* {{ .Message }} ([{{ .AuthorName }}](mailto:{{ .AuthorEmail }}))
74+
{{ end }}
75+
```
76+
* *onlyProjects*: If specified, only issues with these project keys are processed

action.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: "Jira Cloud Action"
2+
description: "Do things in Jira Cloud from Github Actions"
3+
inputs:
4+
url:
5+
description: "The URL to your Jira Cloud instance"
6+
required: true
7+
username:
8+
description: "The user name for API requests"
9+
required: true
10+
token:
11+
description: "The user's token for API requests"
12+
required: true
13+
loglevel:
14+
description: "The loglevel to use"
15+
default: "INFO"
16+
commitParseRegExp:
17+
description: "The Regexp string to use to parse Jira issue ids from commit messages. Requires a named group called id that catches the whole issue id and a group project that catches only the project key"
18+
default: "\\((?P<id>(?P<project>[A-Za-z]+)-[0-9]+)\\)"
19+
commentTemplate:
20+
description: "The go template for the issue comment. Takes the CommitsInfo struct as an input"
21+
default: |
22+
*{{ .CommitsInfo.Type }}*: [{{ .CommitsInfo.AuthorName }}](mailto:{{ .CommitsInfo.AuthorEmail }}) sent {{ .Commits | len }} commit(s) to [{{ .CommitsInfo.Target }}]({{ .CommitsInfo.Target }})
23+
24+
{{ range .Commits }}
25+
* {{ .Message }} ([{{ .AuthorName }}](mailto:{{ .AuthorEmail }}))
26+
{{ end }}
27+
onlyProjects:
28+
description: "If specified, only issues with these project keys are processed"
29+
runs:
30+
using: "docker"
31+
image: "Dockerfile"

cmd/action.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"jira-cloud-github-action/internal"
6+
"log"
7+
"regexp"
8+
"slices"
9+
"text/template"
10+
11+
"github.com/alexflint/go-arg"
12+
"github.com/sirupsen/logrus"
13+
)
14+
15+
func main() {
16+
var args struct {
17+
LogLevel string `arg:"env:INPUT_LOGLEVEL" help:"The loglevel to use" default:"INFO"`
18+
URL string `arg:"required,env:INPUT_URL" help:"The URL to your Jira Cloud instance"`
19+
User string `arg:"required,env:INPUT_USERNAME" help:"The user name for API requests"`
20+
Token string `arg:"required,env:INPUT_TOKEN" help:"The user's token for API requests"`
21+
CommitParseRegExp string `arg:"env:INPUT_COMMITPARSEREGEXP" help:"The Regexp string to use to parse Jira issue ids from commit messages. Requires a named group called id that catches the whole issue id and a group project that catches only the project key" default:"\\((?P<id>(?P<project>[A-Za-z]+)-[0-9]+)\\)"`
22+
CommentTemplate string `arg:"env:INPUT_COMMENTTEMPLATE" help:"The go template for the issue comment. Takes the CommentInfo struct as an input" default:"{{ .CommitsInfo.Type }}"`
23+
DryRun bool `arg:"env:INPUT_DRYRUN" help:"Don't actually do something, just write what would be done'" default:"false"`
24+
OnlyProjects []string `arg:"env:INPUT_ONLYPROJECTS" help:"Only allow commenting on these projects"`
25+
}
26+
arg.MustParse(&args)
27+
if l, err := logrus.ParseLevel(args.LogLevel); err != nil {
28+
log.Fatal(err)
29+
} else {
30+
logrus.SetLevel(l)
31+
}
32+
33+
var matcher *regexp.Regexp
34+
if m, err := regexp.Compile(args.CommitParseRegExp); err != nil {
35+
logrus.Fatalf("Can not parse regular expression %s: %v", args.CommitParseRegExp, err)
36+
} else {
37+
matcher = m
38+
}
39+
40+
api := internal.NewJiraAPI(args.URL, args.User, args.Token)
41+
42+
var commentTemplate *template.Template
43+
if t, err := template.New("comment").Parse(args.CommentTemplate); err != nil {
44+
log.Fatalf("Can not parse template %s: %v", args.CommentTemplate, err)
45+
} else {
46+
commentTemplate = t
47+
}
48+
49+
commitsFetchers := []internal.CommitsFetcher{&internal.PushCommitFetcher{}, &internal.PullRequestCommitFetcher{}}
50+
51+
for _, fetcher := range commitsFetchers {
52+
if fetcher.Test() {
53+
logrus.Infof("Found %s", fetcher.GetInfo().Type)
54+
if commits, err := fetcher.GetCommits(); err != nil {
55+
log.Fatal(err)
56+
} else {
57+
for _, commit := range commits {
58+
matches := matcher.FindStringSubmatch(commit.Message)
59+
if len(matches) > 0 {
60+
project := matches[matcher.SubexpIndex("project")]
61+
if len(args.OnlyProjects) > 0 && !slices.Contains(args.OnlyProjects, project) {
62+
continue
63+
}
64+
issueID := matches[matcher.SubexpIndex("id")]
65+
commentInfo := internal.CommentInfo{
66+
CommitsInfo: fetcher.GetInfo(),
67+
Commits: commits,
68+
}
69+
var commentWriter = bytes.Buffer{}
70+
if err := commentTemplate.Execute(&commentWriter, commentInfo); err != nil {
71+
log.Fatalf("Can not execute template %s with object %v: %v", args.CommentTemplate, commentInfo, err)
72+
}
73+
if err := api.CommentWorkItem(issueID, commentWriter.String()); err != nil {
74+
log.Fatal(err)
75+
}
76+
}
77+
}
78+
}
79+
}
80+
}
81+
}

go.mod

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module jira-cloud-github-action
2+
3+
go 1.24
4+
5+
require (
6+
github.com/alexflint/go-arg v1.5.1
7+
github.com/go-git/go-git/v5 v5.16.2
8+
github.com/go-resty/resty/v2 v2.16.5
9+
github.com/google/go-github/v31 v31.0.0
10+
github.com/posener/goaction v1.2.1
11+
github.com/sirupsen/logrus v1.9.3
12+
github.com/summonio/markdown-to-adf v0.0.0-20230304000134-6bf21369bac1
13+
)
14+
15+
require (
16+
dario.cat/mergo v1.0.0 // indirect
17+
github.com/Microsoft/go-winio v0.6.2 // indirect
18+
github.com/ProtonMail/go-crypto v1.1.6 // indirect
19+
github.com/alexflint/go-scalar v1.2.0 // indirect
20+
github.com/cloudflare/circl v1.6.1 // indirect
21+
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
22+
github.com/emirpasic/gods v1.18.1 // indirect
23+
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
24+
github.com/go-git/go-billy/v5 v5.6.2 // indirect
25+
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
26+
github.com/google/go-querystring v1.0.0 // indirect
27+
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
28+
github.com/kevinburke/ssh_config v1.2.0 // indirect
29+
github.com/pjbgf/sha1cd v0.3.2 // indirect
30+
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
31+
github.com/skeema/knownhosts v1.3.1 // indirect
32+
github.com/xanzy/ssh-agent v0.3.3 // indirect
33+
github.com/yuin/goldmark v1.5.4 // indirect
34+
golang.org/x/crypto v0.37.0 // indirect
35+
golang.org/x/net v0.39.0 // indirect
36+
golang.org/x/sys v0.32.0 // indirect
37+
gopkg.in/warnings.v0 v0.1.2 // indirect
38+
)

0 commit comments

Comments
 (0)