Skip to content

Commit 157f1a8

Browse files
committedFeb 26, 2019
init
0 parents  commit 157f1a8

10 files changed

+2468
-0
lines changed
 

‎.eslintrc.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "@atlassian-partner-engineering",
3+
"rules": {
4+
"no-plusplus": "off"
5+
}
6+
}
7+

‎Dockerfile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM node:10-alpine
2+
3+
LABEL "name"="Jira Comment"
4+
LABEL "maintainer"="Dima Rudzik <drudzik+githubactions@atlassian.net>"
5+
LABEL "version"="1.0.0"
6+
7+
LABEL "com.github.actions.name"="Jira Comment"
8+
LABEL "com.github.actions.description"="Add a comment to a Jira issue"
9+
LABEL "com.github.actions.icon"="check-square"
10+
LABEL "com.github.actions.color"="blue"
11+
12+
RUN apk update && apk add --no-cache ca-certificates
13+
14+
ADD https://github.com/atlassian/gajira/raw/master/bin/gagas .
15+
ADD . .
16+
RUN npm i
17+
RUN chmod +x /entrypoint.sh
18+
RUN chmod +x /gagas
19+
20+
ENTRYPOINT ["/entrypoint.sh"]

‎README.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Jira Comment
2+
Add a comment to an issue
3+
4+
5+
## Usage
6+
To add comment to an issue you need to specify an issue key and a comment in action args, like:
7+
8+
`"Hello from GitHub actions" --issue=INC-2`
9+
10+
You can interpolate fields from GitHub event which triggered the workflow into a comment body. For example, you can specify pusher name in comment body by including `pusher.name` field from [push](https://developer.github.com/v3/activity/events/types/#pushevent) event:
11+
12+
"{{ pusher.name }} pushed to repository: {{ repository.full_name }}"
13+
14+
Which will result in comment:
15+
16+
Codertocat pushed to repository: Codertocat/Hello-World
17+
18+
----
19+
## Action Spec
20+
21+
### Environment variables
22+
- None
23+
24+
### Arguments
25+
- `--issue` - An issue key to add a comment for
26+
27+
### Reads fields from config file at $HOME/jira/config.yml
28+
- `issue`
29+
30+
### Writes fields to config file at $HOME/jira/config.yml
31+
- None

‎action.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const _ = require('lodash')
2+
const Jira = require('./common/net/Jira')
3+
4+
module.exports = class {
5+
constructor ({ githubEvent, argv, config }) {
6+
this.Jira = new Jira({
7+
baseUrl: config.baseUrl,
8+
token: config.token,
9+
email: config.email,
10+
})
11+
12+
this.config = config
13+
this.argv = argv
14+
this.githubEvent = githubEvent
15+
}
16+
17+
async execute () {
18+
const issueId = this.argv.issue
19+
const rawComment = this.argv._.join(' ')
20+
21+
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g
22+
const compiled = _.template(rawComment)
23+
const interpolatedComment = compiled({ event: this.githubEvent })
24+
25+
console.log(`Adding comment to ${issueId}: ${interpolatedComment}`)
26+
await this.Jira.addComment(issueId, { body: interpolatedComment })
27+
28+
return {}
29+
}
30+
}

‎common/net/Jira.js

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const { get } = require('lodash')
2+
3+
const serviceName = 'jira'
4+
const { format } = require('url')
5+
const client = require('./client')(serviceName)
6+
7+
class Jira {
8+
constructor ({ baseUrl, token, email }) {
9+
this.baseUrl = baseUrl
10+
this.token = token
11+
this.email = email
12+
}
13+
14+
async addComment (issueId, data) {
15+
return this.fetch('addComment', {
16+
pathname: `/rest/api/2/issue/${issueId}/comment`,
17+
}, {
18+
method: 'POST',
19+
body: data,
20+
})
21+
}
22+
23+
async createIssue (body) {
24+
return this.fetch('createIssue',
25+
{ pathname: '/rest/api/2/issue' },
26+
{ method: 'POST', body })
27+
}
28+
29+
async getIssue (issueId, query = {}) {
30+
const { fields = [], expand = [] } = query
31+
32+
try {
33+
return this.fetch('getIssue', {
34+
pathname: `/rest/api/2/issue/${issueId}`,
35+
query: {
36+
fields: fields.join(','),
37+
expand: expand.join(','),
38+
},
39+
})
40+
} catch (error) {
41+
if (get(error, 'res.status') === 404) {
42+
return
43+
}
44+
45+
throw error
46+
}
47+
}
48+
49+
async getIssueTransitions (issueId) {
50+
return this.fetch('getIssueTransitions', {
51+
pathname: `/rest/api/2/issue/${issueId}/transitions`,
52+
}, {
53+
method: 'GET',
54+
})
55+
}
56+
57+
async transitionIssue (issueId, data) {
58+
return this.fetch('transitionIssue', {
59+
pathname: `/rest/api/3/issue/${issueId}/transitions`,
60+
}, {
61+
method: 'POST',
62+
body: data,
63+
})
64+
}
65+
66+
async fetch (apiMethodName,
67+
{ host, pathname, query },
68+
{ method, body, headers = {} } = {}) {
69+
const url = format({
70+
host: host || this.baseUrl,
71+
pathname,
72+
query,
73+
})
74+
75+
if (!method) {
76+
method = 'GET'
77+
}
78+
79+
if (headers['Content-Type'] === undefined) {
80+
headers['Content-Type'] = 'application/json'
81+
}
82+
83+
if (headers.Authorization === undefined) {
84+
headers.Authorization = `Basic ${Buffer.from(`${this.email}:${this.token}`).toString('base64')}`
85+
}
86+
87+
// strong check for undefined
88+
// cause body variable can be 'false' boolean value
89+
if (body && headers['Content-Type'] === 'application/json') {
90+
body = JSON.stringify(body)
91+
}
92+
93+
const state = {
94+
req: {
95+
method,
96+
headers,
97+
body,
98+
url,
99+
},
100+
}
101+
102+
try {
103+
await client(state, `${serviceName}:${apiMethodName}`)
104+
} catch (error) {
105+
const fields = {
106+
originError: error,
107+
source: 'jira',
108+
}
109+
110+
delete state.req.headers
111+
112+
throw Object.assign(
113+
new Error('Jira API error'),
114+
state,
115+
fields
116+
)
117+
}
118+
119+
return state.res.body
120+
}
121+
}
122+
123+
module.exports = Jira

‎common/net/client.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const fetch = require('node-fetch')
2+
// const moment = require('moment')
3+
4+
module.exports = serviceName => async (state, apiMethod = 'unknown') => {
5+
// const startTime = moment.now()
6+
7+
const response = await fetch(state.req.url, state.req)
8+
9+
state.res = {
10+
headers: response.headers.raw(),
11+
status: response.status,
12+
}
13+
14+
// const totalTime = moment.now() - startTime
15+
// const tags = {
16+
// api_method: apiMethod,
17+
// method: state.req.method || 'GET',
18+
// response_code: response.status,
19+
// service: serviceName,
20+
// }
21+
22+
state.res.body = await response.text()
23+
24+
const isJSON = (response.headers.get('content-type') || '').includes('application/json')
25+
26+
if (isJSON && state.res.body) {
27+
state.res.body = JSON.parse(state.res.body)
28+
}
29+
30+
if (!response.ok) {
31+
throw new Error(response.statusText)
32+
}
33+
34+
return state
35+
}

‎entrypoint.sh

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
sh -c "node /index.js $*"
5+
6+
actionSubjectId="comment"
7+
containerId=`echo $GITHUB_REPOSITORY | sha1sum | cut -c1-41`
8+
anonymousId=`echo $GITHUB_ACTOR | sha1sum | cut -c1-41`
9+
10+
/gagas --container-id="$containerId" --action-subject-id="$actionSubjectId" --anonymous-id="$anonymousId"

‎index.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const fs = require('fs')
2+
const YAML = require('yaml')
3+
const yargs = require('yargs')
4+
5+
const cliConfigPath = `${process.env.HOME}/.jira.d/config.yml`
6+
const configPath = `${process.env.HOME}/jira/config.yml`
7+
const Action = require('./action')
8+
9+
// eslint-disable-next-line import/no-dynamic-require
10+
const githubEvent = require(process.env.GITHUB_EVENT_PATH)
11+
const config = YAML.parse(fs.readFileSync(configPath, 'utf8'))
12+
13+
async function exec () {
14+
try {
15+
const result = await new Action({
16+
githubEvent,
17+
argv: parseArgs(),
18+
config,
19+
}).execute()
20+
21+
if (result) {
22+
const yamledResult = YAML.stringify(result)
23+
const extendedConfig = Object.assign({}, config, result)
24+
25+
fs.writeFileSync(configPath, YAML.stringify(extendedConfig))
26+
27+
return fs.appendFileSync(cliConfigPath, yamledResult)
28+
}
29+
30+
console.log('Failed to comment an issue.')
31+
process.exit(78)
32+
} catch (error) {
33+
console.error(error)
34+
process.exit(1)
35+
}
36+
}
37+
38+
function parseArgs () {
39+
yargs
40+
.option('issue', {
41+
alias: 'i',
42+
describe: 'Provide an issue key to add a comment for',
43+
demandOption: !config.issue,
44+
default: config.issue,
45+
type: 'string',
46+
})
47+
48+
yargs
49+
.parserConfiguration({
50+
'parse-numbers': false,
51+
})
52+
53+
return yargs.argv
54+
}
55+
56+
exec()

0 commit comments

Comments
 (0)
Failed to load comments.