Skip to content

Commit 4e3e3c5

Browse files
authored
refactor: split code into a clean architecture (#89)
1 parent e1cb8ad commit 4e3e3c5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+14254
-3034
lines changed

.github/workflows/ci-scan.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ jobs:
168168
with:
169169
sysdig-secure-token: ${{ secrets.KUBELAB_SECURE_API_TOKEN }}
170170
mode: iac
171-
iac-scan-path: ./iac_scan_examples
171+
iac-scan-path: ./tests/fixtures/iac/
172172
# Note: This test assumes these policies exist in the target Sysdig Secure account.
173173
use-policies: '"All Posture Findings", "MITRE DEFEND"'
174174

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ out
233233

234234
# Nuxt.js build / generate output
235235
.nuxt
236-
dist
237236

238237
# Gatsby files
239238
.cache/

dist/index.js

Lines changed: 2062 additions & 963 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.ts

Lines changed: 23 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,35 @@
11
import * as core from '@actions/core';
2-
import fs from 'fs';
3-
import { generateSARIFReport } from './src/sarif';
4-
import { cliScannerName, cliScannerResult, cliScannerURL, executeScan, pullScanner, ScanExecutionResult, ScanMode } from './src/scanner';
5-
import { ActionInputs, defaultSecureEndpoint } from './src/action';
6-
import { generateSummary } from './src/summary';
7-
import { Report, FilterOptions, Severity } from './src/report';
8-
9-
function parseCsvList(str?: string): string[] {
10-
if (!str) return [];
11-
return str.split(",").map(s => s.trim()).filter(s => !!s);
12-
}
13-
14-
export class ExecutionError extends Error {
15-
constructor(stdout: string, stderr: string) {
16-
super("execution error\n\nstdout: " + stdout + "\n\nstderr: " + stderr);
17-
}
18-
}
19-
20-
function writeReport(reportData: string) {
21-
fs.writeFileSync("./report.json", reportData);
22-
core.setOutput("scanReport", "./report.json");
23-
}
24-
25-
export async function run() {
26-
2+
import { RunScanUseCase } from './src/application/use-cases/RunScanUseCase';
3+
import { GitHubActionsInputProvider } from './src/infrastructure/github/GitHubActionsInputProvider';
4+
import { SysdigCliScanner } from './src/infrastructure/sysdig/SysdigCliScanner';
5+
import { SarifReportPresenter } from './src/infrastructure/github/SarifReportPresenter';
6+
import { SummaryReportPresenter } from './src/infrastructure/github/SummaryReportPresenter';
7+
import { IReportPresenter } from './src/application/ports/IReportPresenter';
8+
9+
async function run(): Promise<void> {
2710
try {
28-
let opts = ActionInputs.parseActionInputs();
29-
opts.printOptions();
30-
let scanFlags = opts.composeFlags();
11+
const inputProvider = new GitHubActionsInputProvider();
12+
const config = inputProvider.getInputs();
3113

32-
let scanResult: ScanExecutionResult;
33-
// Download CLI Scanner from 'cliScannerURL'
34-
let retCode = await pullScanner(opts.cliScannerURL);
35-
if (retCode == 0) {
36-
// Execute Scanner
37-
scanResult = await executeScan(scanFlags);
38-
39-
retCode = scanResult.ReturnCode;
40-
if (retCode == 0 || retCode == 1) {
41-
// Transform Scan Results to other formats such as SARIF
42-
if (opts.mode == ScanMode.vm) {
43-
await processScanResult(scanResult, opts);
44-
}
45-
} else {
46-
core.error("Terminating scan. Scanner couldn't be executed.")
47-
}
48-
} else {
49-
core.error("Terminating scan. Scanner couldn't be pulled.")
50-
}
14+
const scanner = new SysdigCliScanner();
5115

52-
if (opts.stopOnFailedPolicyEval && retCode == 1) {
53-
core.setFailed(`Stopping because Policy Evaluation was FAILED.`);
54-
} else if (opts.standalone && retCode == 0) {
55-
core.info("Policy Evaluation was OMITTED.");
56-
} else if (retCode == 0) {
57-
core.info("Policy Evaluation was PASSED.");
58-
} else if (opts.stopOnProcessingError && retCode > 1) {
59-
core.setFailed(`Stopping because the scanner terminated with an error.`);
60-
} // else: Don't stop regardless the outcome.
16+
const presenters: IReportPresenter[] = [
17+
new SarifReportPresenter(),
18+
];
6119

62-
} catch (error) {
63-
if (core.getInput('stop-on-processing-error') == 'true') {
64-
core.setFailed(`Unexpected error: ${error instanceof Error ? error.stack : String(error)}`);
20+
if (!config.skipSummary) {
21+
presenters.push(new SummaryReportPresenter());
6522
}
66-
core.error(`Unexpected error: ${error instanceof Error ? error.stack : String(error)}`);
67-
}
68-
}
6923

70-
export async function processScanResult(result: ScanExecutionResult, opts: ActionInputs) {
71-
writeReport(result.Output);
72-
73-
let report: Report;
74-
try {
75-
report = JSON.parse(result.Output);
76-
} catch (error) {
77-
core.error("Error parsing analysis JSON report: " + error + ". Output was: " + result.Output);
78-
throw new ExecutionError(result.Output, result.Error);
79-
}
80-
81-
if (report) {
82-
const filters: FilterOptions = {
83-
minSeverity: (opts.severityAtLeast && opts.severityAtLeast.toLowerCase() !== "any")
84-
? opts.severityAtLeast.toLowerCase() as Severity
85-
: undefined,
86-
packageTypes: parseCsvList(opts.packageTypes),
87-
notPackageTypes: parseCsvList(opts.notPackageTypes),
88-
excludeAccepted: opts.excludeAccepted,
89-
};
90-
91-
core.info("Generating SARIF Report...")
92-
generateSARIFReport(report, opts.groupByPackage, filters);
93-
94-
if (!opts.skipSummary) {
95-
core.info("Generating Summary...")
96-
await generateSummary(opts, report, filters);
24+
const useCase = new RunScanUseCase(scanner, presenters, inputProvider);
25+
await useCase.execute();
26+
} catch (error) {
27+
if (error instanceof Error) {
28+
core.setFailed(error.message);
9729
} else {
98-
core.info("Skipping Summary...")
30+
core.setFailed(`Unknown error: ${error}`);
9931
}
10032
}
10133
}
10234

103-
export {
104-
cliScannerURL,
105-
defaultSecureEndpoint,
106-
pullScanner,
107-
cliScannerName,
108-
executeScan,
109-
cliScannerResult,
110-
};
111-
112-
if (require.main === module) {
113-
run();
114-
}
35+
run();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class ReportParsingError extends Error {
2+
constructor(message: string) {
3+
super(message);
4+
this.name = "ReportParsingError";
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class ScanExecutionError extends Error {
2+
constructor(message: string) {
3+
super(message);
4+
this.name = "ScanExecutionError";
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class ScannerPullError extends Error {
2+
constructor(message: string) {
3+
super(message);
4+
this.name = "ScannerPullError";
5+
}
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ScanConfig } from "./ScanConfig";
2+
3+
export interface IInputProvider {
4+
getInputs(): ScanConfig;
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { FilterOptions } from "../../domain/services/filtering";
2+
import { ScanResult } from "../../domain/scanresult";
3+
4+
export interface IReportPresenter {
5+
generateReport(data: ScanResult, groupByPackage: boolean, filters?: FilterOptions): void;
6+
}

0 commit comments

Comments
 (0)