Skip to content

Commit 028cd8f

Browse files
authored
feat: add output-file option, default to random directory output in temp (#346)
Signed-off-by: Keith Zantow <[email protected]>
1 parent 6e00665 commit 028cd8f

File tree

6 files changed

+75
-32
lines changed

6 files changed

+75
-32
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ The inputs `image`, `path`, and `sbom` are mutually exclusive to specify the sou
128128
| `registry-password` | The registry password to use when authenticating to an external registry | |
129129
| `fail-build` | Fail the build if a vulnerability is found with a higher severity. That severity defaults to `medium` and can be set with `severity-cutoff`. | `true` |
130130
| `output-format` | Set the output parameter after successful action execution. Valid choices are `json`, `sarif`, and `table`, where `table` output will print to the console instead of generating a file. | `sarif` |
131+
| `output-file` | File to output the Grype scan results to. Defaults to a file in the system temp directory, available in the action outputs | |
131132
| `severity-cutoff` | Optionally specify the minimum vulnerability severity to trigger a failure. Valid choices are "negligible", "low", "medium", "high" and "critical". Any vulnerability with a severity less than this value will lead to a "warning" result. Default is "medium". | `medium` |
132133
| `only-fixed` | Specify whether to only report vulnerabilities that have a fix available. | `false` |
133134
| `add-cpes-if-none` | Specify whether to autogenerate missing CPEs. | `false` |

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ inputs:
2121
description: 'Set the output parameter after successful action execution. Valid choices are "json", "sarif", and "table".'
2222
required: false
2323
default: "sarif"
24+
output-file:
25+
description: 'The file to output the grype scan results to'
26+
required: false
2427
severity-cutoff:
2528
description: 'Optionally specify the minimum vulnerability severity to trigger an "error" level ACS result. Valid choices are "negligible", "low", "medium", "high" and "critical". Any vulnerability with a severity less than this value will lead to a "warning" result. Default is "medium".'
2629
required: false

dist/index.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,13 @@ async function run() {
148148
const byCve = core.getInput("by-cve") || "false";
149149
const vex = core.getInput("vex") || "";
150150
const cacheDb = core.getInput("cache-db") || "false";
151+
const outputFile = core.getInput("output-file") || "";
151152
const out = await runScan({
152153
source,
153154
failBuild,
154155
severityCutoff,
155156
onlyFixed,
157+
outputFile,
156158
outputFormat,
157159
addCpesIfNone,
158160
byCve,
@@ -299,6 +301,7 @@ async function runScan({
299301
failBuild,
300302
severityCutoff,
301303
onlyFixed,
304+
outputFile,
302305
outputFormat,
303306
addCpesIfNone,
304307
byCve,
@@ -341,6 +344,15 @@ async function runScan({
341344

342345
cmdArgs.push("-o", outputFormat);
343346

347+
// always output to a file, this is read later to print table output
348+
if (!outputFile) {
349+
outputFile = path.join(
350+
fs.mkdtempSync(path.join(os.tmpdir(), "grype-")),
351+
"output",
352+
);
353+
}
354+
cmdArgs.push("--file", outputFile);
355+
344356
if (
345357
!SEVERITY_LIST.some(
346358
(item) =>
@@ -403,23 +415,16 @@ async function runScan({
403415
}
404416
cmdArgs.push(source);
405417

406-
const { stdout, exitCode } = await runCommand(grypeCommand, cmdArgs, env);
418+
const { exitCode } = await runCommand(grypeCommand, cmdArgs, env);
407419

408-
switch (outputFormat) {
409-
case "sarif": {
410-
const SARIF_FILE = "./results.sarif";
411-
fs.writeFileSync(SARIF_FILE, stdout);
412-
out.sarif = SARIF_FILE;
413-
break;
414-
}
415-
case "json": {
416-
const REPORT_FILE = "./results.json";
417-
fs.writeFileSync(REPORT_FILE, stdout);
418-
out.json = REPORT_FILE;
419-
break;
420+
out[outputFormat] = outputFile;
421+
if (outputFormat === "table") {
422+
try {
423+
const report = fs.readFileSync(outputFile);
424+
core.info(report.toString());
425+
} catch (e) {
426+
core.warning(`error writing table output contents: ${e}`);
420427
}
421-
default: // e.g. table
422-
core.info(stdout);
423428
}
424429

425430
// If there is a non-zero exit status code there are a couple of potential reporting paths

index.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,13 @@ async function run() {
134134
const byCve = core.getInput("by-cve") || "false";
135135
const vex = core.getInput("vex") || "";
136136
const cacheDb = core.getInput("cache-db") || "false";
137+
const outputFile = core.getInput("output-file") || "";
137138
const out = await runScan({
138139
source,
139140
failBuild,
140141
severityCutoff,
141142
onlyFixed,
143+
outputFile,
142144
outputFormat,
143145
addCpesIfNone,
144146
byCve,
@@ -285,6 +287,7 @@ async function runScan({
285287
failBuild,
286288
severityCutoff,
287289
onlyFixed,
290+
outputFile,
288291
outputFormat,
289292
addCpesIfNone,
290293
byCve,
@@ -327,6 +330,15 @@ async function runScan({
327330

328331
cmdArgs.push("-o", outputFormat);
329332

333+
// always output to a file, this is read later to print table output
334+
if (!outputFile) {
335+
outputFile = path.join(
336+
fs.mkdtempSync(path.join(os.tmpdir(), "grype-")),
337+
"output",
338+
);
339+
}
340+
cmdArgs.push("--file", outputFile);
341+
330342
if (
331343
!SEVERITY_LIST.some(
332344
(item) =>
@@ -389,23 +401,16 @@ async function runScan({
389401
}
390402
cmdArgs.push(source);
391403

392-
const { stdout, exitCode } = await runCommand(grypeCommand, cmdArgs, env);
404+
const { exitCode } = await runCommand(grypeCommand, cmdArgs, env);
393405

394-
switch (outputFormat) {
395-
case "sarif": {
396-
const SARIF_FILE = "./results.sarif";
397-
fs.writeFileSync(SARIF_FILE, stdout);
398-
out.sarif = SARIF_FILE;
399-
break;
400-
}
401-
case "json": {
402-
const REPORT_FILE = "./results.json";
403-
fs.writeFileSync(REPORT_FILE, stdout);
404-
out.json = REPORT_FILE;
405-
break;
406+
out[outputFormat] = outputFile;
407+
if (outputFormat === "table") {
408+
try {
409+
const report = fs.readFileSync(outputFile);
410+
core.info(report.toString());
411+
} catch (e) {
412+
core.warning(`error writing table output contents: ${e}`);
406413
}
407-
default: // e.g. table
408-
core.info(stdout);
409414
}
410415

411416
// If there is a non-zero exit status code there are a couple of potential reporting paths

tests/action.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe("Github action", () => {
4747
image: "",
4848
path: "tests/fixtures/npm-project",
4949
"fail-build": "true",
50+
"output-file": "./results.json",
5051
"output-format": "json",
5152
"severity-cutoff": "medium",
5253
"add-cpes-if-none": "true",
@@ -63,6 +64,7 @@ describe("Github action", () => {
6364
image: "",
6465
path: "tests/fixtures/npm-project",
6566
"fail-build": "true",
67+
"output-file": "./results.sarif",
6668
"output-format": "sarif",
6769
"severity-cutoff": "medium",
6870
"add-cpes-if-none": "true",

tests/grype_command.test.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,53 @@ describe("Grype command args", () => {
1111
const args = await mockRun({
1212
source: "dir:.",
1313
"fail-build": "false",
14+
"output-file": "the-output-file",
1415
"output-format": "sarif",
1516
"severity-cutoff": "high",
1617
version: "0.6.0",
1718
"only-fixed": "false",
1819
"add-cpes-if-none": "false",
1920
"by-cve": "false",
2021
});
21-
expect(args).toEqual(["-o", "sarif", "--fail-on", "high", "dir:."]);
22+
expect(args).toEqual([
23+
"-o",
24+
"sarif",
25+
"--file",
26+
"the-output-file",
27+
"--fail-on",
28+
"high",
29+
"dir:.",
30+
]);
2231
});
2332

2433
it("is invoked with values", async () => {
2534
const args = await mockRun({
2635
image: "asdf",
2736
"fail-build": "false",
37+
"output-file": "the-output-file",
2838
"output-format": "json",
2939
"severity-cutoff": "low",
3040
version: "0.6.0",
3141
"only-fixed": "false",
3242
"add-cpes-if-none": "false",
3343
"by-cve": "false",
3444
});
35-
expect(args).toEqual(["-o", "json", "--fail-on", "low", "asdf"]);
45+
expect(args).toEqual([
46+
"-o",
47+
"json",
48+
"--file",
49+
"the-output-file",
50+
"--fail-on",
51+
"low",
52+
"asdf",
53+
]);
3654
});
3755

3856
it("adds missing CPEs if requested", async () => {
3957
const args = await mockRun({
4058
image: "asdf",
4159
"fail-build": "false",
60+
"output-file": "the-output-file",
4261
"output-format": "json",
4362
"severity-cutoff": "low",
4463
version: "0.6.0",
@@ -49,6 +68,8 @@ describe("Grype command args", () => {
4968
expect(args).toEqual([
5069
"-o",
5170
"json",
71+
"--file",
72+
"the-output-file",
5273
"--fail-on",
5374
"low",
5475
"--add-cpes-if-none",
@@ -60,6 +81,7 @@ describe("Grype command args", () => {
6081
const args = await mockRun({
6182
image: "asdf",
6283
"fail-build": "false",
84+
"output-file": "the-output-file",
6385
"output-format": "json",
6486
"severity-cutoff": "low",
6587
version: "0.6.0",
@@ -71,6 +93,8 @@ describe("Grype command args", () => {
7193
expect(args).toEqual([
7294
"-o",
7395
"json",
96+
"--file",
97+
"the-output-file",
7498
"--fail-on",
7599
"low",
76100
"--add-cpes-if-none",
@@ -84,13 +108,16 @@ describe("Grype command args", () => {
84108
const args = await mockRun({
85109
path: "asdf",
86110
"fail-build": "false",
111+
"output-file": "the-output-file",
87112
"output-format": "table",
88113
"severity-cutoff": "low",
89114
"by-cve": "true",
90115
});
91116
expect(args).toEqual([
92117
"-o",
93118
"table",
119+
"--file",
120+
"the-output-file",
94121
"--fail-on",
95122
"low",
96123
"--by-cve",

0 commit comments

Comments
 (0)