Skip to content

Commit 8ad616a

Browse files
SNOW-153244 setup actions (#170)
1 parent 6f9b9da commit 8ad616a

File tree

10 files changed

+493
-0
lines changed

10 files changed

+493
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Create a jira issue from a GH issue
2+
3+
Stolen from [gajira-create](https://github.com/atlassian/gajira-create) and modified to support required custom fields in use at snowflake.
4+
5+
Most of the dependencies (gajira-login, etc) have been folded into this action
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
this.preprocessArgs()
19+
20+
const { argv } = this
21+
const projectKey = argv.project
22+
const issuetypeName = argv.issuetype
23+
24+
// map custom fields
25+
const { projects } = await this.Jira.getCreateMeta({
26+
expand: 'projects.issuetypes.fields',
27+
projectKeys: projectKey,
28+
issuetypeNames: issuetypeName,
29+
})
30+
31+
if (projects.length === 0) {
32+
console.error(`project '${projectKey}' not found`)
33+
34+
return
35+
}
36+
37+
const [project] = projects
38+
39+
if (project.issuetypes.length === 0) {
40+
console.error(`issuetype '${issuetypeName}' not found`)
41+
42+
return
43+
}
44+
45+
let providedFields = [{
46+
key: 'project',
47+
value: {
48+
key: projectKey,
49+
},
50+
}, {
51+
key: 'issuetype',
52+
value: {
53+
name: issuetypeName,
54+
},
55+
}, {
56+
key: 'summary',
57+
value: argv.summary,
58+
}]
59+
60+
if (argv.assignee) {
61+
providedFields.push({
62+
key: 'assignee',
63+
value: {
64+
name: argv.assignee
65+
}
66+
})
67+
}
68+
69+
if (argv.area) {
70+
providedFields.push({
71+
key: 'customfield_11401',
72+
value: {
73+
value: argv.area
74+
}
75+
})
76+
}
77+
78+
if (argv.description) {
79+
providedFields.push({
80+
key: 'description',
81+
value: argv.description,
82+
})
83+
}
84+
85+
const payload = providedFields.reduce((acc, field) => {
86+
acc.fields[field.key] = field.value
87+
88+
return acc
89+
}, {
90+
fields: {},
91+
})
92+
93+
const issue = await this.Jira.createIssue(payload)
94+
95+
return { issue: issue.key }
96+
}
97+
98+
preprocessArgs () {
99+
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g
100+
const summaryTmpl = _.template(this.argv.summary)
101+
const descriptionTmpl = _.template(this.argv.description)
102+
103+
this.argv.summary = summaryTmpl({ event: this.githubEvent })
104+
this.argv.description = descriptionTmpl({ event: this.githubEvent })
105+
}
106+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Jira Create issue
2+
description: Create a new Jira issue
3+
branding:
4+
icon: 'check-square'
5+
color: 'blue'
6+
inputs:
7+
project:
8+
description: Key of the project
9+
required: true
10+
issuetype:
11+
description: "Type of the issue to be created. Example: 'Incident'"
12+
required: true
13+
summary:
14+
description: Issue summary
15+
required: true
16+
description:
17+
description: Issue description
18+
required: false
19+
outputs:
20+
issue:
21+
description: Key of the newly created issue
22+
runs:
23+
using: 'node12'
24+
main: 'dist/index.js'
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 getCreateMeta (query) {
15+
return this.fetch('getCreateMeta', { pathname: '/rest/api/2/issue/createmeta', query })
16+
}
17+
18+
async createIssue (body) {
19+
return this.fetch('createIssue',
20+
{ pathname: '/rest/api/2/issue' },
21+
{ method: 'POST', body })
22+
}
23+
24+
async getIssue (issueId, query = {}) {
25+
const { fields = [], expand = [] } = query
26+
27+
try {
28+
return this.fetch('getIssue', {
29+
pathname: `/rest/api/2/issue/${issueId}`,
30+
query: {
31+
fields: fields.join(','),
32+
expand: expand.join(','),
33+
},
34+
})
35+
} catch (error) {
36+
if (get(error, 'res.status') === 404) {
37+
return
38+
}
39+
40+
throw error
41+
}
42+
}
43+
44+
async getIssueTransitions (issueId) {
45+
return this.fetch('getIssueTransitions', {
46+
pathname: `/rest/api/2/issue/${issueId}/transitions`,
47+
}, {
48+
method: 'GET',
49+
})
50+
}
51+
52+
async transitionIssue (issueId, data) {
53+
return this.fetch('transitionIssue', {
54+
pathname: `/rest/api/3/issue/${issueId}/transitions`,
55+
}, {
56+
method: 'POST',
57+
body: data,
58+
})
59+
}
60+
61+
async fetch (apiMethodName,
62+
{ host, pathname, query },
63+
{ method, body, headers = {} } = {}) {
64+
const url = format({
65+
host: host || this.baseUrl,
66+
pathname,
67+
query,
68+
})
69+
70+
if (!method) {
71+
method = 'GET'
72+
}
73+
74+
if (headers['Content-Type'] === undefined) {
75+
headers['Content-Type'] = 'application/json'
76+
}
77+
78+
if (headers.Authorization === undefined) {
79+
headers.Authorization = `Basic ${Buffer.from(`${this.email}:${this.token}`).toString('base64')}`
80+
}
81+
82+
// strong check for undefined
83+
// cause body variable can be 'false' boolean value
84+
if (body && headers['Content-Type'] === 'application/json') {
85+
body = JSON.stringify(body)
86+
}
87+
88+
const state = {
89+
req: {
90+
method,
91+
headers,
92+
body,
93+
url,
94+
},
95+
}
96+
97+
try {
98+
await client(state, `${serviceName}:${apiMethodName}`)
99+
} catch (error) {
100+
const fields = {
101+
originError: error,
102+
source: 'jira',
103+
}
104+
105+
delete state.req.headers
106+
107+
throw Object.assign(
108+
new Error('Jira API error'),
109+
state,
110+
fields,
111+
{ jiraError: state.res.body.errors })
112+
}
113+
114+
return state.res.body
115+
}
116+
}
117+
118+
module.exports = Jira
Lines changed: 35 additions & 0 deletions
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+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const fs = require('fs')
2+
const core = require('@actions/core')
3+
4+
const Action = require('./action')
5+
6+
// eslint-disable-next-line import/no-dynamic-require
7+
const githubEvent = require(process.env.GITHUB_EVENT_PATH)
8+
const config = {
9+
baseUrl: process.env.JIRA_BASE_URL,
10+
token: process.env.JIRA_API_TOKEN,
11+
email: process.env.JIRA_USER_EMAIL
12+
}
13+
14+
async function exec () {
15+
try {
16+
const args = {
17+
project: process.env.INPUT_PROJECT,
18+
issuetype: process.env.INPUT_TYPE,
19+
summary: process.env.INPUT_SUMMARY,
20+
description: process.env.INPUT_DESCRIPTION,
21+
area: process.env.INPUT_AREA,
22+
assignee: process.env.INPUT_ASSIGNEE
23+
}
24+
25+
console.log("ARGS")
26+
console.log(JSON.stringify(args, null, 4))
27+
28+
const result = await new Action({
29+
githubEvent,
30+
argv: args,
31+
config,
32+
}).execute()
33+
34+
if (result) {
35+
// result.issue is the issue key
36+
console.log(`Created issue: ${result.issue}`)
37+
38+
// Expose created issue's key as an output
39+
core.setOutput("issue", result.issue)
40+
41+
return true
42+
}
43+
44+
console.log('Failed to create issue.')
45+
process.exit(78)
46+
} catch (error) {
47+
console.error(error)
48+
process.exit(1)
49+
}
50+
}
51+
52+
exec()

0 commit comments

Comments
 (0)