use GitHub context to branch processing (#22)

This commit is contained in:
Naoki Oketani
2019-12-13 12:18:28 +09:00
committed by GitHub
parent ac19a7500b
commit 3e659c8c99
14 changed files with 3579 additions and 80 deletions

View File

@@ -14,6 +14,6 @@ jobs:
run: npm ci
- uses: oke-py/npm-audit-action@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
issue_assignees: oke-py
issue_labels: vulnerability

View File

@@ -27,4 +27,5 @@ jobs:
- uses: actions/checkout@v1
- uses: ./
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
issue_title: npm audit run by test job

View File

@@ -43,7 +43,7 @@ jobs:
run: npm ci
- uses: oke-py/npm-audit-action@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
issue_assignees: oke-py
issue_labels: vulnerability,test
```

View File

@@ -1,3 +1,87 @@
describe('main', () => {
test.todo('Add a test suite')
import * as fs from 'fs'
import * as path from 'path'
import {mocked} from 'ts-jest/utils'
import axios, {AxiosResponse} from 'axios'
import {Audit} from '../src/audit'
import {run} from '../src/main'
import * as pr from '../src/pr'
jest.mock('../src/audit')
jest.mock('../src/pr')
describe('run', () => {
beforeEach(() => {
// initialize mock
mocked(Audit).mockClear()
mocked(pr).createComment.mockClear()
})
test('does not call pr.createComment if vulnerabilities are not found', () => {
mocked(Audit).mockImplementation((): any => {
return {
stdout: fs.readFileSync(
path.join(__dirname, 'testdata/audit/success.txt')
),
status: 0,
run: (): void => {
return
},
foundVulnerability: (): boolean => {
return true
}
}
})
mocked(pr).createComment.mockResolvedValue({
config: {},
headers: {},
status: 201,
statusText: 'Created',
data: {
value: []
}
})
const audit = new Audit()
run()
expect(pr.createComment).not.toHaveBeenCalled()
})
test('calls pr.createComment if vulnerabilities are found in PR', () => {
mocked(Audit).mockImplementation((): any => {
return {
stdout: fs.readFileSync(
path.join(__dirname, 'testdata/audit/error.txt')
),
status: 1,
run: (): void => {
return
},
foundVulnerability: (): boolean => {
return true
},
strippedStdout: (): string => {
return path.join(__dirname, 'testdata/audit/error.txt')
}
}
})
mocked(pr).createComment.mockResolvedValue({
config: {},
headers: {},
status: 201,
statusText: 'Created',
data: {
value: []
}
})
process.env.INPUT_GITHUB_CONTEXT =
'{ "event_name": "pull_request", "event": { "number": 100} }'
process.env.INPUT_GITHUB_TOKEN = '***'
process.env.GITHUB_REPOSITORY = 'alice/example'
const audit = new Audit()
run()
expect(pr.createComment).toHaveBeenCalled()
})
})

19
__tests__/testdata/audit/error.txt vendored Normal file
View File

@@ -0,0 +1,19 @@
=== npm audit security report ===
# Run npm update strapi --depth 1 to resolve 1 vulnerability
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ High │ Command Injection │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package │ strapi │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ strapi │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path │ strapi │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info │ https://npmjs.com/advisories/1424 │
└───────────────┴──────────────────────────────────────────────────────────────┘
found 1 high severity vulnerability in 897105 scanned packages
run `npm audit fix` to fix 1 of them.

5
__tests__/testdata/audit/success.txt vendored Normal file
View File

@@ -0,0 +1,5 @@
=== npm audit security report ===
found 0 vulnerabilities
in 892282 scanned packages

View File

@@ -2,7 +2,11 @@ name: 'npm audit action'
description: 'run npm audit'
author: 'Naoki Oketani <okepy.naoki@gmail.com>'
inputs:
token:
github_context:
description: 'The `github` context'
default: ${{ toJson(github) }}
required: false
github_token:
description: 'GitHub access token used to create an issue'
required: true
issue_assignees:

3401
dist/index.js vendored

File diff suppressed because one or more lines are too long

41
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "npm-audit-action",
"version": "1.0.0",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -965,6 +965,22 @@
"integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==",
"dev": true
},
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"requires": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
},
"dependencies": {
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
}
}
},
"axobject-query": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.1.tgz",
@@ -2577,6 +2593,29 @@
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
"dev": true
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "npm-audit-action",
"version": "1.0.0",
"version": "1.1.0",
"private": true,
"description": "GitHub Action to run `npm audit`",
"main": "lib/main.js",
@@ -29,6 +29,7 @@
"@actions/core": "^1.2.0",
"@actions/github": "^1.1.0",
"@octokit/rest": "^16.35.0",
"axios": "^0.19.0",
"strip-ansi": "^6.0.0"
},
"devDependencies": {

View File

@@ -1,4 +1,5 @@
import {spawnSync, SpawnSyncReturns} from 'child_process'
import stripAnsi from 'strip-ansi'
export class Audit {
stdout: string = ''
@@ -27,4 +28,8 @@ export class Audit {
// `npm audit` return 1 when it found vulnerabilities
return this.status === 1
}
public strippedStdout(): string {
return `\`\`\`\n${stripAnsi(this.stdout)}\n\`\`\``
}
}

View File

@@ -6,10 +6,16 @@ export function getIssueOption(body: string): IssueOption {
let labels
if (core.getInput('issue_assignees')) {
assignees = core.getInput('issue_assignees').replace(/\s+/g, '').split(',')
assignees = core
.getInput('issue_assignees')
.replace(/\s+/g, '')
.split(',')
}
if (core.getInput('issue_labels')) {
labels = core.getInput('issue_labels').replace(/\s+/g, '').split(',')
labels = core
.getInput('issue_labels')
.replace(/\s+/g, '')
.split(',')
}
return {

View File

@@ -1,37 +1,50 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import stripAnsi from 'strip-ansi'
import Octokit, {IssuesCreateResponse} from '@octokit/rest'
import {Audit} from './audit'
import * as issue from './issue'
import {IssueOption} from './interface'
import * as issue from './issue'
import * as pr from './pr'
async function run(): Promise<void> {
export async function run(): Promise<void> {
try {
// run `npm audit`
const audit = new Audit()
audit.run()
core.info(audit.stdout)
if (!audit.foundVulnerability()) {
// vulnerabilities are not found
return
if (audit.foundVulnerability()) {
// vulnerabilities are found
// get GitHub information
const ctx = JSON.parse(core.getInput('github_context'))
const token: string = core.getInput('github_token', {required: true})
const client: Octokit = new github.GitHub(token)
if (ctx.event_name === 'pull_request') {
await pr.createComment(
token,
github.context.repo.owner,
github.context.repo.repo,
ctx.event.number,
audit.strippedStdout()
)
core.setFailed('This repo has some vulnerabilities')
return
} else {
core.debug('open an issue')
// remove control characters and create a code block
const issueBody = audit.strippedStdout()
const option: IssueOption = issue.getIssueOption(issueBody)
const {
data: createdIssue
}: Octokit.Response<IssuesCreateResponse> = await client.issues.create({
...github.context.repo,
...option
})
core.debug(`#${createdIssue.number}`)
}
}
core.debug('open an issue')
const token: string = core.getInput('token', {required: true})
const client: Octokit = new github.GitHub(token)
// remove control characters and create a code block
const issueBody = `\`\`\`\n${stripAnsi(audit.stdout)}\n\`\`\``
const option: IssueOption = issue.getIssueOption(issueBody)
const {
data: createdIssue
}: Octokit.Response<IssuesCreateResponse> = await client.issues.create({
...github.context.repo,
...option
})
core.debug(`#${createdIssue.number}`)
} catch (error) {
core.setFailed(error.message)
}

19
src/pr.ts Normal file
View File

@@ -0,0 +1,19 @@
import axios, {AxiosResponse} from 'axios'
export async function createComment(
token: string,
owner: string,
repo: string,
prNumber: number,
body: string
): Promise<AxiosResponse> {
const instance = axios.create({
baseURL: 'https://api.github.com',
headers: {
Authorization: `token ${token}`
}
})
return instance.post(`/repos/${owner}/${repo}/issues/${prNumber}/comments`, {
body
})
}