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

refactor: Separate the process of input parameters #91

Merged
merged 6 commits into from
Jun 3, 2021
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
3 changes: 3 additions & 0 deletions __tests__/helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as fs from 'fs';
import * as path from 'path';
import { Downloader } from '../src/downloader';

export const template = path.join(__dirname, '../src/template/default.tpl');

const downloader = new Downloader();

export function removeTrivyCmd(path: string) {
Expand Down
36 changes: 36 additions & 0 deletions __tests__/inputs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Inputs } from '../src/inputs';
import { template } from './helper';

describe('Inputs class Test', () => {
const initEnv = process.env;

beforeEach(() => {
process.env = {
INPUT_TOKEN: 'xxxxx',
INPUT_IMAGE: 'yyyyy',
...initEnv
};
});

test('Specify required parameters only', () => {
expect(() => new Inputs()).not.toThrow();
});

test('Specify all parameter', () => {
process.env = {
INPUT_TOKEN: 'xxx',
INPUT_IMAGE: 'yyy',
INPUT_TRIVY_VERSION: '0.18.3',
INPUT_SEVERITY: 'HIGH',
INPUT_VULN_TYPE: 'os',
INPUT_IGNORE_UNFIXED: 'true',
INPUT_TEMPLATE: template,
INPUT_ISSUE_TITLE: 'hello',
INPUT_ISSUE_LABEL: 'world',
INPUT_ISSUE_ASSIGNEE: 'aaaa',
...initEnv
};
const inputs = new Inputs();
expect(() => inputs.validate()).not.toThrow();
});
});
30 changes: 3 additions & 27 deletions __tests__/trivy.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as path from 'path';
import { Downloader } from '../src/downloader';
import { scan } from '../src/trivy';
import { TrivyOption } from '../src/interface';
import { TrivyCmdOption } from '../src/interface';
import { removeTrivyCmd } from './helper';

const downloader = new Downloader();
Expand All @@ -22,7 +22,7 @@ describe('Trivy scan', () => {
});

test('with valid option', () => {
const option: TrivyOption = {
const option: TrivyCmdOption = {
severity: 'HIGH,CRITICAL',
vulnType: 'os,library',
ignoreUnfixed: true,
Expand All @@ -33,7 +33,7 @@ describe('Trivy scan', () => {
});

test('without ignoreUnfixed', () => {
const option: TrivyOption = {
const option: TrivyCmdOption = {
severity: 'HIGH,CRITICAL',
vulnType: 'os,library',
ignoreUnfixed: false,
Expand All @@ -42,28 +42,4 @@ describe('Trivy scan', () => {
const result: string = scan(trivyPath, image, option) as string;
expect(result.length).toBeGreaterThanOrEqual(1);
});

test('with invalid severity', () => {
const invalidOption: TrivyOption = {
severity: 'INVALID',
vulnType: 'os,library',
ignoreUnfixed: true,
template
};
expect(() => {
scan(trivyPath, image, invalidOption);
}).toThrowError('Trivy option error: INVALID is unknown severity');
});

test('with invalid vulnType', () => {
const invalidOption: TrivyOption = {
severity: 'HIGH',
vulnType: 'INVALID',
ignoreUnfixed: true,
template
};
expect(() => {
scan(trivyPath, image, invalidOption);
}).toThrowError('Trivy option error: INVALID is unknown vuln-type');
});
});
48 changes: 48 additions & 0 deletions __tests__/validator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { TrivyCmdOptionValidator } from '../src/validator';
import { template } from './helper';

describe('TrivyCmdOptionValidator Test', () => {
test('Correct option', () => {
const validator = new TrivyCmdOptionValidator({
severity: 'HIGH',
vulnType: 'os',
ignoreUnfixed: false,
template
});
expect(() => validator.validate()).not.toThrow();
});

test('Invalid severity', () => {
const validator = new TrivyCmdOptionValidator({
severity: '?',
vulnType: 'os',
ignoreUnfixed: false,
template
});
expect(() => validator.validate()).toThrow(
'Trivy option error: ? is unknown severity'
);
});

test('Invalid vuln_type', () => {
const validator = new TrivyCmdOptionValidator({
severity: 'HIGH',
vulnType: '?',
ignoreUnfixed: false,
template
});
expect(() => validator.validate()).toThrow(
'Trivy option error: ? is unknown vuln-type'
);
});

test('Invalid template', () => {
const validator = new TrivyCmdOptionValidator({
severity: 'HIGH',
vulnType: 'os',
ignoreUnfixed: false,
template: '?'
});
expect(() => validator.validate()).toThrow('Could not find ?');
});
});
41 changes: 9 additions & 32 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,29 @@
import * as core from '@actions/core';
import { Downloader } from './downloader';
import { GitHub } from './github';
import { Inputs } from './inputs';
import { scan } from './trivy';
import { TrivyOption } from './interface';

async function run(): Promise<void> {
const trivyVersion = core.getInput('trivy_version').replace(/^v/, '');
const image = core.getInput('image') || process.env.IMAGE_NAME;

if (!image) {
throw new Error('Please specify scan target image name');
}

const trivyOption: TrivyOption = {
severity: core.getInput('severity').replace(/\s+/g, ''),
vulnType: core.getInput('vuln_type').replace(/\s+/g, ''),
ignoreUnfixed: core.getInput('ignore_unfixed').toLowerCase() === 'true',
template: core.getInput('template') || `${__dirname}/template/default.tpl`,
};
const inputs = new Inputs();
inputs.validate();

const downloader = new Downloader();
const trivyCmdPath = await downloader.download(trivyVersion);
const result = scan(trivyCmdPath, image, trivyOption);
const trivyCmdPath = await downloader.download(inputs.trivy.version);
const result = scan(trivyCmdPath, inputs.image, inputs.trivy.option);

if (!result) {
return;
}

const issueOption = {
title: core.getInput('issue_title'),
body: result,
labels: core
.getInput('issue_label')
.replace(/\s+/g, '')
.split(','),
assignees: core
.getInput('issue_assignee')
.replace(/\s+/g, '')
.split(','),
};
const token = core.getInput('token', { required: true });
const github = new GitHub(token);
const output = await github.createOrUpdateIssue(image, issueOption);
const github = new GitHub(inputs.token);
const issueOption = { body: result, ...inputs.issue };
const output = await github.createOrUpdateIssue(inputs.image, issueOption);

core.setOutput('html_url', output.htmlUrl);
core.setOutput('issue_number', output.issueNumber.toString());

if (core.getInput('fail_on_vulnerabilities') === 'true') {
if (inputs.fail_on_vulnerabilities) {
throw new Error('Abnormal termination because vulnerabilities found');
}
}
Expand Down
52 changes: 52 additions & 0 deletions src/inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as core from '@actions/core';
import { IssueInputs, TrivyInputs } from './interface';
import { TrivyCmdOptionValidator } from './validator';

export class Inputs {
token: string;
image: string;
trivy: TrivyInputs;
issue: IssueInputs;
fail_on_vulnerabilities: boolean;

constructor() {
this.token = core.getInput('token', { required: true });

const image = core.getInput('image') || process.env.IMAGE_NAME;
if (!image) {
throw new Error('Please specify target image');
}
this.image = image;

this.trivy = {
version: core.getInput('trivy_version').replace(/^v/, ''),
option: {
severity: core.getInput('severity').replace(/\s+/g, ''),
vulnType: core.getInput('vuln_type').replace(/\s+/g, ''),
ignoreUnfixed: core.getInput('ignore_unfixed').toLowerCase() === 'true',
template:
core.getInput('template') || `${__dirname}/template/default.tpl`
}
};

this.issue = {
title: core.getInput('issue_title'),
labels: core
.getInput('issue_label')
.replace(/\s+/g, '')
.split(','),
assignees: core
.getInput('issue_assignee')
.replace(/\s+/g, '')
.split(',')
};

this.fail_on_vulnerabilities =
core.getInput('fail_on_vulnerabilities') === 'true';
}

validate(): void {
const trivy = new TrivyCmdOptionValidator(this.trivy.option);
trivy.validate();
}
}
18 changes: 15 additions & 3 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
export interface IssueOption {
export interface Validator {
validate(): void;
}

export interface IssueInputs {
title: string;
body: string;
labels?: string[];
assignees?: string[];
}

export interface IssueOption extends IssueInputs {
body: string;
}

export interface IssueResponse {
issueNumber: number;
htmlUrl: string;
}

export interface TrivyOption {
export interface TrivyInputs {
version: string;
option: TrivyCmdOption;
}

export interface TrivyCmdOption {
severity: string;
vulnType: string;
ignoreUnfixed: boolean;
Expand Down
42 changes: 2 additions & 40 deletions src/trivy.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { spawnSync } from 'child_process';
import * as core from '@actions/core';
import { TrivyOption } from './interface';
import { TrivyCmdOption } from './interface';

export function scan(
trivyPath: string,
image: string,
option: TrivyOption
option: TrivyCmdOption
): string | undefined {
validateOption(option);

const args = [
'--severity',
option.severity,
Expand Down Expand Up @@ -44,39 +42,3 @@ export function scan(
stderr: ${result.stderr}`);
}
}

function validateOption(option: TrivyOption): void {
validateSeverity(option.severity.split(','));
validateVulnType(option.vulnType.split(','));
}

function validateSeverity(severities: string[]): boolean {
const allowedSeverities = /UNKNOWN|LOW|MEDIUM|HIGH|CRITICAL/;
if (!validateArrayOption(allowedSeverities, severities)) {
throw new Error(
`Trivy option error: ${severities.join(',')} is unknown severity.
Trivy supports UNKNOWN, LOW, MEDIUM, HIGH and CRITICAL.`
);
}
return true;
}

function validateVulnType(vulnTypes: string[]): boolean {
const allowedVulnTypes = /os|library/;
if (!validateArrayOption(allowedVulnTypes, vulnTypes)) {
throw new Error(
`Trivy option error: ${vulnTypes.join(',')} is unknown vuln-type.
Trivy supports os and library.`
);
}
return true;
}

function validateArrayOption(allowedValue: RegExp, options: string[]): boolean {
for (const option of options) {
if (!allowedValue.test(option)) {
return false;
}
}
return true;
}
Loading