diff --git a/CHANGELOG.md b/CHANGELOG.md index a40362d..6a7b99e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0] - 2023-09-11 + +### Added + +- Make `detect-version` optional by downloading latest +- Support major version pattern on `detect-version` + ## [1.0.0] - 2023-09-10 ### Changed @@ -29,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve logging - Update dependencies and refactor action -[Unreleased]: https://github.com/mercedesbenzio/detect-action/compare/v1.0.0...main +[Unreleased]: https://github.com/mercedesbenzio/detect-action/compare/v1.1.0...main +[1.1.0]: https://github.com/mercedesbenzio/detect-action/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/mercedesbenzio/detect-action/compare/v0.4.0...v1.0.0 [0.4.0]: https://github.com/mercedesbenzio/detect-action/releases/tag/v0.4.0 diff --git a/README.md b/README.md index 6e1d558..7d190b5 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ to run, provide: - Black Duck URL (`blackduck-url`) - Black Duck API Token (`blackduck-api-token`) -- Your desired Detect Version (`detect-version`) to execute +- Your desired Detect Version (`detect-version`), or empty if you want to scan using the latest version - Your _GITHUB\_TOKEN_ (`github-token`) to comment on Pull Requests or hook into GitHub Checks (in most cases, this is `${{ secrets.GITHUB_TOKEN }}`) @@ -366,7 +366,7 @@ After running detect this action will set the following output variables with de - `detect-exit-code-name` - The corresponding human name of the error code. Note that if Detect is not called these variables are not populated. Also, if a mapping for the exit code number is not -found on our side `detect-exit-code-name` we will set it to `UNKOWN`. +found on our side `detect-exit-code-name` we will set it to `UNKNOWN`. ### Include Custom Certificates (Optional) diff --git a/action.yml b/action.yml index 8c81e02..f1fd323 100644 --- a/action.yml +++ b/action.yml @@ -8,8 +8,13 @@ inputs: description: 'Your GitHub token' required: true detect-version: - description: 'The version of Detect to download' - required: true + description: |- + Detect version to be used on Black Duck scan. + The following input patterns are allowed: + - A full version (e.g. 8.4.0) to scan using that exact version. + - A major version (e.g. 8) to scan using the latest version of the given major. + - Empty or not specified, to scan using the latest version of Detect. + required: false blackduck-url: description: 'Url of Black Duck instance' required: true @@ -17,17 +22,17 @@ inputs: description: 'API Token for Black Duck instance' required: true scan-mode: - description: | - Either RAPID or INTELLIGENT, configures how Detect is invoked. - RAPID will not persist the results and disables select Detect functionality for faster results. - INTELLIGENT persists the results and permits all features of Detect. + description: |- + Configures how Detect is invoked. When specified use one of the following: + - RAPID: will not persist the results and disables selected Detect functionality for faster results. + - INTELLIGENT: persists the results and allows all features of Detect. required: false default: 'RAPID' output-path-override: description: 'Override for where to output Detect files, default is $RUNNER_TEMP/blackduck/' required: false detect-trust-cert: - description: | + description: |- When set to true Detect will trust the Black Duck certificate even if the certificate is not in the keystore. default: 'true' diff --git a/dist/index.js b/dist/index.js index c506951..c03216a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -25234,7 +25234,7 @@ class ActionOrchestrator { core.info(`${inputs_1.Input.DETECT_VERSION}: ${this.inputs.detectVersion}.`); core.info(`${inputs_1.Input.OUTPUT_PATH_OVERRIDE}: ${this.inputs.outputPathOverride}.`); core.info(`${inputs_1.Input.SCAN_MODE}: ${this.inputs.scanMode}.`); - const detectPath = await new detect_tool_downloader_1.DetectToolDownloader(this.inputs.detectVersion).download(); + const detectPath = await detect_tool_downloader_1.DetectToolDownloader.getInstance().download(this.inputs.detectVersion); const detectFacade = new detect_facade_1.DetectFacade(constants_1.APPLICATION_NAME, this.inputs, detectPath, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.gitHubCheck, octokit, extended_context_1.extendedContext); @@ -25680,26 +25680,65 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.DetectToolDownloader = exports.TOOL_NAME = void 0; const toolCache = __importStar(__nccwpck_require__(7784)); const path_1 = __importDefault(__nccwpck_require__(1017)); +const HttpClient_1 = __nccwpck_require__(5538); +const constants_1 = __nccwpck_require__(2706); const DETECT_BINARY_REPO_URL = 'https://sig-repo.synopsys.com'; exports.TOOL_NAME = 'detect'; class DetectToolDownloader { - version; - constructor(version) { - this.version = version; + async getDetectVersions() { + const authenticationClient = new HttpClient_1.HttpClient(constants_1.APPLICATION_NAME); + const headers = { + 'X-Result-Detail': 'info' + }; + const httpClientResponse = await authenticationClient.get(`${DETECT_BINARY_REPO_URL}/api/storage/bds-integrations-release/com/synopsys/integration/synopsys-detect?properties`, headers); + const responseBody = await httpClientResponse.readBody(); + return JSON.parse(responseBody); } - getDetectDownloadUrl() { - return `${DETECT_BINARY_REPO_URL}/bds-integrations-release/com/synopsys/integration/synopsys-detect/${this.version}/synopsys-detect-${this.version}.jar`; + async findDetectVersion(version) { + if (version?.match(/^[0-9]+.[0-9]+.[0-9]+$/)) { + return { + url: `${DETECT_BINARY_REPO_URL}/bds-integrations-release/com/synopsys/integration/synopsys-detect/${version}/synopsys-detect-${version}.jar`, + version, + jarName: `synopsys-detect-${version}.jar` + }; + } + let detectVersionKey = 'DETECT_LATEST_'; + if (version?.match(/^[0-9]+/)) { + detectVersionKey = `DETECT_LATEST_${version}`; + } + else if (version) { + throw new Error(`Invalid input version '${version}'`); + } + const detectVersions = await this.getDetectVersions(); + const keys = Object.keys(detectVersions.properties); + const key = keys.filter(x => x.match(detectVersionKey)).at(-1); + if (!key) { + throw new Error(`Cannot find matching key ${detectVersionKey} on detect versions!`); + } + const url = detectVersions.properties[key].at(-1); + if (!url) { + throw new Error(`Cannot find url for property ${key} on detect versions!`); + } + const jarName = url.substring(url.lastIndexOf('/') + 1); + const resultVersion = jarName.substring(jarName.lastIndexOf('-') + 1, jarName.length - 4); + return { url, version: resultVersion, jarName }; } - async download() { - const jarName = `synopsys-detect-${this.version}.jar`; - const cachedDetect = toolCache.find(exports.TOOL_NAME, this.version); + async download(version) { + const detectVersion = await this.findDetectVersion(version); + const cachedDetect = toolCache.find(exports.TOOL_NAME, detectVersion.version); if (cachedDetect) { - return path_1.default.resolve(cachedDetect, jarName); + return path_1.default.resolve(cachedDetect, detectVersion.jarName); + } + const detectDownloadPath = await toolCache.downloadTool(detectVersion.url); + const cachedFolder = await toolCache.cacheFile(detectDownloadPath, detectVersion.jarName, exports.TOOL_NAME, detectVersion.version); + return path_1.default.resolve(cachedFolder, detectVersion.jarName); + } + static instance; + static getInstance() { + if (!DetectToolDownloader.instance) { + DetectToolDownloader.instance = new DetectToolDownloader(); } - const detectDownloadUrl = this.getDetectDownloadUrl(); - const detectDownloadPath = await toolCache.downloadTool(detectDownloadUrl); - const cachedFolder = await toolCache.cacheFile(detectDownloadPath, jarName, exports.TOOL_NAME, this.version); - return path_1.default.resolve(cachedFolder, jarName); + return DetectToolDownloader.instance; } } exports.DetectToolDownloader = DetectToolDownloader; @@ -26207,7 +26246,7 @@ function getInputBlackDuckApiToken() { return core.getInput(Input.BLACKDUCK_API_TOKEN, { required: true }); } function getInputDetectVersion() { - return core.getInput(Input.DETECT_VERSION, { required: true }); + return core.getInput(Input.DETECT_VERSION) ?? undefined; } function getInputScanMode() { return core.getInput(Input.SCAN_MODE).toUpperCase(); diff --git a/package-lock.json b/package-lock.json index 51cf961..fd95838 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "detect-action", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "detect-action", - "version": "1.0.0", + "version": "1.1.0", "license": "Apache 2.0", "dependencies": { "@actions/artifact": "^1.1.2", diff --git a/package.json b/package.json index e245827..d1d9d33 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "detect-action", "description": "Richly integrate Synopsys Detect and Black Duck policy into your GitHub Action pipelines", - "version": "1.0.0", + "version": "1.1.0", "author": "Mercedes-Benz.io", "private": true, "homepage": "https://github.com/mercedesbenzio/detect-action", diff --git a/src/action/action-orchestrator.ts b/src/action/action-orchestrator.ts index 3ed1282..ef451fc 100644 --- a/src/action/action-orchestrator.ts +++ b/src/action/action-orchestrator.ts @@ -39,9 +39,9 @@ export class ActionOrchestrator { ) core.info(`${Input.SCAN_MODE}: ${this.inputs.scanMode}.`) - const detectPath = await new DetectToolDownloader( + const detectPath = await DetectToolDownloader.getInstance().download( this.inputs.detectVersion - ).download() + ) const detectFacade = new DetectFacade( APPLICATION_NAME, diff --git a/src/detect/detect-tool-downloader.ts b/src/detect/detect-tool-downloader.ts index 2f7f36b..690aac8 100644 --- a/src/detect/detect-tool-downloader.ts +++ b/src/detect/detect-tool-downloader.ts @@ -1,39 +1,96 @@ import { ToolDownloader } from '../downloader/tool-downloader' import * as toolCache from '@actions/tool-cache' import path from 'path' +import { HttpClient } from 'typed-rest-client/HttpClient' +import { APPLICATION_NAME } from '../action/constants' +import { IHeaders } from 'typed-rest-client/Interfaces' +import { DetectToolsVersions } from './detect-tools-versions' +import { DetectToolVersion } from './detect-tool-version' const DETECT_BINARY_REPO_URL = 'https://sig-repo.synopsys.com' export const TOOL_NAME = 'detect' export class DetectToolDownloader implements ToolDownloader { - private readonly version: string + private async getDetectVersions(): Promise { + const authenticationClient = new HttpClient(APPLICATION_NAME) + const headers: IHeaders = { + 'X-Result-Detail': 'info' + } - constructor(version: string) { - this.version = version + const httpClientResponse = await authenticationClient.get( + `${DETECT_BINARY_REPO_URL}/api/storage/bds-integrations-release/com/synopsys/integration/synopsys-detect?properties`, + headers + ) + const responseBody = await httpClientResponse.readBody() + return JSON.parse(responseBody) as DetectToolsVersions } - private getDetectDownloadUrl(): string { - return `${DETECT_BINARY_REPO_URL}/bds-integrations-release/com/synopsys/integration/synopsys-detect/${this.version}/synopsys-detect-${this.version}.jar` + private async findDetectVersion( + version?: string + ): Promise { + if (version?.match(/^[0-9]+.[0-9]+.[0-9]+$/)) { + return { + url: `${DETECT_BINARY_REPO_URL}/bds-integrations-release/com/synopsys/integration/synopsys-detect/${version}/synopsys-detect-${version}.jar`, + version, + jarName: `synopsys-detect-${version}.jar` + } + } + + let detectVersionKey = 'DETECT_LATEST_' + + if (version?.match(/^[0-9]+/)) { + detectVersionKey = `DETECT_LATEST_${version}` + } else if (version) { + throw new Error(`Invalid input version '${version}'`) + } + + const detectVersions = await this.getDetectVersions() + const keys = Object.keys(detectVersions.properties) + const key = keys.filter(x => x.match(detectVersionKey)).at(-1) + if (!key) { + throw new Error( + `Cannot find matching key ${detectVersionKey} on detect versions!` + ) + } + const url = detectVersions.properties[key].at(-1) + if (!url) { + throw new Error(`Cannot find url for property ${key} on detect versions!`) + } + + const jarName = url.substring(url.lastIndexOf('/') + 1) + const resultVersion = jarName.substring( + jarName.lastIndexOf('-') + 1, + jarName.length - 4 + ) + + return { url, version: resultVersion, jarName } } - async download(): Promise { - const jarName = `synopsys-detect-${this.version}.jar` + async download(version?: string): Promise { + const detectVersion = await this.findDetectVersion(version) - const cachedDetect = toolCache.find(TOOL_NAME, this.version) + const cachedDetect = toolCache.find(TOOL_NAME, detectVersion.version) if (cachedDetect) { - return path.resolve(cachedDetect, jarName) + return path.resolve(cachedDetect, detectVersion.jarName) } - const detectDownloadUrl = this.getDetectDownloadUrl() - - const detectDownloadPath = await toolCache.downloadTool(detectDownloadUrl) + const detectDownloadPath = await toolCache.downloadTool(detectVersion.url) const cachedFolder = await toolCache.cacheFile( detectDownloadPath, - jarName, + detectVersion.jarName, TOOL_NAME, - this.version + detectVersion.version ) - return path.resolve(cachedFolder, jarName) + return path.resolve(cachedFolder, detectVersion.jarName) + } + + private static instance: DetectToolDownloader | null + + static getInstance(): DetectToolDownloader { + if (!DetectToolDownloader.instance) { + DetectToolDownloader.instance = new DetectToolDownloader() + } + return DetectToolDownloader.instance } } diff --git a/src/detect/detect-tool-version.ts b/src/detect/detect-tool-version.ts new file mode 100644 index 0000000..4f5ff29 --- /dev/null +++ b/src/detect/detect-tool-version.ts @@ -0,0 +1,5 @@ +export interface DetectToolVersion { + url: string + version: string + jarName: string +} diff --git a/src/detect/detect-tools-versions.ts b/src/detect/detect-tools-versions.ts new file mode 100644 index 0000000..d4e0c88 --- /dev/null +++ b/src/detect/detect-tools-versions.ts @@ -0,0 +1,3 @@ +export interface DetectToolsVersions { + properties: Record +} diff --git a/src/input/inputs.ts b/src/input/inputs.ts index dd1ed75..e4c3e45 100644 --- a/src/input/inputs.ts +++ b/src/input/inputs.ts @@ -4,7 +4,7 @@ export interface Inputs { token: string blackDuckUrl: string blackDuckApiToken: string - detectVersion: string + detectVersion?: string scanMode: string outputPathOverride: string detectTrustCertificate: string @@ -52,8 +52,8 @@ function getInputBlackDuckApiToken(): string { return core.getInput(Input.BLACKDUCK_API_TOKEN, { required: true }) } -function getInputDetectVersion(): string { - return core.getInput(Input.DETECT_VERSION, { required: true }) +function getInputDetectVersion(): string | undefined { + return core.getInput(Input.DETECT_VERSION) ?? undefined } function getInputScanMode(): string {