Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
drudzikatlassian committed Feb 26, 2019
0 parents commit 157f1a8
Show file tree
Hide file tree
Showing 10 changed files with 2,468 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@atlassian-partner-engineering",
"rules": {
"no-plusplus": "off"
}
}

20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:10-alpine

LABEL "name"="Jira Comment"
LABEL "maintainer"="Dima Rudzik <drudzik+githubactions@atlassian.net>"
LABEL "version"="1.0.0"

LABEL "com.github.actions.name"="Jira Comment"
LABEL "com.github.actions.description"="Add a comment to a Jira issue"
LABEL "com.github.actions.icon"="check-square"
LABEL "com.github.actions.color"="blue"

RUN apk update && apk add --no-cache ca-certificates

ADD https://github.com/atlassian/gajira/raw/master/bin/gagas .
ADD . .
RUN npm i
RUN chmod +x /entrypoint.sh
RUN chmod +x /gagas

ENTRYPOINT ["/entrypoint.sh"]
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Jira Comment
Add a comment to an issue


## Usage
To add comment to an issue you need to specify an issue key and a comment in action args, like:

`"Hello from GitHub actions" --issue=INC-2`

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:

"{{ pusher.name }} pushed to repository: {{ repository.full_name }}"

Which will result in comment:

Codertocat pushed to repository: Codertocat/Hello-World

----
## Action Spec

### Environment variables
- None

### Arguments
- `--issue` - An issue key to add a comment for

### Reads fields from config file at $HOME/jira/config.yml
- `issue`

### Writes fields to config file at $HOME/jira/config.yml
- None
30 changes: 30 additions & 0 deletions action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const _ = require('lodash')
const Jira = require('./common/net/Jira')

module.exports = class {
constructor ({ githubEvent, argv, config }) {
this.Jira = new Jira({
baseUrl: config.baseUrl,
token: config.token,
email: config.email,
})

this.config = config
this.argv = argv
this.githubEvent = githubEvent
}

async execute () {
const issueId = this.argv.issue
const rawComment = this.argv._.join(' ')

_.templateSettings.interpolate = /{{([\s\S]+?)}}/g
const compiled = _.template(rawComment)
const interpolatedComment = compiled({ event: this.githubEvent })

console.log(`Adding comment to ${issueId}: ${interpolatedComment}`)
await this.Jira.addComment(issueId, { body: interpolatedComment })

return {}
}
}
123 changes: 123 additions & 0 deletions common/net/Jira.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const { get } = require('lodash')

const serviceName = 'jira'
const { format } = require('url')
const client = require('./client')(serviceName)

class Jira {
constructor ({ baseUrl, token, email }) {
this.baseUrl = baseUrl
this.token = token
this.email = email
}

async addComment (issueId, data) {
return this.fetch('addComment', {
pathname: `/rest/api/2/issue/${issueId}/comment`,
}, {
method: 'POST',
body: data,
})
}

async createIssue (body) {
return this.fetch('createIssue',
{ pathname: '/rest/api/2/issue' },
{ method: 'POST', body })
}

async getIssue (issueId, query = {}) {
const { fields = [], expand = [] } = query

try {
return this.fetch('getIssue', {
pathname: `/rest/api/2/issue/${issueId}`,
query: {
fields: fields.join(','),
expand: expand.join(','),
},
})
} catch (error) {
if (get(error, 'res.status') === 404) {
return
}

throw error
}
}

async getIssueTransitions (issueId) {
return this.fetch('getIssueTransitions', {
pathname: `/rest/api/2/issue/${issueId}/transitions`,
}, {
method: 'GET',
})
}

async transitionIssue (issueId, data) {
return this.fetch('transitionIssue', {
pathname: `/rest/api/3/issue/${issueId}/transitions`,
}, {
method: 'POST',
body: data,
})
}

async fetch (apiMethodName,
{ host, pathname, query },
{ method, body, headers = {} } = {}) {
const url = format({
host: host || this.baseUrl,
pathname,
query,
})

if (!method) {
method = 'GET'
}

if (headers['Content-Type'] === undefined) {
headers['Content-Type'] = 'application/json'
}

if (headers.Authorization === undefined) {
headers.Authorization = `Basic ${Buffer.from(`${this.email}:${this.token}`).toString('base64')}`
}

// strong check for undefined
// cause body variable can be 'false' boolean value
if (body && headers['Content-Type'] === 'application/json') {
body = JSON.stringify(body)
}

const state = {
req: {
method,
headers,
body,
url,
},
}

try {
await client(state, `${serviceName}:${apiMethodName}`)
} catch (error) {
const fields = {
originError: error,
source: 'jira',
}

delete state.req.headers

throw Object.assign(
new Error('Jira API error'),
state,
fields
)
}

return state.res.body
}
}

module.exports = Jira
35 changes: 35 additions & 0 deletions common/net/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const fetch = require('node-fetch')
// const moment = require('moment')

module.exports = serviceName => async (state, apiMethod = 'unknown') => {
// const startTime = moment.now()

const response = await fetch(state.req.url, state.req)

state.res = {
headers: response.headers.raw(),
status: response.status,
}

// const totalTime = moment.now() - startTime
// const tags = {
// api_method: apiMethod,
// method: state.req.method || 'GET',
// response_code: response.status,
// service: serviceName,
// }

state.res.body = await response.text()

const isJSON = (response.headers.get('content-type') || '').includes('application/json')

if (isJSON && state.res.body) {
state.res.body = JSON.parse(state.res.body)
}

if (!response.ok) {
throw new Error(response.statusText)
}

return state
}
10 changes: 10 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh
set -eu

sh -c "node /index.js $*"

actionSubjectId="comment"
containerId=`echo $GITHUB_REPOSITORY | sha1sum | cut -c1-41`
anonymousId=`echo $GITHUB_ACTOR | sha1sum | cut -c1-41`

/gagas --container-id="$containerId" --action-subject-id="$actionSubjectId" --anonymous-id="$anonymousId"
56 changes: 56 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const fs = require('fs')
const YAML = require('yaml')
const yargs = require('yargs')

const cliConfigPath = `${process.env.HOME}/.jira.d/config.yml`
const configPath = `${process.env.HOME}/jira/config.yml`
const Action = require('./action')

// eslint-disable-next-line import/no-dynamic-require
const githubEvent = require(process.env.GITHUB_EVENT_PATH)
const config = YAML.parse(fs.readFileSync(configPath, 'utf8'))

async function exec () {
try {
const result = await new Action({
githubEvent,
argv: parseArgs(),
config,
}).execute()

if (result) {
const yamledResult = YAML.stringify(result)
const extendedConfig = Object.assign({}, config, result)

fs.writeFileSync(configPath, YAML.stringify(extendedConfig))

return fs.appendFileSync(cliConfigPath, yamledResult)
}

console.log('Failed to comment an issue.')
process.exit(78)
} catch (error) {
console.error(error)
process.exit(1)
}
}

function parseArgs () {
yargs
.option('issue', {
alias: 'i',
describe: 'Provide an issue key to add a comment for',
demandOption: !config.issue,
default: config.issue,
type: 'string',
})

yargs
.parserConfiguration({
'parse-numbers': false,
})

return yargs.argv
}

exec()
Loading

0 comments on commit 157f1a8

Please sign in to comment.