Skip to content

Commit d5aa5b6

Browse files
Add table option for output-format vulnerabilities in console (anchore#135)
1 parent 961fe4c commit d5aa5b6

File tree

5 files changed

+95
-42
lines changed

5 files changed

+95
-42
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ The inputs `image`, `path`, and `sbom` are mutually exclusive to specify the sou
125125
| `sbom` | The SBOM to scan | N/A |
126126
| `registry-username` | The registry username to use when authenticating to an external registry | |
127127
| `registry-password` | The registry password to use when authenticating to an external registry | |
128-
| `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` |
129-
| `output-format` | Set the output parameter after successful action execution. Valid choices are "json" and "sarif" | `sarif` |
130-
| `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"` |
128+
| `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` |
129+
| `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` |
130+
| `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` |
131131

132132
### Action Outputs
133133

action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ inputs:
1818
required: false
1919
default: "true"
2020
output-format:
21-
description: 'Set the output parameter after successful action execution. Valid choices are "json" and "sarif".'
21+
description: 'Set the output parameter after successful action execution. Valid choices are "json", "sarif", and "table".'
2222
required: false
2323
default: "sarif"
2424
severity-cutoff:

dist/index.js

+24-19
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
139139
}
140140

141141
const SEVERITY_LIST = ["negligible", "low", "medium", "high", "critical"];
142-
const FORMAT_LIST = ["sarif", "json"];
142+
const FORMAT_LIST = ["sarif", "json", "table"];
143143
let cmdArgs = [];
144144

145145
if (core.isDebug()) {
@@ -227,34 +227,39 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
227227
core.debug(cmdOutput);
228228
}
229229

230-
if (outputFormat === "sarif") {
231-
const SARIF_FILE = "./results.sarif";
232-
fs.writeFileSync(SARIF_FILE, cmdOutput);
233-
out.sarif = SARIF_FILE;
234-
} else {
235-
const REPORT_FILE = "./results.json";
236-
fs.writeFileSync(REPORT_FILE, cmdOutput);
237-
out.report = REPORT_FILE;
238-
}
239-
240-
if (failBuild === true && exitCode > 0) {
241-
core.setFailed(
242-
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
243-
);
230+
switch (outputFormat) {
231+
case "sarif": {
232+
const SARIF_FILE = "./results.sarif";
233+
fs.writeFileSync(SARIF_FILE, cmdOutput);
234+
out.sarif = SARIF_FILE;
235+
break;
236+
}
237+
case "json": {
238+
const REPORT_FILE = "./results.json";
239+
fs.writeFileSync(REPORT_FILE, cmdOutput);
240+
out.report = REPORT_FILE;
241+
break;
242+
}
243+
default: // e.g. table
244+
core.info(cmdOutput);
244245
}
245246

246247
// If there is a non-zero exit status code there are a couple of potential reporting paths
247-
if (failBuild === false && exitCode > 0) {
248-
// There was a non-zero exit status but it wasn't because of failing severity, this must be
249-
// a grype problem
248+
if (exitCode > 0) {
250249
if (!severityCutoff) {
250+
// There was a non-zero exit status but it wasn't because of failing severity, this must be
251+
// a grype problem
251252
core.warning("grype had a non-zero exit status when running");
253+
} else if (failBuild === true) {
254+
core.setFailed(
255+
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
256+
);
252257
} else {
253258
// There is a non-zero exit status code with severity cut off, although there is still a chance this is grype
254259
// that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the
255260
// Actions UI
256261
core.warning(
257-
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
262+
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
258263
);
259264
}
260265
}

index.js

+24-19
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
124124
}
125125

126126
const SEVERITY_LIST = ["negligible", "low", "medium", "high", "critical"];
127-
const FORMAT_LIST = ["sarif", "json"];
127+
const FORMAT_LIST = ["sarif", "json", "table"];
128128
let cmdArgs = [];
129129

130130
if (core.isDebug()) {
@@ -212,34 +212,39 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
212212
core.debug(cmdOutput);
213213
}
214214

215-
if (outputFormat === "sarif") {
216-
const SARIF_FILE = "./results.sarif";
217-
fs.writeFileSync(SARIF_FILE, cmdOutput);
218-
out.sarif = SARIF_FILE;
219-
} else {
220-
const REPORT_FILE = "./results.json";
221-
fs.writeFileSync(REPORT_FILE, cmdOutput);
222-
out.report = REPORT_FILE;
223-
}
224-
225-
if (failBuild === true && exitCode > 0) {
226-
core.setFailed(
227-
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
228-
);
215+
switch (outputFormat) {
216+
case "sarif": {
217+
const SARIF_FILE = "./results.sarif";
218+
fs.writeFileSync(SARIF_FILE, cmdOutput);
219+
out.sarif = SARIF_FILE;
220+
break;
221+
}
222+
case "json": {
223+
const REPORT_FILE = "./results.json";
224+
fs.writeFileSync(REPORT_FILE, cmdOutput);
225+
out.report = REPORT_FILE;
226+
break;
227+
}
228+
default: // e.g. table
229+
core.info(cmdOutput);
229230
}
230231

231232
// If there is a non-zero exit status code there are a couple of potential reporting paths
232-
if (failBuild === false && exitCode > 0) {
233-
// There was a non-zero exit status but it wasn't because of failing severity, this must be
234-
// a grype problem
233+
if (exitCode > 0) {
235234
if (!severityCutoff) {
235+
// There was a non-zero exit status but it wasn't because of failing severity, this must be
236+
// a grype problem
236237
core.warning("grype had a non-zero exit status when running");
238+
} else if (failBuild === true) {
239+
core.setFailed(
240+
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
241+
);
237242
} else {
238243
// There is a non-zero exit status code with severity cut off, although there is still a chance this is grype
239244
// that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the
240245
// Actions UI
241246
core.warning(
242-
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
247+
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
243248
);
244249
}
245250
}

tests/action_args.test.js

+43
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,47 @@ describe("Github action args", () => {
7373
spyInput.mockRestore();
7474
spyOutput.mockRestore();
7575
});
76+
77+
it("runs with table output", async () => {
78+
const inputs = {
79+
image: "localhost:5000/match-coverage/debian:latest",
80+
"fail-build": "true",
81+
"output-format": "table",
82+
"severity-cutoff": "medium",
83+
};
84+
const spyInput = jest.spyOn(core, "getInput").mockImplementation((name) => {
85+
try {
86+
return inputs[name];
87+
} finally {
88+
inputs[name] = true;
89+
}
90+
});
91+
92+
const outputs = {};
93+
const spyOutput = jest
94+
.spyOn(core, "setOutput")
95+
.mockImplementation((name, value) => {
96+
outputs[name] = value;
97+
});
98+
99+
let stdout = "";
100+
const spyStdout = jest.spyOn(core, "info").mockImplementation((value) => {
101+
stdout += value;
102+
});
103+
104+
await run();
105+
106+
Object.keys(inputs).map((name) => {
107+
expect(inputs[name]).toBe(true);
108+
});
109+
110+
expect(stdout).toContain("VULNERABILITY");
111+
112+
expect(outputs["sarif"]).toBeFalsy();
113+
expect(outputs["json"]).toBeFalsy();
114+
115+
spyInput.mockRestore();
116+
spyOutput.mockRestore();
117+
spyStdout.mockRestore();
118+
});
76119
});

0 commit comments

Comments
 (0)