Support user-provided base/head refs & non-PR workflows

This commit is contained in:
Will Da Silva
2022-07-21 15:47:05 -04:00
parent b15d68a617
commit 388b1a309d
9 changed files with 164 additions and 36 deletions

View File

@@ -1,12 +1,11 @@
# dependency-review-action
This action scans your pull requests for dependency changes and will raise an error if any new dependencies have existing vulnerabilities. The action is supported by an [API endpoint](https://docs.github.com/en/rest/reference/dependency-graph#dependency-review) that diffs the dependencies between any two revisions.
This action scans your pull requests for dependency changes and will raise an error if any dependencies introduced/updated between the head and base ref have existing vulnerabilities. The action is supported by an [API endpoint](https://docs.github.com/en/rest/reference/dependency-graph#dependency-review) that diffs the dependencies between any two revisions.
The action is available for all public repositories, as well as private repositories that have GitHub Advanced Security licensed.
<img width="854" alt="Screen Shot 2022-03-31 at 1 10 51 PM" src="https://user-images.githubusercontent.com/2161/161042286-b22d7dd3-13cb-458d-8744-ce70ed9bf562.png">
## Installation
1. Add a new YAML workflow to your `.github/workflows` folder:
@@ -31,6 +30,7 @@ jobs:
Please keep in mind that you need a GitHub Advanced Security license if you're running this action on private repos.
## Configuration
You can pass additional options to the Dependency Review
Action using your workflow file. Here's an example workflow with
all the possible configurations:
@@ -52,6 +52,10 @@ jobs:
# Possible values: "critical", "high", "moderate", "low"
# fail-on-severity: critical
#
# Possible values: Any available git ref
# base-ref: ${{ github.event.pull_request.base.ref }}
# head-ref: ${{ github.event.pull_request.head.ref }}
#
# You can only include one of these two options: `allow-licenses` and `deny-licenses`
#
# Possible values: Any `spdx_id` value(s) from https://docs.github.com/en/rest/licenses
@@ -61,6 +65,10 @@ jobs:
# deny-licenses: LGPL-2.0, BSD-2-Clause
```
When the workflow with this action is caused by a `pull_request` or `pull_request_target` event,
the `base-ref` and `head-ref` values have defaults as shown above. If the workflow is caused by
any other event, the `base-ref` and `head-ref` options must be explicitly set in the config.
### Vulnerability Severity
By default the action will fail on any pull request that contains a
@@ -107,13 +115,13 @@ to filter. A couple of examples:
**Important**
* The action will only accept one of the two parameters; an error will
be raised if you provide both.
* By default both parameters are empty (no license checking is
performed).
* We don't have license information for all of your dependents. If we
can't detect the license for a dependency **we will inform you, but the
action won't fail**.
- The action will only accept one of the two parameters; an error will
be raised if you provide both.
- By default both parameters are empty (no license checking is
performed).
- We don't have license information for all of your dependents. If we
can't detect the license for a dependency **we will inform you, but the
action won't fail**.
## Blocking pull requests
@@ -131,4 +139,5 @@ We are grateful for any contributions made to this project.
Please read [CONTRIBUTING.MD](https://github.com/actions/dependency-review-action/blob/main/CONTRIBUTING.md) to get started.
## License
This project is released under the [MIT License](https://github.com/actions/dependency-review-action/blob/main/LICENSE).

View File

@@ -1,5 +1,6 @@
import {expect, test, beforeEach} from '@jest/globals'
import {readConfig} from '../src/config'
import {getRefs} from '../src/git-refs'
// GitHub Action inputs come in the form of environment variables
// with an INPUT prefix (e.g. INPUT_FAIL-ON-SEVERITY)
@@ -13,6 +14,8 @@ function clearInputs() {
delete process.env['INPUT_FAIL-ON-SEVERITY']
delete process.env['INPUT_ALLOW-LICENSES']
delete process.env['INPUT_DENY-LICENSES']
delete process.env['INPUT_BASE-REF']
delete process.env['INPUT_HEAD-REF']
}
beforeEach(() => {
@@ -51,3 +54,25 @@ test('it raises an error when given an unknown severity', async () => {
setInput('fail-on-severity', 'zombies')
expect(() => readConfig()).toThrow()
})
test('it uses the given refs when the event is not a pull request', async () => {
setInput('base-ref', 'a-custom-base-ref')
setInput('head-ref', 'a-custom-head-ref')
const refs = getRefs(readConfig(), {
payload: {},
eventName: 'workflow_dispatch'
})
expect(refs.base).toEqual('a-custom-base-ref')
expect(refs.head).toEqual('a-custom-head-ref')
})
test('it raises an error when no refs are provided and the event is not a pull request', async () => {
const options = readConfig()
expect(() =>
getRefs(options, {
payload: {},
eventName: 'workflow_dispatch'
})
).toThrow()
})

View File

@@ -10,6 +10,12 @@ inputs:
description: Don't block PRs below this severity. Possible values are `low`, `moderate`, `high`, `critical`.
required: false
default: 'low'
base-ref:
description: The base git ref to be used for this check. Has a default value when the workflow event is `pull_request` or `pull_request_target`. Must be provided otherwise.
required: false
head-ref:
description: The head git ref to be used for this check. Has a default value when the workflow event is `pull_request` or `pull_request_target`. Must be provided otherwise.
required: false
allow-licenses:
description: Comma-separated list of allowed licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
required: false

68
dist/index.js generated vendored
View File

@@ -59,6 +59,47 @@ function compare({ owner, repo, baseRef, headRef }) {
exports.compare = compare;
/***/ }),
/***/ 1086:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getRefs = void 0;
const schemas_1 = __nccwpck_require__(8774);
function getRefs(config, context) {
let base_ref = config.base_ref;
let head_ref = config.head_ref;
// If possible, source default base & head refs from the GitHub event.
// The base/head ref from the config take priority, if provided.
if (context.eventName === 'pull_request' ||
context.eventName === 'pull_request_target') {
const pull_request = schemas_1.PullRequestSchema.parse(context.payload.pull_request);
base_ref = base_ref || pull_request.base.sha;
head_ref = head_ref || pull_request.head.sha;
}
if (!base_ref && !head_ref) {
throw new Error('Both a base ref and head ref must be provided, either via the `base_ref`/`head_ref` ' +
'config options, or by running a `pull_request`/`pull_request_target` workflow.');
}
else if (!base_ref) {
throw new Error('A base ref must be provided, either via the `base_ref` config option, ' +
'or by running a `pull_request`/`pull_request_target` workflow.');
}
else if (!head_ref) {
throw new Error('A head ref must be provided, either via the `head_ref` config option, ' +
'or by running a `pull_request`/`pull_request_target` workflow.');
}
return {
base: base_ref,
head: head_ref
};
}
exports.getRefs = getRefs;
/***/ }),
/***/ 3247:
@@ -157,24 +198,21 @@ const dependencyGraph = __importStar(__nccwpck_require__(4966));
const github = __importStar(__nccwpck_require__(5438));
const ansi_styles_1 = __importDefault(__nccwpck_require__(6844));
const request_error_1 = __nccwpck_require__(537);
const schemas_1 = __nccwpck_require__(8774);
const config_1 = __nccwpck_require__(6373);
const filter_1 = __nccwpck_require__(8752);
const licenses_1 = __nccwpck_require__(3247);
const git_refs_1 = __nccwpck_require__(1086);
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (github.context.eventName !== 'pull_request') {
throw new Error(`This run was triggered by the "${github.context.eventName}" event, which is unsupported. Please ensure you are using the "pull_request" event for this workflow.`);
}
const pull_request = schemas_1.PullRequestSchema.parse(github.context.payload.pull_request);
const config = (0, config_1.readConfig)();
const refs = (0, git_refs_1.getRefs)(config, github.context);
const changes = yield dependencyGraph.compare({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
baseRef: pull_request.base.sha,
headRef: pull_request.head.sha
baseRef: refs.base,
headRef: refs.head
});
const config = (0, config_1.readConfig)();
const minSeverity = config.fail_on_severity;
let failed = false;
const licenses = {
@@ -319,7 +357,9 @@ exports.ConfigurationOptionsSchema = z
.object({
fail_on_severity: z.enum(exports.SEVERITIES).default('low'),
allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([])
deny_licenses: z.array(z.string()).default([]),
base_ref: z.string(),
head_ref: z.string()
})
.partial()
.refine(obj => !(obj.allow_licenses && obj.deny_licenses), 'Your workflow file has both an allow_licenses list and deny_licenses list, but you can only set one or the other.');
@@ -14017,10 +14057,14 @@ function readConfig() {
if (allow_licenses !== undefined && deny_licenses !== undefined) {
throw new Error("Can't specify both allow_licenses and deny_licenses");
}
const base_ref = getOptionalInput('base-ref');
const head_ref = getOptionalInput('head-ref');
return {
fail_on_severity,
allow_licenses: allow_licenses === null || allow_licenses === void 0 ? void 0 : allow_licenses.split(',').map(x => x.trim()),
deny_licenses: deny_licenses === null || deny_licenses === void 0 ? void 0 : deny_licenses.split(',').map(x => x.trim())
deny_licenses: deny_licenses === null || deny_licenses === void 0 ? void 0 : deny_licenses.split(',').map(x => x.trim()),
base_ref,
head_ref
};
}
exports.readConfig = readConfig;
@@ -14122,7 +14166,9 @@ exports.ConfigurationOptionsSchema = z
.object({
fail_on_severity: z.enum(exports.SEVERITIES).default('low'),
allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([])
deny_licenses: z.array(z.string()).default([]),
base_ref: z.string(),
head_ref: z.string()
})
.partial()
.refine(obj => !(obj.allow_licenses && obj.deny_licenses), 'Your workflow file has both an allow_licenses list and deny_licenses list, but you can only set one or the other.');

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -19,9 +19,14 @@ export function readConfig(): ConfigurationOptions {
throw new Error("Can't specify both allow_licenses and deny_licenses")
}
const base_ref = getOptionalInput('base-ref')
const head_ref = getOptionalInput('head-ref')
return {
fail_on_severity,
allow_licenses: allow_licenses?.split(',').map(x => x.trim()),
deny_licenses: deny_licenses?.split(',').map(x => x.trim())
deny_licenses: deny_licenses?.split(',').map(x => x.trim()),
base_ref,
head_ref
}
}

42
src/git-refs.ts Normal file
View File

@@ -0,0 +1,42 @@
import {PullRequestSchema, ConfigurationOptions} from './schemas'
export function getRefs(
config: ConfigurationOptions,
context: {payload: {pull_request?: unknown}; eventName: string}
): {base: string; head: string} {
let base_ref = config.base_ref
let head_ref = config.head_ref
// If possible, source default base & head refs from the GitHub event.
// The base/head ref from the config take priority, if provided.
if (
context.eventName === 'pull_request' ||
context.eventName === 'pull_request_target'
) {
const pull_request = PullRequestSchema.parse(context.payload.pull_request)
base_ref = base_ref || pull_request.base.sha
head_ref = head_ref || pull_request.head.sha
}
if (!base_ref && !head_ref) {
throw new Error(
'Both a base ref and head ref must be provided, either via the `base_ref`/`head_ref` ' +
'config options, or by running a `pull_request`/`pull_request_target` workflow.'
)
} else if (!base_ref) {
throw new Error(
'A base ref must be provided, either via the `base_ref` config option, ' +
'or by running a `pull_request`/`pull_request_target` workflow.'
)
} else if (!head_ref) {
throw new Error(
'A head ref must be provided, either via the `head_ref` config option, ' +
'or by running a `pull_request`/`pull_request_target` workflow.'
)
}
return {
base: base_ref,
head: head_ref
}
}

View File

@@ -3,31 +3,24 @@ import * as dependencyGraph from './dependency-graph'
import * as github from '@actions/github'
import styles from 'ansi-styles'
import {RequestError} from '@octokit/request-error'
import {Change, PullRequestSchema, Severity} from './schemas'
import {Change, Severity} from './schemas'
import {readConfig} from '../src/config'
import {filterChangesBySeverity} from '../src/filter'
import {getDeniedLicenseChanges} from './licenses'
import {getRefs} from './git-refs'
async function run(): Promise<void> {
try {
if (github.context.eventName !== 'pull_request') {
throw new Error(
`This run was triggered by the "${github.context.eventName}" event, which is unsupported. Please ensure you are using the "pull_request" event for this workflow.`
)
}
const pull_request = PullRequestSchema.parse(
github.context.payload.pull_request
)
const config = readConfig()
const refs = getRefs(config, github.context)
const changes = await dependencyGraph.compare({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
baseRef: pull_request.base.sha,
headRef: pull_request.head.sha
baseRef: refs.base,
headRef: refs.head
})
const config = readConfig()
const minSeverity = config.fail_on_severity
let failed = false

View File

@@ -34,7 +34,9 @@ export const ConfigurationOptionsSchema = z
.object({
fail_on_severity: z.enum(SEVERITIES).default('low'),
allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([])
deny_licenses: z.array(z.string()).default([]),
base_ref: z.string(),
head_ref: z.string()
})
.partial()
.refine(