Skip to content

Commit

Permalink
Merge pull request #91 from lazy-actions/refactor/inputs
Browse files Browse the repository at this point in the history
refactor: Separate the process of input parameters
  • Loading branch information
homoluctus authored Jun 3, 2021
2 parents 245f58d + f95014a commit 5862e97
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 102 deletions.
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

0 comments on commit 5862e97

Please sign in to comment.