Compare commits

...

25 Commits

Author SHA1 Message Date
Federico Builes 88502badc9 Update README.md
Co-authored-by: Sarah Aladetan <sarahkemi@github.com>
2022-09-22 08:03:23 +02:00
Federico Builes ff7c97a976 adding dist 2022-09-21 17:03:01 +02:00
Federico Builes 4d3b8e5269 Clarify code a bit. 2022-09-21 17:01:00 +02:00
Federico Builes 38ee6e8360 Improve scopes example in new docs. 2022-09-21 16:53:20 +02:00
Federico Builes 54cd9a7cba Merge branch 'main' into external-config
# Conflicts:
#	README.md
#	__tests__/config.test.ts
#	dist/index.js.map
#	src/config.ts
#	src/schemas.ts
2022-09-21 16:50:02 +02:00
Federico Builes c4693c00ac Raise errors for invalid values in the external config. 2022-09-21 16:30:05 +02:00
Sarah Aladetan e89f113be2 add callout to checkout main when updating major version tag 2022-09-20 13:21:38 -07:00
Federico Builes eef7e39202 Accept options from both sources, prioritize external config. 2022-09-20 15:52:34 +02:00
Federico Builes 37dc32836b Merge branch 'main' into external-config 2022-09-20 15:29:28 +02:00
Federico Builes 890361387d Updating dist. 2022-09-20 15:16:25 +02:00
Federico Builes 61f19e6447 Let the users set the path for the config file. 2022-09-20 15:15:14 +02:00
Federico Builes aeb9ff5438 adding dist 2022-09-19 17:34:53 +02:00
Federico Builes 1ef21ab130 Leave a failing test for tomorrow! 2022-09-19 17:34:12 +02:00
Federico Builes 3c95902dd6 Adding more tests for the config file. 2022-09-19 17:29:25 +02:00
Federico Builes 4b4ec08f7b Make sure we get rid of the ridiculous dashes in the names. 2022-09-19 17:28:59 +02:00
Federico Builes a91c3ac205 Split reading inline/external configuration options. 2022-09-19 17:28:44 +02:00
Federico Builes bf0cb7fac4 Add a default config file. 2022-09-19 17:28:20 +02:00
Federico Builes 07a7056819 Update README to include config-file option. 2022-09-19 16:46:42 +02:00
Federico Builes b93fcee7ff Raise an error if the config file is not found. 2022-09-19 16:36:45 +02:00
Federico Builes 8bac022bfd Merge branch 'main' into external-config 2022-09-19 16:14:41 +02:00
Federico Builes 0ba71661e5 Adding failing tests. 2022-09-16 14:32:09 +02:00
Federico Builes 8ef181b2cb Read a hardcoded config file. 2022-09-16 14:30:57 +02:00
Federico Builes 7e2a489d03 Merge branch 'main' into external-config 2022-09-16 13:55:17 +02:00
Federico Builes 6dfe5fd567 Force line-breaks. 2022-09-06 14:36:50 +02:00
Federico Builes 71a0ed0a31 Updating the README to include instructions for both config file options. 2022-09-06 14:30:39 +02:00
13 changed files with 8613 additions and 51 deletions
+1
View File
@@ -0,0 +1 @@
fail-on-severity: low
+1 -1
View File
@@ -101,7 +101,7 @@ major version number (e.g. `v1`) in their workflows while
automatically getting all the
minor/patch updates.
To do this just force-create a new annotated tag and push it:
To do this just checkout `main`, force-create a new annotated tag, and push it:
```
git tag -fa v2 -m "Updating v2 to 2.3.4"
git push origin v2 --force
+123 -26
View File
@@ -64,9 +64,124 @@ jobs:
## 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:
Configure this action by either using an external configuration file,
or by inlining these options in your workflow file.
### Options
#### config-file
A string representing the path to an external configuraton file. By
default external configuration files are not used.
**Possible values**: A string representing the absolute path to the
configuration file.
**Example**: `config-file: ./.github/dependency-review-config.yml`.
#### fail-on-severity
Configure the severity level for alerting. See "[Vulnerability Severity](https://github.com/actions/dependency-review-action#vulnerability-severity)".
**Possible values**: `critical`, `high`, `moderate`, `low`.
**Example**: `fail-on-severity: moderate`.
#### fail-on-scopes
A list of strings representing the build environments you want to
support. The default value is `development, runtime`.
**Possible values**: `development`, `runtime`, `unknown`
**Inline example**: `fail-on-scopes: development, runtime`
**YAML example**:
```yaml
# this prevents scanning development dependencies
fail-on-scopes:
- runtime
```
#### allow-licenses
Only allow the licenses in this list. See "[Licenses](https://github.com/actions/dependency-review-action#licenses)".
**Possible values**: Any `spdx_id` value(s) from
https://docs.github.com/en/rest/licenses.
**Inline example**: `allow-licenses: BSD-3-Clause, MIT`
**YAML example**:
```yaml
allow-licenses:
- BSD-3-Clause
- MIT
```
#### deny-licenses
Add a custom list of licenses you want to block. See
"[Licenses](https://github.com/actions/dependency-review-action#licenses)".
**Possible values**: Any `spdx_id` value(s) from
https://docs.github.com/en/rest/licenses.
**Inline example**: `deny-licenses: LGPL-2.0, BSD-2-Clause`
**YAML example**:
```yaml
deny-licenses:
- LGPL-2.0
- BSD-2-Clause
```
#### base-ref/head-ref
Provide custom git references for the git base/head when performing
the comparison. If you are using pull requests, or
`pull_request_target` events you do not need to worry about setting
this. The values need to be specified for all other event types.
**Possible values**: Any valid git ref(s) in your project.
**Example**:
```yaml
base-ref: 8bb8a58d6a4028b6c2e314d5caaf273f57644896
head-ref: 69af5638bf660cf218aad5709a4c100e42a2f37b
```
### Configuration File
You can use an external configuration file to specify the settings for
this Action.
Start by specifying that you will be using an external configuration
file:
```yaml
- name: Dependency Review
uses: actions/dependency-review-action@v2
with:
config-file: "./.github/dependency-review-config.yml"
```
And then create the file in the path you just specified. **All of these fields are
optional**:
```yaml
fail-on-severity: "critical"
allow-licenses:
- "GPL-3.0"
- "BSD-3-Clause"
- "MIT"
```
### Inline Configuration
You can pass options to the Dependency Review
Action using your workflow file. Here's an example of what the full
file would look like:
```yaml
name: 'Dependency Review'
@@ -82,29 +197,11 @@ jobs:
- name: Dependency Review
uses: actions/dependency-review-action@v2
with:
# Possible values: "critical", "high", "moderate", "low"
# fail-on-severity: critical
#
# Possible values in comma separated list: "unknown", "runtime", or "development"
# fail-on-scopes: runtime, development
#
# 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 Enterprise Server.
#
# Possible values: Any `spdx_id` value(s) from https://docs.github.com/en/rest/licenses
# allow-licenses: GPL-3.0, BSD-3-Clause, MIT
#
# Possible values: Any `spdx_id` value(s) from https://docs.github.com/en/rest/licenses
# deny-licenses: LGPL-2.0, BSD-2-Clause
```
fail-on-severity: moderate
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.
# Use comma-separated names to pass list arguments:
deny-licenses: LGPL-2.0, BSD-2-Clause
```
### Vulnerability Severity
@@ -161,7 +258,7 @@ to filter. A couple of examples:
deny-licenses: Apache-1.1, Apache-2.0
```
**Important**
### Considerations
- Checking for licenses is not supported on Enterprise Server.
- The action will only accept one of the two parameters; an error will
+59 -1
View File
@@ -1,5 +1,5 @@
import {expect, test, beforeEach} from '@jest/globals'
import {readConfig} from '../src/config'
import {readConfig, readConfigFile} from '../src/config'
import {getRefs} from '../src/git-refs'
// GitHub Action inputs come in the form of environment variables
@@ -16,6 +16,7 @@ function clearInputs() {
'FAIL-ON-SCOPES',
'ALLOW-LICENSES',
'DENY-LICENSES',
'CONFIG-FILE',
'BASE-REF',
'HEAD-REF'
]
@@ -84,10 +85,66 @@ test('it raises an error when no refs are provided and the event is not a pull r
).toThrow()
})
test('it reads an external config file', async () => {
let options = readConfigFile('./__tests__/fixtures/config-allow-sample.yml')
expect(options.fail_on_severity).toEqual('critical')
expect(options.allow_licenses).toEqual(['BSD', 'GPL 2'])
})
test('raises an error when the the config file was not found', async () => {
expect(() => readConfigFile('fixtures/i-dont-exist')).toThrow()
})
test('it parses options from both sources', async () => {
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml')
let options = readConfig()
expect(options.fail_on_severity).toEqual('critical')
setInput('base-ref', 'a-custom-base-ref')
options = readConfig()
expect(options.base_ref).toEqual('a-custom-base-ref')
})
test('in case of conflicts, the external config is the source of truth', async () => {
setInput('config-file', './__tests__/fixtures/config-allow-sample.yml') // this will set fail-on-severity to 'critical'
let options = readConfig()
expect(options.fail_on_severity).toEqual('critical')
// 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 () => {
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
let options = readConfig()
expect(options.allow_licenses).toEqual(undefined)
expect(options.deny_licenses).toEqual(undefined)
setInput('config-file', './__tests__/fixtures/license-config-sample.yml')
options = readConfig()
expect(options.fail_on_severity).toEqual('low')
})
test('it accepts an external configuration filename', async () => {
setInput('config-file', './__tests__/fixtures/no-licenses-config.yml')
const options = readConfig()
expect(options.fail_on_severity).toEqual('critical')
})
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')
expect(() => readConfig()).toThrow()
})
test('it defaults to runtime scope', async () => {
const options = readConfig()
expect(options.fail_on_scopes).toEqual(['runtime'])
})
test('it parses custom scopes preference', async () => {
setInput('fail-on-scopes', 'runtime, development')
let options = readConfig()
@@ -98,6 +155,7 @@ test('it parses custom scopes preference', async () => {
options = readConfig()
expect(options.fail_on_scopes).toEqual(['development'])
})
test('it raises an error when given invalid scope', async () => {
setInput('fail-on-scopes', 'runtime, zombies')
expect(() => readConfig()).toThrow()
@@ -0,0 +1,3 @@
fail-on-severity: 'so many zombies'
deny-licenses:
- MIT
@@ -0,0 +1 @@
allow_licenses: ['MIT', 'GPL 2']
+3
View File
@@ -20,6 +20,9 @@ inputs:
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
config-file:
description: A filepath to the configuration file for the action.
required: false
allow-licenses:
description: Comma-separated list of allowed licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
required: false
Generated Vendored
+8347 -12
View File
File diff suppressed because it is too large Load Diff
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+17
View File
@@ -697,6 +697,23 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
yaml
ISC
Copyright Eemeli Aro <eemeli@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
zod
MIT
MIT License
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "dependency-review-action",
"version": "2.1.0",
"version": "2.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "dependency-review-action",
"version": "2.1.0",
"version": "2.2.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.9.1",
+49 -5
View File
@@ -1,6 +1,14 @@
import * as fs from 'fs'
import path from 'path'
import YAML from 'yaml'
import * as core from '@actions/core'
import * as z from 'zod'
import {ConfigurationOptions, SEVERITIES, SCOPES} from './schemas'
import {
ConfigurationOptions,
ConfigurationOptionsSchema,
SeveritySchema,
SCOPES
} from './schemas'
function getOptionalInput(name: string): string | undefined {
const value = core.getInput(name)
@@ -16,14 +24,29 @@ function parseList(list: string | undefined): string[] | undefined {
}
export function readConfig(): ConfigurationOptions {
const fail_on_severity = z
.enum(SEVERITIES)
.default('low')
.parse(getOptionalInput('fail-on-severity'))
const externalConfig = getOptionalInput('config-file')
if (externalConfig !== undefined) {
const config = readConfigFile(externalConfig)
// the reasoning behind reading the inline config when an external
// config file is provided is that we still want to allow users to
// 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 = getOptionalInput('allow-licenses')
const deny_licenses = getOptionalInput('deny-licenses')
@@ -43,3 +66,24 @@ export function readConfig(): ConfigurationOptions {
head_ref
}
}
export function readConfigFile(filePath: string): ConfigurationOptions {
let data
try {
data = fs.readFileSync(path.resolve(filePath), 'utf-8')
} catch (error: unknown) {
throw error
}
data = YAML.parse(data)
// get rid of the ugly dashes from the actions conventions
for (const key of Object.keys(data)) {
if (key.includes('-')) {
data[key.replace(/-/g, '_')] = data[key]
delete data[key]
}
}
const values = ConfigurationOptionsSchema.parse(data)
return values
}
+6 -3
View File
@@ -3,6 +3,8 @@ import * as z from 'zod'
export const SEVERITIES = ['critical', 'high', 'moderate', 'low'] as const
export const SCOPES = ['unknown', 'runtime', 'development'] as const
export const SeveritySchema = z.enum(SEVERITIES).default('low')
export const ChangeSchema = z.object({
change_type: z.enum(['added', 'removed']),
manifest: z.string(),
@@ -16,7 +18,7 @@ export const ChangeSchema = z.object({
vulnerabilities: z
.array(
z.object({
severity: z.enum(SEVERITIES),
severity: SeveritySchema,
advisory_ghsa_id: z.string(),
advisory_summary: z.string(),
advisory_url: z.string()
@@ -34,10 +36,11 @@ export const PullRequestSchema = z.object({
export const ConfigurationOptionsSchema = z
.object({
fail_on_severity: z.enum(SEVERITIES).default('low'),
fail_on_severity: SeveritySchema,
fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']),
allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([]),
config_file: z.string().optional().default('false'),
base_ref: z.string(),
head_ref: z.string()
})
@@ -52,5 +55,5 @@ export const ChangesSchema = z.array(ChangeSchema)
export type Change = z.infer<typeof ChangeSchema>
export type Changes = z.infer<typeof ChangesSchema>
export type ConfigurationOptions = z.infer<typeof ConfigurationOptionsSchema>
export type Severity = typeof SEVERITIES[number]
export type Severity = z.infer<typeof SeveritySchema>
export type Scope = typeof SCOPES[number]