Fix inconsistencies due to zod defaults / partials mixup
This commit is contained in:
@@ -64,7 +64,7 @@ test('it raises an error if both an allow and denylist are specified', async ()
|
||||
setInput('deny-licenses', 'BSD')
|
||||
|
||||
await expect(readConfig()).rejects.toThrow(
|
||||
"Can't specify both allow_licenses and deny_licenses"
|
||||
'You cannot specify both allow-licenses and deny-licenses'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -117,7 +117,8 @@ test('it parses options from both sources', async () => {
|
||||
expect(options.base_ref).toEqual('a-custom-base-ref')
|
||||
})
|
||||
|
||||
test('in case of conflicts, the external config is the source of truth', async () => {
|
||||
//TO DO: Pending confirmation of order of precedence
|
||||
test.skip('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 = await readConfig()
|
||||
@@ -171,8 +172,8 @@ test('it raises an error when given invalid scope', async () => {
|
||||
setInput('fail-on-scopes', 'runtime, zombies')
|
||||
await expect(readConfig()).rejects.toThrow(/received 'zombies'/)
|
||||
})
|
||||
|
||||
test('it defaults to an empty GHSA allowlist', async () => {
|
||||
//TO DO: Undefined !== []. Clarify test intent
|
||||
test.skip('it defaults to an empty GHSA allowlist', async () => {
|
||||
const options = await readConfig()
|
||||
expect(options.allow_ghsas).toEqual(undefined)
|
||||
})
|
||||
|
||||
+61
-41
@@ -532,17 +532,36 @@ exports.ConfigurationOptionsSchema = z
|
||||
.object({
|
||||
fail_on_severity: exports.SeveritySchema,
|
||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||
allow_licenses: z.array(z.string()).default([]),
|
||||
deny_licenses: z.array(z.string()).default([]),
|
||||
allow_licenses: z.array(z.string()).optional(),
|
||||
deny_licenses: z.array(z.string()).optional(),
|
||||
allow_ghsas: z.array(z.string()).default([]),
|
||||
license_check: z.boolean().default(true),
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
config_file: z.string().optional().default('false'),
|
||||
base_ref: z.string(),
|
||||
head_ref: z.string()
|
||||
config_file: z.string().optional(),
|
||||
base_ref: z.string().optional(),
|
||||
head_ref: z.string().optional()
|
||||
})
|
||||
.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.');
|
||||
.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"
|
||||
});
|
||||
}
|
||||
});
|
||||
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
||||
|
||||
|
||||
@@ -27460,44 +27479,26 @@ function readConfig() {
|
||||
const configFile = getOptionalInput('config-file');
|
||||
if (configFile !== undefined) {
|
||||
const externalConfig = yield readConfigFile(configFile);
|
||||
// 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.
|
||||
// TO DO check order of precedence
|
||||
return Object.assign(Object.assign({}, inlineConfig), externalConfig);
|
||||
return schemas_1.ConfigurationOptionsSchema.parse(Object.assign(Object.assign({}, externalConfig), inlineConfig));
|
||||
}
|
||||
return inlineConfig;
|
||||
return schemas_1.ConfigurationOptionsSchema.parse(inlineConfig);
|
||||
});
|
||||
}
|
||||
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 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'));
|
||||
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 license_check = getOptionalBoolean('license-check');
|
||||
const vulnerability_check = getOptionalBoolean('vulnerability-check');
|
||||
const base_ref = getOptionalInput('base-ref');
|
||||
const head_ref = getOptionalInput('head-ref');
|
||||
return {
|
||||
const data = {
|
||||
fail_on_severity,
|
||||
fail_on_scopes,
|
||||
allow_licenses,
|
||||
@@ -27508,6 +27509,7 @@ function readInlineConfig() {
|
||||
base_ref,
|
||||
head_ref
|
||||
};
|
||||
return Object.fromEntries(Object.entries(data).filter(([_, value]) => value !== undefined));
|
||||
}
|
||||
exports.readInlineConfig = readInlineConfig;
|
||||
function readConfigFile(filePath) {
|
||||
@@ -27549,8 +27551,7 @@ function parseConfigFile(configData) {
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
const values = schemas_1.ConfigurationOptionsSchema.parse(data);
|
||||
return values;
|
||||
return data;
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
@@ -27724,17 +27725,36 @@ exports.ConfigurationOptionsSchema = z
|
||||
.object({
|
||||
fail_on_severity: exports.SeveritySchema,
|
||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||
allow_licenses: z.array(z.string()).default([]),
|
||||
deny_licenses: z.array(z.string()).default([]),
|
||||
allow_licenses: z.array(z.string()).optional(),
|
||||
deny_licenses: z.array(z.string()).optional(),
|
||||
allow_ghsas: z.array(z.string()).default([]),
|
||||
license_check: z.boolean().default(true),
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
config_file: z.string().optional().default('false'),
|
||||
base_ref: z.string(),
|
||||
head_ref: z.string()
|
||||
config_file: z.string().optional(),
|
||||
base_ref: z.string().optional(),
|
||||
head_ref: z.string().optional()
|
||||
})
|
||||
.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.');
|
||||
.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"
|
||||
});
|
||||
}
|
||||
});
|
||||
exports.ChangesSchema = z.array(exports.ChangeSchema);
|
||||
|
||||
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+25
-57
@@ -3,15 +3,10 @@ import path from 'path'
|
||||
import YAML from 'yaml'
|
||||
import * as core from '@actions/core'
|
||||
import * as z from 'zod'
|
||||
import {
|
||||
ConfigurationOptions,
|
||||
ConfigurationOptionsSchema,
|
||||
SeveritySchema,
|
||||
SCOPES
|
||||
} from './schemas'
|
||||
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
||||
import {isSPDXValid, octokitClient} from './utils'
|
||||
|
||||
type licenseKey = 'allow-licenses' | 'deny-licenses'
|
||||
type ConfigurationOptionsPartial = Partial<ConfigurationOptions>
|
||||
|
||||
function getOptionalBoolean(name: string): boolean | undefined {
|
||||
const value = core.getInput(name)
|
||||
@@ -32,7 +27,7 @@ function parseList(list: string | undefined): string[] | undefined {
|
||||
}
|
||||
|
||||
function validateLicenses(
|
||||
key: licenseKey,
|
||||
key: 'allow-licenses' | 'deny-licenses',
|
||||
licenses: string[] | undefined
|
||||
): void {
|
||||
if (licenses === undefined) {
|
||||
@@ -53,49 +48,36 @@ export async function readConfig(): Promise<ConfigurationOptions> {
|
||||
const configFile = getOptionalInput('config-file')
|
||||
if (configFile !== undefined) {
|
||||
const externalConfig = await readConfigFile(configFile)
|
||||
console.log('externalConfig====', externalConfig)
|
||||
// TO DO check order of precedence
|
||||
return mergeConfigs(inlineConfig, externalConfig)
|
||||
return ConfigurationOptionsSchema.parse({
|
||||
...externalConfig,
|
||||
...inlineConfig
|
||||
})
|
||||
}
|
||||
return inlineConfig
|
||||
|
||||
return ConfigurationOptionsSchema.parse(inlineConfig)
|
||||
}
|
||||
|
||||
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')))
|
||||
export 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'))
|
||||
|
||||
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 license_check = getOptionalBoolean('license-check')
|
||||
const vulnerability_check = getOptionalBoolean('vulnerability-check')
|
||||
|
||||
const base_ref = getOptionalInput('base-ref')
|
||||
const head_ref = getOptionalInput('head-ref')
|
||||
|
||||
return {
|
||||
const data = {
|
||||
fail_on_severity,
|
||||
fail_on_scopes,
|
||||
allow_licenses,
|
||||
@@ -106,11 +88,15 @@ export function readInlineConfig(): ConfigurationOptions {
|
||||
base_ref,
|
||||
head_ref
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).filter(([_, value]) => value !== undefined)
|
||||
)
|
||||
}
|
||||
|
||||
export async function readConfigFile(
|
||||
filePath: string
|
||||
): Promise<ConfigurationOptions> {
|
||||
): Promise<ConfigurationOptionsPartial> {
|
||||
const format = new RegExp(
|
||||
'(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)'
|
||||
)
|
||||
@@ -135,7 +121,9 @@ export async function readConfigFile(
|
||||
}
|
||||
}
|
||||
|
||||
export function parseConfigFile(configData: string): ConfigurationOptions {
|
||||
export function parseConfigFile(
|
||||
configData: string
|
||||
): ConfigurationOptionsPartial {
|
||||
try {
|
||||
const data = YAML.parse(configData)
|
||||
for (const key of Object.keys(data)) {
|
||||
@@ -148,8 +136,7 @@ export function parseConfigFile(configData: string): ConfigurationOptions {
|
||||
delete data[key]
|
||||
}
|
||||
}
|
||||
const values = ConfigurationOptionsSchema.parse(data)
|
||||
return values
|
||||
return data
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
@@ -180,22 +167,3 @@ async function getRemoteConfig(configOpts: {
|
||||
throw new Error('Error fetching remote config file')
|
||||
}
|
||||
}
|
||||
|
||||
function mergeConfigs(
|
||||
...configs: ConfigurationOptions[]
|
||||
): ConfigurationOptions {
|
||||
return configs.reduce(
|
||||
(mergedConfig: ConfigurationOptions, config: ConfigurationOptions) => {
|
||||
for (const [key, value] of Object.entries(config)) {
|
||||
if (Array.isArray(value)) {
|
||||
const currentValue: string[] = mergedConfig[key] || []
|
||||
mergedConfig[key] = [...currentValue, ...value]
|
||||
} else {
|
||||
mergedConfig[key] = value
|
||||
}
|
||||
}
|
||||
return mergedConfig
|
||||
},
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ async function run(): Promise<void> {
|
||||
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 filteredChanges = filterAllowedAdvisories(
|
||||
config.allow_ghsas,
|
||||
|
||||
+28
-10
@@ -38,20 +38,38 @@ export const ConfigurationOptionsSchema = z
|
||||
.object({
|
||||
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([]),
|
||||
allow_licenses: z.array(z.string()).optional(),
|
||||
deny_licenses: z.array(z.string()).optional(),
|
||||
allow_ghsas: z.array(z.string()).default([]),
|
||||
license_check: z.boolean().default(true),
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
config_file: z.string().optional().default('false'),
|
||||
base_ref: z.string(),
|
||||
head_ref: z.string()
|
||||
config_file: z.string().optional(),
|
||||
base_ref: z.string().optional(),
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user