Skip to content

Commit

Permalink
feat: add output-file option, default to random directory output in t…
Browse files Browse the repository at this point in the history
…emp (#346)

Signed-off-by: Keith Zantow <[email protected]>
  • Loading branch information
kzantow authored Dec 11, 2024
1 parent 6e00665 commit 028cd8f
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 32 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ The inputs `image`, `path`, and `sbom` are mutually exclusive to specify the sou
| `registry-password` | The registry password to use when authenticating to an external registry | |
| `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` |
| `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` |
| `output-file` | File to output the Grype scan results to. Defaults to a file in the system temp directory, available in the action outputs | |
| `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` |
| `only-fixed` | Specify whether to only report vulnerabilities that have a fix available. | `false` |
| `add-cpes-if-none` | Specify whether to autogenerate missing CPEs. | `false` |
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ inputs:
description: 'Set the output parameter after successful action execution. Valid choices are "json", "sarif", and "table".'
required: false
default: "sarif"
output-file:
description: 'The file to output the grype scan results to'
required: false
severity-cutoff:
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".'
required: false
Expand Down
35 changes: 20 additions & 15 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,13 @@ async function run() {
const byCve = core.getInput("by-cve") || "false";
const vex = core.getInput("vex") || "";
const cacheDb = core.getInput("cache-db") || "false";
const outputFile = core.getInput("output-file") || "";
const out = await runScan({
source,
failBuild,
severityCutoff,
onlyFixed,
outputFile,
outputFormat,
addCpesIfNone,
byCve,
Expand Down Expand Up @@ -299,6 +301,7 @@ async function runScan({
failBuild,
severityCutoff,
onlyFixed,
outputFile,
outputFormat,
addCpesIfNone,
byCve,
Expand Down Expand Up @@ -341,6 +344,15 @@ async function runScan({

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

// always output to a file, this is read later to print table output
if (!outputFile) {
outputFile = path.join(
fs.mkdtempSync(path.join(os.tmpdir(), "grype-")),
"output",
);
}
cmdArgs.push("--file", outputFile);

if (
!SEVERITY_LIST.some(
(item) =>
Expand Down Expand Up @@ -403,23 +415,16 @@ async function runScan({
}
cmdArgs.push(source);

const { stdout, exitCode } = await runCommand(grypeCommand, cmdArgs, env);
const { exitCode } = await runCommand(grypeCommand, cmdArgs, env);

switch (outputFormat) {
case "sarif": {
const SARIF_FILE = "./results.sarif";
fs.writeFileSync(SARIF_FILE, stdout);
out.sarif = SARIF_FILE;
break;
}
case "json": {
const REPORT_FILE = "./results.json";
fs.writeFileSync(REPORT_FILE, stdout);
out.json = REPORT_FILE;
break;
out[outputFormat] = outputFile;
if (outputFormat === "table") {
try {
const report = fs.readFileSync(outputFile);
core.info(report.toString());
} catch (e) {
core.warning(`error writing table output contents: ${e}`);
}
default: // e.g. table
core.info(stdout);
}

// If there is a non-zero exit status code there are a couple of potential reporting paths
Expand Down
35 changes: 20 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,13 @@ async function run() {
const byCve = core.getInput("by-cve") || "false";
const vex = core.getInput("vex") || "";
const cacheDb = core.getInput("cache-db") || "false";
const outputFile = core.getInput("output-file") || "";
const out = await runScan({
source,
failBuild,
severityCutoff,
onlyFixed,
outputFile,
outputFormat,
addCpesIfNone,
byCve,
Expand Down Expand Up @@ -285,6 +287,7 @@ async function runScan({
failBuild,
severityCutoff,
onlyFixed,
outputFile,
outputFormat,
addCpesIfNone,
byCve,
Expand Down Expand Up @@ -327,6 +330,15 @@ async function runScan({

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

// always output to a file, this is read later to print table output
if (!outputFile) {
outputFile = path.join(
fs.mkdtempSync(path.join(os.tmpdir(), "grype-")),
"output",
);
}
cmdArgs.push("--file", outputFile);

if (
!SEVERITY_LIST.some(
(item) =>
Expand Down Expand Up @@ -389,23 +401,16 @@ async function runScan({
}
cmdArgs.push(source);

const { stdout, exitCode } = await runCommand(grypeCommand, cmdArgs, env);
const { exitCode } = await runCommand(grypeCommand, cmdArgs, env);

switch (outputFormat) {
case "sarif": {
const SARIF_FILE = "./results.sarif";
fs.writeFileSync(SARIF_FILE, stdout);
out.sarif = SARIF_FILE;
break;
}
case "json": {
const REPORT_FILE = "./results.json";
fs.writeFileSync(REPORT_FILE, stdout);
out.json = REPORT_FILE;
break;
out[outputFormat] = outputFile;
if (outputFormat === "table") {
try {
const report = fs.readFileSync(outputFile);
core.info(report.toString());
} catch (e) {
core.warning(`error writing table output contents: ${e}`);
}
default: // e.g. table
core.info(stdout);
}

// If there is a non-zero exit status code there are a couple of potential reporting paths
Expand Down
2 changes: 2 additions & 0 deletions tests/action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe("Github action", () => {
image: "",
path: "tests/fixtures/npm-project",
"fail-build": "true",
"output-file": "./results.json",
"output-format": "json",
"severity-cutoff": "medium",
"add-cpes-if-none": "true",
Expand All @@ -63,6 +64,7 @@ describe("Github action", () => {
image: "",
path: "tests/fixtures/npm-project",
"fail-build": "true",
"output-file": "./results.sarif",
"output-format": "sarif",
"severity-cutoff": "medium",
"add-cpes-if-none": "true",
Expand Down
31 changes: 29 additions & 2 deletions tests/grype_command.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,53 @@ describe("Grype command args", () => {
const args = await mockRun({
source: "dir:.",
"fail-build": "false",
"output-file": "the-output-file",
"output-format": "sarif",
"severity-cutoff": "high",
version: "0.6.0",
"only-fixed": "false",
"add-cpes-if-none": "false",
"by-cve": "false",
});
expect(args).toEqual(["-o", "sarif", "--fail-on", "high", "dir:."]);
expect(args).toEqual([
"-o",
"sarif",
"--file",
"the-output-file",
"--fail-on",
"high",
"dir:.",
]);
});

it("is invoked with values", async () => {
const args = await mockRun({
image: "asdf",
"fail-build": "false",
"output-file": "the-output-file",
"output-format": "json",
"severity-cutoff": "low",
version: "0.6.0",
"only-fixed": "false",
"add-cpes-if-none": "false",
"by-cve": "false",
});
expect(args).toEqual(["-o", "json", "--fail-on", "low", "asdf"]);
expect(args).toEqual([
"-o",
"json",
"--file",
"the-output-file",
"--fail-on",
"low",
"asdf",
]);
});

it("adds missing CPEs if requested", async () => {
const args = await mockRun({
image: "asdf",
"fail-build": "false",
"output-file": "the-output-file",
"output-format": "json",
"severity-cutoff": "low",
version: "0.6.0",
Expand All @@ -49,6 +68,8 @@ describe("Grype command args", () => {
expect(args).toEqual([
"-o",
"json",
"--file",
"the-output-file",
"--fail-on",
"low",
"--add-cpes-if-none",
Expand All @@ -60,6 +81,7 @@ describe("Grype command args", () => {
const args = await mockRun({
image: "asdf",
"fail-build": "false",
"output-file": "the-output-file",
"output-format": "json",
"severity-cutoff": "low",
version: "0.6.0",
Expand All @@ -71,6 +93,8 @@ describe("Grype command args", () => {
expect(args).toEqual([
"-o",
"json",
"--file",
"the-output-file",
"--fail-on",
"low",
"--add-cpes-if-none",
Expand All @@ -84,13 +108,16 @@ describe("Grype command args", () => {
const args = await mockRun({
path: "asdf",
"fail-build": "false",
"output-file": "the-output-file",
"output-format": "table",
"severity-cutoff": "low",
"by-cve": "true",
});
expect(args).toEqual([
"-o",
"table",
"--file",
"the-output-file",
"--fail-on",
"low",
"--by-cve",
Expand Down

0 comments on commit 028cd8f

Please sign in to comment.