diff --git a/__tests__/licenses.test.ts b/__tests__/licenses.test.ts index 2204500..f803219 100644 --- a/__tests__/licenses.test.ts +++ b/__tests__/licenses.test.ts @@ -100,6 +100,20 @@ const complexLicenseChange: Change = { ] } +const unlicensedChange: Change = { + change_type: 'added', + manifest: '.github/workflows/ci.yml', + ecosystem: 'actions', + name: 'foo-org/actions-repo/.github/workflows/some-action.yml', + version: '1.1.1', + package_url: + 'pkg:githubactions/foo-org/actions-repo/.github/workflows/some-action.yml@1.1.1', + license: null, + source_repository_url: 'github.com/some-repo', + scope: 'development', + vulnerabilities: [] +} + jest.mock('@actions/core') const mockOctokit = { @@ -313,4 +327,15 @@ describe('GH License API fallback', () => { expect(mockOctokit.rest.licenses.getForRepo).not.toHaveBeenCalled() expect(unlicensed.length).toEqual(0) }) + + test('it does not call licenses API if the package is excluded', async () => { + const {unlicensed} = await getInvalidLicenseChanges([unlicensedChange], { + licenseExclusions: [ + 'pkg:githubactions/foo-org/actions-repo/.github/workflows/some-action.yml' + ] + }) + + expect(mockOctokit.rest.licenses.getForRepo).not.toHaveBeenCalled() + expect(unlicensed.length).toEqual(0) + }) }) diff --git a/src/licenses.ts b/src/licenses.ts index 1f20f5f..650cbe5 100644 --- a/src/licenses.ts +++ b/src/licenses.ts @@ -1,6 +1,6 @@ import {Change, Changes} from './schemas' import {octokitClient} from './utils' -import {parsePURL} from './purl' +import {parsePURL, PackageURL} from './purl' import * as spdx from './spdx' /** @@ -36,34 +36,8 @@ export async function getInvalidLicenseChanges( } ) - const groupedChanges = await groupChanges(changes) + const groupedChanges = await groupChanges(changes, licenseExclusions) - // Takes the changes from the groupedChanges object and filters out the ones that are part of the exclusions list - // It does by creating a new PackageURL object from the change and comparing it to the exclusions list - groupedChanges.licensed = groupedChanges.licensed.filter(change => { - if (change.package_url.length === 0) { - return true - } - - const changeAsPackageURL = parsePURL(encodeURI(change.package_url)) - - // We want to find if the licenseExclusion list contains the PackageURL of the Change - // If it does, we want to filter it out and therefore return false - // If it doesn't, we want to keep it and therefore return true - if ( - licenseExclusions !== null && - licenseExclusions !== undefined && - licenseExclusions.findIndex( - exclusion => - exclusion.type === changeAsPackageURL.type && - exclusion.name === changeAsPackageURL.name - ) !== -1 - ) { - return false - } else { - return true - } - }) const licensedChanges: Changes = groupedChanges.licensed const invalidLicenseChanges: InvalidLicenseChanges = { @@ -172,16 +146,47 @@ const truncatedDGLicense = (license: string): boolean => license.length === 255 && !spdx.isValid(license) async function groupChanges( - changes: Changes + changes: Changes, + licenseExclusions: PackageURL[] | null = null ): Promise> { const result: Record = { licensed: [], unlicensed: [] } + let candidateChanges = changes + + // If a package is excluded from license checking, we don't bother trying to + // fetch the license for it and we leave it off of the `licensed` and + // `unlicensed` lists. + if (licenseExclusions !== null && licenseExclusions !== undefined) { + candidateChanges = candidateChanges.filter(change => { + if (change.package_url.length === 0) { + return true + } + + const changeAsPackageURL = parsePURL(encodeURI(change.package_url)) + + // We want to find if the licenseExclusion list contains the PackageURL of the Change + // If it does, we want to filter it out and therefore return false + // If it doesn't, we want to keep it and therefore return true + if ( + licenseExclusions.findIndex( + exclusion => + exclusion.type === changeAsPackageURL.type && + exclusion.name === changeAsPackageURL.name + ) !== -1 + ) { + return false + } else { + return true + } + }) + } + const ghChanges = [] - for (const change of changes) { + for (const change of candidateChanges) { if (change.change_type === 'removed') { continue }