From 02fd052b00ad4c99d9a2b52d065ee2893f41b256 Mon Sep 17 00:00:00 2001 From: Agustin Groh <77737320+agustingroh@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:26:17 -0300 Subject: [PATCH] feat: SP-1343 Add configurable Copyleft license options to pipeline * feat: SP-1343 Add configurable Copyleft license options to pipeline --- README.md | 27 ++++++----- action.yml | 9 ++++ dist/index.js | 67 +++++++++++++++++++++++---- package-lock.json | 4 +- package.json | 2 +- src/app.input.ts | 3 ++ src/policies/copyleft-policy-check.ts | 18 +++---- src/utils/license.utils.ts | 23 +++++++++ 8 files changed, 120 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 8215e13..073555e 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,20 @@ For example workflow runs, check out our ### Action Input Parameters -| **Parameter** | **Description** | **Required** | **Default** | -|--------------------------|------------------------------------------------------------------------------------|--------------|-------------------------------------| -| output.filepath | Scan output file name. | Optional | `results.json` | -| sbom.enabled | Enable or disable scanning based on the SBOM file | Optional | `true` | -| sbom.filepath | Filepath of the SBOM file to be used for scanning | Optional | `sbom.json` | -| sbom.type | Type of SBOM operation: either 'identify' or 'ignore | Optional | `identify` | -| dependencies.enabled | Option to enable or disable scanning of dependencies. | Optional | `false` | -| policies | List of policies separated by commas, options available are: copyleft, undeclared. | Optional | - | -| policies.halt_on_failure | Halt check on policy failure. If set to false checks will not fail. | Optional | `true` | -| api.url | SCANOSS API URL | Optional | `https://osskb.org/api/scan/direct` | -| api.key | SCANOSS API Key | Optional | - | +| **Parameter** | **Description** | **Required** | **Default** | +|----------------------------|------------------------------------------------------------------------------------------------------|--------------|-------------------------------------| +| output.filepath | Scan output file name. | Optional | `results.json` | +| sbom.enabled | Enable or disable scanning based on the SBOM file | Optional | `true` | +| sbom.filepath | Filepath of the SBOM file to be used for scanning | Optional | `sbom.json` | +| sbom.type | Type of SBOM operation: either 'identify' or 'ignore | Optional | `identify` | +| dependencies.enabled | Option to enable or disable scanning of dependencies. | Optional | `false` | +| policies | List of policies separated by commas, options available are: copyleft, undeclared. | Optional | - | +| policies.halt_on_failure | Halt check on policy failure. If set to false checks will not fail. | Optional | `true` | +| api.url | SCANOSS API URL | Optional | `https://osskb.org/api/scan/direct` | +| api.key | SCANOSS API Key | Optional | - | +| licenses.copyleft.include | List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list. | Optional | - | +| licenses.copyleft.exclude | List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list. | Optional | - | +| licenses.copyleft.explicit | Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list. | Optional | - | ### Action Output Parameters @@ -78,7 +81,7 @@ the output into your custom workflow The SCANOSS Code Scan Action includes two configurable policies: 1. Copyleft: This policy checks if any component or code snippet is associated with a copyleft license. If such a - license is detected, the pull request (PR) is rejected. + license is detected, the pull request (PR) is rejected. The default list of Copyleft licenses is defined in the following [file](https://github.com/scanoss/gha-code-scan/blob/main/src/utils/license.utils.ts). 2. Undeclared: This policy compares the components detected in the repository against those declared in an sbom.json file (customizable through the sbom.filepath parameter). If there are undeclared components, the PR is rejected. diff --git a/action.yml b/action.yml index d5bc7cc..645503e 100644 --- a/action.yml +++ b/action.yml @@ -43,6 +43,15 @@ inputs: description: 'Your GitHub token' required: false default: ${{ github.token }} + licenses.copyleft.include: + description: 'List of Copyleft licenses to append to the default list. Provide licenses as a comma-separated list.' + required: false + licenses.copyleft.exclude: + description: 'List of Copyleft licenses to remove from default list. Provide licenses as a comma-separated list.' + required: false + licenses.copyleft.explicit: + description: 'Explicit list of Copyleft licenses to consider. Provide licenses as a comma-separated list.' + required: false outputs: result-filepath: diff --git a/dist/index.js b/dist/index.js index b4eaf61..fd78825 100644 --- a/dist/index.js +++ b/dist/index.js @@ -125780,7 +125780,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.REPO_DIR = exports.GITHUB_TOKEN = exports.OUTPUT_FILEPATH = exports.API_URL = exports.API_KEY = exports.DEPENDENCIES_ENABLED = exports.SBOM_TYPE = exports.SBOM_FILEPATH = exports.SBOM_ENABLED = exports.POLICIES_HALT_ON_FAILURE = exports.POLICIES = void 0; +exports.REPO_DIR = exports.COPYLEFT_LICENSE_EXPLICIT = exports.COPYLEFT_LICENSE_EXCLUDE = exports.COPYLEFT_LICENSE_INCLUDE = exports.GITHUB_TOKEN = exports.OUTPUT_FILEPATH = exports.API_URL = exports.API_KEY = exports.DEPENDENCIES_ENABLED = exports.SBOM_TYPE = exports.SBOM_FILEPATH = exports.SBOM_ENABLED = exports.POLICIES_HALT_ON_FAILURE = exports.POLICIES = void 0; const core = __importStar(__nccwpck_require__(42186)); exports.POLICIES = core.getInput('policies'); exports.POLICIES_HALT_ON_FAILURE = core.getInput('policies.halt_on_failure') === 'true'; @@ -125792,6 +125792,9 @@ exports.API_KEY = core.getInput('api.key'); exports.API_URL = core.getInput('api.url'); exports.OUTPUT_FILEPATH = core.getInput('output.filepath'); exports.GITHUB_TOKEN = core.getInput('github.token'); +exports.COPYLEFT_LICENSE_INCLUDE = core.getInput('licenses.copyleft.include'); +exports.COPYLEFT_LICENSE_EXCLUDE = core.getInput('licenses.copyleft.exclude'); +exports.COPYLEFT_LICENSE_EXPLICIT = core.getInput('licenses.copyleft.explicit'); exports.REPO_DIR = process.env.GITHUB_WORKSPACE; @@ -126032,14 +126035,16 @@ class CopyleftPolicyCheck extends policy_check_1.PolicyCheck { const rows = []; components.forEach(component => { component.licenses.forEach(license => { - const copyleftIcon = license_utils_1.licenseUtil.isCopyLeft(license.spdxid?.trim().toLowerCase()) ? 'YES' : 'NO'; - rows.push([ - component.purl, - component.version, - license.spdxid, - `${license_utils_1.licenseUtil.getOSADL(license?.spdxid) || ''}`, - copyleftIcon - ]); + if (license_utils_1.licenseUtil.isCopyLeft(license.spdxid?.trim().toLowerCase())) { + const copyleftIcon = license_utils_1.licenseUtil.isCopyLeft(license.spdxid?.trim().toLowerCase()) ? 'YES' : 'NO'; + rows.push([ + component.purl, + component.version, + license.spdxid, + `${license_utils_1.licenseUtil.getOSADL(license?.spdxid) || ''}`, + copyleftIcon + ]); + } }); }); return `### Copyleft licenses \n ${(0, markdown_utils_1.generateTable)(headers, rows, centeredColumns)}`; @@ -127011,12 +127016,37 @@ exports.createCommentOnPR = createCommentOnPR; /***/ }), /***/ 52210: -/***/ ((__unused_webpack_module, exports) => { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "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; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.licenseUtil = exports.LicenseUtil = void 0; +const inputs = __importStar(__nccwpck_require__(483)); +const core = __importStar(__nccwpck_require__(42186)); class LicenseUtil { BASE_OSADL_URL = 'https://spdx.org/licenses'; HTML = 'html'; @@ -127048,7 +127078,24 @@ class LicenseUtil { ].map(l => l.toLowerCase())); copyLeftLicenses = new Set(); init() { + if (inputs.COPYLEFT_LICENSE_EXPLICIT) { + const explicitCopyleftLicenses = inputs.COPYLEFT_LICENSE_EXPLICIT.split(',').map(pn => pn.trim().toLowerCase()); + core.debug(`Explicit licenses: ${explicitCopyleftLicenses}`); + this.copyLeftLicenses = new Set(explicitCopyleftLicenses); + return; + } + core.debug(`Explicit licenses not defined, setting default licenses...`); this.copyLeftLicenses = this.defaultCopyleftLicenses; + if (inputs.COPYLEFT_LICENSE_INCLUDE) { + const includedCopyleftLicenses = inputs.COPYLEFT_LICENSE_INCLUDE.split(',').map(pn => pn.trim()); + core.debug(`Included copyleft licenses: ${includedCopyleftLicenses}`); + includedCopyleftLicenses.forEach(l => this.copyLeftLicenses.add(l.toLowerCase())); + } + if (inputs.COPYLEFT_LICENSE_EXCLUDE) { + const excludedCopyleftLicenses = inputs.COPYLEFT_LICENSE_EXCLUDE.split(',').map(pn => pn.trim()); + core.debug(`Excluded copyleft licenses: ${excludedCopyleftLicenses}`); + excludedCopyleftLicenses.forEach(l => this.copyLeftLicenses.delete(l.toLowerCase())); + } } isCopyLeft(spdxid) { return this.copyLeftLicenses.has(spdxid); diff --git a/package-lock.json b/package-lock.json index 868116c..c36a0ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanoss-code-scan-action", - "version": "0.1.7", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scanoss-code-scan-action", - "version": "0.1.7", + "version": "0.2.0", "license": "MIT", "dependencies": { "@actions/artifact": "^2.1.0", diff --git a/package.json b/package.json index 557891e..2e9212f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "scanoss-code-scan-action", "description": "SCANOSS Code Scan Action", - "version": "0.1.7", + "version": "0.2.0", "author": "SCANOSS", "private": true, "homepage": "https://github.com/scanoss/code-scan-action/", diff --git a/src/app.input.ts b/src/app.input.ts index c8fa341..7578d4f 100644 --- a/src/app.input.ts +++ b/src/app.input.ts @@ -33,4 +33,7 @@ export const API_KEY = core.getInput('api.key'); export const API_URL = core.getInput('api.url'); export const OUTPUT_FILEPATH = core.getInput('output.filepath'); export const GITHUB_TOKEN = core.getInput('github.token'); +export const COPYLEFT_LICENSE_INCLUDE = core.getInput('licenses.copyleft.include'); +export const COPYLEFT_LICENSE_EXCLUDE = core.getInput('licenses.copyleft.exclude'); +export const COPYLEFT_LICENSE_EXPLICIT = core.getInput('licenses.copyleft.explicit'); export const REPO_DIR = process.env.GITHUB_WORKSPACE as string; diff --git a/src/policies/copyleft-policy-check.ts b/src/policies/copyleft-policy-check.ts index 8f9aed5..b9ccefd 100644 --- a/src/policies/copyleft-policy-check.ts +++ b/src/policies/copyleft-policy-check.ts @@ -106,14 +106,16 @@ export class CopyleftPolicyCheck extends PolicyCheck { components.forEach(component => { component.licenses.forEach(license => { - const copyleftIcon = licenseUtil.isCopyLeft(license.spdxid?.trim().toLowerCase()) ? 'YES' : 'NO'; - rows.push([ - component.purl, - component.version, - license.spdxid, - `${licenseUtil.getOSADL(license?.spdxid) || ''}`, - copyleftIcon - ]); + if (licenseUtil.isCopyLeft(license.spdxid?.trim().toLowerCase())) { + const copyleftIcon = licenseUtil.isCopyLeft(license.spdxid?.trim().toLowerCase()) ? 'YES' : 'NO'; + rows.push([ + component.purl, + component.version, + license.spdxid, + `${licenseUtil.getOSADL(license?.spdxid) || ''}`, + copyleftIcon + ]); + } }); }); return `### Copyleft licenses \n ${generateTable(headers, rows, centeredColumns)}`; diff --git a/src/utils/license.utils.ts b/src/utils/license.utils.ts index 970ed36..52401c0 100644 --- a/src/utils/license.utils.ts +++ b/src/utils/license.utils.ts @@ -1,3 +1,6 @@ +import * as inputs from '../app.input'; +import * as core from '@actions/core'; + export class LicenseUtil { private BASE_OSADL_URL = 'https://spdx.org/licenses'; private HTML = 'html'; @@ -34,7 +37,27 @@ export class LicenseUtil { private copyLeftLicenses = new Set(); private init(): void { + if (inputs.COPYLEFT_LICENSE_EXPLICIT) { + const explicitCopyleftLicenses = inputs.COPYLEFT_LICENSE_EXPLICIT.split(',').map(pn => pn.trim().toLowerCase()); + core.debug(`Explicit licenses: ${explicitCopyleftLicenses}`); + this.copyLeftLicenses = new Set(explicitCopyleftLicenses); + return; + } + + core.debug(`Explicit licenses not defined, setting default licenses...`); this.copyLeftLicenses = this.defaultCopyleftLicenses; + + if (inputs.COPYLEFT_LICENSE_INCLUDE) { + const includedCopyleftLicenses = inputs.COPYLEFT_LICENSE_INCLUDE.split(',').map(pn => pn.trim()); + core.debug(`Included copyleft licenses: ${includedCopyleftLicenses}`); + includedCopyleftLicenses.forEach(l => this.copyLeftLicenses.add(l.toLowerCase())); + } + + if (inputs.COPYLEFT_LICENSE_EXCLUDE) { + const excludedCopyleftLicenses = inputs.COPYLEFT_LICENSE_EXCLUDE.split(',').map(pn => pn.trim()); + core.debug(`Excluded copyleft licenses: ${excludedCopyleftLicenses}`); + excludedCopyleftLicenses.forEach(l => this.copyLeftLicenses.delete(l.toLowerCase())); + } } isCopyLeft(spdxid: string): boolean {