From 3b410dc4ade4fba19d668598c272f202ffe3b33c Mon Sep 17 00:00:00 2001 From: cnagadya Date: Fri, 4 Nov 2022 09:05:45 +0000 Subject: [PATCH] Load remote config file --- scripts/scan_pr | 4 ++- src/config.ts | 87 ++++++++++++++++++++++++++++++++++++------------- src/licenses.ts | 13 +++----- src/utils.ts | 8 +++++ 4 files changed, 80 insertions(+), 32 deletions(-) diff --git a/scripts/scan_pr b/scripts/scan_pr index 2a3fd00..32d91ac 100755 --- a/scripts/scan_pr +++ b/scripts/scan_pr @@ -64,7 +64,9 @@ event_file.close action_inputs = { "repo-token": github_token, - "config-file": config_file + "config-file": config_file, + "remote-config-file": "actions/dependency-review-action/.github/dependency-review-config.yml@external-config", + "config-repository-token": github_token } dev_cmd_env = { diff --git a/src/config.ts b/src/config.ts index 908b86d..aca8bf2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,7 +9,7 @@ import { SeveritySchema, SCOPES } from './schemas' -import {isSPDXValid} from './utils' +import {isSPDXValid, octokitClient} from './utils' type licenseKey = 'allow-licenses' | 'deny-licenses' @@ -47,19 +47,29 @@ function validateLicenses( } } -export function readConfig(): ConfigurationOptions { - 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 async function readConfig(): Promise { + const externalToken = getOptionalInput('config-repository-token') + const remoteConfigFile = getOptionalInput('remote-config-file') + const repoConfigFile = getOptionalInput('config-file') + + const inlineConfig = readInlineConfig() + let remoteConfig: ConfigurationOptions = {} + let repoConfig: ConfigurationOptions = {} + + if (externalToken !== undefined) { + if (remoteConfigFile === undefined) { + throw new Error('Missing required parameter: remote-config-file.') + } + remoteConfig = readConfigFile(await getRemoteConfig(remoteConfigFile)) } + if (repoConfigFile !== undefined) { + repoConfig = readConfigFile(getRepoConfig(repoConfigFile)) + } + // 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 {...inlineConfig, ...remoteConfig, ...repoConfig} } export function readInlineConfig(): ConfigurationOptions { @@ -110,16 +120,8 @@ export function readInlineConfig(): ConfigurationOptions { } } -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) - +export function readConfigFile(configData: string): ConfigurationOptions { + const data = YAML.parse(configData) for (const key of Object.keys(data)) { if (key === 'allow-licenses' || key === 'deny-licenses') { validateLicenses(key, data[key]) @@ -133,3 +135,42 @@ export function readConfigFile(filePath: string): ConfigurationOptions { const values = ConfigurationOptionsSchema.parse(data) return values } + +function getRepoConfig(filePath: string): string { + try { + return fs.readFileSync(path.resolve(filePath), 'utf-8') + } catch (error) { + throw error + } +} + +async function getRemoteConfig(configFile: string): Promise { + const format = new RegExp( + '(?[^/]+)/(?[^/]+)/(?[^@]+)@(?.*)' + ) + + const pieces = format.exec(configFile) + if (pieces === null || pieces.groups === undefined || pieces.length < 5) { + throw new Error('Invalid remote config file format.') + } + try { + const {data} = await octokitClient( + 'config-repository-token' + ).rest.repos.getContent({ + mediaType: { + format: 'raw' + }, + owner: pieces.groups.owner, + repo: pieces.groups.repo, + path: pieces.groups.path, + ref: pieces.groups.ref + }) + if (data === undefined) { + throw new Error('Invalid content') + } + console.log('Checking data value', data, data === 'fail-on-severity: low\n') + return data as unknown as string + } catch (error) { + throw error + } +} diff --git a/src/licenses.ts b/src/licenses.ts index 709fd8a..97dcc14 100644 --- a/src/licenses.ts +++ b/src/licenses.ts @@ -1,8 +1,6 @@ -import * as core from '@actions/core' import spdxSatisfies from 'spdx-satisfies' -import {Octokit} from 'octokit' import {Change, Changes} from './schemas' -import {isSPDXValid} from './utils' +import {isSPDXValid, octokitClient as octokitClient} from './utils' /** * Loops through a list of changes, filtering and returning the @@ -76,12 +74,11 @@ const fetchGHLicense = async ( owner: string, repo: string ): Promise => { - const octokit = new Octokit({ - auth: core.getInput('repo-token', {required: true}) - }) - 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 } catch (_) { return null diff --git a/src/utils.ts b/src/utils.ts index d995dfa..6f0f445 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +import * as core from '@actions/core' +import {Octokit} from 'octokit' import spdxParse from 'spdx-expression-parse' import {Changes} from './schemas' @@ -38,3 +40,9 @@ export function isSPDXValid(license: string): boolean { return false } } + +export function octokitClient(token = 'repo-token'): Octokit { + return new Octokit({ + auth: core.getInput(token, {required: true}) + }) +}