Merge pull request #306 from actions/external-repo-config
Read configuration from external repositories
This commit is contained in:
@@ -71,13 +71,20 @@ or by inlining these options in your workflow file.
|
|||||||
|
|
||||||
### config-file
|
### config-file
|
||||||
|
|
||||||
A string representing the path to an external configuration file. By
|
A string representing the path to a configuraton file. It can be a
|
||||||
default external configuration files are not used.
|
local file, or a file located in an external repository. You can use
|
||||||
|
this syntax for external repositories: `OWNER/REPOSITORY/FILENAME@BRANCH`.
|
||||||
|
|
||||||
**Possible values**: A string representing the absolute path to the
|
If the configuration file is located in an external private repository,
|
||||||
configuration file.
|
use the [external-repo-token](#external-repo-token) parameter of the
|
||||||
|
action to specify a token that has read access to the repository.
|
||||||
|
|
||||||
**Example**: `config-file: ./.github/dependency-review-config.yml`.
|
**Possible values**: A string representing a path to a file located
|
||||||
|
in the current repository, or in an external one.
|
||||||
|
|
||||||
|
**Example**: `config-file: ./.github/dependency-review-config.yml # local file`.
|
||||||
|
|
||||||
|
**Example**: `config-file: github/octorepo/dependency-review-config.yml@main # external repo`
|
||||||
|
|
||||||
### fail-on-severity
|
### fail-on-severity
|
||||||
|
|
||||||
@@ -141,9 +148,9 @@ deny-licenses:
|
|||||||
|
|
||||||
### allow-ghsas
|
### allow-ghsas
|
||||||
|
|
||||||
Add a custom list of GitHub Advisory IDs that can be skipped during detection.
|
A list of GitHub Security Advisory IDs that can be skipped during detection.
|
||||||
|
|
||||||
**Possible values**: Any valid advisory GHSA ids.
|
**Possible values**: Any valid GHSAs from the [GitHub Advisory Database](https://github.com/advisories).
|
||||||
|
|
||||||
**Inline example**: `allow-ghsas: GHSA-abcd-1234-5679, GHSA-efgh-1234-5679`
|
**Inline example**: `allow-ghsas: GHSA-abcd-1234-5679, GHSA-efgh-1234-5679`
|
||||||
|
|
||||||
@@ -185,6 +192,19 @@ base-ref: 8bb8a58d6a4028b6c2e314d5caaf273f57644896
|
|||||||
head-ref: 69af5638bf660cf218aad5709a4c100e42a2f37b
|
head-ref: 69af5638bf660cf218aad5709a4c100e42a2f37b
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### external-repo-token
|
||||||
|
|
||||||
|
A token for fetching external configuration files if they live in
|
||||||
|
an external private repository.
|
||||||
|
|
||||||
|
Visit the [developer settings](https://github.com/settings/tokens) to
|
||||||
|
create a new personal access token with `read` permissions for the
|
||||||
|
repository that hosts the config file.
|
||||||
|
|
||||||
|
**Possible values**: Any GitHub token with read access to the external repository.
|
||||||
|
|
||||||
|
**Example**: `external-repo-token: ghp_123456789abcdef...`
|
||||||
|
|
||||||
### Configuration File
|
### Configuration File
|
||||||
|
|
||||||
You can use an external configuration file to specify the settings for
|
You can use an external configuration file to specify the settings for
|
||||||
|
|||||||
+79
-70
@@ -1,5 +1,5 @@
|
|||||||
import {expect, test, beforeEach} from '@jest/globals'
|
import {expect, test, beforeEach} from '@jest/globals'
|
||||||
import {readConfig, readConfigFile} from '../src/config'
|
import {readConfig} from '../src/config'
|
||||||
import {getRefs} from '../src/git-refs'
|
import {getRefs} from '../src/git-refs'
|
||||||
import * as Utils from '../src/utils'
|
import * as Utils from '../src/utils'
|
||||||
|
|
||||||
@@ -39,43 +39,53 @@ beforeEach(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('it defaults to low severity', async () => {
|
test('it defaults to low severity', async () => {
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.fail_on_severity).toEqual('low')
|
expect(config.fail_on_severity).toEqual('low')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it reads custom configs', async () => {
|
test('it reads custom configs', async () => {
|
||||||
setInput('fail-on-severity', 'critical')
|
setInput('fail-on-severity', 'critical')
|
||||||
setInput('allow-licenses', ' BSD, GPL 2')
|
setInput('allow-licenses', ' BSD, GPL 2')
|
||||||
|
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.fail_on_severity).toEqual('critical')
|
expect(config.fail_on_severity).toEqual('critical')
|
||||||
expect(options.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it defaults to empty allow/deny lists ', async () => {
|
test('it defaults to empty allow/deny lists ', async () => {
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
|
|
||||||
expect(options.allow_licenses).toEqual(undefined)
|
expect(config.allow_licenses).toEqual(undefined)
|
||||||
expect(options.deny_licenses).toEqual(undefined)
|
expect(config.deny_licenses).toEqual(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it raises an error if both an allow and denylist are specified', async () => {
|
test('it raises an error if both an allow and denylist are specified', async () => {
|
||||||
setInput('allow-licenses', 'MIT')
|
setInput('allow-licenses', 'MIT')
|
||||||
setInput('deny-licenses', 'BSD')
|
setInput('deny-licenses', 'BSD')
|
||||||
|
|
||||||
expect(() => readConfig()).toThrow()
|
await expect(readConfig()).rejects.toThrow(
|
||||||
|
'You cannot specify both allow-licenses and deny-licenses'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
test('it raises an error if an empty allow list is specified', async () => {
|
||||||
|
setInput('config-file', './__tests__/fixtures/config-empty-allow-sample.yml')
|
||||||
|
|
||||||
|
await expect(readConfig()).rejects.toThrow(
|
||||||
|
'You should provide at least one license in allow-licenses'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it raises an error when given an unknown severity', async () => {
|
test('it raises an error when given an unknown severity', async () => {
|
||||||
setInput('fail-on-severity', 'zombies')
|
setInput('fail-on-severity', 'zombies')
|
||||||
expect(() => readConfig()).toThrow()
|
|
||||||
|
await expect(readConfig()).rejects.toThrow(/received 'zombies'/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it uses the given refs when the event is not a pull request', async () => {
|
test('it uses the given refs when the event is not a pull request', async () => {
|
||||||
setInput('base-ref', 'a-custom-base-ref')
|
setInput('base-ref', 'a-custom-base-ref')
|
||||||
setInput('head-ref', 'a-custom-head-ref')
|
setInput('head-ref', 'a-custom-head-ref')
|
||||||
|
|
||||||
const refs = getRefs(readConfig(), {
|
const refs = getRefs(await readConfig(), {
|
||||||
payload: {},
|
payload: {},
|
||||||
eventName: 'workflow_dispatch'
|
eventName: 'workflow_dispatch'
|
||||||
})
|
})
|
||||||
@@ -84,9 +94,9 @@ test('it uses the given refs when the event is not a pull request', async () =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('it raises an error when no refs are provided and the event is not a pull request', async () => {
|
test('it raises an error when no refs are provided and the event is not a pull request', async () => {
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getRefs(options, {
|
getRefs(config, {
|
||||||
payload: {},
|
payload: {},
|
||||||
eventName: 'workflow_dispatch'
|
eventName: 'workflow_dispatch'
|
||||||
})
|
})
|
||||||
@@ -94,133 +104,132 @@ test('it raises an error when no refs are provided and the event is not a pull r
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('it reads an external config file', async () => {
|
test('it reads an external config file', async () => {
|
||||||
let options = readConfigFile('./__tests__/fixtures/config-allow-sample.yml')
|
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
|
||||||
expect(options.fail_on_severity).toEqual('critical')
|
|
||||||
expect(options.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
const config = await readConfig()
|
||||||
|
expect(config.fail_on_severity).toEqual('critical')
|
||||||
|
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('raises an error when the the config file was not found', async () => {
|
test('raises an error when the the config file was not found', async () => {
|
||||||
expect(() => readConfigFile('fixtures/i-dont-exist')).toThrow()
|
setInput('config-file', 'fixtures/i-dont-exist')
|
||||||
|
await expect(readConfig()).rejects.toThrow(/Unable to fetch config file/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it parses options from both sources', async () => {
|
test('it parses options from both sources', async () => {
|
||||||
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
|
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
|
||||||
|
|
||||||
let options = readConfig()
|
let config = await readConfig()
|
||||||
expect(options.fail_on_severity).toEqual('critical')
|
expect(config.fail_on_severity).toEqual('critical')
|
||||||
|
|
||||||
setInput('base-ref', 'a-custom-base-ref')
|
setInput('base-ref', 'a-custom-base-ref')
|
||||||
options = readConfig()
|
config = await readConfig()
|
||||||
expect(options.base_ref).toEqual('a-custom-base-ref')
|
expect(config.base_ref).toEqual('a-custom-base-ref')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('in case of conflicts, the external config is the source of truth', async () => {
|
test('in case of conflicts, the inline config is the source of truth', async () => {
|
||||||
|
setInput('fail-on-severity', 'low')
|
||||||
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml') // this will set fail-on-severity to 'critical'
|
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml') // this will set fail-on-severity to 'critical'
|
||||||
|
|
||||||
let options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.fail_on_severity).toEqual('critical')
|
expect(config.fail_on_severity).toEqual('low')
|
||||||
|
|
||||||
// this should not overwite the previous value
|
|
||||||
setInput('fail-on-severity', 'low')
|
|
||||||
options = readConfig()
|
|
||||||
expect(options.fail_on_severity).toEqual('critical')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it uses the default values when loading external files', async () => {
|
test('it uses the default values when loading external files', async () => {
|
||||||
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
||||||
let options = readConfig()
|
let config = await readConfig()
|
||||||
expect(options.allow_licenses).toEqual(undefined)
|
expect(config.allow_licenses).toEqual(undefined)
|
||||||
expect(options.deny_licenses).toEqual(undefined)
|
expect(config.deny_licenses).toEqual(undefined)
|
||||||
|
|
||||||
setInput('config-file', './__tests__/fixtures/license-config-sample.yml')
|
setInput('config-file', './__tests__/fixtures/license-config-sample.yml')
|
||||||
options = readConfig()
|
config = await readConfig()
|
||||||
expect(options.fail_on_severity).toEqual('low')
|
expect(config.fail_on_severity).toEqual('low')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it accepts an external configuration filename', async () => {
|
test('it accepts an external configuration filename', async () => {
|
||||||
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.fail_on_severity).toEqual('critical')
|
expect(config.fail_on_severity).toEqual('critical')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it raises an error when given an unknown severity in an external config file', async () => {
|
test('it raises an error when given an unknown severity in an external config file', async () => {
|
||||||
setInput('config-file', './__tests__/fixtures/invalid-severity-config.yml')
|
setInput('config-file', './__tests__/fixtures/invalid-severity-config.yml')
|
||||||
expect(() => readConfig()).toThrow()
|
await expect(readConfig()).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it defaults to runtime scope', async () => {
|
test('it defaults to runtime scope', async () => {
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.fail_on_scopes).toEqual(['runtime'])
|
expect(config.fail_on_scopes).toEqual(['runtime'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it parses custom scopes preference', async () => {
|
test('it parses custom scopes preference', async () => {
|
||||||
setInput('fail-on-scopes', 'runtime, development')
|
setInput('fail-on-scopes', 'runtime, development')
|
||||||
let options = readConfig()
|
let config = await readConfig()
|
||||||
expect(options.fail_on_scopes).toEqual(['runtime', 'development'])
|
expect(config.fail_on_scopes).toEqual(['runtime', 'development'])
|
||||||
|
|
||||||
clearInputs()
|
clearInputs()
|
||||||
setInput('fail-on-scopes', 'development')
|
setInput('fail-on-scopes', 'development')
|
||||||
options = readConfig()
|
config = await readConfig()
|
||||||
expect(options.fail_on_scopes).toEqual(['development'])
|
expect(config.fail_on_scopes).toEqual(['development'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it raises an error when given invalid scope', async () => {
|
test('it raises an error when given invalid scope', async () => {
|
||||||
setInput('fail-on-scopes', 'runtime, zombies')
|
setInput('fail-on-scopes', 'runtime, zombies')
|
||||||
expect(() => readConfig()).toThrow()
|
await expect(readConfig()).rejects.toThrow(/received 'zombies'/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it defaults to an empty GHSA allowlist', async () => {
|
test('it defaults to an empty GHSA allowlist', async () => {
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.allow_ghsas).toEqual(undefined)
|
expect(config.allow_ghsas).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it successfully parses GHSA allowlist', async () => {
|
test('it successfully parses GHSA allowlist', async () => {
|
||||||
setInput('allow-ghsas', 'GHSA-abcd-1234-5679, GHSA-efgh-1234-5679')
|
setInput('allow-ghsas', 'GHSA-abcd-1234-5679, GHSA-efgh-1234-5679')
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.allow_ghsas).toEqual([
|
expect(config.allow_ghsas).toEqual([
|
||||||
'GHSA-abcd-1234-5679',
|
'GHSA-abcd-1234-5679',
|
||||||
'GHSA-efgh-1234-5679'
|
'GHSA-efgh-1234-5679'
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it defaults to checking licenses', async () => {
|
test('it defaults to checking licenses', async () => {
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.license_check).toBe(true)
|
expect(config.license_check).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it parses the license-check input', async () => {
|
test('it parses the license-check input', async () => {
|
||||||
setInput('license-check', 'false')
|
setInput('license-check', 'false')
|
||||||
let options = readConfig()
|
let config = await readConfig()
|
||||||
expect(options.license_check).toEqual(false)
|
expect(config.license_check).toEqual(false)
|
||||||
|
|
||||||
clearInputs()
|
clearInputs()
|
||||||
setInput('license-check', 'true')
|
setInput('license-check', 'true')
|
||||||
options = readConfig()
|
config = await readConfig()
|
||||||
expect(options.license_check).toEqual(true)
|
expect(config.license_check).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it defaults to checking vulnerabilities', async () => {
|
test('it defaults to checking vulnerabilities', async () => {
|
||||||
const options = readConfig()
|
const config = await readConfig()
|
||||||
expect(options.vulnerability_check).toBe(true)
|
expect(config.vulnerability_check).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it parses the vulnerability-check input', async () => {
|
test('it parses the vulnerability-check input', async () => {
|
||||||
setInput('vulnerability-check', 'false')
|
setInput('vulnerability-check', 'false')
|
||||||
let options = readConfig()
|
let config = await readConfig()
|
||||||
expect(options.vulnerability_check).toEqual(false)
|
expect(config.vulnerability_check).toEqual(false)
|
||||||
|
|
||||||
clearInputs()
|
clearInputs()
|
||||||
setInput('vulnerability-check', 'true')
|
setInput('vulnerability-check', 'true')
|
||||||
options = readConfig()
|
config = await readConfig()
|
||||||
expect(options.vulnerability_check).toEqual(true)
|
expect(config.vulnerability_check).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it is not possible to disable both checks', async () => {
|
test('it is not possible to disable both checks', async () => {
|
||||||
setInput('license-check', 'false')
|
setInput('license-check', 'false')
|
||||||
setInput('vulnerability-check', 'false')
|
setInput('vulnerability-check', 'false')
|
||||||
expect(() => {
|
await expect(readConfig()).rejects.toThrow(
|
||||||
readConfig()
|
/Can't disable both license-check and vulnerability-check/
|
||||||
}).toThrow("Can't disable both license-check and vulnerability-check")
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('licenses that are not valid SPDX licenses', () => {
|
describe('licenses that are not valid SPDX licenses', () => {
|
||||||
@@ -230,15 +239,15 @@ describe('licenses that are not valid SPDX licenses', () => {
|
|||||||
|
|
||||||
test('it raises an error for invalid licenses in allow-licenses', async () => {
|
test('it raises an error for invalid licenses in allow-licenses', async () => {
|
||||||
setInput('allow-licenses', ' BSD, GPL 2')
|
setInput('allow-licenses', ' BSD, GPL 2')
|
||||||
expect(() => {
|
await expect(readConfig()).rejects.toThrow(
|
||||||
readConfig()
|
'Invalid license(s) in allow-licenses: BSD, GPL 2'
|
||||||
}).toThrow('Invalid license(s) in allow-licenses: BSD, GPL 2')
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it raises an error for invalid licenses in deny-licenses', async () => {
|
test('it raises an error for invalid licenses in deny-licenses', async () => {
|
||||||
setInput('deny-licenses', ' BSD, GPL 2')
|
setInput('deny-licenses', ' BSD, GPL 2')
|
||||||
expect(() => {
|
await expect(readConfig()).rejects.toThrow(
|
||||||
readConfig()
|
'Invalid license(s) in deny-licenses: BSD, GPL 2'
|
||||||
}).toThrow('Invalid license(s) in deny-licenses: BSD, GPL 2')
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fail_on_severity: critical
|
||||||
|
allow_licenses: []
|
||||||
@@ -99,17 +99,6 @@ test('it adds license inside the deny list to forbidden changes', async () => {
|
|||||||
expect(forbidden.length).toEqual(1)
|
expect(forbidden.length).toEqual(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
// This is more of a "here's a behavior that might be surprising" than an actual
|
|
||||||
// thing we want in the system. Please remove this test after refactoring.
|
|
||||||
test('it adds all licenses to forbidden changes when allow is provided an empty array', async () => {
|
|
||||||
const changes: Changes = [npmChange, rubyChange]
|
|
||||||
let {forbidden} = await getInvalidLicenseChanges(changes, {
|
|
||||||
allow: [],
|
|
||||||
deny: ['BSD']
|
|
||||||
})
|
|
||||||
expect(forbidden.length).toBe(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('it does not add license outside the allow list to forbidden changes if it is in removed changes', async () => {
|
test('it does not add license outside the allow list to forbidden changes if it is in removed changes', async () => {
|
||||||
const changes: Changes = [
|
const changes: Changes = [
|
||||||
{...npmChange, change_type: 'removed'},
|
{...npmChange, change_type: 'removed'},
|
||||||
|
|||||||
+5
-1
@@ -21,7 +21,7 @@ inputs:
|
|||||||
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.
|
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
|
required: false
|
||||||
config-file:
|
config-file:
|
||||||
description: A filepath to the configuration file for the action.
|
description: A path to the configuration file for the action.
|
||||||
required: false
|
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")
|
||||||
@@ -32,6 +32,10 @@ inputs:
|
|||||||
allow-ghsas:
|
allow-ghsas:
|
||||||
description: Comma-separated list of allowed Github Advisory IDs (e.g. "GHSA-abcd-1234-5679, GHSA-efgh-1234-5679")
|
description: Comma-separated list of allowed Github Advisory IDs (e.g. "GHSA-abcd-1234-5679, GHSA-efgh-1234-5679")
|
||||||
required: false
|
required: false
|
||||||
|
external-repo-token:
|
||||||
|
description: A token for fetching external configuration file if it lives in another repository. It is required if the repository is private
|
||||||
|
required: false
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node16'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|||||||
+230
-110
@@ -107,29 +107,6 @@ exports.getRefs = getRefs;
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
||||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
||||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
||||||
}
|
|
||||||
Object.defineProperty(o, k2, desc);
|
|
||||||
}) : (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
o[k2] = m[k];
|
|
||||||
}));
|
|
||||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
||||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
||||||
}) : function(o, v) {
|
|
||||||
o["default"] = v;
|
|
||||||
});
|
|
||||||
var __importStar = (this && this.__importStar) || function (mod) {
|
|
||||||
if (mod && mod.__esModule) return mod;
|
|
||||||
var result = {};
|
|
||||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
||||||
__setModuleDefault(result, mod);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
@@ -144,9 +121,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.getInvalidLicenseChanges = void 0;
|
exports.getInvalidLicenseChanges = void 0;
|
||||||
const core = __importStar(__nccwpck_require__(2186));
|
|
||||||
const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424));
|
const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424));
|
||||||
const octokit_1 = __nccwpck_require__(7467);
|
|
||||||
const utils_1 = __nccwpck_require__(918);
|
const utils_1 = __nccwpck_require__(918);
|
||||||
/**
|
/**
|
||||||
* Loops through a list of changes, filtering and returning the
|
* Loops through a list of changes, filtering and returning the
|
||||||
@@ -205,11 +180,11 @@ function getInvalidLicenseChanges(changes, licenses) {
|
|||||||
exports.getInvalidLicenseChanges = getInvalidLicenseChanges;
|
exports.getInvalidLicenseChanges = getInvalidLicenseChanges;
|
||||||
const fetchGHLicense = (owner, repo) => __awaiter(void 0, void 0, void 0, function* () {
|
const fetchGHLicense = (owner, repo) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
const octokit = new octokit_1.Octokit({
|
|
||||||
auth: core.getInput('repo-token', { required: true })
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
const response = yield octokit.rest.licenses.getForRepo({ owner, repo });
|
const response = yield (0, utils_1.octokitClient)().rest.licenses.getForRepo({
|
||||||
|
owner,
|
||||||
|
repo
|
||||||
|
});
|
||||||
return (_b = (_a = response.data.license) === null || _a === void 0 ? void 0 : _a.spdx_id) !== null && _b !== void 0 ? _b : null;
|
return (_b = (_a = response.data.license) === null || _a === void 0 ? void 0 : _a.spdx_id) !== null && _b !== void 0 ? _b : null;
|
||||||
}
|
}
|
||||||
catch (_) {
|
catch (_) {
|
||||||
@@ -350,7 +325,7 @@ const utils_1 = __nccwpck_require__(918);
|
|||||||
function run() {
|
function run() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
try {
|
try {
|
||||||
const config = (0, config_1.readConfig)();
|
const config = yield (0, config_1.readConfig)();
|
||||||
const refs = (0, git_refs_1.getRefs)(config, github.context);
|
const refs = (0, git_refs_1.getRefs)(config, github.context);
|
||||||
const changes = yield dependencyGraph.compare({
|
const changes = yield dependencyGraph.compare({
|
||||||
owner: github.context.repo.owner,
|
owner: github.context.repo.owner,
|
||||||
@@ -557,17 +532,36 @@ exports.ConfigurationOptionsSchema = z
|
|||||||
.object({
|
.object({
|
||||||
fail_on_severity: exports.SeveritySchema,
|
fail_on_severity: exports.SeveritySchema,
|
||||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||||
allow_licenses: z.array(z.string()).default([]),
|
allow_licenses: z.array(z.string()).optional(),
|
||||||
deny_licenses: z.array(z.string()).default([]),
|
deny_licenses: z.array(z.string()).optional(),
|
||||||
allow_ghsas: z.array(z.string()).default([]),
|
allow_ghsas: z.array(z.string()).default([]),
|
||||||
license_check: z.boolean().default(true),
|
license_check: z.boolean().default(true),
|
||||||
vulnerability_check: z.boolean().default(true),
|
vulnerability_check: z.boolean().default(true),
|
||||||
config_file: z.string().optional().default('false'),
|
config_file: z.string().optional(),
|
||||||
base_ref: z.string(),
|
base_ref: z.string().optional(),
|
||||||
head_ref: z.string()
|
head_ref: z.string().optional()
|
||||||
})
|
})
|
||||||
.partial()
|
.superRefine((config, context) => {
|
||||||
.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.');
|
if (config.allow_licenses && config.deny_licenses) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'You cannot specify both allow-licenses and deny-licenses'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.allow_licenses && config.allow_licenses.length < 1) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'You should provide at least one license in allow-licenses'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.license_check === false &&
|
||||||
|
config.vulnerability_check === false) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "Can't disable both license-check and vulnerability-check"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
||||||
|
|
||||||
|
|
||||||
@@ -741,11 +735,36 @@ exports.addScannedDependencies = addScannedDependencies;
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
exports.octokitClient = exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
||||||
|
const core = __importStar(__nccwpck_require__(2186));
|
||||||
|
const octokit_1 = __nccwpck_require__(7467);
|
||||||
const spdx_expression_parse_1 = __importDefault(__nccwpck_require__(1620));
|
const spdx_expression_parse_1 = __importDefault(__nccwpck_require__(1620));
|
||||||
function groupDependenciesByManifest(changes) {
|
function groupDependenciesByManifest(changes) {
|
||||||
var _a;
|
var _a;
|
||||||
@@ -783,6 +802,17 @@ function isSPDXValid(license) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.isSPDXValid = isSPDXValid;
|
exports.isSPDXValid = isSPDXValid;
|
||||||
|
function octokitClient(token = 'repo-token', required = true) {
|
||||||
|
const opts = {};
|
||||||
|
// auth is only added if token is present. For remote config files in public
|
||||||
|
// repos the token is optional, so it could be undefined.
|
||||||
|
const auth = core.getInput(token, { required });
|
||||||
|
if (auth !== undefined) {
|
||||||
|
opts['auth'] = auth;
|
||||||
|
}
|
||||||
|
return new octokit_1.Octokit(opts);
|
||||||
|
}
|
||||||
|
exports.octokitClient = octokitClient;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -27397,11 +27427,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
__setModuleDefault(result, mod);
|
__setModuleDefault(result, mod);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.readConfigFile = exports.readInlineConfig = exports.readConfig = void 0;
|
exports.readConfig = void 0;
|
||||||
const fs = __importStar(__nccwpck_require__(7147));
|
const fs = __importStar(__nccwpck_require__(7147));
|
||||||
const path_1 = __importDefault(__nccwpck_require__(1017));
|
const path_1 = __importDefault(__nccwpck_require__(1017));
|
||||||
const yaml_1 = __importDefault(__nccwpck_require__(4083));
|
const yaml_1 = __importDefault(__nccwpck_require__(4083));
|
||||||
@@ -27409,6 +27448,43 @@ const core = __importStar(__nccwpck_require__(2186));
|
|||||||
const z = __importStar(__nccwpck_require__(3301));
|
const z = __importStar(__nccwpck_require__(3301));
|
||||||
const schemas_1 = __nccwpck_require__(1129);
|
const schemas_1 = __nccwpck_require__(1129);
|
||||||
const utils_1 = __nccwpck_require__(1314);
|
const utils_1 = __nccwpck_require__(1314);
|
||||||
|
function readConfig() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const inlineConfig = readInlineConfig();
|
||||||
|
const configFile = getOptionalInput('config-file');
|
||||||
|
if (configFile !== undefined) {
|
||||||
|
const externalConfig = yield readConfigFile(configFile);
|
||||||
|
return schemas_1.ConfigurationOptionsSchema.parse(Object.assign(Object.assign({}, externalConfig), inlineConfig));
|
||||||
|
}
|
||||||
|
return schemas_1.ConfigurationOptionsSchema.parse(inlineConfig);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.readConfig = readConfig;
|
||||||
|
function readInlineConfig() {
|
||||||
|
const fail_on_severity = getOptionalInput('fail-on-severity');
|
||||||
|
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'));
|
||||||
|
const allow_licenses = parseList(getOptionalInput('allow-licenses'));
|
||||||
|
const deny_licenses = parseList(getOptionalInput('deny-licenses'));
|
||||||
|
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'));
|
||||||
|
const license_check = getOptionalBoolean('license-check');
|
||||||
|
const vulnerability_check = getOptionalBoolean('vulnerability-check');
|
||||||
|
const base_ref = getOptionalInput('base-ref');
|
||||||
|
const head_ref = getOptionalInput('head-ref');
|
||||||
|
validateLicenses('allow-licenses', allow_licenses);
|
||||||
|
validateLicenses('deny-licenses', deny_licenses);
|
||||||
|
const keys = {
|
||||||
|
fail_on_severity,
|
||||||
|
fail_on_scopes,
|
||||||
|
allow_licenses,
|
||||||
|
deny_licenses,
|
||||||
|
allow_ghsas,
|
||||||
|
license_check,
|
||||||
|
vulnerability_check,
|
||||||
|
base_ref,
|
||||||
|
head_ref
|
||||||
|
};
|
||||||
|
return Object.fromEntries(Object.entries(keys).filter(([_, value]) => value !== undefined));
|
||||||
|
}
|
||||||
function getOptionalBoolean(name) {
|
function getOptionalBoolean(name) {
|
||||||
const value = core.getInput(name);
|
const value = core.getInput(name);
|
||||||
return value.length > 0 ? core.getBooleanInput(name) : undefined;
|
return value.length > 0 ? core.getBooleanInput(name) : undefined;
|
||||||
@@ -27434,71 +27510,35 @@ function validateLicenses(key, licenses) {
|
|||||||
throw new Error(`Invalid license(s) in ${key}: ${invalid_licenses.join(', ')}`);
|
throw new Error(`Invalid license(s) in ${key}: ${invalid_licenses.join(', ')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function readConfig() {
|
function readConfigFile(filePath) {
|
||||||
const externalConfig = getOptionalInput('config-file');
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
if (externalConfig !== undefined) {
|
// match a remote config (e.g. 'owner/repo/filepath@someref')
|
||||||
const config = readConfigFile(externalConfig);
|
const format = new RegExp('(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)');
|
||||||
// the reasoning behind reading the inline config when an external
|
let data;
|
||||||
// config file is provided is that we still want to allow users to
|
const pieces = format.exec(filePath);
|
||||||
// pass inline options in the presence of an external config file.
|
try {
|
||||||
const inlineConfig = readInlineConfig();
|
if ((pieces === null || pieces === void 0 ? void 0 : pieces.groups) && pieces.length === 5) {
|
||||||
// the external config takes precedence
|
data = yield getRemoteConfig({
|
||||||
return Object.assign({}, inlineConfig, config);
|
owner: pieces.groups.owner,
|
||||||
|
repo: pieces.groups.repo,
|
||||||
|
path: pieces.groups.path,
|
||||||
|
ref: pieces.groups.ref
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return readInlineConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.readConfig = readConfig;
|
|
||||||
function readInlineConfig() {
|
|
||||||
const fail_on_severity = schemas_1.SeveritySchema.parse(getOptionalInput('fail-on-severity'));
|
|
||||||
const fail_on_scopes = z
|
|
||||||
.array(z.enum(schemas_1.SCOPES))
|
|
||||||
.default(['runtime'])
|
|
||||||
.parse(parseList(getOptionalInput('fail-on-scopes')));
|
|
||||||
const allow_licenses = parseList(getOptionalInput('allow-licenses'));
|
|
||||||
const deny_licenses = parseList(getOptionalInput('deny-licenses'));
|
|
||||||
if (allow_licenses !== undefined && deny_licenses !== undefined) {
|
|
||||||
throw new Error("Can't specify both allow_licenses and deny_licenses");
|
|
||||||
}
|
|
||||||
validateLicenses('allow-licenses', allow_licenses);
|
|
||||||
validateLicenses('deny-licenses', deny_licenses);
|
|
||||||
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'));
|
|
||||||
const license_check = z
|
|
||||||
.boolean()
|
|
||||||
.default(true)
|
|
||||||
.parse(getOptionalBoolean('license-check'));
|
|
||||||
const vulnerability_check = z
|
|
||||||
.boolean()
|
|
||||||
.default(true)
|
|
||||||
.parse(getOptionalBoolean('vulnerability-check'));
|
|
||||||
if (license_check === false && vulnerability_check === false) {
|
|
||||||
throw new Error("Can't disable both license-check and vulnerability-check");
|
|
||||||
}
|
|
||||||
const base_ref = getOptionalInput('base-ref');
|
|
||||||
const head_ref = getOptionalInput('head-ref');
|
|
||||||
return {
|
|
||||||
fail_on_severity,
|
|
||||||
fail_on_scopes,
|
|
||||||
allow_licenses,
|
|
||||||
deny_licenses,
|
|
||||||
allow_ghsas,
|
|
||||||
license_check,
|
|
||||||
vulnerability_check,
|
|
||||||
base_ref,
|
|
||||||
head_ref
|
|
||||||
};
|
|
||||||
}
|
|
||||||
exports.readInlineConfig = readInlineConfig;
|
|
||||||
function readConfigFile(filePath) {
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = fs.readFileSync(path_1.default.resolve(filePath), 'utf-8');
|
data = fs.readFileSync(path_1.default.resolve(filePath), 'utf-8');
|
||||||
}
|
}
|
||||||
catch (error) {
|
return parseConfigFile(data);
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
data = yaml_1.default.parse(data);
|
catch (error) {
|
||||||
|
core.debug(error);
|
||||||
|
throw new Error('Unable to fetch config file');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function parseConfigFile(configData) {
|
||||||
|
try {
|
||||||
|
const data = yaml_1.default.parse(configData);
|
||||||
for (const key of Object.keys(data)) {
|
for (const key of Object.keys(data)) {
|
||||||
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
||||||
validateLicenses(key, data[key]);
|
validateLicenses(key, data[key]);
|
||||||
@@ -27509,10 +27549,35 @@ function readConfigFile(filePath) {
|
|||||||
delete data[key];
|
delete data[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const values = schemas_1.ConfigurationOptionsSchema.parse(data);
|
return data;
|
||||||
return values;
|
}
|
||||||
|
catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getRemoteConfig(configOpts) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
const { data } = yield (0, utils_1.octokitClient)('external-repo-token', false).rest.repos.getContent({
|
||||||
|
mediaType: {
|
||||||
|
format: 'raw'
|
||||||
|
},
|
||||||
|
owner: configOpts.owner,
|
||||||
|
repo: configOpts.repo,
|
||||||
|
path: configOpts.path,
|
||||||
|
ref: configOpts.ref
|
||||||
|
});
|
||||||
|
// When using mediaType.format = 'raw', the response.data is a string
|
||||||
|
// but this is not reflected in the return type of getContent, so we're
|
||||||
|
// casting the return value to a string.
|
||||||
|
return z.string().parse(data);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
core.debug(error);
|
||||||
|
throw new Error('Error fetching remote config file');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
exports.readConfigFile = readConfigFile;
|
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -27658,17 +27723,36 @@ exports.ConfigurationOptionsSchema = z
|
|||||||
.object({
|
.object({
|
||||||
fail_on_severity: exports.SeveritySchema,
|
fail_on_severity: exports.SeveritySchema,
|
||||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||||
allow_licenses: z.array(z.string()).default([]),
|
allow_licenses: z.array(z.string()).optional(),
|
||||||
deny_licenses: z.array(z.string()).default([]),
|
deny_licenses: z.array(z.string()).optional(),
|
||||||
allow_ghsas: z.array(z.string()).default([]),
|
allow_ghsas: z.array(z.string()).default([]),
|
||||||
license_check: z.boolean().default(true),
|
license_check: z.boolean().default(true),
|
||||||
vulnerability_check: z.boolean().default(true),
|
vulnerability_check: z.boolean().default(true),
|
||||||
config_file: z.string().optional().default('false'),
|
config_file: z.string().optional(),
|
||||||
base_ref: z.string(),
|
base_ref: z.string().optional(),
|
||||||
head_ref: z.string()
|
head_ref: z.string().optional()
|
||||||
})
|
})
|
||||||
.partial()
|
.superRefine((config, context) => {
|
||||||
.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.');
|
if (config.allow_licenses && config.deny_licenses) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'You cannot specify both allow-licenses and deny-licenses'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.allow_licenses && config.allow_licenses.length < 1) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'You should provide at least one license in allow-licenses'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.license_check === false &&
|
||||||
|
config.vulnerability_check === false) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "Can't disable both license-check and vulnerability-check"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
||||||
|
|
||||||
|
|
||||||
@@ -27679,11 +27763,36 @@ exports.ChangesSchema = z.array(exports.ChangeSchema);
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
exports.octokitClient = exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
||||||
|
const core = __importStar(__nccwpck_require__(2186));
|
||||||
|
const octokit_1 = __nccwpck_require__(7467);
|
||||||
const spdx_expression_parse_1 = __importDefault(__nccwpck_require__(1620));
|
const spdx_expression_parse_1 = __importDefault(__nccwpck_require__(1620));
|
||||||
function groupDependenciesByManifest(changes) {
|
function groupDependenciesByManifest(changes) {
|
||||||
var _a;
|
var _a;
|
||||||
@@ -27721,6 +27830,17 @@ function isSPDXValid(license) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.isSPDXValid = isSPDXValid;
|
exports.isSPDXValid = isSPDXValid;
|
||||||
|
function octokitClient(token = 'repo-token', required = true) {
|
||||||
|
const opts = {};
|
||||||
|
// auth is only added if token is present. For remote config files in public
|
||||||
|
// repos the token is optional, so it could be undefined.
|
||||||
|
const auth = core.getInput(token, { required });
|
||||||
|
if (auth !== undefined) {
|
||||||
|
opts['auth'] = auth;
|
||||||
|
}
|
||||||
|
return new octokit_1.Octokit(opts);
|
||||||
|
}
|
||||||
|
exports.octokitClient = octokitClient;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+107
-77
@@ -3,15 +3,57 @@ import path from 'path'
|
|||||||
import YAML from 'yaml'
|
import YAML from 'yaml'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import {
|
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
||||||
ConfigurationOptions,
|
import {isSPDXValid, octokitClient} from './utils'
|
||||||
ConfigurationOptionsSchema,
|
|
||||||
SeveritySchema,
|
|
||||||
SCOPES
|
|
||||||
} from './schemas'
|
|
||||||
import {isSPDXValid} from './utils'
|
|
||||||
|
|
||||||
type licenseKey = 'allow-licenses' | 'deny-licenses'
|
type ConfigurationOptionsPartial = Partial<ConfigurationOptions>
|
||||||
|
|
||||||
|
export async function readConfig(): Promise<ConfigurationOptions> {
|
||||||
|
const inlineConfig = readInlineConfig()
|
||||||
|
|
||||||
|
const configFile = getOptionalInput('config-file')
|
||||||
|
if (configFile !== undefined) {
|
||||||
|
const externalConfig = await readConfigFile(configFile)
|
||||||
|
|
||||||
|
return ConfigurationOptionsSchema.parse({
|
||||||
|
...externalConfig,
|
||||||
|
...inlineConfig
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfigurationOptionsSchema.parse(inlineConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
function readInlineConfig(): ConfigurationOptionsPartial {
|
||||||
|
const fail_on_severity = getOptionalInput('fail-on-severity')
|
||||||
|
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'))
|
||||||
|
const allow_licenses = parseList(getOptionalInput('allow-licenses'))
|
||||||
|
const deny_licenses = parseList(getOptionalInput('deny-licenses'))
|
||||||
|
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
|
||||||
|
const license_check = getOptionalBoolean('license-check')
|
||||||
|
const vulnerability_check = getOptionalBoolean('vulnerability-check')
|
||||||
|
const base_ref = getOptionalInput('base-ref')
|
||||||
|
const head_ref = getOptionalInput('head-ref')
|
||||||
|
|
||||||
|
validateLicenses('allow-licenses', allow_licenses)
|
||||||
|
validateLicenses('deny-licenses', deny_licenses)
|
||||||
|
|
||||||
|
const keys = {
|
||||||
|
fail_on_severity,
|
||||||
|
fail_on_scopes,
|
||||||
|
allow_licenses,
|
||||||
|
deny_licenses,
|
||||||
|
allow_ghsas,
|
||||||
|
license_check,
|
||||||
|
vulnerability_check,
|
||||||
|
base_ref,
|
||||||
|
head_ref
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(keys).filter(([_, value]) => value !== undefined)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function getOptionalBoolean(name: string): boolean | undefined {
|
function getOptionalBoolean(name: string): boolean | undefined {
|
||||||
const value = core.getInput(name)
|
const value = core.getInput(name)
|
||||||
@@ -32,7 +74,7 @@ function parseList(list: string | undefined): string[] | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateLicenses(
|
function validateLicenses(
|
||||||
key: licenseKey,
|
key: 'allow-licenses' | 'deny-licenses',
|
||||||
licenses: string[] | undefined
|
licenses: string[] | undefined
|
||||||
): void {
|
): void {
|
||||||
if (licenses === undefined) {
|
if (licenses === undefined) {
|
||||||
@@ -47,79 +89,38 @@ function validateLicenses(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readConfig(): ConfigurationOptions {
|
async function readConfigFile(
|
||||||
const externalConfig = getOptionalInput('config-file')
|
filePath: string
|
||||||
if (externalConfig !== undefined) {
|
): Promise<ConfigurationOptionsPartial> {
|
||||||
const config = readConfigFile(externalConfig)
|
// match a remote config (e.g. 'owner/repo/filepath@someref')
|
||||||
// the reasoning behind reading the inline config when an external
|
const format = new RegExp(
|
||||||
// config file is provided is that we still want to allow users to
|
'(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)'
|
||||||
// pass inline options in the presence of an external config file.
|
|
||||||
const inlineConfig = readInlineConfig()
|
|
||||||
// the external config takes precedence
|
|
||||||
return Object.assign({}, inlineConfig, config)
|
|
||||||
} else {
|
|
||||||
return readInlineConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readInlineConfig(): ConfigurationOptions {
|
|
||||||
const fail_on_severity = SeveritySchema.parse(
|
|
||||||
getOptionalInput('fail-on-severity')
|
|
||||||
)
|
)
|
||||||
const fail_on_scopes = z
|
|
||||||
.array(z.enum(SCOPES))
|
|
||||||
.default(['runtime'])
|
|
||||||
.parse(parseList(getOptionalInput('fail-on-scopes')))
|
|
||||||
|
|
||||||
const allow_licenses = parseList(getOptionalInput('allow-licenses'))
|
let data: string
|
||||||
const deny_licenses = parseList(getOptionalInput('deny-licenses'))
|
const pieces = format.exec(filePath)
|
||||||
|
|
||||||
if (allow_licenses !== undefined && deny_licenses !== undefined) {
|
|
||||||
throw new Error("Can't specify both allow_licenses and deny_licenses")
|
|
||||||
}
|
|
||||||
validateLicenses('allow-licenses', allow_licenses)
|
|
||||||
validateLicenses('deny-licenses', deny_licenses)
|
|
||||||
|
|
||||||
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
|
|
||||||
|
|
||||||
const license_check = z
|
|
||||||
.boolean()
|
|
||||||
.default(true)
|
|
||||||
.parse(getOptionalBoolean('license-check'))
|
|
||||||
const vulnerability_check = z
|
|
||||||
.boolean()
|
|
||||||
.default(true)
|
|
||||||
.parse(getOptionalBoolean('vulnerability-check'))
|
|
||||||
if (license_check === false && vulnerability_check === false) {
|
|
||||||
throw new Error("Can't disable both license-check and vulnerability-check")
|
|
||||||
}
|
|
||||||
|
|
||||||
const base_ref = getOptionalInput('base-ref')
|
|
||||||
const head_ref = getOptionalInput('head-ref')
|
|
||||||
|
|
||||||
return {
|
|
||||||
fail_on_severity,
|
|
||||||
fail_on_scopes,
|
|
||||||
allow_licenses,
|
|
||||||
deny_licenses,
|
|
||||||
allow_ghsas,
|
|
||||||
license_check,
|
|
||||||
vulnerability_check,
|
|
||||||
base_ref,
|
|
||||||
head_ref
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readConfigFile(filePath: string): ConfigurationOptions {
|
|
||||||
let data
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (pieces?.groups && pieces.length === 5) {
|
||||||
|
data = await getRemoteConfig({
|
||||||
|
owner: pieces.groups.owner,
|
||||||
|
repo: pieces.groups.repo,
|
||||||
|
path: pieces.groups.path,
|
||||||
|
ref: pieces.groups.ref
|
||||||
|
})
|
||||||
|
} else {
|
||||||
data = fs.readFileSync(path.resolve(filePath), 'utf-8')
|
data = fs.readFileSync(path.resolve(filePath), 'utf-8')
|
||||||
} catch (error: unknown) {
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
data = YAML.parse(data)
|
return parseConfigFile(data)
|
||||||
|
} catch (error) {
|
||||||
|
core.debug(error as string)
|
||||||
|
throw new Error('Unable to fetch config file')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseConfigFile(configData: string): ConfigurationOptionsPartial {
|
||||||
|
try {
|
||||||
|
const data = YAML.parse(configData)
|
||||||
for (const key of Object.keys(data)) {
|
for (const key of Object.keys(data)) {
|
||||||
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
||||||
validateLicenses(key, data[key])
|
validateLicenses(key, data[key])
|
||||||
@@ -130,6 +131,35 @@ export function readConfigFile(filePath: string): ConfigurationOptions {
|
|||||||
delete data[key]
|
delete data[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const values = ConfigurationOptionsSchema.parse(data)
|
return data
|
||||||
return values
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRemoteConfig(configOpts: {
|
||||||
|
[key: string]: string
|
||||||
|
}): Promise<string> {
|
||||||
|
try {
|
||||||
|
const {data} = await octokitClient(
|
||||||
|
'external-repo-token',
|
||||||
|
false
|
||||||
|
).rest.repos.getContent({
|
||||||
|
mediaType: {
|
||||||
|
format: 'raw'
|
||||||
|
},
|
||||||
|
owner: configOpts.owner,
|
||||||
|
repo: configOpts.repo,
|
||||||
|
path: configOpts.path,
|
||||||
|
ref: configOpts.ref
|
||||||
|
})
|
||||||
|
|
||||||
|
// When using mediaType.format = 'raw', the response.data is a string
|
||||||
|
// but this is not reflected in the return type of getContent, so we're
|
||||||
|
// casting the return value to a string.
|
||||||
|
return z.string().parse(data as unknown)
|
||||||
|
} catch (error) {
|
||||||
|
core.debug(error as string)
|
||||||
|
throw new Error('Error fetching remote config file')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-8
@@ -1,8 +1,6 @@
|
|||||||
import * as core from '@actions/core'
|
|
||||||
import spdxSatisfies from 'spdx-satisfies'
|
import spdxSatisfies from 'spdx-satisfies'
|
||||||
import {Octokit} from 'octokit'
|
|
||||||
import {Change, Changes} from './schemas'
|
import {Change, Changes} from './schemas'
|
||||||
import {isSPDXValid} from './utils'
|
import {isSPDXValid, octokitClient} from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loops through a list of changes, filtering and returning the
|
* Loops through a list of changes, filtering and returning the
|
||||||
@@ -76,12 +74,11 @@ const fetchGHLicense = async (
|
|||||||
owner: string,
|
owner: string,
|
||||||
repo: string
|
repo: string
|
||||||
): Promise<string | null> => {
|
): Promise<string | null> => {
|
||||||
const octokit = new Octokit({
|
|
||||||
auth: core.getInput('repo-token', {required: true})
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await octokit.rest.licenses.getForRepo({owner, repo})
|
const response = await octokitClient().rest.licenses.getForRepo({
|
||||||
|
owner,
|
||||||
|
repo
|
||||||
|
})
|
||||||
return response.data.license?.spdx_id ?? null
|
return response.data.license?.spdx_id ?? null
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
+2
-2
@@ -18,7 +18,7 @@ import {groupDependenciesByManifest} from './utils'
|
|||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const config = readConfig()
|
const config = await readConfig()
|
||||||
const refs = getRefs(config, github.context)
|
const refs = getRefs(config, github.context)
|
||||||
|
|
||||||
const changes = await dependencyGraph.compare({
|
const changes = await dependencyGraph.compare({
|
||||||
@@ -28,7 +28,7 @@ async function run(): Promise<void> {
|
|||||||
headRef: refs.head
|
headRef: refs.head
|
||||||
})
|
})
|
||||||
|
|
||||||
const minSeverity = config.fail_on_severity as Severity
|
const minSeverity = config.fail_on_severity
|
||||||
const scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes)
|
const scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes)
|
||||||
const filteredChanges = filterAllowedAdvisories(
|
const filteredChanges = filterAllowedAdvisories(
|
||||||
config.allow_ghsas,
|
config.allow_ghsas,
|
||||||
|
|||||||
+28
-10
@@ -38,20 +38,38 @@ export const ConfigurationOptionsSchema = z
|
|||||||
.object({
|
.object({
|
||||||
fail_on_severity: SeveritySchema,
|
fail_on_severity: SeveritySchema,
|
||||||
fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']),
|
fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']),
|
||||||
allow_licenses: z.array(z.string()).default([]),
|
allow_licenses: z.array(z.string()).optional(),
|
||||||
deny_licenses: z.array(z.string()).default([]),
|
deny_licenses: z.array(z.string()).optional(),
|
||||||
allow_ghsas: z.array(z.string()).default([]),
|
allow_ghsas: z.array(z.string()).default([]),
|
||||||
license_check: z.boolean().default(true),
|
license_check: z.boolean().default(true),
|
||||||
vulnerability_check: z.boolean().default(true),
|
vulnerability_check: z.boolean().default(true),
|
||||||
config_file: z.string().optional().default('false'),
|
config_file: z.string().optional(),
|
||||||
base_ref: z.string(),
|
base_ref: z.string().optional(),
|
||||||
head_ref: z.string()
|
head_ref: z.string().optional()
|
||||||
|
})
|
||||||
|
.superRefine((config, context) => {
|
||||||
|
if (config.allow_licenses && config.deny_licenses) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'You cannot specify both allow-licenses and deny-licenses'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (config.allow_licenses && config.allow_licenses.length < 1) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'You should provide at least one license in allow-licenses'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
config.license_check === false &&
|
||||||
|
config.vulnerability_check === false
|
||||||
|
) {
|
||||||
|
context.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "Can't disable both license-check and vulnerability-check"
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.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.'
|
|
||||||
)
|
|
||||||
|
|
||||||
export const ChangesSchema = z.array(ChangeSchema)
|
export const ChangesSchema = z.array(ChangeSchema)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import * as core from '@actions/core'
|
||||||
|
import {Octokit} from 'octokit'
|
||||||
import spdxParse from 'spdx-expression-parse'
|
import spdxParse from 'spdx-expression-parse'
|
||||||
import {Changes} from './schemas'
|
import {Changes} from './schemas'
|
||||||
|
|
||||||
@@ -38,3 +40,16 @@ export function isSPDXValid(license: string): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function octokitClient(token = 'repo-token', required = true): Octokit {
|
||||||
|
const opts: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
// auth is only added if token is present. For remote config files in public
|
||||||
|
// repos the token is optional, so it could be undefined.
|
||||||
|
const auth = core.getInput(token, {required})
|
||||||
|
if (auth !== undefined) {
|
||||||
|
opts['auth'] = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Octokit(opts)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user