Add PR suggestions

hand-off-lucas
Devashish 2 years ago
parent 0272dc3705
commit 355112b7a8
No known key found for this signature in database
GPG Key ID: 4642E918377AE2A4

@ -26,7 +26,7 @@ require (
github.com/hashicorp/packer-plugin-amazon v1.2.1
github.com/hashicorp/packer-plugin-sdk v0.5.4
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869
github.com/klauspost/compress v1.13.6 // indirect
github.com/klauspost/compress v1.13.6
github.com/klauspost/pgzip v1.2.5
github.com/masterzen/winrm v0.0.0-20210623064412-3b76017826b0
github.com/mattn/go-runewidth v0.0.13 // indirect
@ -40,7 +40,7 @@ require (
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db // indirect
github.com/pkg/sftp v1.13.2 // indirect
github.com/posener/complete v1.2.3
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/ulikunitz/xz v0.5.10
github.com/zclconf/go-cty v1.13.3
github.com/zclconf/go-cty-yaml v1.0.1
@ -58,6 +58,7 @@ require (
)
require (
github.com/CycloneDX/cyclonedx-go v0.9.1
github.com/go-openapi/strfmt v0.21.10
github.com/oklog/ulid v1.3.1
github.com/pierrec/lz4/v4 v4.1.18

@ -20,6 +20,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns=
github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/CycloneDX/cyclonedx-go v0.9.1 h1:yffaWOZsv77oTJa/SdVZYdgAgFioCeycBUKkqS2qzQM=
github.com/CycloneDX/cyclonedx-go v0.9.1/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
@ -78,6 +80,8 @@ github.com/biogo/hts v1.4.3 h1:vir2yUTiRkPvtp6ZTpzh9lWTKQJZXJKZ563rpAQAsRM=
github.com/biogo/hts v1.4.3/go.mod h1:eW40HJ1l2ExK9C+yvvoRSftInqWsf3ue+zAEjzCGWjA=
github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk=
github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
@ -502,8 +506,9 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -515,8 +520,10 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
@ -537,6 +544,10 @@ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3k
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=

@ -304,12 +304,9 @@ func (b *CoreBuild) Run(ctx context.Context, originalUi packersdk.Ui) ([]packers
return nil, err
}
if len(b.Provisioners) > 0 {
for _, p := range b.Provisioners {
sbomInternalProvisioner, ok := p.Provisioner.(*SBOMInternalProvisioner)
if !ok {
continue
}
for _, p := range b.Provisioners {
sbomInternalProvisioner, ok := p.Provisioner.(*SBOMInternalProvisioner)
if ok {
b.SBOMFilesCompressed = append(b.SBOMFilesCompressed, sbomInternalProvisioner.CompressedData)
}
}

@ -6,7 +6,6 @@ package packer
import (
"context"
"fmt"
"io"
"log"
"os"
@ -244,7 +243,6 @@ func (p *DebuggedProvisioner) Provision(ctx context.Context, ui packersdk.Ui, co
// press before the provisioner is actually run.
type SBOMInternalProvisioner struct {
Provisioner packersdk.Provisioner
TempFileLoc string
CompressedData []byte
}
@ -269,23 +267,28 @@ func (p *SBOMInternalProvisioner) Provision(
if err != nil {
return fmt.Errorf("failed to create internal temporary file for Packer SBOM: %s", err)
}
defer tmpFile.Close()
// Close the file handle before passing the name to the underlying provisioner
tmpFileName := tmpFile.Name()
if err = tmpFile.Close(); err != nil {
return fmt.Errorf("failed to close temporary file for Packer SBOM %s: %s", tmpFileName, err)
}
defer func(name string) {
fileRemoveErr := os.Remove(name)
if fileRemoveErr != nil {
log.Printf("Error removing SBOM temporary file %s: %s", name, fileRemoveErr)
}
}(p.TempFileLoc)
}(tmpFile.Name())
generatedData["dst"] = tmpFile.Name()
p.TempFileLoc = tmpFile.Name()
err = p.Provisioner.Provision(ctx, ui, comm, generatedData)
if err != nil {
return err
}
compressedData, err := p.compressFile(p.TempFileLoc)
compressedData, err := p.compressFile(tmpFile.Name())
if err != nil {
return err
}
@ -294,25 +297,18 @@ func (p *SBOMInternalProvisioner) Provision(
}
func (p *SBOMInternalProvisioner) compressFile(filePath string) ([]byte, error) {
sourceFile, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer sourceFile.Close()
data, err := io.ReadAll(sourceFile)
data, err := os.ReadFile(filePath)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to read file %s: %w", filePath, err)
}
encoder, err := zstd.NewWriter(nil)
encoder, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create zstd encoder: %w", err)
}
defer encoder.Close()
compressedData := encoder.EncodeAll(data, nil)
fmt.Printf(fmt.Sprintf("SBOM file compressed successfully. Size: %d bytes", len(compressedData)))
log.Printf("SBOM file compressed successfully. Size: %d bytes\n", len(compressedData))
return compressedData, nil
}

@ -8,26 +8,40 @@ package hcp_sbom
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/CycloneDX/cyclonedx-go"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"path/filepath"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Source string `mapstructure:"source" required:"true"`
Destination string `mapstructure:"destination"`
ctx interpolate.Context
// Source is a required field that specifies the path to the SBOM file that
// needs to be downloaded.
// It can be a file path or a URL.
Source string `mapstructure:"source" required:"true"`
// Destination is an optional field that specifies the path where the SBOM
// file will be downloaded to for the user.
// The 'Destination' must be a writable location. If the destination is a file,
// the SBOM will be saved or overwritten at that path. If the destination is
// a directory, a file will be created within the directory to store the SBOM.
// Any parent directories for the destination must already exist and be
// writable by the provisioning user (generally not root), otherwise,
// a "Permission Denied" error will occur. If the source path is a file,
// it is recommended that the destination path be a file as well.
Destination string `mapstructure:"destination"`
ctx interpolate.Context
}
type Provisioner struct {
@ -67,7 +81,7 @@ func (p *Provisioner) Provision(
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
generatedData map[string]interface{},
) error {
ui.Say(
log.Printf(
fmt.Sprintf("Starting to provision with hcp-sbom using source: %s",
p.config.Source,
),
@ -81,14 +95,17 @@ func (p *Provisioner) Provision(
// Download the file for Packer
destPath, downloadErr := p.downloadSBOMForPacker(ui, comm, generatedData)
if downloadErr != nil {
return fmt.Errorf("failed to download file: %w", downloadErr)
return fmt.Errorf("failed to download Packer SBOM file: %w", downloadErr)
}
// Download the file for user
p.downloadSBOMForUser(ui, comm)
downloadErr = p.downloadSBOMForUser(ui, comm)
if downloadErr != nil {
return fmt.Errorf("failed to download User SBOM file: %w", downloadErr)
}
// Validate the file
ui.Say(fmt.Sprintf("Validating SBOM file %s", destPath))
log.Printf(fmt.Sprintf("Validating SBOM file: %s\n", destPath))
validationErr := p.validateSBOM(ui, destPath)
if validationErr != nil {
return fmt.Errorf("failed to validate SBOM file: %w", validationErr)
@ -108,40 +125,27 @@ func (p *Provisioner) downloadSBOMForPacker(
return p.config.Destination, fmt.Errorf("error interpolating source: %s", err)
}
// FIXME:: Do we really need this?
// Check if the source is a JSON file
if filepath.Ext(src) != ".json" {
return p.config.Destination, fmt.Errorf(
"packer SBOM source file is not a JSON file: %s", src,
)
}
// Download the file for Packer
desti, ok := generatedData["dst"] // this has been set by HCPSBOMInternalProvisioner.Provision
if !ok {
return "", fmt.Errorf("failed to find location for Packer SBOM file")
dst, ok := generatedData["dst"].(string) // this has been set by HCPSBOMInternalProvisioner.Provision
if !ok || dst == "" {
return "", fmt.Errorf("destination path for Packer SBOM file is not valid")
}
dst := fmt.Sprintf("%v", desti)
// Ensure the destination directory exists
dir := filepath.Dir(dst)
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
return dst, fmt.Errorf("failed to create destination directory for Packer SBOM: %s", err)
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return dst, fmt.Errorf("failed to create destination directory for Packer SBOM: %w", err)
}
// Open the destination file
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return dst, fmt.Errorf("failed to open destination file: %s", err)
return dst, fmt.Errorf("failed to open destination file for Packer SBOM: %s", err)
}
defer f.Close()
// Create MultiWriter for the current progress
pf := io.MultiWriter(f)
// Download the file
ui.Say(fmt.Sprintf("Downloading SBOM file %s for Packer => %s", src, dst))
if err = comm.Download(src, pf); err != nil {
if err = comm.Download(src, f); err != nil {
ui.Error(fmt.Sprintf("download failed for Packer SBOM file: %s", err))
return dst, err
}
@ -149,101 +153,84 @@ func (p *Provisioner) downloadSBOMForPacker(
return dst, nil
}
// downloadSBOMForUser downloads a SBOM from a specified source to a local
// destination given by user. It works with all communicators from packersdk.
// downloadSBOMForUser downloads a Software Bill of Materials (SBOM) file from a specified source
// to a local destination path on the machine.
func (p *Provisioner) downloadSBOMForUser(
ui packersdk.Ui, comm packersdk.Communicator,
) {
src, err := interpolate.Render(p.config.Source, &p.config.ctx)
if err != nil {
ui.Say(fmt.Sprintf("error interpolating source: %s", err))
return
}
// Determine the destination path
) error {
dst := p.config.Destination
if dst == "" {
ui.Say("skipped downloading SBOM file for user because 'Destination' is not provided")
return
log.Println("skipped downloading user SBOM file because 'Destination' is not provided")
return nil
}
dst, err = interpolate.Render(dst, &p.config.ctx)
dst, err := interpolate.Render(dst, &p.config.ctx)
if err != nil {
ui.Say(fmt.Sprintf("error interpolating SBOM file destination: %s", err))
return
return fmt.Errorf("error interpolating SBOM file destination from user: %s\n", err)
}
if strings.HasSuffix(dst, "/") {
info, err := os.Stat(dst)
if err != nil {
ui.Say(fmt.Sprintf("failed to stat destination for SBOM: %s", err))
return
}
src, err := interpolate.Render(p.config.Source, &p.config.ctx)
if err != nil {
return fmt.Errorf("error interpolating source: %s", err)
}
if info.IsDir() {
tmpFile, err := os.CreateTemp(dst, "packer-user-sbom-*.json")
if err != nil {
ui.Say(fmt.Sprintf("failed to create file for Packer SBOM: %s", err))
return
// Check if the destination exists and determine its type
info, err := os.Stat(dst)
if err != nil {
if os.IsNotExist(err) {
// If destination doesn't exist, assume it's a file path and ensure parent directories are created
dir := filepath.Dir(dst)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create destination directory for user SBOM: %s\n", err)
}
dst = tmpFile.Name()
tmpFile.Close()
} else {
return fmt.Errorf("failed to stat destination for user SBOM: %s\n", err)
}
}
// Ensure the destination directory exists
dir := filepath.Dir(dst)
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
ui.Say(fmt.Sprintf("failed to create destination directory for Packer SBOM: %s", err))
return
} else if info.IsDir() {
// If the destination is a directory, create a temporary file inside it
tmpFile, err := os.CreateTemp(dst, "packer-user-sbom-*.json")
if err != nil {
return fmt.Errorf("failed to create temporary file in user SBOM directory %s: %s", dst, err)
}
dst = tmpFile.Name()
tmpFile.Close()
}
// Open the destination file
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
ui.Say(fmt.Sprintf("failed to open destination file: %s", err))
return
return fmt.Errorf("failed to open destination file for user SBOM: %s", err)
}
defer f.Close()
// Create MultiWriter for the current progress
pf := io.MultiWriter(f)
// Download the file
ui.Say(fmt.Sprintf("Downloading SBOM file for user %s => %s", src, dst))
if err = comm.Download(src, pf); err != nil {
ui.Error(fmt.Sprintf("download failed for user SBOM file: %s", err))
return
if err = comm.Download(src, f); err != nil {
return fmt.Errorf("download failed for user SBOM file: %s", err)
}
}
type SBOM struct {
BomFormat string `json:"bomFormat"`
SpecVersion string `json:"specVersion"`
ui.Say(fmt.Sprintf("User SBOM file successfully downloaded to: %s\n", dst))
return nil
}
// validateSBOM validates CycloneDX SBOM files
func (p *Provisioner) validateSBOM(ui packersdk.Ui, filePath string) error {
sourceFile, err := os.Open(filePath)
if err != nil {
return err
return fmt.Errorf("failed to open file %s: %w", filePath, err)
}
defer sourceFile.Close()
data, err := io.ReadAll(sourceFile)
if err != nil {
return err
}
var sbom SBOM
if err := json.Unmarshal(data, &sbom); err != nil {
return fmt.Errorf("failed to unmarshal JSON: %w", err)
decoder := cyclonedx.NewBOMDecoder(sourceFile, cyclonedx.BOMFileFormatJSON)
bom := new(cyclonedx.BOM)
if err := decoder.Decode(bom); err != nil {
return fmt.Errorf("failed to decode CycloneDX SBOM: %w", err)
}
if sbom.BomFormat != "CycloneDX" {
return fmt.Errorf("invalid bomFormat: %s", sbom.BomFormat)
if bom.BOMFormat != "CycloneDX" {
return fmt.Errorf("invalid bomFormat: %s, expected CycloneDX", bom.BOMFormat)
}
if sbom.SpecVersion == "" {
if bom.SpecVersion.String() == "" {
return fmt.Errorf("specVersion is required")
}

@ -136,35 +136,35 @@ func TestValidateSBOM(t *testing.T) {
tests := []struct {
name string
sbom SBOM
sbom map[string]interface{}
expectError bool
errorMsg string
}{
{
name: "Valid SBOM",
sbom: SBOM{
BomFormat: "CycloneDX",
SpecVersion: "1.0",
sbom: map[string]interface{}{
"bomFormat": "CycloneDX",
"specVersion": "1.0",
},
expectError: false,
},
{
name: "Invalid BomFormat",
sbom: SBOM{
BomFormat: "InvalidFormat",
SpecVersion: "1.0",
sbom: map[string]interface{}{
"bomFormat": "InvalidFormat",
"specVersion": "1.0",
},
expectError: true,
errorMsg: "invalid bomFormat: InvalidFormat",
errorMsg: "invalid bomFormat: InvalidFormat, expected CycloneDX",
},
{
name: "Empty SpecVersion",
sbom: SBOM{
BomFormat: "CycloneDX",
SpecVersion: "",
sbom: map[string]interface{}{
"bomFormat": "CycloneDX",
"specVersion": "",
},
expectError: true,
errorMsg: "specVersion is required",
errorMsg: "failed to decode CycloneDX SBOM: invalid specification version",
},
}

Loading…
Cancel
Save