Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SCP-139 Adds undeclared policy report #31

Merged
merged 1 commit into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions __tests__/result-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ScannerResults } from '../src/services/result.interfaces';
import { getComponents, getLicenses, License } from '../src/services/result.service';

Check warning on line 2 in __tests__/result-service.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'getComponents' is defined but never used

Check warning on line 2 in __tests__/result-service.test.ts

View workflow job for this annotation

GitHub Actions / TypeScript Tests

'getComponents' is defined but never used

const licenseTableTest: { name: string; description: string; content: string; licenses: License[] }[] = [
{
Expand Down Expand Up @@ -60,15 +60,3 @@
});
}
});

describe('Test components service', () => {
const t = licenseTableTest[3];
it(`test c`, () => {
const scannerResults = JSON.parse(t.content) as ScannerResults;
const components = getComponents(scannerResults);

const componentsWithCopyleft = components.filter(component =>
component.licenses.some(license => !!license.copyleft)
);
});
});
64 changes: 55 additions & 9 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/policies/copyleft-policy-check.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ScannerResults } from '../services/result.interfaces';
import { CHECK_NAME } from '../app.config';
import { PolicyCheck } from './policy-check';
import { Component, getComponents, getLicenses } from '../services/result.service';
import { Component, getComponents } from '../services/result.service';
import { generateTable } from '../utils/markdown.utils';

export class CopyleftPolicyCheck extends PolicyCheck {
Expand Down Expand Up @@ -34,8 +34,8 @@ export class CopyleftPolicyCheck extends PolicyCheck {
: `### :x: Policy Fail \n #### ${components.length} component(s) with copyleft licenses were found. \n See details for more information.`;
}

private getDetails(components: Component[]): string {
if (components.length === 0) return '';
private getDetails(components: Component[]): string | undefined {
if (components.length === 0) return undefined;

const headers = ['Component', 'Version', 'License', 'URL', 'Copyleft'];
const rows: string[][] = [];
Expand Down
6 changes: 3 additions & 3 deletions src/policies/policy-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { GitHub } from '@actions/github/lib/utils';
import * as inputs from '../app.input';

export enum CONCLUSION {

Check warning on line 8 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'CONCLUSION' is already declared in the upper scope on line 8 column 13

Check warning on line 8 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / TypeScript Tests

'CONCLUSION' is already declared in the upper scope on line 8 column 13
ActionRequired = 'action_required',
Cancelled = 'cancelled',
Failure = 'failure',
Expand All @@ -16,7 +16,7 @@
TimedOut = 'timed_out'
}

export enum STATUS {

Check warning on line 19 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'STATUS' is already declared in the upper scope on line 19 column 13

Check warning on line 19 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / TypeScript Tests

'STATUS' is already declared in the upper scope on line 19 column 13
UNINITIALIZED = 'UNINITIALIZED',
INITIALIZED = 'INITIALIZED',
RUNNING = 'RUNNING',
Expand All @@ -30,7 +30,7 @@

private checkRunId: number;

private _raw: any;

Check warning on line 33 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

Unexpected any. Specify a different type

Check warning on line 33 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / TypeScript Tests

Unexpected any. Specify a different type

private _status: STATUS;

Expand All @@ -44,7 +44,7 @@
this.checkRunId = -1;
}

async start(): Promise<any> {

Check warning on line 47 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

Unexpected any. Specify a different type

Check warning on line 47 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / TypeScript Tests

Unexpected any. Specify a different type
const result = await this.octokit.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
Expand All @@ -67,7 +67,7 @@
return this._conclusion;
}

get raw(): any {

Check warning on line 70 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / TypeScript Tests

Unexpected any. Specify a different type
return this._raw;
}

Expand All @@ -75,7 +75,7 @@
return `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/job/${this.raw.id}`;
}

async run(scannerResults: ScannerResults): Promise<void> {

Check warning on line 78 in src/policies/policy-check.ts

View workflow job for this annotation

GitHub Actions / TypeScript Tests

'scannerResults' is defined but never used
if (this._status === STATUS.UNINITIALIZED)
throw new Error(`Error on finish. Policy "${this.checkName}" is not created.`);

Expand All @@ -83,18 +83,18 @@
this._status = STATUS.RUNNING;
}

protected async success(summary: string, text: string): Promise<void> {
protected async success(summary: string, text?: string): Promise<void> {
this._conclusion = CONCLUSION.Success;
return await this.finish(summary, text);
}

protected async reject(summary: string, text: string): Promise<void> {
protected async reject(summary: string, text?: string): Promise<void> {
if (inputs.POLICIES_HALT_ON_FAILURE) this._conclusion = CONCLUSION.Failure;
else this._conclusion = CONCLUSION.Neutral;
return await this.finish(summary, text);
}

protected async finish(summary: string, text: string): Promise<void> {
protected async finish(summary: string, text?: string): Promise<void> {
core.debug(`Finish policy check: ${this.checkName}. (conclusion=${this._conclusion})`);
this._status = STATUS.FINISHED;

Expand Down
45 changes: 39 additions & 6 deletions src/policies/undeclared-policy-check.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { PolicyCheck } from './policy-check';
import { CHECK_NAME } from '../app.config';
import { ScannerResults } from '../services/result.interfaces';
import { getComponents } from '../services/result.service';
import { Component, getComponents } from '../services/result.service';
import * as inputs from '../app.input';
import { parseSbom } from '../utils/sbom.utils';
import { generateTable } from '../utils/markdown.utils';

export class UndeclaredPolicyCheck extends PolicyCheck {
constructor() {
Expand All @@ -13,21 +14,53 @@ export class UndeclaredPolicyCheck extends PolicyCheck {
async run(scannerResults: ScannerResults): Promise<void> {
super.run(scannerResults);

const nonDeclaredComponents = new Set<string>();
const nonDeclaredComponents: Component[] = [];

const comps = getComponents(scannerResults);
const sbom = await parseSbom(inputs.SBOM_FILEPATH);

comps.forEach(c => {
if (!sbom.components.some(component => component.purl === c.purl)) {
nonDeclaredComponents.add(c.purl);
nonDeclaredComponents.push(c);
}
});

if (!nonDeclaredComponents.size) {
this.success('Completed succesfully', 'Undeclared components were not found');
const summary = this.getSummary(nonDeclaredComponents);
const details = this.getDetails(nonDeclaredComponents);

if (nonDeclaredComponents.length === 0) {
this.success(summary, details);
} else {
this.reject('Failure', `Undeclared components were found: ${JSON.stringify(Array.from(nonDeclaredComponents))}`);
this.reject(summary, details);
}
}

private getSummary(components: Component[]): string {
return components.length === 0
? '### :white_check_mark: Policy Pass \n #### Not undeclared components were found'
: `### :x: Policy Fail \n #### ${components.length} undeclared component(s) were found. \n See details for more information.`;
}

private getDetails(components: Component[]): string | undefined {
if (components.length === 0) return undefined;

const headers = ['Component', 'Version', 'License'];
const rows: string[][] = [];

components.forEach(component => {
const licenses = component.licenses.map(l => l.spdxid).join(' - ');
rows.push([component.purl, component.version, licenses]);
});

const snippet = JSON.stringify(
components.map(({ purl }) => ({ purl })),
null,
4
);

let content = `### Undeclared components \n ${generateTable(headers, rows)}`;
content += `#### Add the following snippet into your \`sbom.json\` file \n \`\`\`json \n ${snippet} \n \`\`\``;

return content;
}
}
31 changes: 28 additions & 3 deletions src/services/result.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,41 @@ export function getComponents(results: ScannerResults): Component[] {
components.push({
purl: d.purl,
version: d.version,
licenses: d.licenses.map(l => ({ spdxid: l.spdx_id, copyleft: null, url: null, count: 1 }))
licenses: d.licenses
.map(l => ({ spdxid: l.spdx_id, copyleft: null, url: null, count: 1 }))
.filter(l => l.spdxid)
});
}
}
}
}

//Merge duplicates purls
// Merge duplicates
const componentMap = new Map<string, Component>();
components.forEach((component: Component) => {
const key = `${component.purl}-${component.version}`;
const existingComponent = componentMap.get(key);
if (existingComponent) {
const licenses = [...existingComponent.licenses, ...component.licenses];
} else {
componentMap.set(key, component);
}

// Remove duplicates licenses
const spdxidSet = new Set<string>();
const uniqueLicenses: License[] = [];
component.licenses.forEach(license => {
if (!spdxidSet.has(license.spdxid)) {
spdxidSet.add(license.spdxid);
uniqueLicenses.push(license);
}
});
component.licenses = uniqueLicenses;
});

const unqiqueComponents = [...componentMap.values()];

return components;
return unqiqueComponents;
}

export function getLicenses(results: ScannerResults): License[] {
Expand Down
Loading