Skip to content

Commit 637a129

Browse files
authored
Add sub-action to download Grype (#152)
1 parent 4a24b5a commit 637a129

File tree

8 files changed

+229
-161
lines changed

8 files changed

+229
-161
lines changed

GrypeVersion.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.GRYPE_VERSION = "v0.34.4";

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,33 @@ You may add a `.grype.yaml` file at your repository root
174174
for more [Grype configuration](https://github.com/anchore/grype#configuration)
175175
such as [ignoring certain matches](https://github.com/anchore/grype#specifying-matches-to-ignore).
176176

177+
## anchore/scan-action/download-grype
178+
179+
A sub-action to [download Grype](download-grype/action.yml).
180+
181+
Input parameters:
182+
183+
| Parameter | Description | Default |
184+
| --------------- | ------------------------------------------------------------------------------------------------------------ | ------- |
185+
| `grype-version` | An optional Grype version to download, defaults to the pinned version in [GrypeVersion.js](GrypeVersion.js). | |
186+
187+
Output parameters:
188+
189+
| Parameter | Description |
190+
| --------- | -------------------------------------------------------------------- |
191+
| `cmd` | a reference to the [Grype](https://github.com/anchore/grype) binary. |
192+
193+
`cmd` can be referenced in a workflow like other output parameters:
194+
`${{ steps.<step-id>.outputs.cmd }}`
195+
196+
Example usage:
197+
198+
```yaml
199+
- uses: anchore/scan-action/download-grype@v3
200+
id: grype
201+
- run: ${{steps.grype.outputs.cmd}} dir:.
202+
```
203+
177204
## Contributing
178205

179206
We love contributions, feedback, and bug reports. For issues with the invocation of this action, file [issues](https://github.com/anchore/scan-action/issues) in this repository.

dist/index.js

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ module.exports =
22
/******/ (() => { // webpackBootstrap
33
/******/ var __webpack_modules__ = ({
44

5+
/***/ 244:
6+
/***/ ((__unused_webpack_module, exports) => {
7+
8+
exports.GRYPE_VERSION = "v0.34.4";
9+
10+
11+
/***/ }),
12+
513
/***/ 932:
614
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
715

@@ -10,42 +18,10 @@ const core = __webpack_require__(186);
1018
const { exec } = __webpack_require__(514);
1119
const fs = __webpack_require__(747);
1220
const stream = __webpack_require__(413);
21+
const { GRYPE_VERSION } = __webpack_require__(244);
1322

1423
const grypeBinary = "grype";
15-
const grypeVersion = "0.34.4";
16-
17-
// Find all 'content-*.json' files in the directory. dirname should include the full path
18-
function findContent(searchDir) {
19-
let contentFiles = [];
20-
let match = /content-.*\.json/;
21-
var dirItems = fs.readdirSync(searchDir);
22-
if (dirItems) {
23-
for (let i = 0; i < dirItems.length; i++) {
24-
if (match.test(dirItems[i])) {
25-
contentFiles.push(`${searchDir}/${dirItems[i]}`);
26-
}
27-
}
28-
} else {
29-
core.debug("no dir content found");
30-
}
31-
32-
core.debug(contentFiles.toString());
33-
return contentFiles;
34-
}
35-
36-
// Load the json content of each file in a list and return them as a list
37-
function loadContent(files) {
38-
let contents = [];
39-
if (files) {
40-
files.forEach((item) => contents.push(JSON.parse(fs.readFileSync(item))));
41-
}
42-
return contents;
43-
}
44-
45-
// Merge the multiple content output types into a single array
46-
function mergeResults(contentArray) {
47-
return contentArray.reduce((merged, n) => merged.concat(n.content), []);
48-
}
24+
const grypeVersion = core.getInput("grype-version") || GRYPE_VERSION;
4925

5026
async function downloadGrype(version) {
5127
let url = `https://raw.githubusercontent.com/anchore/grype/main/install.sh`;
@@ -58,7 +34,7 @@ async function downloadGrype(version) {
5834
// Make sure the tool's executable bit is set
5935
await exec(`chmod +x ${installPath}`);
6036

61-
let cmd = `${installPath} -b ${installPath}_grype v${version}`;
37+
let cmd = `${installPath} -b ${installPath}_grype ${version}`;
6238
await exec(cmd);
6339
let grypePath = `${installPath}_grype/grype`;
6440

@@ -75,6 +51,7 @@ async function installGrype(version) {
7551

7652
// Add tool to path for this and future actions to use
7753
core.addPath(grypePath);
54+
return `${grypePath}/${grypeBinary}`;
7855
}
7956

8057
function sourceInput() {
@@ -255,15 +232,22 @@ module.exports = {
255232
run,
256233
runScan,
257234
installGrype,
258-
mergeResults,
259-
findContent,
260-
loadContent,
261235
};
262236

263237
if (require.main === require.cache[eval('__filename')]) {
264-
run().catch((err) => {
265-
throw new Error(err);
266-
});
238+
const entrypoint = core.getInput("run");
239+
switch (entrypoint) {
240+
case "download-grype": {
241+
installGrype(grypeVersion).then((path) => {
242+
core.info(`Downloaded Grype to: ${path}`);
243+
core.setOutput("cmd", path);
244+
});
245+
break;
246+
}
247+
default: {
248+
run().then();
249+
}
250+
}
267251
}
268252

269253

download-grype/action.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: "Download Grype"
2+
author: "Anchore"
3+
description: "Downloads the Grype binary and provides a path to execute it"
4+
branding:
5+
color: blue
6+
icon: check-circle
7+
inputs:
8+
grype-version:
9+
description: "A specific version of Grype to install"
10+
required: false
11+
run:
12+
description: "Flag to indicate which sub-action to run"
13+
required: false
14+
default: "download-grype"
15+
outputs:
16+
cmd:
17+
description: "An absolute path to the Grype executable"
18+
runs:
19+
using: "node12"
20+
main: "../dist/index.js"

index.js

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,10 @@ const core = require("@actions/core");
33
const { exec } = require("@actions/exec");
44
const fs = require("fs");
55
const stream = require("stream");
6+
const { GRYPE_VERSION } = require("./GrypeVersion");
67

78
const grypeBinary = "grype";
8-
const grypeVersion = "0.34.4";
9-
10-
// Find all 'content-*.json' files in the directory. dirname should include the full path
11-
function findContent(searchDir) {
12-
let contentFiles = [];
13-
let match = /content-.*\.json/;
14-
var dirItems = fs.readdirSync(searchDir);
15-
if (dirItems) {
16-
for (let i = 0; i < dirItems.length; i++) {
17-
if (match.test(dirItems[i])) {
18-
contentFiles.push(`${searchDir}/${dirItems[i]}`);
19-
}
20-
}
21-
} else {
22-
core.debug("no dir content found");
23-
}
24-
25-
core.debug(contentFiles.toString());
26-
return contentFiles;
27-
}
28-
29-
// Load the json content of each file in a list and return them as a list
30-
function loadContent(files) {
31-
let contents = [];
32-
if (files) {
33-
files.forEach((item) => contents.push(JSON.parse(fs.readFileSync(item))));
34-
}
35-
return contents;
36-
}
37-
38-
// Merge the multiple content output types into a single array
39-
function mergeResults(contentArray) {
40-
return contentArray.reduce((merged, n) => merged.concat(n.content), []);
41-
}
9+
const grypeVersion = core.getInput("grype-version") || GRYPE_VERSION;
4210

4311
async function downloadGrype(version) {
4412
let url = `https://raw.githubusercontent.com/anchore/grype/main/install.sh`;
@@ -51,7 +19,7 @@ async function downloadGrype(version) {
5119
// Make sure the tool's executable bit is set
5220
await exec(`chmod +x ${installPath}`);
5321

54-
let cmd = `${installPath} -b ${installPath}_grype v${version}`;
22+
let cmd = `${installPath} -b ${installPath}_grype ${version}`;
5523
await exec(cmd);
5624
let grypePath = `${installPath}_grype/grype`;
5725

@@ -68,6 +36,7 @@ async function installGrype(version) {
6836

6937
// Add tool to path for this and future actions to use
7038
core.addPath(grypePath);
39+
return `${grypePath}/${grypeBinary}`;
7140
}
7241

7342
function sourceInput() {
@@ -248,13 +217,20 @@ module.exports = {
248217
run,
249218
runScan,
250219
installGrype,
251-
mergeResults,
252-
findContent,
253-
loadContent,
254220
};
255221

256222
if (require.main === module) {
257-
run().catch((err) => {
258-
throw new Error(err);
259-
});
223+
const entrypoint = core.getInput("run");
224+
switch (entrypoint) {
225+
case "download-grype": {
226+
installGrype(grypeVersion).then((path) => {
227+
core.info(`Downloaded Grype to: ${path}`);
228+
core.setOutput("cmd", path);
229+
});
230+
break;
231+
}
232+
default: {
233+
run().then();
234+
}
235+
}
260236
}

jest.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
module.exports = {
22
setupFiles: ["<rootDir>/.jest/setEnvVars.js"],
33
verbose: true,
4-
testPathIgnorePatterns: ["action.test.js"],
54
};

tests/action.test.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,39 @@
1-
const child_process = require('child_process');
2-
const path = require('path');
3-
const process = require('process');
1+
const child_process = require("child_process");
2+
const os = require("os");
3+
const path = require("path");
4+
const process = require("process");
45

5-
// shows how the runner will run a javascript action with env / stdout protocol
6-
test('test runs', () => {
7-
process.env['INPUT_DEBUG'] = 'true';
8-
process.env['INPUT_IMAGE-REFERENCE'] = 'docker.io/alpine:latest';
9-
const index_path = path.join(__dirname, '../index.js');
10-
console.log(child_process.execSync(`node ${index_path}`, {env: process.env}).toString());
11-
});
6+
const actionPath = path.join(__dirname, "../index.js");
7+
8+
// Execute the action, and return any outputs
9+
function runAction(inputs) {
10+
// reverse core.js: const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
11+
for (const k in inputs) {
12+
process.env[`INPUT_${k}`.toUpperCase()] = inputs[k];
13+
}
14+
// capture stdout
15+
const stdout = child_process
16+
.execSync(`node ${actionPath}`, {
17+
env: process.env,
18+
})
19+
.toString("utf8");
20+
const outputs = {};
21+
// reverse setOutput command calls like:
22+
// ::set-output name=cmd::/tmp/actions/cache/grype/0.34.4/x64/grype
23+
for (const line of stdout.split(os.EOL)) {
24+
const groups = line.match(/::set-output name=(\w+)::(.*)$/);
25+
if (groups && groups.length > 2) {
26+
outputs[groups[1]] = groups[2];
27+
}
28+
}
29+
return outputs;
30+
}
31+
32+
describe("sbom-action", () => {
33+
it("runs download-grype", () => {
34+
const outputs = runAction({
35+
run: "download-grype",
36+
});
37+
expect(outputs.cmd).toBeDefined();
38+
});
39+
});

0 commit comments

Comments
 (0)