Merge pull request #200 from actions/willdasilva-fork

Support user-provided base/head refs & non-PR workflows
This commit is contained in:
Federico Builes
2022-08-18 15:30:04 +02:00
committed by GitHub
9 changed files with 178 additions and 32 deletions
+23 -2
View File
@@ -1,12 +1,12 @@
# dependency-review-action # 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 vulnerabilities or invalid licenses are being introduced. 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. 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"> <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 ## Installation
**Please keep in mind that you need a [GitHub Advanced Security](https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security) license if you're running this action on private repositories.** **Please keep in mind that you need a [GitHub Advanced Security](https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security) license if you're running this action on private repositories.**
@@ -58,6 +58,7 @@ jobs:
``` ```
## Configuration ## Configuration
You can pass additional options to the Dependency Review You can pass additional options to the Dependency Review
Action using your workflow file. Here's an example workflow with Action using your workflow file. Here's an example workflow with
all the possible configurations: all the possible configurations:
@@ -79,6 +80,10 @@ jobs:
# Possible values: "critical", "high", "moderate", "low" # Possible values: "critical", "high", "moderate", "low"
# fail-on-severity: critical # 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`. These options are not supported on GHES. # You can only include one of these two options: `allow-licenses` and `deny-licenses`. These options are not supported on GHES.
# #
# Possible values: Any `spdx_id` value(s) from https://docs.github.com/en/rest/licenses # Possible values: Any `spdx_id` value(s) from https://docs.github.com/en/rest/licenses
@@ -88,6 +93,11 @@ jobs:
# deny-licenses: LGPL-2.0, BSD-2-Clause # 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 the 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 configuration file.
### Vulnerability Severity ### Vulnerability Severity
By default the action will fail on any pull request that contains a By default the action will fail on any pull request that contains a
@@ -134,6 +144,15 @@ to filter. A couple of examples:
**Important** **Important**
<<<<<<< HEAD
- 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**.
=======
* Checking for licenses is not supported on GHES. * Checking for licenses is not supported on GHES.
* The action will only accept one of the two parameters; an error will * The action will only accept one of the two parameters; an error will
be raised if you provide both. be raised if you provide both.
@@ -142,6 +161,7 @@ performed).
* We don't have license information for all of your dependents. If we * 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 can't detect the license for a dependency **we will inform you, but the
action won't fail**. action won't fail**.
>>>>>>> main
## Blocking pull requests ## Blocking pull requests
@@ -159,4 +179,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. Please read [CONTRIBUTING.MD](https://github.com/actions/dependency-review-action/blob/main/CONTRIBUTING.md) to get started.
## License ## License
This project is released under the [MIT License](https://github.com/actions/dependency-review-action/blob/main/LICENSE). This project is released under the [MIT License](https://github.com/actions/dependency-review-action/blob/main/LICENSE).
+34 -3
View File
@@ -1,5 +1,6 @@
import {expect, test, beforeEach} from '@jest/globals' import {expect, test, beforeEach} from '@jest/globals'
import {readConfig} from '../src/config' import {readConfig} from '../src/config'
import {getRefs} from '../src/git-refs'
// GitHub Action inputs come in the form of environment variables // GitHub Action inputs come in the form of environment variables
// with an INPUT prefix (e.g. INPUT_FAIL-ON-SEVERITY) // with an INPUT prefix (e.g. INPUT_FAIL-ON-SEVERITY)
@@ -10,9 +11,17 @@ function setInput(input: string, value: string) {
// We want a clean ENV before each test. We use `delete` // We want a clean ENV before each test. We use `delete`
// since we want `undefined` values and not empty strings. // since we want `undefined` values and not empty strings.
function clearInputs() { function clearInputs() {
delete process.env['INPUT_FAIL-ON-SEVERITY'] const allowedOptions = [
delete process.env['INPUT_ALLOW-LICENSES'] 'FAIL-ON-SEVERITY',
delete process.env['INPUT_DENY-LICENSES'] 'ALLOW-LICENSES',
'DENY-LICENSES',
'BASE-REF',
'HEAD-REF'
]
allowedOptions.forEach(option => {
delete process.env[`INPUT_${option.toUpperCase()}`]
})
} }
beforeEach(() => { beforeEach(() => {
@@ -51,3 +60,25 @@ test('it raises an error when given an unknown severity', async () => {
setInput('fail-on-severity', 'zombies') setInput('fail-on-severity', 'zombies')
expect(() => readConfig()).toThrow() 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()
})
+6
View File
@@ -10,6 +10,12 @@ inputs:
description: Don't block PRs below this severity. Possible values are `low`, `moderate`, `high`, `critical`. description: Don't block PRs below this severity. Possible values are `low`, `moderate`, `high`, `critical`.
required: false required: false
default: 'low' 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: allow-licenses:
description: Comma-separated list of allowed licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause") description: Comma-separated list of allowed licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
required: false required: false
Generated Vendored
+57 -11
View File
@@ -59,6 +59,47 @@ function compare({ owner, repo, baseRef, headRef }) {
exports.compare = compare; 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: /***/ 3247:
@@ -157,24 +198,21 @@ const dependencyGraph = __importStar(__nccwpck_require__(4966));
const github = __importStar(__nccwpck_require__(5438)); const github = __importStar(__nccwpck_require__(5438));
const ansi_styles_1 = __importDefault(__nccwpck_require__(6844)); const ansi_styles_1 = __importDefault(__nccwpck_require__(6844));
const request_error_1 = __nccwpck_require__(537); const request_error_1 = __nccwpck_require__(537);
const schemas_1 = __nccwpck_require__(8774);
const config_1 = __nccwpck_require__(6373); const config_1 = __nccwpck_require__(6373);
const filter_1 = __nccwpck_require__(8752); const filter_1 = __nccwpck_require__(8752);
const licenses_1 = __nccwpck_require__(3247); const licenses_1 = __nccwpck_require__(3247);
const git_refs_1 = __nccwpck_require__(1086);
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
if (github.context.eventName !== 'pull_request') { const config = (0, config_1.readConfig)();
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 refs = (0, git_refs_1.getRefs)(config, github.context);
}
const pull_request = schemas_1.PullRequestSchema.parse(github.context.payload.pull_request);
const changes = yield dependencyGraph.compare({ const changes = yield dependencyGraph.compare({
owner: github.context.repo.owner, owner: github.context.repo.owner,
repo: github.context.repo.repo, repo: github.context.repo.repo,
baseRef: pull_request.base.sha, baseRef: refs.base,
headRef: pull_request.head.sha headRef: refs.head
}); });
const config = (0, config_1.readConfig)();
const minSeverity = config.fail_on_severity; const minSeverity = config.fail_on_severity;
let failed = false; let failed = false;
const licenses = { const licenses = {
@@ -319,7 +357,9 @@ exports.ConfigurationOptionsSchema = z
.object({ .object({
fail_on_severity: z.enum(exports.SEVERITIES).default('low'), fail_on_severity: z.enum(exports.SEVERITIES).default('low'),
allow_licenses: z.array(z.string()).default([]), 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() .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.'); .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.');
@@ -14696,10 +14736,14 @@ function readConfig() {
if (allow_licenses !== undefined && deny_licenses !== undefined) { if (allow_licenses !== undefined && deny_licenses !== undefined) {
throw new Error("Can't specify both allow_licenses and deny_licenses"); 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 { return {
fail_on_severity, fail_on_severity,
allow_licenses: allow_licenses === null || allow_licenses === void 0 ? void 0 : allow_licenses.split(',').map(x => x.trim()), 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; exports.readConfig = readConfig;
@@ -14801,7 +14845,9 @@ exports.ConfigurationOptionsSchema = z
.object({ .object({
fail_on_severity: z.enum(exports.SEVERITIES).default('low'), fail_on_severity: z.enum(exports.SEVERITIES).default('low'),
allow_licenses: z.array(z.string()).default([]), 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() .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.'); .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.');
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+6 -1
View File
@@ -19,9 +19,14 @@ export function readConfig(): ConfigurationOptions {
throw new Error("Can't specify both allow_licenses and deny_licenses") 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 { return {
fail_on_severity, fail_on_severity,
allow_licenses: allow_licenses?.split(',').map(x => x.trim()), 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
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
}
}
+6 -13
View File
@@ -3,31 +3,24 @@ import * as dependencyGraph from './dependency-graph'
import * as github from '@actions/github' import * as github from '@actions/github'
import styles from 'ansi-styles' import styles from 'ansi-styles'
import {RequestError} from '@octokit/request-error' import {RequestError} from '@octokit/request-error'
import {Change, PullRequestSchema, Severity} from './schemas' import {Change, Severity} from './schemas'
import {readConfig} from '../src/config' import {readConfig} from '../src/config'
import {filterChangesBySeverity} from '../src/filter' import {filterChangesBySeverity} from '../src/filter'
import {getDeniedLicenseChanges} from './licenses' import {getDeniedLicenseChanges} from './licenses'
import {getRefs} from './git-refs'
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
if (github.context.eventName !== 'pull_request') { const config = readConfig()
throw new Error( const refs = getRefs(config, github.context)
`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 changes = await dependencyGraph.compare({ const changes = await dependencyGraph.compare({
owner: github.context.repo.owner, owner: github.context.repo.owner,
repo: github.context.repo.repo, repo: github.context.repo.repo,
baseRef: pull_request.base.sha, baseRef: refs.base,
headRef: pull_request.head.sha headRef: refs.head
}) })
const config = readConfig()
const minSeverity = config.fail_on_severity const minSeverity = config.fail_on_severity
let failed = false let failed = false
+3 -1
View File
@@ -34,7 +34,9 @@ export const ConfigurationOptionsSchema = z
.object({ .object({
fail_on_severity: z.enum(SEVERITIES).default('low'), fail_on_severity: z.enum(SEVERITIES).default('low'),
allow_licenses: z.array(z.string()).default([]), 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() .partial()
.refine( .refine(