From da3d5c4799c5b4a05be54a9e796b26c5e8197707 Mon Sep 17 00:00:00 2001 From: Joel Rudsberg Date: Tue, 3 Dec 2024 16:17:08 +0100 Subject: [PATCH] Trying out improved test.yml approach for checking SBOM contents --- .github/workflows/test.yml | 22 +----- __tests__/sbom.test.ts | 10 +-- __tests__/sbom/main-test-app/verify-sbom.cmd | 14 ++++ __tests__/sbom/main-test-app/verify-sbom.sh | 19 +++++ dist/cleanup/index.js | 71 ++++++++----------- dist/main/index.js | 73 +++++++++----------- src/features/sbom.ts | 6 +- 7 files changed, 106 insertions(+), 109 deletions(-) create mode 100644 __tests__/sbom/main-test-app/verify-sbom.cmd create mode 100644 __tests__/sbom/main-test-app/verify-sbom.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8888349..7fe246e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -436,31 +436,13 @@ jobs: run: | cd __tests__/sbom/main-test-app mvn -Pnative package - cd target - echo "Checking for 'pkg:maven/org.json/json@20211205'" - grep -q 'pkg:maven/org.json/json@20211205' sbom.sbom.json || exit 1 - echo "Checking for 'main-test-app'" - grep -q '"main-test-app"' sbom.sbom.json || exit 1 - echo "Checking for 'svm'" - grep -q '"svm"' sbom.sbom.json || exit 1 - echo "Checking for 'nativeimage'" - grep -q '"nativeimage"' sbom.sbom.json || exit 1 - echo "SBOM was successfully generated and contained the expected contents" + sh verify-sbom.sh shell: bash if: runner.os != 'Windows' - name: Build Maven project and verify SBOM was generated (Windows) run: | cd __tests__\sbom\main-test-app mvn -Pnative package - cd target - echo "Checking for 'pkg:maven/org.json/json@20211205'" - findstr /c:"pkg:maven/org.json/json@20211205" sbom.sbom.json || exit /b 1 - echo "Checking for 'main-test-app'" - findstr /c:"\"main-test-app\"" sbom.sbom.json || exit /b 1 - echo "Checking for 'svm'" - findstr /c:"\"svm\"" sbom.sbom.json || exit /b 1 - echo "Checking for 'nativeimage'" - findstr /c:"\"nativeimage\"" sbom.sbom.json || exit /b 1 - echo "SBOM was successfully generated and contained the expected contents" + cmd /c verify-sbom.cmd shell: cmd if: runner.os == 'Windows' \ No newline at end of file diff --git a/__tests__/sbom.test.ts b/__tests__/sbom.test.ts index 91be547..1c65e4b 100644 --- a/__tests__/sbom.test.ts +++ b/__tests__/sbom.test.ts @@ -227,7 +227,7 @@ describe('sbom feature', () => { mockFindSBOM([]) - await expect(processSBOM()).rejects.toBeInstanceOf(Error); + await expect(processSBOM()).rejects.toBeInstanceOf(Error) }) it('should throw when JSON contains an invalid SBOM', async () => { @@ -282,9 +282,11 @@ describe('sbom feature', () => { }) it('should handle GitHub API submission errors gracefully', async () => { - mockGithubAPIReturnValue(new Error('API submission failed')) - - await expect(setUpAndProcessSBOM(sampleSBOM)).rejects.toBeInstanceOf(Error); + mockGithubAPIReturnValue(new Error('API submission failed')) + + await expect(setUpAndProcessSBOM(sampleSBOM)).rejects.toBeInstanceOf( + Error + ) }) }) }) diff --git a/__tests__/sbom/main-test-app/verify-sbom.cmd b/__tests__/sbom/main-test-app/verify-sbom.cmd new file mode 100644 index 0000000..fa08935 --- /dev/null +++ b/__tests__/sbom/main-test-app/verify-sbom.cmd @@ -0,0 +1,14 @@ +@echo off +cd target + +for %%p in ( + "\"pkg:maven/org.json/json@20211205\"" + "\"main-test-app\"" + "\"svm\"" + "\"nativeimage\"" +) do ( + echo Checking for %%p + findstr /c:%%p sbom.sbom.json || exit /b 1 +) + +echo SBOM was successfully generated and contained the expected contents \ No newline at end of file diff --git a/__tests__/sbom/main-test-app/verify-sbom.sh b/__tests__/sbom/main-test-app/verify-sbom.sh new file mode 100644 index 0000000..aba0a15 --- /dev/null +++ b/__tests__/sbom/main-test-app/verify-sbom.sh @@ -0,0 +1,19 @@ +#!/bin/bash +cd target + +required_patterns=( + '"pkg:maven/org.json/json@20211205"' + '"main-test-app"' + '"svm"' + '"nativeimage"' +) + +for pattern in "${required_patterns[@]}"; do + echo "Checking for $pattern" + if ! grep -q "$pattern" sbom.sbom.json; then + echo "Pattern not found: $pattern" + exit 1 + fi +done + +echo "SBOM was successfully generated and contained the expected contents" \ No newline at end of file diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 928cbba..28c32a9 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -91180,7 +91180,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.SBOM_FILE_SUFFIX = exports.INPUT_NI_SBOM = void 0; exports.setUpSBOMSupport = setUpSBOMSupport; exports.processSBOM = processSBOM; exports.mapToComponentsWithDependencies = mapToComponentsWithDependencies; @@ -91190,63 +91189,59 @@ const fs = __importStar(__nccwpck_require__(7147)); const github = __importStar(__nccwpck_require__(5438)); const glob = __importStar(__nccwpck_require__(8090)); const path_1 = __nccwpck_require__(1017); -exports.INPUT_NI_SBOM = 'native-image-enable-sbom'; -exports.SBOM_FILE_SUFFIX = '.sbom.json'; -function setUpSBOMSupport() { - const isSbomEnabled = core.getInput(exports.INPUT_NI_SBOM) === 'true'; - if (!isSbomEnabled) { +const semver_1 = __nccwpck_require__(1383); +const INPUT_NI_SBOM = 'native-image-enable-sbom'; +const SBOM_FILE_SUFFIX = '.sbom.json'; +const MIN_JAVA_VERSION = 24; +function setUpSBOMSupport(javaVersion, distribution) { + if (isFeatureNotEnabled()) { return; } + if (distribution !== c.DISTRIBUTION_GRAALVM) { + throw new Error(`The '${INPUT_NI_SBOM}' option is only supported for Oracle GraalVM (distribution '${c.DISTRIBUTION_GRAALVM}'), but found distribution '${distribution}'.`); + } + if ((0, semver_1.major)(javaVersion) < MIN_JAVA_VERSION) { + throw new Error(`The '${INPUT_NI_SBOM}' option is only supported for GraalVM for JDK ${MIN_JAVA_VERSION} or later, but found java-version '${javaVersion}'.`); + } let options = process.env[c.NATIVE_IMAGE_OPTIONS_ENV] || ''; if (options.length > 0) { options += ' '; } options += '--enable-sbom=export'; core.exportVariable(c.NATIVE_IMAGE_OPTIONS_ENV, options); - core.info('Enabled SBOM generation for Native Image builds'); + core.info('Enabled SBOM generation for Native Image build'); } function processSBOM() { return __awaiter(this, void 0, void 0, function* () { - const isSbomEnabled = core.getInput(exports.INPUT_NI_SBOM) === 'true'; - if (!isSbomEnabled) { + if (isFeatureNotEnabled()) { return; } const sbomPath = yield findSBOMFilePath(); - if (!sbomPath) { - return; - } try { const sbomContent = fs.readFileSync(sbomPath, 'utf8'); const sbomData = parseSBOM(sbomContent); - if (!sbomData) { - return; - } const components = mapToComponentsWithDependencies(sbomData); - if (components.length === 0) { - return; - } printSBOMContent(components); const snapshot = convertSBOMToSnapshot(sbomPath, components); - if (snapshot) { - yield submitDependencySnapshot(snapshot); - } + yield submitDependencySnapshot(snapshot); } catch (error) { - core.warning(`Failed to process SBOM file: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to process SBOM file: ${error instanceof Error ? error.message : String(error)}`); } }); } +function isFeatureNotEnabled() { + return core.getInput(INPUT_NI_SBOM) === 'false'; +} function findSBOMFilePath() { return __awaiter(this, void 0, void 0, function* () { - const globber = yield glob.create(`**/*${exports.SBOM_FILE_SUFFIX}`); + const globber = yield glob.create(`**/*${SBOM_FILE_SUFFIX}`); const sbomFiles = yield globber.glob(); if (sbomFiles.length === 0) { - logSkippingSubmission('No SBOM file found. Make sure native-image build completed successfully.'); - return null; + throw new Error('No SBOM file found. Make sure native-image build completed successfully.'); } if (sbomFiles.length > 1) { - logSkippingSubmission(`Found multiple SBOM files: ${sbomFiles.join(', ')}.`); - return null; + throw new Error(`Expected one SBOM file but found multiple: ${sbomFiles.join(', ')}.`); } core.info(`Found SBOM file: ${sbomFiles[0]}`); return sbomFiles[0]; @@ -91258,15 +91253,13 @@ function parseSBOM(jsonString) { return sbomData; } catch (error) { - core.warning(`Failed to parse SBOM JSON: ${error instanceof Error ? error.message : String(error)}`); - return null; + throw new Error(`Failed to parse SBOM JSON: ${error instanceof Error ? error.message : String(error)}`); } } -// Maps the SBOM data to a list of components with their dependencies +// Maps the SBOM to a list of components with their dependencies function mapToComponentsWithDependencies(sbom) { if (!sbom || sbom.components.length === 0) { - logSkippingSubmission('Invalid SBOM data or no components found.'); - return []; + throw new Error('Invalid SBOM data or no components found.'); } return sbom.components.map((component) => { var _a, _b; @@ -91290,7 +91283,7 @@ function printSBOMContent(components) { } core.info('=================='); } -function mapToSnapshotResolved(components) { +function mapComponentsToGithubAPIFormat(components) { return Object.fromEntries(components .filter(component => { if (!component.purl) { @@ -91309,9 +91302,8 @@ function mapToSnapshotResolved(components) { function convertSBOMToSnapshot(sbomPath, components) { const context = github.context; const sbomFileName = (0, path_1.basename)(sbomPath); - if (!sbomFileName.endsWith(exports.SBOM_FILE_SUFFIX)) { - logSkippingSubmission(`Invalid SBOM file name: ${sbomFileName}. Expected a file ending with ${exports.SBOM_FILE_SUFFIX}.`); - return null; + if (!sbomFileName.endsWith(SBOM_FILE_SUFFIX)) { + throw new Error(`Invalid SBOM file name: ${sbomFileName}. Expected a file ending with ${SBOM_FILE_SUFFIX}.`); } return { version: 0, @@ -91331,7 +91323,7 @@ function convertSBOMToSnapshot(sbomPath, components) { manifests: { [sbomFileName]: { name: sbomFileName, - resolved: mapToSnapshotResolved(components), + resolved: mapComponentsToGithubAPIFormat(components), metadata: { generated_by: 'SBOM generated by GraalVM Native Image', action_version: c.ACTION_VERSION @@ -91364,13 +91356,10 @@ function submitDependencySnapshot(snapshotData) { core.info('Dependency snapshot submitted successfully.'); } catch (error) { - core.warning(`Failed to submit dependency snapshot for SBOM: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to submit dependency snapshot for SBOM: ${error instanceof Error ? error.message : String(error)}`); } }); } -function logSkippingSubmission(prefix) { - core.warning(`${prefix} Skipping submission to GitHub Dependency API.`); -} /***/ }), diff --git a/dist/main/index.js b/dist/main/index.js index df8dcbb..830fbfe 100644 --- a/dist/main/index.js +++ b/dist/main/index.js @@ -91976,7 +91976,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.SBOM_FILE_SUFFIX = exports.INPUT_NI_SBOM = void 0; exports.setUpSBOMSupport = setUpSBOMSupport; exports.processSBOM = processSBOM; exports.mapToComponentsWithDependencies = mapToComponentsWithDependencies; @@ -91986,63 +91985,59 @@ const fs = __importStar(__nccwpck_require__(7147)); const github = __importStar(__nccwpck_require__(5438)); const glob = __importStar(__nccwpck_require__(8090)); const path_1 = __nccwpck_require__(1017); -exports.INPUT_NI_SBOM = 'native-image-enable-sbom'; -exports.SBOM_FILE_SUFFIX = '.sbom.json'; -function setUpSBOMSupport() { - const isSbomEnabled = core.getInput(exports.INPUT_NI_SBOM) === 'true'; - if (!isSbomEnabled) { +const semver_1 = __nccwpck_require__(1383); +const INPUT_NI_SBOM = 'native-image-enable-sbom'; +const SBOM_FILE_SUFFIX = '.sbom.json'; +const MIN_JAVA_VERSION = 24; +function setUpSBOMSupport(javaVersion, distribution) { + if (isFeatureNotEnabled()) { return; } + if (distribution !== c.DISTRIBUTION_GRAALVM) { + throw new Error(`The '${INPUT_NI_SBOM}' option is only supported for Oracle GraalVM (distribution '${c.DISTRIBUTION_GRAALVM}'), but found distribution '${distribution}'.`); + } + if ((0, semver_1.major)(javaVersion) < MIN_JAVA_VERSION) { + throw new Error(`The '${INPUT_NI_SBOM}' option is only supported for GraalVM for JDK ${MIN_JAVA_VERSION} or later, but found java-version '${javaVersion}'.`); + } let options = process.env[c.NATIVE_IMAGE_OPTIONS_ENV] || ''; if (options.length > 0) { options += ' '; } options += '--enable-sbom=export'; core.exportVariable(c.NATIVE_IMAGE_OPTIONS_ENV, options); - core.info('Enabled SBOM generation for Native Image builds'); + core.info('Enabled SBOM generation for Native Image build'); } function processSBOM() { return __awaiter(this, void 0, void 0, function* () { - const isSbomEnabled = core.getInput(exports.INPUT_NI_SBOM) === 'true'; - if (!isSbomEnabled) { + if (isFeatureNotEnabled()) { return; } const sbomPath = yield findSBOMFilePath(); - if (!sbomPath) { - return; - } try { const sbomContent = fs.readFileSync(sbomPath, 'utf8'); const sbomData = parseSBOM(sbomContent); - if (!sbomData) { - return; - } const components = mapToComponentsWithDependencies(sbomData); - if (components.length === 0) { - return; - } printSBOMContent(components); const snapshot = convertSBOMToSnapshot(sbomPath, components); - if (snapshot) { - yield submitDependencySnapshot(snapshot); - } + yield submitDependencySnapshot(snapshot); } catch (error) { - core.warning(`Failed to process SBOM file: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to process SBOM file: ${error instanceof Error ? error.message : String(error)}`); } }); } +function isFeatureNotEnabled() { + return core.getInput(INPUT_NI_SBOM) === 'false'; +} function findSBOMFilePath() { return __awaiter(this, void 0, void 0, function* () { - const globber = yield glob.create(`**/*${exports.SBOM_FILE_SUFFIX}`); + const globber = yield glob.create(`**/*${SBOM_FILE_SUFFIX}`); const sbomFiles = yield globber.glob(); if (sbomFiles.length === 0) { - logSkippingSubmission('No SBOM file found. Make sure native-image build completed successfully.'); - return null; + throw new Error('No SBOM file found. Make sure native-image build completed successfully.'); } if (sbomFiles.length > 1) { - logSkippingSubmission(`Found multiple SBOM files: ${sbomFiles.join(', ')}.`); - return null; + throw new Error(`Expected one SBOM file but found multiple: ${sbomFiles.join(', ')}.`); } core.info(`Found SBOM file: ${sbomFiles[0]}`); return sbomFiles[0]; @@ -92054,15 +92049,13 @@ function parseSBOM(jsonString) { return sbomData; } catch (error) { - core.warning(`Failed to parse SBOM JSON: ${error instanceof Error ? error.message : String(error)}`); - return null; + throw new Error(`Failed to parse SBOM JSON: ${error instanceof Error ? error.message : String(error)}`); } } -// Maps the SBOM data to a list of components with their dependencies +// Maps the SBOM to a list of components with their dependencies function mapToComponentsWithDependencies(sbom) { if (!sbom || sbom.components.length === 0) { - logSkippingSubmission('Invalid SBOM data or no components found.'); - return []; + throw new Error('Invalid SBOM data or no components found.'); } return sbom.components.map((component) => { var _a, _b; @@ -92086,7 +92079,7 @@ function printSBOMContent(components) { } core.info('=================='); } -function mapToSnapshotResolved(components) { +function mapComponentsToGithubAPIFormat(components) { return Object.fromEntries(components .filter(component => { if (!component.purl) { @@ -92105,9 +92098,8 @@ function mapToSnapshotResolved(components) { function convertSBOMToSnapshot(sbomPath, components) { const context = github.context; const sbomFileName = (0, path_1.basename)(sbomPath); - if (!sbomFileName.endsWith(exports.SBOM_FILE_SUFFIX)) { - logSkippingSubmission(`Invalid SBOM file name: ${sbomFileName}. Expected a file ending with ${exports.SBOM_FILE_SUFFIX}.`); - return null; + if (!sbomFileName.endsWith(SBOM_FILE_SUFFIX)) { + throw new Error(`Invalid SBOM file name: ${sbomFileName}. Expected a file ending with ${SBOM_FILE_SUFFIX}.`); } return { version: 0, @@ -92127,7 +92119,7 @@ function convertSBOMToSnapshot(sbomPath, components) { manifests: { [sbomFileName]: { name: sbomFileName, - resolved: mapToSnapshotResolved(components), + resolved: mapComponentsToGithubAPIFormat(components), metadata: { generated_by: 'SBOM generated by GraalVM Native Image', action_version: c.ACTION_VERSION @@ -92160,13 +92152,10 @@ function submitDependencySnapshot(snapshotData) { core.info('Dependency snapshot submitted successfully.'); } catch (error) { - core.warning(`Failed to submit dependency snapshot for SBOM: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to submit dependency snapshot for SBOM: ${error instanceof Error ? error.message : String(error)}`); } }); } -function logSkippingSubmission(prefix) { - core.warning(`${prefix} Skipping submission to GitHub Dependency API.`); -} /***/ }), @@ -93161,7 +93150,7 @@ function run() { yield (0, cache_2.restore)(cache); } (0, reports_1.setUpNativeImageBuildReports)(isGraalVMforJDK17OrLater, javaVersion, graalVMVersion); - (0, sbom_1.setUpSBOMSupport)(); + (0, sbom_1.setUpSBOMSupport)(javaVersion, distribution); core.startGroup(`Successfully set up '${(0, path_1.basename)(graalVMHome)}'`); yield (0, exec_1.exec)((0, path_1.join)(graalVMHome, 'bin', `java${c.EXECUTABLE_SUFFIX}`), [ javaVersion.startsWith('8') ? '-version' : '--version' diff --git a/src/features/sbom.ts b/src/features/sbom.ts index 5ff4602..6c3f407 100644 --- a/src/features/sbom.ts +++ b/src/features/sbom.ts @@ -75,7 +75,9 @@ async function findSBOMFilePath(): Promise { } if (sbomFiles.length > 1) { - throw new Error (`Expected one SBOM file but found multiple: ${sbomFiles.join(', ')}.`) + throw new Error( + `Expected one SBOM file but found multiple: ${sbomFiles.join(', ')}.` + ) } core.info(`Found SBOM file: ${sbomFiles[0]}`) @@ -277,4 +279,4 @@ interface DependencySnapshot { > } > -} \ No newline at end of file +}