From 35dd9397f9b47445d43dc32e42e4f74450dbd4ec Mon Sep 17 00:00:00 2001 From: Amrutha G Date: Thu, 24 Sep 2026 16:29:51 +0530 Subject: [PATCH 1/4] Add minimal reusable SBOM generation action (generate-sbom) --- .github/workflows/reusable-generate-sbom.yml | 85 ++++++++++++++ actions/OpenSourceActions.psd1 | 4 +- actions/OpenSourceActions.psm1 | 51 +++++++++ dispatchers.json | 63 ++++++++++ docs/actions/generate-sbom.md | 110 ++++++++++++++++++ docs/index.md | 1 + generate-sbom/action.yml | 76 +++++++++++++ mkdocs.yml | 1 + scripts/generate-sbom/GenerateSbom.ps1 | 114 +++++++++++++++++++ 9 files changed, 504 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/reusable-generate-sbom.yml create mode 100644 docs/actions/generate-sbom.md create mode 100644 generate-sbom/action.yml create mode 100644 scripts/generate-sbom/GenerateSbom.ps1 diff --git a/.github/workflows/reusable-generate-sbom.yml b/.github/workflows/reusable-generate-sbom.yml new file mode 100644 index 00000000..17e42ddd --- /dev/null +++ b/.github/workflows/reusable-generate-sbom.yml @@ -0,0 +1,85 @@ +name: Reusable - Generate SBOM + +on: + workflow_call: + inputs: + lvproj_path: + description: 'Path to the .lvproj file to scan.' + required: true + type: string + labview_version: + required: true + type: string + labview_bitness: + required: true + type: string + build_spec_name: + required: true + type: string + target_name: + required: false + type: string + default: 'My Computer' + product_name: + required: true + type: string + product_version: + required: true + type: string + output_path: + required: true + type: string + format: + required: false + type: string + default: 'cyclonedx' + schema_version: + required: false + type: string + default: '1.5' + artifact_name: + description: 'Name for the uploaded SBOM artifact.' + required: false + type: string + default: 'sbom' + +permissions: + contents: read + +jobs: + generate-sbom: + runs-on: windows-latest + steps: + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 + # workflow_call runs in its own fresh job, so VIPM must be installed here + # even if the caller's own job already has it - self-contained by design. + - name: Install VIPM CLI + shell: pwsh + run: | + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri 'https://traffic.libsyn.com/secure/jkinc/vipm-26.3.4025-windows-setup.exe' -OutFile "$env:TEMP\vipm-setup.exe" + Start-Process -Wait -FilePath "$env:TEMP\vipm-setup.exe" -ArgumentList '/exenoui /qn' + Remove-Item "$env:TEMP\vipm-setup.exe" -Force + $vipmExe = Get-ChildItem -Path "$env:ProgramFiles", "${env:ProgramFiles(x86)}" -Filter vipm.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName + if (-not $vipmExe) { throw "vipm.exe was not found under Program Files after installation." } + "$(Split-Path $vipmExe -Parent)" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + & $vipmExe --version + - uses: ni/open-source/generate-sbom@main + with: + lvproj_path: ${{ inputs.lvproj_path }} + labview_version: ${{ inputs.labview_version }} + labview_bitness: ${{ inputs.labview_bitness }} + build_spec_name: ${{ inputs.build_spec_name }} + target_name: ${{ inputs.target_name }} + product_name: ${{ inputs.product_name }} + product_version: ${{ inputs.product_version }} + output_path: ${{ inputs.output_path }} + format: ${{ inputs.format }} + schema_version: ${{ inputs.schema_version }} + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + if: success() + with: + name: ${{ inputs.artifact_name }} + path: ${{ inputs.output_path }} + retention-days: 90 + if-no-files-found: error diff --git a/actions/OpenSourceActions.psd1 b/actions/OpenSourceActions.psd1 index a9e703b0..9f88b841 100644 --- a/actions/OpenSourceActions.psd1 +++ b/actions/OpenSourceActions.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'OpenSourceActions.psm1' - ModuleVersion = '1.0.0' + ModuleVersion = '1.1.0' GUID = '8c0a64c5-6a52-4d7e-9f74-8f79f21f1b2c' Author = 'LabVIEW Community CI/CD' CompanyName = 'LabVIEW Community' @@ -22,6 +22,7 @@ 'Invoke-CloseLabVIEW' 'Invoke-ConfigureLabview' 'Invoke-GenerateReleaseNotes' + 'Invoke-GenerateSbom' 'Invoke-MissingInProject' 'Invoke-ModifyVIPBDisplayInfo' 'Invoke-PrepareLabVIEWSource' @@ -41,6 +42,7 @@ PSData = @{ ReleaseNotes = @( '1.0.0 - Initial release: adapters for apply-vipc, build-lvlibp, missing-in-project, run-unit-tests; Format-UnitTestReport helper; discovery flags; DryRun; cross-platform g-cli support.' + '1.1.0 - Add generate-sbom adapter: minimal CycloneDX SBOM generation via vipm sbom, assuming VIPM is already installed/activated on the runner.' ) } } diff --git a/actions/OpenSourceActions.psm1 b/actions/OpenSourceActions.psm1 index fd12abd9..8761783e 100644 --- a/actions/OpenSourceActions.psm1 +++ b/actions/OpenSourceActions.psm1 @@ -804,4 +804,55 @@ function Invoke-ViaLvDocker { DockerImage = $DockerImage } return Invoke-OpenSourceActionScript -ScriptSegments @('via-lv-docker','RunViaLvDocker.ps1') -Arguments $args -DryRun:$DryRun -gcliPath $gcliPath +} + +# Generates a CycloneDX SBOM for a LabVIEW build spec using `vipm sbom`. +# Assumes VIPM CLI is already installed/activated and LabVIEW is available on +# the runner; the caller is responsible for that (same as ni/labview-icon-editor#540's +# own "Install VIPM CLI" step). +# LvprojPath: Path to the .lvproj file to scan. +# LabVIEWVersion: LabVIEW version (YYYY). +# LabVIEWBitness: "32" or "64". +# BuildSpecName: Name of the build specification to scope the SBOM to. +# TargetName: LabVIEW project target containing the build spec (default: "My Computer"). +# ProductName: Name recorded in the SBOM's metadata.component. +# ProductVersion: Version recorded in the SBOM's metadata.component. +# OutputPath: Output file path for the generated SBOM. +# Format: SBOM output format (default: "cyclonedx"). +# SchemaVersion: CycloneDX schema version (default: "1.5"). +# DryRun: If set, prints the command instead of executing it. +function Invoke-GenerateSbom { + [CmdletBinding()] + param( + [Parameter(Mandatory)] [string] $LvprojPath, + [Parameter(Mandatory)] [string] $LabVIEWVersion, + [Parameter(Mandatory)] [string] $LabVIEWBitness, + [Parameter(Mandatory)] [string] $BuildSpecName, + [Parameter()] [string] $TargetName = "My Computer", + [Parameter(Mandatory)] [string] $ProductName, + [Parameter(Mandatory)] [string] $ProductVersion, + [Parameter(Mandatory)] [string] $OutputPath, + [Parameter()] [string] $Format = "cyclonedx", + [Parameter()] [string] $SchemaVersion = "1.5", + [switch] $DryRun + ) + Write-Information "Invoking GenerateSbom" -InformationAction Continue + + $result = Invoke-OpenSourceActionScript ` + -ScriptSegments @('generate-sbom', 'GenerateSbom.ps1') ` + -Arguments @{ + LvprojPath = $LvprojPath + LabVIEWVersion = $LabVIEWVersion + LabVIEWBitness = $LabVIEWBitness + BuildSpecName = $BuildSpecName + TargetName = $TargetName + ProductName = $ProductName + ProductVersion = $ProductVersion + OutputPath = $OutputPath + Format = $Format + SchemaVersion = $SchemaVersion + } ` + -DryRun:$DryRun + + return $result } \ No newline at end of file diff --git a/dispatchers.json b/dispatchers.json index 126b4f7a..fe52ed7e 100644 --- a/dispatchers.json +++ b/dispatchers.json @@ -583,6 +583,69 @@ } } }, + "Invoke-GenerateSbom": { + "description": "Generates a CycloneDX SBOM for a LabVIEW build spec using `vipm sbom`. Assumes VIPM CLI is already installed/activated and LabVIEW is available on the runner; the caller is responsible for that (same as ni/labview-icon-editor#540's own \"Install VIPM CLI\" step). LvprojPath: Path to the .lvproj file to scan. LabVIEWVersion: LabVIEW version (YYYY). LabVIEWBitness: \"32\" or \"64\". BuildSpecName: Name of the build specification to scope the SBOM to. TargetName: LabVIEW project target containing the build spec (default: \"My Computer\"). ProductName: Name recorded in the SBOM's metadata.component. ProductVersion: Version recorded in the SBOM's metadata.component. OutputPath: Output file path for the generated SBOM. Format: SBOM output format (default: \"cyclonedx\"). SchemaVersion: CycloneDX schema version (default: \"1.5\"). DryRun: If set, prints the command instead of executing it.", + "parameters": { + "BuildSpecName": { + "type": "string", + "required": true, + "description": "Name of the build specification to scope the SBOM to" + }, + "DryRun": { + "type": "boolean", + "required": false, + "description": "If set, prints the command instead of executing it" + }, + "Format": { + "type": "string", + "required": false, + "default": "cyclonedx", + "description": "SBOM output format (default: \"cyclonedx\")" + }, + "LabVIEWBitness": { + "type": "string", + "required": true, + "description": "\"32\" or \"64\"" + }, + "LabVIEWVersion": { + "type": "string", + "required": true, + "description": "LabVIEW version (YYYY)" + }, + "LvprojPath": { + "type": "string", + "required": true, + "description": "Path to the" + }, + "OutputPath": { + "type": "string", + "required": true, + "description": "Output file path for the generated SBOM" + }, + "ProductName": { + "type": "string", + "required": true, + "description": "Name recorded in the SBOM's metadata" + }, + "ProductVersion": { + "type": "string", + "required": true, + "description": "Version recorded in the SBOM's metadata" + }, + "SchemaVersion": { + "type": "string", + "required": false, + "default": "1.5", + "description": "CycloneDX schema version (default: \"1" + }, + "TargetName": { + "type": "string", + "required": false, + "default": "My Computer", + "description": "LabVIEW project target containing the build spec (default: \"My Computer\")" + } + } + }, "Invoke-MissingInProject": { "description": "Lists files referenced in a LabVIEW project that are missing on disk. LVVersion: LabVIEW version of the project. SupportedBitness: Target LabVIEW bitness (32- or 64-bit). ProjectFile: Path to the .lvproj file to analyze. DryRun: If set, prints the command instead of executing it. gcliPath: Optional path prepended to PATH for locating the g CLI.", "parameters": { diff --git a/docs/actions/generate-sbom.md b/docs/actions/generate-sbom.md new file mode 100644 index 00000000..4812a2ed --- /dev/null +++ b/docs/actions/generate-sbom.md @@ -0,0 +1,110 @@ +# generate-sbom + +## Purpose + +Generate a CycloneDX Software Bill of Materials (SBOM) for a LabVIEW build specification using `vipm sbom`. Minimal implementation ported from [ni/labview-icon-editor#540](https://github.com/ni/labview-icon-editor/pull/540): assumes VIPM CLI is already installed/activated and LabVIEW is available on the runner. Additional execution modes (Docker-based builds, headless LabVIEW/VI Server warm-up) can be added later if a real consumer requires them. + +## Parameters + +Common parameters are described in [Common parameters](../common-parameters.md). + +### Required + +- **LvprojPath** (`string`): Path to the `.lvproj` file to scan. +- **LabVIEWVersion** (`string`): LabVIEW version (`YYYY`). +- **LabVIEWBitness** (`string`): `"32"` or `"64"`. +- **BuildSpecName** (`string`): Name of the build specification to scope the SBOM to. +- **ProductName** (`string`): Name recorded in the SBOM's `metadata.component`. +- **ProductVersion** (`string`): Version recorded in the SBOM's `metadata.component`. +- **OutputPath** (`string`): Output file path for the generated SBOM. + +### Optional + +- **TargetName** (`string`): LabVIEW project target containing the build spec (default: `"My Computer"`). +- **Format** (`string`): SBOM output format (default: `"cyclonedx"`). +- **SchemaVersion** (`string`): CycloneDX schema version (default: `"1.5"`). + +### GitHub Action inputs + +| Input | CLI parameter | Description | +| --- | --- | --- | +| `lvproj_path` | `LvprojPath` | Path to the `.lvproj` file to scan. | +| `labview_version` | `LabVIEWVersion` | LabVIEW version (`YYYY`). | +| `labview_bitness` | `LabVIEWBitness` | `"32"` or `"64"`. | +| `build_spec_name` | `BuildSpecName` | Build specification to scope the SBOM to. | +| `target_name` | `TargetName` | LabVIEW project target (default: `"My Computer"`). | +| `product_name` | `ProductName` | Name recorded in the SBOM. | +| `product_version` | `ProductVersion` | Version recorded in the SBOM. | +| `output_path` | `OutputPath` | Output file path for the generated SBOM. | +| `format` | `Format` | SBOM output format (default: `"cyclonedx"`). | +| `schema_version` | `SchemaVersion` | CycloneDX schema version (default: `"1.5"`). | +| `log_level` | `LogLevel` | Verbosity level (ERROR\|WARN\|INFO\|DEBUG). | +| `dry_run` | `DryRun` | If true, simulate the action without side effects. | + +## Prerequisites + +This action does **not** install or activate VIPM, and does not set up LabVIEW. The caller's workflow (or the reusable workflow below) must ensure `vipm` is on `PATH` and LabVIEW is available before this step runs. + +## Examples + +### CLI + +```powershell +pwsh -File actions/Invoke-OSAction.ps1 -ActionName generate-sbom -ArgsJson '{ + "LvprojPath": "lv_icon_editor.lvproj", + "LabVIEWVersion": "2026", + "LabVIEWBitness": "64", + "BuildSpecName": "Editor Packed Library", + "ProductName": "Icon Editor Packed Library", + "ProductVersion": "1.0.0.0", + "OutputPath": "builds/lv_icon_editor_Editor Packed Library.cdx.sbom.json" +}' +``` + +### GitHub Action (direct, as a step in an existing job) + +```yaml +- name: Install VIPM CLI + # ... existing install/activate steps ... +- name: Generate SBOM + uses: ni/open-source/generate-sbom@v1 + with: + lvproj_path: lv_icon_editor.lvproj + labview_version: '2026' + labview_bitness: '64' + build_spec_name: 'Editor Packed Library' + product_name: 'Icon Editor Packed Library' + product_version: '1.0.0.0' + output_path: 'builds/lv_icon_editor_Editor Packed Library.cdx.sbom.json' +``` + +### Example consumer workflow (reusable workflow) + +The reusable workflow is self-contained (it installs VIPM CLI itself, since `workflow_call` runs in its own fresh job) and also uploads the SBOM as an artifact: + +```yaml +name: Generate SBOM + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + generate-sbom: + uses: ni/open-source/.github/workflows/reusable-generate-sbom.yml@v1 + with: + lvproj_path: lv_icon_editor.lvproj + labview_version: '2026' + labview_bitness: '64' + build_spec_name: 'Editor Packed Library' + product_name: 'Icon Editor Packed Library' + product_version: '1.0.0.0' + output_path: 'builds/lv_icon_editor_Editor Packed Library.cdx.sbom.json' + artifact_name: 'sbom' +``` + +## Return Codes + +- `0` - success +- non-zero - `vipm` not found on `PATH`, `vipm sbom` failure, or no SBOM file produced diff --git a/docs/index.md b/docs/index.md index 44c5aaba..ef832c4d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,6 +30,7 @@ Open Source LabVIEW Actions unifies LabVIEW [CI/CD](glossary.md#ci-cd) scripts b | [build-vi-package](actions/build-vi-package.md) | Update VIPB display information and build a VI package using g-cli. | | [close-labview](actions/close-labview.md) | Gracefully close a running LabVIEW instance via g-cli. | | [generate-release-notes](actions/generate-release-notes.md) | Generate release notes from the git history and write them to a markdown file. | +| [generate-sbom](actions/generate-sbom.md) | Generate a CycloneDX SBOM for a LabVIEW build spec using `vipm sbom`. | | [missing-in-project](actions/missing-in-project.md) | Check that all files in a LabVIEW project are present by scanning for items missing from the `.lvproj`. | | [modify-vipb-display-info](actions/modify-vipb-display-info.md) | Update display information in a VIPB file and rebuild the VI package. | | [prepare-labview-source](actions/prepare-labview-source.md) | Run PrepareIESource.vi via g-cli to unzip components and configure LabVIEW for building. | diff --git a/generate-sbom/action.yml b/generate-sbom/action.yml new file mode 100644 index 00000000..5d593c50 --- /dev/null +++ b/generate-sbom/action.yml @@ -0,0 +1,76 @@ +name: 'Generate SBOM' +description: 'Generates a CycloneDX SBOM for a LabVIEW build spec using vipm sbom. Assumes VIPM CLI is already installed/activated on the runner.' +inputs: + lvproj_path: + description: 'Path to the .lvproj file to scan.' + required: true + labview_version: + description: 'LabVIEW version (YYYY).' + required: true + labview_bitness: + description: '"32" or "64".' + required: true + build_spec_name: + description: 'Name of the build specification to scope the SBOM to.' + required: true + target_name: + description: 'LabVIEW project target containing the build spec.' + default: 'My Computer' + required: false + product_name: + description: 'Name recorded in the SBOM metadata.component.' + required: true + product_version: + description: 'Version recorded in the SBOM metadata.component.' + required: true + output_path: + description: 'Output file path for the generated SBOM.' + required: true + format: + description: 'SBOM output format.' + default: 'cyclonedx' + required: false + schema_version: + description: 'CycloneDX schema version.' + default: '1.5' + required: false + working_directory: + description: 'Working directory where the action will run.' + required: false + log_level: + description: 'Verbosity level (ERROR|WARN|INFO|DEBUG).' + default: 'INFO' + required: false + dry_run: + description: 'If true, simulate the action without side effects.' + default: false + required: false +runs: + using: 'composite' + steps: + - name: Dispatch generate-sbom + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $args = @{ + LvprojPath = '${{ inputs.lvproj_path }}' + LabVIEWVersion = '${{ inputs.labview_version }}' + LabVIEWBitness = '${{ inputs.labview_bitness }}' + BuildSpecName = '${{ inputs.build_spec_name }}' + TargetName = '${{ inputs.target_name }}' + ProductName = '${{ inputs.product_name }}' + ProductVersion = '${{ inputs.product_version }}' + OutputPath = '${{ inputs.output_path }}' + Format = '${{ inputs.format }}' + SchemaVersion = '${{ inputs.schema_version }}' + } + $params = @{ + ActionName = 'generate-sbom' + Args = $args + LogLevel = '${{ inputs.log_level }}' + } + if ('${{ inputs.dry_run }}' -eq 'true') { $params['DryRun'] = $true } + if ('${{ inputs.working_directory }}') { $params['WorkingDirectory'] = '${{ inputs.working_directory }}' } + $script = Join-Path $env:GITHUB_ACTION_PATH '..' 'actions' 'common-dispatch.ps1' + & $script @params + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/mkdocs.yml b/mkdocs.yml index 09448f77..9873dc2d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,6 +57,7 @@ nav: - Build VI Package: actions/build-vi-package.md - Close LabVIEW: actions/close-labview.md - Generate Release Notes: actions/generate-release-notes.md + - Generate SBOM: actions/generate-sbom.md - Missing in Project: actions/missing-in-project.md - Modify VIPB Display Info: actions/modify-vipb-display-info.md - Prepare LabVIEW Source: actions/prepare-labview-source.md diff --git a/scripts/generate-sbom/GenerateSbom.ps1 b/scripts/generate-sbom/GenerateSbom.ps1 new file mode 100644 index 00000000..cc4d98a1 --- /dev/null +++ b/scripts/generate-sbom/GenerateSbom.ps1 @@ -0,0 +1,114 @@ +<# +.SYNOPSIS + Generates a CycloneDX SBOM for a LabVIEW build spec using `vipm sbom`. + +.DESCRIPTION + Minimal wrapper: assumes VIPM CLI is already installed/activated and LabVIEW + is available on the runner (the consumer's workflow is responsible for that, + same as ni/labview-icon-editor#540's own "Install VIPM CLI" step). Just runs + `vipm sbom` with the given flags and verifies the output file exists. + See docs.vipm.io/cli/command-reference#vipm-sbom. + +.PARAMETER LvprojPath + Path to the .lvproj file to scan. + +.PARAMETER LabVIEWVersion + LabVIEW version (YYYY). + +.PARAMETER LabVIEWBitness + "32" or "64". + +.PARAMETER BuildSpecName + Name of the build specification within the .lvproj to scope the SBOM to. + +.PARAMETER TargetName + LabVIEW project target containing the build spec. Defaults to "My Computer". + +.PARAMETER ProductName + Name recorded in the SBOM's metadata.component. + +.PARAMETER ProductVersion + Version recorded in the SBOM's metadata.component. + +.PARAMETER OutputPath + Output file path for the generated SBOM. + +.PARAMETER Format + SBOM output format. Defaults to "cyclonedx". + +.PARAMETER SchemaVersion + CycloneDX schema version. Defaults to "1.5". + +.NOTES + Leaf script for the 'generate-sbom' dispatcher action. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$LvprojPath, + + [Parameter(Mandatory = $true)] + [string]$LabVIEWVersion, + + [Parameter(Mandatory = $true)] + [string]$LabVIEWBitness, + + [Parameter(Mandatory = $true)] + [string]$BuildSpecName, + + [Parameter(Mandatory = $false)] + [string]$TargetName = "My Computer", + + [Parameter(Mandatory = $true)] + [string]$ProductName, + + [Parameter(Mandatory = $true)] + [string]$ProductVersion, + + [Parameter(Mandatory = $true)] + [string]$OutputPath, + + [Parameter(Mandatory = $false)] + [string]$Format = "cyclonedx", + + [Parameter(Mandatory = $false)] + [string]$SchemaVersion = "1.5" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +try { + if (-not (Get-Command vipm -ErrorAction SilentlyContinue)) { + throw "vipm CLI not found on PATH. Install and activate VIPM before calling generate-sbom." + } + + Write-Information "Generating SBOM for '$LvprojPath' (build spec '$BuildSpecName')..." -InformationAction Continue + + vipm sbom $LvprojPath ` + --format $Format ` + --schema-version $SchemaVersion ` + --labview-version $LabVIEWVersion ` + --labview-bitness $LabVIEWBitness ` + --lvproj-build-spec $BuildSpecName ` + --lvproj-target $TargetName ` + --product-name $ProductName ` + --product-version $ProductVersion ` + --output $OutputPath + + if ($LASTEXITCODE -ne 0) { + throw "vipm sbom failed with exit code $LASTEXITCODE" + } + + if (-not (Test-Path $OutputPath)) { + throw "No SBOM file found at $OutputPath. Ensure vipm sbom completed successfully." + } + + Write-Information "SBOM verified: $OutputPath" -InformationAction Continue + exit 0 +} +catch { + Write-Error "GenerateSbom failed: $_" + exit 1 +} From e92713aec46744b7f7e2f148f3e447e3d5a5db27 Mon Sep 17 00:00:00 2001 From: Amrutha G Date: Fri, 25 Sep 2026 12:25:51 +0530 Subject: [PATCH 2/4] Add SBOM workflow traceability test [REQ-042] --- docs/actions/generate-sbom.md | 2 +- generate-sbom/action.yml | 2 +- requirements.json | 10 ++++- scripts/generate-sbom/GenerateSbom.ps1 | 11 ++--- tests/pester/GenerateSbom.Workflow.Tests.ps1 | 44 ++++++++++++++++++++ 5 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 tests/pester/GenerateSbom.Workflow.Tests.ps1 diff --git a/docs/actions/generate-sbom.md b/docs/actions/generate-sbom.md index 4812a2ed..d0eda168 100644 --- a/docs/actions/generate-sbom.md +++ b/docs/actions/generate-sbom.md @@ -2,7 +2,7 @@ ## Purpose -Generate a CycloneDX Software Bill of Materials (SBOM) for a LabVIEW build specification using `vipm sbom`. Minimal implementation ported from [ni/labview-icon-editor#540](https://github.com/ni/labview-icon-editor/pull/540): assumes VIPM CLI is already installed/activated and LabVIEW is available on the runner. Additional execution modes (Docker-based builds, headless LabVIEW/VI Server warm-up) can be added later if a real consumer requires them. +Generate a CycloneDX Software Bill of Materials (SBOM) for a LabVIEW build specification using `vipm sbom`. Minimal implementation ported from [ni/labview-icon-editor#540](https://github.com/ni/labview-icon-editor/pull/540): requires VIPM CLI to be available on the runner and LabVIEW to be available for the target project. The reusable workflow installs VIPM itself, and additional execution modes (Docker-based builds, headless LabVIEW/VI Server warm-up) can be added later if a real consumer requires them. ## Parameters diff --git a/generate-sbom/action.yml b/generate-sbom/action.yml index 5d593c50..619d9b4e 100644 --- a/generate-sbom/action.yml +++ b/generate-sbom/action.yml @@ -1,5 +1,5 @@ name: 'Generate SBOM' -description: 'Generates a CycloneDX SBOM for a LabVIEW build spec using vipm sbom. Assumes VIPM CLI is already installed/activated on the runner.' +description: 'Generates a CycloneDX SBOM for a LabVIEW build spec using vipm sbom. Requires VIPM CLI to be available on the runner; the reusable workflow installs it automatically.' inputs: lvproj_path: description: 'Path to the .lvproj file to scan.' diff --git a/requirements.json b/requirements.json index 6f279c99..6ae82b66 100644 --- a/requirements.json +++ b/requirements.json @@ -317,6 +317,14 @@ "BuildSpecGithubHostedWindows.Workflow.Tests" ] }, + { + "id": "REQ-042", + "description": "Workflow tests the composite action defined in generate-sbom/action.yml on the GitHub-hosted Ubuntu runner labeled ubuntu-latest.", + "runner": "ubuntu-latest", + "tests": [ + "GenerateSbom.Workflow.Tests" + ] + }, { "id": "REQIE-001", "description": "After checking out the LabVIEW icon editor repository, PreSequence: The sequencer shall enumerate and record the build matrix used by the workflow (LabVIEW versions and bitness). Acceptance: a 'matrix.json' file exists listing each tuple {\"lv-version\": \"2021\"|\"2023\", \"bitness\": \"32\"|\"64\"} with at least [ [\"2021\",\"32\"], [\"2021\",\"64\"], [\"2023\",\"64\"] ]. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", @@ -428,4 +436,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/scripts/generate-sbom/GenerateSbom.ps1 b/scripts/generate-sbom/GenerateSbom.ps1 index cc4d98a1..031e82a2 100644 --- a/scripts/generate-sbom/GenerateSbom.ps1 +++ b/scripts/generate-sbom/GenerateSbom.ps1 @@ -3,11 +3,12 @@ Generates a CycloneDX SBOM for a LabVIEW build spec using `vipm sbom`. .DESCRIPTION - Minimal wrapper: assumes VIPM CLI is already installed/activated and LabVIEW - is available on the runner (the consumer's workflow is responsible for that, - same as ni/labview-icon-editor#540's own "Install VIPM CLI" step). Just runs - `vipm sbom` with the given flags and verifies the output file exists. - See docs.vipm.io/cli/command-reference#vipm-sbom. + Minimal wrapper: requires VIPM CLI to be available on the runner and LabVIEW + to be available for the target project. The reusable workflow installs VIPM + before invoking this script; callers that invoke the action directly are + responsible for installing VIPM themselves. Just runs `vipm sbom` with the + given flags and verifies the output file exists. See + docs.vipm.io/cli/command-reference#vipm-sbom. .PARAMETER LvprojPath Path to the .lvproj file to scan. diff --git a/tests/pester/GenerateSbom.Workflow.Tests.ps1 b/tests/pester/GenerateSbom.Workflow.Tests.ps1 new file mode 100644 index 00000000..1cf1f6f7 --- /dev/null +++ b/tests/pester/GenerateSbom.Workflow.Tests.ps1 @@ -0,0 +1,44 @@ +#requires -Version 7.0 +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +Describe 'GenerateSbom.Workflow' { + $meta = @{ + requirement = 'REQ-042' + Owner = 'NI' + Evidence = 'tests/pester/GenerateSbom.Workflow.Tests.ps1' + } + + BeforeAll { + $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')).Path + Import-Module (Join-Path $repoRoot 'actions' 'OpenSourceActions.psd1') -Force + } + + It 'validates action.yml exists [REQ-042]' -Tag 'REQ-042' { + $actionPath = Join-Path $repoRoot 'generate-sbom' 'action.yml' + Test-Path $actionPath | Should -Be $true + } + + It 'validates implementation script exists [REQ-042]' -Tag 'REQ-042' { + $scriptPath = Join-Path $repoRoot 'scripts' 'generate-sbom' 'GenerateSbom.ps1' + Test-Path $scriptPath | Should -Be $true + } + + It 'executes dry-run with only required parameters [REQ-042]' -Tag 'REQ-042' { + $result = & "$repoRoot/actions/Invoke-OSAction.ps1" ` + -ActionName 'generate-sbom' ` + -ArgsJson '{"LvprojPath":"test.lvproj","LabVIEWVersion":"2026","LabVIEWBitness":"64","BuildSpecName":"TestBuild","ProductName":"Test Product","ProductVersion":"1.0.0","OutputPath":"sbom.json"}' ` + -DryRun + + $LASTEXITCODE | Should -Be 0 + } + + It 'executes dry-run with all parameters [REQ-042]' -Tag 'REQ-042' { + $result = & "$repoRoot/actions/Invoke-OSAction.ps1" ` + -ActionName 'generate-sbom' ` + -ArgsJson '{"LvprojPath":"test.lvproj","LabVIEWVersion":"2026","LabVIEWBitness":"64","BuildSpecName":"TestBuild","TargetName":"My Computer","ProductName":"Test Product","ProductVersion":"1.0.0","OutputPath":"sbom.json","Format":"cyclonedx","SchemaVersion":"1.5"}' ` + -DryRun + + $LASTEXITCODE | Should -Be 0 + } +} From 5ae4259140606ac43f14c5600276fefbc09fbbb5 Mon Sep 17 00:00:00 2001 From: Amrutha G Date: Fri, 25 Sep 2026 16:00:30 +0530 Subject: [PATCH 3/4] REQ-042: Fix Invoke-GenerateSbom dispatcher DryRun compatibility Add LVVersion/MinimumSupportedLVVersion, LVBitness/SupportedBitness, and Build_Spec aliases to Invoke-GenerateSbom so the shared dispatcher payload in Dispatcher.DryRun.Tests.ps1 satisfies its mandatory parameters. Relax ProductName/ProductVersion to optional since no equivalent field exists in the repo-wide dispatcher payload. Regenerate dispatchers.json and artifacts/linux/* to match. --- actions/OpenSourceActions.psd1 | 3 +- actions/OpenSourceActions.psm1 | 12 +-- artifacts/linux/action-docs.json | 158 +++++++++++++++++++++++++++++++ artifacts/linux/action-docs.md | 37 ++++++++ ci_evidence.txt | 2 +- dispatchers.json | 6 +- 6 files changed, 208 insertions(+), 10 deletions(-) diff --git a/actions/OpenSourceActions.psd1 b/actions/OpenSourceActions.psd1 index 9f88b841..4765a823 100644 --- a/actions/OpenSourceActions.psd1 +++ b/actions/OpenSourceActions.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'OpenSourceActions.psm1' - ModuleVersion = '1.1.0' + ModuleVersion = '1.1.1' GUID = '8c0a64c5-6a52-4d7e-9f74-8f79f21f1b2c' Author = 'LabVIEW Community CI/CD' CompanyName = 'LabVIEW Community' @@ -43,6 +43,7 @@ ReleaseNotes = @( '1.0.0 - Initial release: adapters for apply-vipc, build-lvlibp, missing-in-project, run-unit-tests; Format-UnitTestReport helper; discovery flags; DryRun; cross-platform g-cli support.' '1.1.0 - Add generate-sbom adapter: minimal CycloneDX SBOM generation via vipm sbom, assuming VIPM is already installed/activated on the runner.' + '1.1.1 - Fix Invoke-GenerateSbom dispatcher DryRun compatibility: added LVVersion/MinimumSupportedLVVersion, LVBitness/SupportedBitness, and Build_Spec aliases; relaxed ProductName/ProductVersion to optional (no repo-wide dispatcher payload equivalent) to unblock the shared Dispatcher.DryRun.Tests.ps1 payload.' ) } } diff --git a/actions/OpenSourceActions.psm1 b/actions/OpenSourceActions.psm1 index 8761783e..2806359d 100644 --- a/actions/OpenSourceActions.psm1 +++ b/actions/OpenSourceActions.psm1 @@ -824,13 +824,13 @@ function Invoke-ViaLvDocker { function Invoke-GenerateSbom { [CmdletBinding()] param( - [Parameter(Mandatory)] [string] $LvprojPath, - [Parameter(Mandatory)] [string] $LabVIEWVersion, - [Parameter(Mandatory)] [string] $LabVIEWBitness, - [Parameter(Mandatory)] [string] $BuildSpecName, + [Parameter(Mandatory)][Alias('ProjectPath', 'ProjectFile')] [string] $LvprojPath, + [Parameter(Mandatory)][Alias('LVVersion', 'MinimumSupportedLVVersion')] [string] $LabVIEWVersion, + [Parameter(Mandatory)][Alias('LVBitness', 'SupportedBitness')] [string] $LabVIEWBitness, + [Parameter(Mandatory)][Alias('Build_Spec')] [string] $BuildSpecName, [Parameter()] [string] $TargetName = "My Computer", - [Parameter(Mandatory)] [string] $ProductName, - [Parameter(Mandatory)] [string] $ProductVersion, + [Parameter()] [string] $ProductName = "", + [Parameter()] [string] $ProductVersion = "", [Parameter(Mandatory)] [string] $OutputPath, [Parameter()] [string] $Format = "cyclonedx", [Parameter()] [string] $SchemaVersion = "1.5", diff --git a/artifacts/linux/action-docs.json b/artifacts/linux/action-docs.json index 778b61cc..06f2ede2 100644 --- a/artifacts/linux/action-docs.json +++ b/artifacts/linux/action-docs.json @@ -585,6 +585,71 @@ } } }, + "Invoke-GenerateSbom": { + "description": "Generates a CycloneDX SBOM for a LabVIEW build spec using `vipm sbom`. Assumes VIPM CLI is already installed/activated and LabVIEW is available on the runner; the caller is responsible for that (same as ni/labview-icon-editor#540's own \"Install VIPM CLI\" step). LvprojPath: Path to the .lvproj file to scan. LabVIEWVersion: LabVIEW version (YYYY). LabVIEWBitness: \"32\" or \"64\". BuildSpecName: Name of the build specification to scope the SBOM to. TargetName: LabVIEW project target containing the build spec (default: \"My Computer\"). ProductName: Name recorded in the SBOM's metadata.component. ProductVersion: Version recorded in the SBOM's metadata.component. OutputPath: Output file path for the generated SBOM. Format: SBOM output format (default: \"cyclonedx\"). SchemaVersion: CycloneDX schema version (default: \"1.5\"). DryRun: If set, prints the command instead of executing it.", + "parameters": { + "BuildSpecName": { + "type": "string", + "required": true, + "description": "Name of the build specification to scope the SBOM to" + }, + "DryRun": { + "type": "boolean", + "required": false, + "description": "If set, prints the command instead of executing it" + }, + "Format": { + "type": "string", + "required": false, + "default": "cyclonedx", + "description": "SBOM output format (default: \"cyclonedx\")" + }, + "LabVIEWBitness": { + "type": "string", + "required": true, + "description": "\"32\" or \"64\"" + }, + "LabVIEWVersion": { + "type": "string", + "required": true, + "description": "LabVIEW version (YYYY)" + }, + "LvprojPath": { + "type": "string", + "required": true, + "description": "Path to the" + }, + "OutputPath": { + "type": "string", + "required": true, + "description": "Output file path for the generated SBOM" + }, + "ProductName": { + "type": "string", + "required": false, + "default": "", + "description": "Name recorded in the SBOM's metadata" + }, + "ProductVersion": { + "type": "string", + "required": false, + "default": "", + "description": "Version recorded in the SBOM's metadata" + }, + "SchemaVersion": { + "type": "string", + "required": false, + "default": "1.5", + "description": "CycloneDX schema version (default: \"1" + }, + "TargetName": { + "type": "string", + "required": false, + "default": "My Computer", + "description": "LabVIEW project target containing the build spec (default: \"My Computer\")" + } + } + }, "Invoke-MissingInProject": { "description": "Lists files referenced in a LabVIEW project that are missing on disk. LVVersion: LabVIEW version of the project. SupportedBitness: Target LabVIEW bitness (32- or 64-bit). ProjectFile: Path to the .lvproj file to analyze. DryRun: If set, prints the command instead of executing it. gcliPath: Optional path prepended to PATH for locating the g CLI.", "parameters": { @@ -1892,6 +1957,99 @@ "type": "string" } ], + "generate-sbom": [ + { + "name": "lvproj_path", + "description": "Path to the .lvproj file to scan.", + "required": true, + "default": "", + "type": "string" + }, + { + "name": "labview_version", + "description": "LabVIEW version (YYYY).", + "required": true, + "default": "", + "type": "string" + }, + { + "name": "labview_bitness", + "description": "\"32\" or \"64\".", + "required": true, + "default": "", + "type": "string" + }, + { + "name": "build_spec_name", + "description": "Name of the build specification to scope the SBOM to.", + "required": true, + "default": "", + "type": "string" + }, + { + "name": "target_name", + "description": "LabVIEW project target containing the build spec.", + "required": false, + "default": "My Computer", + "type": "string" + }, + { + "name": "product_name", + "description": "Name recorded in the SBOM metadata.component.", + "required": true, + "default": "", + "type": "string" + }, + { + "name": "product_version", + "description": "Version recorded in the SBOM metadata.component.", + "required": true, + "default": "", + "type": "string" + }, + { + "name": "output_path", + "description": "Output file path for the generated SBOM.", + "required": true, + "default": "", + "type": "string" + }, + { + "name": "format", + "description": "SBOM output format.", + "required": false, + "default": "cyclonedx", + "type": "string" + }, + { + "name": "schema_version", + "description": "CycloneDX schema version.", + "required": false, + "default": "1.5", + "type": "string" + }, + { + "name": "working_directory", + "description": "Working directory where the action will run.", + "required": false, + "default": "", + "type": "string" + }, + { + "name": "log_level", + "description": "Verbosity level (ERROR|WARN|INFO|DEBUG).", + "required": false, + "default": "INFO", + "type": "string" + }, + { + "name": "dry_run", + "description": "If true, simulate the action without side effects.", + "required": false, + "default": false, + "type": "string" + } + ], "missing-in-project": [ { "name": "lv_version", diff --git a/artifacts/linux/action-docs.md b/artifacts/linux/action-docs.md index 1509afdf..1ac1f01c 100644 --- a/artifacts/linux/action-docs.md +++ b/artifacts/linux/action-docs.md @@ -203,6 +203,26 @@ Generates a release notes file from the project's metadata. OutputPath: Path whe pwsh ./actions/Invoke-OSAction.ps1 -ActionName Invoke-GenerateReleaseNotes -ArgsJson '{}' ``` +#### Invoke-GenerateSbom +Generates a CycloneDX SBOM for a LabVIEW build spec using `vipm sbom`. Assumes VIPM CLI is already installed/activated and LabVIEW is available on the runner; the caller is responsible for that (same as ni/labview-icon-editor#540's own "Install VIPM CLI" step). LvprojPath: Path to the .lvproj file to scan. LabVIEWVersion: LabVIEW version (YYYY). LabVIEWBitness: "32" or "64". BuildSpecName: Name of the build specification to scope the SBOM to. TargetName: LabVIEW project target containing the build spec (default: "My Computer"). ProductName: Name recorded in the SBOM's metadata.component. ProductVersion: Version recorded in the SBOM's metadata.component. OutputPath: Output file path for the generated SBOM. Format: SBOM output format (default: "cyclonedx"). SchemaVersion: CycloneDX schema version (default: "1.5"). DryRun: If set, prints the command instead of executing it. +| Parameter | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| BuildSpecName | string | true | | Name of the build specification to scope the SBOM to | +| DryRun | boolean | false | | If set, prints the command instead of executing it | +| Format | string | false | cyclonedx | SBOM output format (default: "cyclonedx") | +| LabVIEWBitness | string | true | | "32" or "64" | +| LabVIEWVersion | string | true | | LabVIEW version (YYYY) | +| LvprojPath | string | true | | Path to the | +| OutputPath | string | true | | Output file path for the generated SBOM | +| ProductName | string | false | | Name recorded in the SBOM's metadata | +| ProductVersion | string | false | | Version recorded in the SBOM's metadata | +| SchemaVersion | string | false | 1.5 | CycloneDX schema version (default: "1 | +| TargetName | string | false | My Computer | LabVIEW project target containing the build spec (default: "My Computer") | + +```powershell +pwsh ./actions/Invoke-OSAction.ps1 -ActionName Invoke-GenerateSbom -ArgsJson '{}' +``` + #### Invoke-MissingInProject Lists files referenced in a LabVIEW project that are missing on disk. LVVersion: LabVIEW version of the project. SupportedBitness: Target LabVIEW bitness (32- or 64-bit). ProjectFile: Path to the .lvproj file to analyze. DryRun: If set, prints the command instead of executing it. gcliPath: Optional path prepended to PATH for locating the g CLI. | Parameter | Type | Required | Default | Description | @@ -579,6 +599,23 @@ server.viscripting.ShowScriptingOperationsInEditor=TRUE | log_level | string | false | INFO | Verbosity level (ERROR|WARN|INFO|DEBUG). | | dry_run | string | false | false | If true, simulate the action without side effects. | +#### generate-sbom +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| lvproj_path | string | true | | Path to the .lvproj file to scan. | +| labview_version | string | true | | LabVIEW version (YYYY). | +| labview_bitness | string | true | | "32" or "64". | +| build_spec_name | string | true | | Name of the build specification to scope the SBOM to. | +| target_name | string | false | My Computer | LabVIEW project target containing the build spec. | +| product_name | string | true | | Name recorded in the SBOM metadata.component. | +| product_version | string | true | | Version recorded in the SBOM metadata.component. | +| output_path | string | true | | Output file path for the generated SBOM. | +| format | string | false | cyclonedx | SBOM output format. | +| schema_version | string | false | 1.5 | CycloneDX schema version. | +| working_directory | string | false | | Working directory where the action will run. | +| log_level | string | false | INFO | Verbosity level (ERROR|WARN|INFO|DEBUG). | +| dry_run | string | false | false | If true, simulate the action without side effects. | + #### missing-in-project | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | diff --git a/ci_evidence.txt b/ci_evidence.txt index dd38ea0a..5fbaf889 100644 --- a/ci_evidence.txt +++ b/ci_evidence.txt @@ -1 +1 @@ -{"pipeline":"Unknown","git_sha":"5fbaea9b62f424424f5c5608977e4a5489f405be","req_status":{"REQ-023":"PASS","REQ-024":"PASS","REQ-025":"PASS","REQ-026":"PASS","REQ-027":"PASS","REQ-028":"PASS","REQ-029":"PASS","REQ-030":"PASS","REQ-031":"PASS","REQ-032":"PASS","REQ-033":"PASS"}} \ No newline at end of file +{"pipeline":"Unknown","git_sha":"e92713aec46744b7f7e2f148f3e447e3d5a5db27","req_status":{"REQ-023":"PASS","REQ-024":"PASS","REQ-025":"PASS","REQ-026":"PASS","REQ-027":"PASS","REQ-028":"PASS","REQ-029":"PASS","REQ-030":"PASS","REQ-031":"PASS","REQ-032":"PASS","REQ-033":"PASS"}} \ No newline at end of file diff --git a/dispatchers.json b/dispatchers.json index fe52ed7e..06567d2d 100644 --- a/dispatchers.json +++ b/dispatchers.json @@ -624,12 +624,14 @@ }, "ProductName": { "type": "string", - "required": true, + "required": false, + "default": "", "description": "Name recorded in the SBOM's metadata" }, "ProductVersion": { "type": "string", - "required": true, + "required": false, + "default": "", "description": "Version recorded in the SBOM's metadata" }, "SchemaVersion": { From 7bee7eb9e9c9fa516ca2454f03ddd4b2954c2e0d Mon Sep 17 00:00:00 2001 From: Amrutha G Date: Thu, 1 Oct 2026 14:40:11 +0530 Subject: [PATCH 4/4] REQ-042: Generate SBOM using Linux workflow --- .github/workflows/reusable-generate-sbom.yml | 117 ++++++++++++++----- docs/actions/generate-sbom.md | 10 +- tests/pester/GenerateSbom.Workflow.Tests.ps1 | 11 ++ 3 files changed, 106 insertions(+), 32 deletions(-) diff --git a/.github/workflows/reusable-generate-sbom.yml b/.github/workflows/reusable-generate-sbom.yml index 17e42ddd..3974388c 100644 --- a/.github/workflows/reusable-generate-sbom.yml +++ b/.github/workflows/reusable-generate-sbom.yml @@ -4,12 +4,14 @@ on: workflow_call: inputs: lvproj_path: - description: 'Path to the .lvproj file to scan.' + description: 'Path to the .lvproj file, relative to the caller repository.' required: true type: string labview_version: - required: true + description: 'Linux container release (YYYYq1 or YYYYq3; minimum 2025q3).' + required: false type: string + default: '2026q1' labview_bitness: required: true type: string @@ -27,6 +29,7 @@ on: required: true type: string output_path: + description: 'SBOM output path, relative to the caller repository.' required: true type: string format: @@ -48,38 +51,98 @@ permissions: jobs: generate-sbom: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 - # workflow_call runs in its own fresh job, so VIPM must be installed here - # even if the caller's own job already has it - self-contained by design. - - name: Install VIPM CLI + - name: Validate LabVIEW Version for Linux Docker + id: validate-version shell: pwsh + env: + LABVIEW_VERSION_INPUT: ${{ inputs.labview_version }} run: | - $ProgressPreference = 'SilentlyContinue' - Invoke-WebRequest -Uri 'https://traffic.libsyn.com/secure/jkinc/vipm-26.3.4025-windows-setup.exe' -OutFile "$env:TEMP\vipm-setup.exe" - Start-Process -Wait -FilePath "$env:TEMP\vipm-setup.exe" -ArgumentList '/exenoui /qn' - Remove-Item "$env:TEMP\vipm-setup.exe" -Force - $vipmExe = Get-ChildItem -Path "$env:ProgramFiles", "${env:ProgramFiles(x86)}" -Filter vipm.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - if (-not $vipmExe) { throw "vipm.exe was not found under Program Files after installation." } - "$(Split-Path $vipmExe -Parent)" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - & $vipmExe --version - - uses: ni/open-source/generate-sbom@main - with: - lvproj_path: ${{ inputs.lvproj_path }} - labview_version: ${{ inputs.labview_version }} - labview_bitness: ${{ inputs.labview_bitness }} - build_spec_name: ${{ inputs.build_spec_name }} - target_name: ${{ inputs.target_name }} - product_name: ${{ inputs.product_name }} - product_version: ${{ inputs.product_version }} - output_path: ${{ inputs.output_path }} - format: ${{ inputs.format }} - schema_version: ${{ inputs.schema_version }} + $lvVersion = $env:LABVIEW_VERSION_INPUT + if ($lvVersion -match '^(\d{4})q([13])$') { + $year = [int]$Matches[1] + $quarter = [int]$Matches[2] + } else { + throw "Invalid LabVIEW version format: $lvVersion. Expected YYYYq1 or YYYYq3 (for example, 2026q1)." + } + + $isValid = ($year -gt 2025) -or (($year -eq 2025) -and ($quarter -ge 3)) + if (-not $isValid) { + throw "LabVIEW version $lvVersion is not available in Linux Docker. Minimum supported version is 2025q3." + } + + "year=$year" >> $env:GITHUB_OUTPUT + - name: Generate SBOM in LabVIEW Linux container + shell: pwsh + env: + LABVIEW_CONTAINER_VERSION: ${{ inputs.labview_version }} + LABVIEW_YEAR: ${{ steps.validate-version.outputs.year }} + LVPROJ_PATH: ${{ inputs.lvproj_path }} + LABVIEW_BITNESS: ${{ inputs.labview_bitness }} + BUILD_SPEC_NAME: ${{ inputs.build_spec_name }} + TARGET_NAME: ${{ inputs.target_name }} + PRODUCT_NAME: ${{ inputs.product_name }} + PRODUCT_VERSION: ${{ inputs.product_version }} + OUTPUT_PATH: ${{ inputs.output_path }} + SBOM_FORMAT: ${{ inputs.format }} + SBOM_SCHEMA_VERSION: ${{ inputs.schema_version }} + run: | + $containerScript = @' + set -e + apt-get update && apt-get install -y wget + wget --no-check-certificate -O /tmp/vipm.deb https://traffic.libsyn.com/secure/jkinc/vipm_26.3.1-4025_amd64.deb + dpkg -i /tmp/vipm.deb + vipm --version + output_file="/workspace/$OUTPUT_PATH" + mkdir -p "$(dirname "$output_file")" + timeout 300 xvfb-run -a vipm sbom "/workspace/$LVPROJ_PATH" \ + --format "$SBOM_FORMAT" \ + --schema-version "$SBOM_SCHEMA_VERSION" \ + --labview-version "$LABVIEW_YEAR" \ + --labview-bitness "$LABVIEW_BITNESS" \ + --lvproj-build-spec "$BUILD_SPEC_NAME" \ + --lvproj-target "$TARGET_NAME" \ + --product-name "$PRODUCT_NAME" \ + --product-version "$PRODUCT_VERSION" \ + --output "$output_file" + '@ + + $workspacePath = $env:GITHUB_WORKSPACE + $labviewImage = "nationalinstruments/labview:$($env:LABVIEW_CONTAINER_VERSION)-linux" + docker run --rm ` + --volume "${workspacePath}:/workspace" ` + --env LVPROJ_PATH ` + --env LABVIEW_YEAR ` + --env LABVIEW_BITNESS ` + --env BUILD_SPEC_NAME ` + --env TARGET_NAME ` + --env PRODUCT_NAME ` + --env PRODUCT_VERSION ` + --env OUTPUT_PATH ` + --env SBOM_FORMAT ` + --env SBOM_SCHEMA_VERSION ` + $labviewImage ` + bash -c $containerScript + + if ($LASTEXITCODE -ne 0) { + throw "SBOM generation inside the LabVIEW Linux container failed with exit code $LASTEXITCODE" + } + - name: Verify SBOM exists + shell: pwsh + env: + OUTPUT_PATH: ${{ inputs.output_path }} + run: | + $sbomPath = Join-Path $env:GITHUB_WORKSPACE $env:OUTPUT_PATH + if (-not (Test-Path -Path $sbomPath -PathType Leaf)) { + throw "No SBOM file found at '$sbomPath'. Ensure vipm sbom completed successfully." + } + Write-Host "SBOM verified: $sbomPath" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 if: success() with: name: ${{ inputs.artifact_name }} path: ${{ inputs.output_path }} retention-days: 90 - if-no-files-found: error + if-no-files-found: error \ No newline at end of file diff --git a/docs/actions/generate-sbom.md b/docs/actions/generate-sbom.md index d0eda168..de937ae2 100644 --- a/docs/actions/generate-sbom.md +++ b/docs/actions/generate-sbom.md @@ -2,7 +2,7 @@ ## Purpose -Generate a CycloneDX Software Bill of Materials (SBOM) for a LabVIEW build specification using `vipm sbom`. Minimal implementation ported from [ni/labview-icon-editor#540](https://github.com/ni/labview-icon-editor/pull/540): requires VIPM CLI to be available on the runner and LabVIEW to be available for the target project. The reusable workflow installs VIPM itself, and additional execution modes (Docker-based builds, headless LabVIEW/VI Server warm-up) can be added later if a real consumer requires them. +Generate a CycloneDX Software Bill of Materials (SBOM) for a LabVIEW build specification using `vipm sbom`. The reusable workflow runs on `ubuntu-latest` and invokes VIPM inside the matching `nationalinstruments/labview:-linux` container, following the Linux flow validated in [ni/labview-icon-editor#540](https://github.com/ni/labview-icon-editor/pull/540). The standalone composite action remains available for callers that already have VIPM and LabVIEW configured. ## Parameters @@ -43,7 +43,7 @@ Common parameters are described in [Common parameters](../common-parameters.md). ## Prerequisites -This action does **not** install or activate VIPM, and does not set up LabVIEW. The caller's workflow (or the reusable workflow below) must ensure `vipm` is on `PATH` and LabVIEW is available before this step runs. +The standalone composite action does **not** install or activate VIPM, and does not set up LabVIEW. The reusable Linux workflow is self-contained: it checks out the caller repository, starts the LabVIEW Linux container, installs VIPM from its Debian package, and generates the SBOM there. Its `labview_version` input is the container release (`YYYYq1` or `YYYYq3`, minimum `2025q3`); the workflow derives the year passed to VIPM. ## Examples @@ -78,9 +78,9 @@ pwsh -File actions/Invoke-OSAction.ps1 -ActionName generate-sbom -ArgsJson '{ output_path: 'builds/lv_icon_editor_Editor Packed Library.cdx.sbom.json' ``` -### Example consumer workflow (reusable workflow) +### Reusable workflow (Linux container) -The reusable workflow is self-contained (it installs VIPM CLI itself, since `workflow_call` runs in its own fresh job) and also uploads the SBOM as an artifact: +Actor Framework, Icon Editor, and other consumer repositories can call the reusable workflow. It uploads the generated SBOM as an artifact retained for 90 days: ```yaml name: Generate SBOM @@ -95,7 +95,7 @@ jobs: uses: ni/open-source/.github/workflows/reusable-generate-sbom.yml@v1 with: lvproj_path: lv_icon_editor.lvproj - labview_version: '2026' + labview_version: '2026q1' labview_bitness: '64' build_spec_name: 'Editor Packed Library' product_name: 'Icon Editor Packed Library' diff --git a/tests/pester/GenerateSbom.Workflow.Tests.ps1 b/tests/pester/GenerateSbom.Workflow.Tests.ps1 index 1cf1f6f7..7dd951dc 100644 --- a/tests/pester/GenerateSbom.Workflow.Tests.ps1 +++ b/tests/pester/GenerateSbom.Workflow.Tests.ps1 @@ -24,6 +24,17 @@ Describe 'GenerateSbom.Workflow' { Test-Path $scriptPath | Should -Be $true } + It 'uses the tested Linux LabVIEW container flow [REQ-042]' -Tag 'REQ-042' { + $workflowPath = Join-Path $repoRoot '.github' 'workflows' 'reusable-generate-sbom.yml' + $workflow = Get-Content $workflowPath -Raw + + $workflow | Should -Match 'runs-on:\s*ubuntu-latest' + $workflow | Should -Match 'vipm_26\.3\.1-4025_amd64\.deb' + $workflow | Should -Match 'dpkg -i /tmp/vipm\.deb' + $workflow | Should -Match 'timeout 300 xvfb-run -a vipm sbom' + $workflow | Should -Match 'actions/upload-artifact@' + } + It 'executes dry-run with only required parameters [REQ-042]' -Tag 'REQ-042' { $result = & "$repoRoot/actions/Invoke-OSAction.ps1" ` -ActionName 'generate-sbom' `