Skip to content

Commit c18e41a

Browse files
authored
feat: add compatibility with cyclonedx-gomod v1 (#10)
* install dependencies Signed-off-by: nscuro <[email protected]> * add compatibility with cyclonedx-gomod v1 Signed-off-by: nscuro <[email protected]> * update copyright Signed-off-by: nscuro <[email protected]> * fix string split Signed-off-by: nscuro <[email protected]> * fix addPath invocation Signed-off-by: nscuro <[email protected]> * update documentation Signed-off-by: nscuro <[email protected]> * update documentation Signed-off-by: nscuro <[email protected]> Closes #9
1 parent 2be55ef commit c18e41a

20 files changed

+548
-347
lines changed

NOTICE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
CycloneDX GitHub Action for Go Modules
2-
Copyright (c) Niklas Düster
2+
Copyright (c) OWASP Foundation
33

44
This product includes software developed by the
55
CycloneDX community (https://cyclonedx.org/).

README.md

Lines changed: 18 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,71 +8,33 @@ GitHub action to generate a CycloneDX SBOM for Go modules.
88

99
### `version`
1010

11-
**Required** The version of cyclonedx-gomod to use. Can be a version range, in which case the latest version matching the range is chosen.
11+
**Required**. The version of *cyclonedx-gomod* to use.
12+
Can be a version range, in which case the latest version matching the range is chosen.
13+
Minimum allowed version is v1.0.0. Must either be an [existing semantic version](https://github.com/CycloneDX/cyclonedx-gomod/releases)
14+
(e.g. `v1.0.0`, `1.0.0`) or a [version range](https://github.com/npm/node-semver#ranges).
1215

13-
Must either be an [existing semantic version](https://github.com/CycloneDX/cyclonedx-gomod/releases) (e.g. `v0.8.1`, `0.8.1`), [version range](https://github.com/npm/node-semver#ranges) or `latest`.
16+
### `args`
1417

15-
> ⚠ Only versions `>= v0.8.1` are supported. Specifying versions below that will cause the workflow to fail.
16-
17-
> Using `latest` is generally not recommended and will produce a warning, as it may fail your workflow
18-
> unexpectedly due to breaking changes in newer *cyclonedx-gomod* versions.
19-
> As of v0.3.0, version ranges are supported. Instead of `latest`, consider using `^v0`, `^v0.8` or similar instead.
20-
21-
### `include-stdlib`
22-
23-
Include Go standard library as component and dependency of the module. Default `false`.
24-
25-
### `include-test`
26-
27-
Include test dependencies. Default `false`.
28-
29-
### `json`
30-
31-
Output in JSON format. Default `false`.
32-
33-
### `module`
34-
35-
Path to Go module. Default `'.'`.
36-
37-
### `omit-serial-number`
38-
39-
Omit serial number. Default `false`.
40-
41-
### `omit-version-prefix`
42-
43-
Omit "v" version prefix. Default `false`.
44-
45-
### `output`
46-
47-
Output path. Default `'-'` (stdout).
48-
49-
### `reproducible`
50-
51-
Make the SBOM reproducible by omitting dynamic content. Default `false`.
52-
53-
### `resolve-licenses`
54-
55-
Resolve module licenses. Default `false`.
56-
57-
### `type`
58-
59-
Type of the main component. Default `'application'`.
18+
**Optional**. Arguments to pass to *cyclonedx-gomod*.
19+
Please refer to the [*cyclonedx-gomod* documentation](https://github.com/CycloneDX/cyclonedx-gomod#usage) for usage instructions.
20+
When not set, *cyclonedx-gomod* will only be downloaded, but not executed.
21+
It'll be made available via `$PATH` and can be used by later steps of the workflow.
6022

6123
## Example usage
6224

6325
```yaml
64-
- name: Generate SBOM JSON
26+
# Download and invoke cyclonedx-gomod in a single step
27+
- name: Generate SBOM
6528
uses: CycloneDX/[email protected]
6629
with:
67-
json: true
68-
output: bom.json
69-
resolve-licenses: true
70-
version: ^v0
30+
version: v1
31+
args: mod -licenses -json -output bom.json
7132

72-
- name: Generate SBOM XML
33+
# Just download cyclonedx-gomod and call it in a later step
34+
- name: Download cyclonedx-gomod
7335
uses: CycloneDX/[email protected]
7436
with:
75-
output: bom.xml
76-
resolve-licenses: true
77-
version: latest
37+
version: v1.0.0
38+
- name: Generate SBOM
39+
run: cyclonedx-gomod app -licenses -files -output bom.xml -main cmd/acme-app
7840
```

action.yml

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,18 @@ name: CycloneDX GoMod Generate SBOM
22
author: Niklas Düster
33
description: Github action to generate a CycloneDX SBOM for Go modules
44
inputs:
5-
include-stdlib:
6-
description: Include Go standard library as component and dependency of the module
7-
default: 'false'
8-
required: false
9-
include-test:
10-
description: Include test dependencies
11-
default: 'false'
12-
required: false
13-
json:
14-
description: Output in JSON format
15-
default: 'false'
16-
required: false
17-
module:
18-
description: Path to Go module
19-
default: '.'
20-
required: false
21-
omit-serial-number:
22-
description: Omit serial number
23-
default: 'false'
24-
required: false
25-
omit-version-prefix:
26-
description: Omit "v" version prefix
27-
default: 'false'
28-
required: false
29-
output:
30-
description: Output path
31-
default: '-'
32-
required: false
33-
reproducible:
34-
description: Make the SBOM reproducible by omitting dynamic content
35-
default: 'false'
36-
required: false
37-
resolve-licenses:
38-
description: Resolve module licenses
39-
default: 'false'
40-
required: false
41-
type:
42-
description: Type of the main component
43-
default: application
5+
args:
6+
description: |
7+
Arguments to pass to cyclonedx-gomod.
8+
Please refer to the cyclonedx-gomod documentation for usage instructions.
9+
When not set, *cyclonedx-gomod* will only be downloaded, but not executed.
10+
It'll be made available via $PATH and can be used by later steps of the workflow.
4411
required: false
4512
version:
46-
description: The version of cyclonedx-gomod to use. Can be a version range, in which case the latest version matching the range is chosen
13+
description: |
14+
The version of cyclonedx-gomod to use.
15+
Can be a version range, in which case the latest version matching the range is chosen.
16+
Minimum allowed version is v1.0.0. Must either be an existing semantic version (e.g. v1.0.0, 1.0.0) or a version range.
4717
required: true
4818
runs:
4919
using: 'node12'

index.js

Lines changed: 30 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,24 @@
1313
// limitations under the License.
1414
//
1515
// SPDX-License-Identifier: Apache-2.0
16-
// Copyright (c) Niklas Düster. All Rights Reserved.
16+
// Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818
const core = require('@actions/core');
1919
const exec = require('@actions/exec');
20-
const fs = require('fs');
2120
const http = require('@actions/http-client');
2221
const io = require('@actions/io');
2322
const os = require('os');
2423
const path = require('path');
2524
const semver = require('semver');
2625
const toolCache = require('@actions/tool-cache');
27-
const util = require('util');
2826

2927
const input = {
30-
includeStdLib: core.getBooleanInput('include-stdlib'),
31-
includeTest: core.getBooleanInput('include-test'),
32-
json: core.getBooleanInput('json'),
33-
module: core.getInput('module'),
34-
omitSerialNumber: core.getBooleanInput('omit-serial-number'),
35-
omitVersionPrefix: core.getBooleanInput('omit-version-prefix'),
36-
output: core.getInput('output') || '-',
37-
reproducible: core.getBooleanInput('reproducible'),
38-
resolveLicenses: core.getBooleanInput('resolve-licenses'),
39-
type: core.getInput('type') || 'application',
28+
args: core.getInput('args'),
4029
version: core.getInput('version'),
4130
};
4231

4332
const baseDownloadUrl = 'https://github.com/CycloneDX/cyclonedx-gomod/releases/download';
44-
const minimumSupportedVersion = 'v0.8.1';
33+
const minimumSupportedVersion = 'v1.0.0';
4534

4635
function buildDownloadUrl(version) {
4736
let fileExtension = "tar.gz";
@@ -52,28 +41,23 @@ function buildDownloadUrl(version) {
5241
fileExtension = 'zip';
5342
}
5443

55-
let architecture = os.arch()
56-
if (architecture === 'ia32' || architecture === 'x32') {
57-
architecture = 'x86';
44+
let architecture = '';
45+
switch (os.arch()) {
46+
case 'x64':
47+
architecture = 'amd64';
48+
break;
49+
case 'ia32':
50+
case 'x32':
51+
architecture = '386';
52+
break;
53+
default:
54+
architecture = os.arch();
55+
break;
5856
}
5957

6058
return `${baseDownloadUrl}/v${version}/cyclonedx-gomod_${version}_${platform}_${architecture}.${fileExtension}`;
6159
}
6260

63-
async function getLatestReleaseVersion(httpClient) {
64-
core.info('Determining latest release version of cyclonedx-gomod');
65-
const responseJson = await httpClient.getJson('https://api.github.com/repos/CycloneDX/cyclonedx-gomod/releases/latest');
66-
if (responseJson === null) { // HTTP 404
67-
throw new Error('Fetching latest release of cyclonedx-gomod failed: not found');
68-
} else if (responseJson.statusCode !== 200) {
69-
throw new Error(`Unexpected response status: ${responseJson.statusCode}`);
70-
}
71-
72-
const version = responseJson.result.tag_name;
73-
core.info(`Latest version is ${version}`);
74-
return version;
75-
}
76-
7761
async function getReleaseVersionMatchingRange(httpClient, range) {
7862
core.info(`Determining latest release version of cyclonedx-gomod satisfying "${range}"`);
7963
const responseJson = await httpClient.getJson('https://api.github.com/repos/CycloneDX/cyclonedx-gomod/releases');
@@ -98,11 +82,14 @@ async function install(version) {
9882
core.info('Extracting archive');
9983
let installDir = "";
10084
if (downloadUrl.endsWith('.zip')) {
101-
installDir = await toolCache.extractZip(archivePath, process.env.HOME);
85+
installDir = await toolCache.extractZip(archivePath);
10286
} else {
103-
installDir = await toolCache.extractTar(archivePath, process.env.HOME);
87+
installDir = await toolCache.extractTar(archivePath);
10488
}
10589

90+
core.info(`Adding ${installDir} to \$PATH`)
91+
core.addPath(installDir);
92+
10693
return path.join(installDir, 'cyclonedx-gomod');
10794
}
10895

@@ -114,56 +101,20 @@ async function run() {
114101
await io.which('go', true);
115102

116103
let versionToInstall = input.version;
117-
if (versionToInstall.toLowerCase() === 'latest') {
118-
core.warning('Using version "latest" is not recommended, please use version ranges instead!');
119-
versionToInstall = await getLatestReleaseVersion(httpClient);
120-
} else {
121-
if (!semver.validRange(versionToInstall)) {
122-
throw new Error('version must be a valid version range, see https://github.com/npm/node-semver#advanced-range-syntax')
123-
}
124-
125-
versionToInstall = await getReleaseVersionMatchingRange(httpClient, versionToInstall);
126-
127-
if (semver.lt(versionToInstall, minimumSupportedVersion)) {
128-
throw new Error(`cyclonedx-gomod versions below ${minimumSupportedVersion} are not supported`);
129-
}
104+
if (!semver.validRange(versionToInstall)) {
105+
throw new Error('version must be a valid version range, see https://github.com/npm/node-semver#advanced-range-syntax')
130106
}
131-
132-
const binaryPath = await install(versionToInstall.replace(/^v/, ''));
133-
134-
// Assemble cyclonedx-gomod arguments
135-
let args = ['-output', input.output, '-type', input.type];
136-
if (input.includeStdLib) {
137-
args.push('-std');
138-
}
139-
if (input.includeTest) {
140-
args.push('-test');
141-
}
142-
if (input.json) {
143-
args.push('-json');
144-
}
145-
if (input.module !== '') {
146-
args.push('-module', input.module);
147-
}
148-
if (input.omitSerialNumber) {
149-
args.push('-noserial');
150-
}
151-
if (input.omitVersionPrefix) {
152-
args.push('-novprefix');
153-
}
154-
if (input.reproducible) {
155-
args.push('-reproducible');
156-
}
157-
if (input.resolveLicenses) {
158-
args.push('-licenses');
107+
versionToInstall = await getReleaseVersionMatchingRange(httpClient, versionToInstall);
108+
if (semver.lt(versionToInstall, minimumSupportedVersion)) {
109+
throw new Error(`cyclonedx-gomod versions below ${minimumSupportedVersion} are not supported`);
159110
}
160111

161-
await exec.exec(binaryPath, args);
112+
const binaryPath = await install(versionToInstall.replace(/^v/, ''));
162113

163-
if (input.output !== '-') {
164-
const readFile = util.promisify(fs.readFile);
165-
const sbomContent = await readFile(input.output);
166-
core.info(`SBOM content:\n${sbomContent.toString('utf-8')}`);
114+
if (input.args != '') {
115+
await exec.exec(binaryPath, input.args.split(/\s+/));
116+
} else {
117+
core.info('no arguments configured, will not execute cyclonedx-gomod')
167118
}
168119
} catch (error) {
169120
core.setFailed(error.message);

0 commit comments

Comments
 (0)