use GitHub context to branch processing (#22)
This commit is contained in:
2
.github/workflows/daily.yml
vendored
2
.github/workflows/daily.yml
vendored
@@ -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
|
||||
|
||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -27,4 +27,5 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: ./
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue_title: npm audit run by test job
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
19
__tests__/testdata/audit/error.txt
vendored
Normal 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
5
__tests__/testdata/audit/success.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
=== npm audit security report ===
|
||||
|
||||
found 0 vulnerabilities
|
||||
in 892282 scanned packages
|
||||
@@ -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
3401
dist/index.js
vendored
File diff suppressed because one or more lines are too long
41
package-lock.json
generated
41
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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\`\`\``
|
||||
}
|
||||
}
|
||||
|
||||
10
src/issue.ts
10
src/issue.ts
@@ -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 {
|
||||
|
||||
57
src/main.ts
57
src/main.ts
@@ -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
19
src/pr.ts
Normal 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
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user