Skip to content

Commit

Permalink
feat: add compatibility with cyclonedx-gomod v1 (#10)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nscuro authored Oct 1, 2021
1 parent 2be55ef commit c18e41a
Show file tree
Hide file tree
Showing 20 changed files with 548 additions and 347 deletions.
2 changes: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CycloneDX GitHub Action for Go Modules
Copyright (c) Niklas Düster
Copyright (c) OWASP Foundation

This product includes software developed by the
CycloneDX community (https://cyclonedx.org/).
74 changes: 18 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,71 +8,33 @@ GitHub action to generate a CycloneDX SBOM for Go modules.

### `version`

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

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`.
### `args`

> ⚠ Only versions `>= v0.8.1` are supported. Specifying versions below that will cause the workflow to fail.
> Using `latest` is generally not recommended and will produce a warning, as it may fail your workflow
> unexpectedly due to breaking changes in newer *cyclonedx-gomod* versions.
> As of v0.3.0, version ranges are supported. Instead of `latest`, consider using `^v0`, `^v0.8` or similar instead.
### `include-stdlib`

Include Go standard library as component and dependency of the module. Default `false`.

### `include-test`

Include test dependencies. Default `false`.

### `json`

Output in JSON format. Default `false`.

### `module`

Path to Go module. Default `'.'`.

### `omit-serial-number`

Omit serial number. Default `false`.

### `omit-version-prefix`

Omit "v" version prefix. Default `false`.

### `output`

Output path. Default `'-'` (stdout).

### `reproducible`

Make the SBOM reproducible by omitting dynamic content. Default `false`.

### `resolve-licenses`

Resolve module licenses. Default `false`.

### `type`

Type of the main component. Default `'application'`.
**Optional**. Arguments to pass to *cyclonedx-gomod*.
Please refer to the [*cyclonedx-gomod* documentation](https://github.com/CycloneDX/cyclonedx-gomod#usage) for usage instructions.
When not set, *cyclonedx-gomod* will only be downloaded, but not executed.
It'll be made available via `$PATH` and can be used by later steps of the workflow.

## Example usage

```yaml
- name: Generate SBOM JSON
# Download and invoke cyclonedx-gomod in a single step
- name: Generate SBOM
uses: CycloneDX/[email protected]
with:
json: true
output: bom.json
resolve-licenses: true
version: ^v0
version: v1
args: mod -licenses -json -output bom.json

- name: Generate SBOM XML
# Just download cyclonedx-gomod and call it in a later step
- name: Download cyclonedx-gomod
uses: CycloneDX/[email protected]
with:
output: bom.xml
resolve-licenses: true
version: latest
version: v1.0.0
- name: Generate SBOM
run: cyclonedx-gomod app -licenses -files -output bom.xml -main cmd/acme-app
```
50 changes: 10 additions & 40 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,18 @@ name: CycloneDX GoMod Generate SBOM
author: Niklas Düster
description: Github action to generate a CycloneDX SBOM for Go modules
inputs:
include-stdlib:
description: Include Go standard library as component and dependency of the module
default: 'false'
required: false
include-test:
description: Include test dependencies
default: 'false'
required: false
json:
description: Output in JSON format
default: 'false'
required: false
module:
description: Path to Go module
default: '.'
required: false
omit-serial-number:
description: Omit serial number
default: 'false'
required: false
omit-version-prefix:
description: Omit "v" version prefix
default: 'false'
required: false
output:
description: Output path
default: '-'
required: false
reproducible:
description: Make the SBOM reproducible by omitting dynamic content
default: 'false'
required: false
resolve-licenses:
description: Resolve module licenses
default: 'false'
required: false
type:
description: Type of the main component
default: application
args:
description: |
Arguments to pass to cyclonedx-gomod.
Please refer to the cyclonedx-gomod documentation for usage instructions.
When not set, *cyclonedx-gomod* will only be downloaded, but not executed.
It'll be made available via $PATH and can be used by later steps of the workflow.
required: false
version:
description: The version of cyclonedx-gomod to use. Can be a version range, in which case the latest version matching the range is chosen
description: |
The version of cyclonedx-gomod to use.
Can be a version range, in which case the latest version matching the range is chosen.
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.
required: true
runs:
using: 'node12'
Expand Down
109 changes: 30 additions & 79 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,24 @@
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) Niklas Düster. All Rights Reserved.
// Copyright (c) OWASP Foundation. All Rights Reserved.

const core = require('@actions/core');
const exec = require('@actions/exec');
const fs = require('fs');
const http = require('@actions/http-client');
const io = require('@actions/io');
const os = require('os');
const path = require('path');
const semver = require('semver');
const toolCache = require('@actions/tool-cache');
const util = require('util');

const input = {
includeStdLib: core.getBooleanInput('include-stdlib'),
includeTest: core.getBooleanInput('include-test'),
json: core.getBooleanInput('json'),
module: core.getInput('module'),
omitSerialNumber: core.getBooleanInput('omit-serial-number'),
omitVersionPrefix: core.getBooleanInput('omit-version-prefix'),
output: core.getInput('output') || '-',
reproducible: core.getBooleanInput('reproducible'),
resolveLicenses: core.getBooleanInput('resolve-licenses'),
type: core.getInput('type') || 'application',
args: core.getInput('args'),
version: core.getInput('version'),
};

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

function buildDownloadUrl(version) {
let fileExtension = "tar.gz";
Expand All @@ -52,28 +41,23 @@ function buildDownloadUrl(version) {
fileExtension = 'zip';
}

let architecture = os.arch()
if (architecture === 'ia32' || architecture === 'x32') {
architecture = 'x86';
let architecture = '';
switch (os.arch()) {
case 'x64':
architecture = 'amd64';
break;
case 'ia32':
case 'x32':
architecture = '386';
break;
default:
architecture = os.arch();
break;
}

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

async function getLatestReleaseVersion(httpClient) {
core.info('Determining latest release version of cyclonedx-gomod');
const responseJson = await httpClient.getJson('https://api.github.com/repos/CycloneDX/cyclonedx-gomod/releases/latest');
if (responseJson === null) { // HTTP 404
throw new Error('Fetching latest release of cyclonedx-gomod failed: not found');
} else if (responseJson.statusCode !== 200) {
throw new Error(`Unexpected response status: ${responseJson.statusCode}`);
}

const version = responseJson.result.tag_name;
core.info(`Latest version is ${version}`);
return version;
}

async function getReleaseVersionMatchingRange(httpClient, range) {
core.info(`Determining latest release version of cyclonedx-gomod satisfying "${range}"`);
const responseJson = await httpClient.getJson('https://api.github.com/repos/CycloneDX/cyclonedx-gomod/releases');
Expand All @@ -98,11 +82,14 @@ async function install(version) {
core.info('Extracting archive');
let installDir = "";
if (downloadUrl.endsWith('.zip')) {
installDir = await toolCache.extractZip(archivePath, process.env.HOME);
installDir = await toolCache.extractZip(archivePath);
} else {
installDir = await toolCache.extractTar(archivePath, process.env.HOME);
installDir = await toolCache.extractTar(archivePath);
}

core.info(`Adding ${installDir} to \$PATH`)
core.addPath(installDir);

return path.join(installDir, 'cyclonedx-gomod');
}

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

let versionToInstall = input.version;
if (versionToInstall.toLowerCase() === 'latest') {
core.warning('Using version "latest" is not recommended, please use version ranges instead!');
versionToInstall = await getLatestReleaseVersion(httpClient);
} else {
if (!semver.validRange(versionToInstall)) {
throw new Error('version must be a valid version range, see https://github.com/npm/node-semver#advanced-range-syntax')
}

versionToInstall = await getReleaseVersionMatchingRange(httpClient, versionToInstall);

if (semver.lt(versionToInstall, minimumSupportedVersion)) {
throw new Error(`cyclonedx-gomod versions below ${minimumSupportedVersion} are not supported`);
}
if (!semver.validRange(versionToInstall)) {
throw new Error('version must be a valid version range, see https://github.com/npm/node-semver#advanced-range-syntax')
}

const binaryPath = await install(versionToInstall.replace(/^v/, ''));

// Assemble cyclonedx-gomod arguments
let args = ['-output', input.output, '-type', input.type];
if (input.includeStdLib) {
args.push('-std');
}
if (input.includeTest) {
args.push('-test');
}
if (input.json) {
args.push('-json');
}
if (input.module !== '') {
args.push('-module', input.module);
}
if (input.omitSerialNumber) {
args.push('-noserial');
}
if (input.omitVersionPrefix) {
args.push('-novprefix');
}
if (input.reproducible) {
args.push('-reproducible');
}
if (input.resolveLicenses) {
args.push('-licenses');
versionToInstall = await getReleaseVersionMatchingRange(httpClient, versionToInstall);
if (semver.lt(versionToInstall, minimumSupportedVersion)) {
throw new Error(`cyclonedx-gomod versions below ${minimumSupportedVersion} are not supported`);
}

await exec.exec(binaryPath, args);
const binaryPath = await install(versionToInstall.replace(/^v/, ''));

if (input.output !== '-') {
const readFile = util.promisify(fs.readFile);
const sbomContent = await readFile(input.output);
core.info(`SBOM content:\n${sbomContent.toString('utf-8')}`);
if (input.args != '') {
await exec.exec(binaryPath, input.args.split(/\s+/));
} else {
core.info('no arguments configured, will not execute cyclonedx-gomod')
}
} catch (error) {
core.setFailed(error.message);
Expand Down
Loading

0 comments on commit c18e41a

Please sign in to comment.