diff --git a/go.mod b/go.mod index bd377d0f4..4e9aff68b 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index f2f214db7..ab4d7d655 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/packer/build.go b/packer/build.go index c1dac8944..560bcd5b5 100644 --- a/packer/build.go +++ b/packer/build.go @@ -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) } } diff --git a/packer/provisioner.go b/packer/provisioner.go index abd8faa88..e44b48d8b 100644 --- a/packer/provisioner.go +++ b/packer/provisioner.go @@ -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 } diff --git a/provisioner/hcp_sbom/provisioner.go b/provisioner/hcp_sbom/provisioner.go index 23984ec75..ee3127194 100644 --- a/provisioner/hcp_sbom/provisioner.go +++ b/provisioner/hcp_sbom/provisioner.go @@ -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") } diff --git a/provisioner/hcp_sbom/provisioner_test.go b/provisioner/hcp_sbom/provisioner_test.go index ef4475072..8509bea70 100644 --- a/provisioner/hcp_sbom/provisioner_test.go +++ b/provisioner/hcp_sbom/provisioner_test.go @@ -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", }, }