Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add output-file option, default to random directory output in temp #346

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading