mirror of
https://github.com/hashicorp/packer.git
synced 2026-10-08 15:40:27 -04:00
Merge branch 'poc/provisioner-hcp-sbom' into poc/with-api-imported
This commit is contained in:
+2
-2
@@ -28,7 +28,7 @@ import (
|
||||
shelllocalpostprocessor "github.com/hashicorp/packer/post-processor/shell-local"
|
||||
breakpointprovisioner "github.com/hashicorp/packer/provisioner/breakpoint"
|
||||
fileprovisioner "github.com/hashicorp/packer/provisioner/file"
|
||||
hcp_sbomprovisioner "github.com/hashicorp/packer/provisioner/hcp_sbom"
|
||||
hcpsbomprovisioner "github.com/hashicorp/packer/provisioner/hcp-sbom"
|
||||
powershellprovisioner "github.com/hashicorp/packer/provisioner/powershell"
|
||||
shellprovisioner "github.com/hashicorp/packer/provisioner/shell"
|
||||
shelllocalprovisioner "github.com/hashicorp/packer/provisioner/shell-local"
|
||||
@@ -49,7 +49,7 @@ var Builders = map[string]packersdk.Builder{
|
||||
var Provisioners = map[string]packersdk.Provisioner{
|
||||
"breakpoint": new(breakpointprovisioner.Provisioner),
|
||||
"file": new(fileprovisioner.Provisioner),
|
||||
"hcp_sbom": new(hcp_sbomprovisioner.Provisioner),
|
||||
"hcp-sbom": new(hcpsbomprovisioner.Provisioner),
|
||||
"powershell": new(powershellprovisioner.Provisioner),
|
||||
"shell": new(shellprovisioner.Provisioner),
|
||||
"shell-local": new(shelllocalprovisioner.Provisioner),
|
||||
|
||||
@@ -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,10 +58,12 @@ 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
|
||||
github.com/shirou/gopsutil/v3 v3.23.4
|
||||
github.com/spdx/tools-golang v0.5.5
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -78,6 +80,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
|
||||
@@ -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=
|
||||
@@ -38,6 +40,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antchfx/xmlquery v1.3.5 h1:I7TuBRqsnfFuL11ruavGm911Awx9IqSdiU6W/ztSmVw=
|
||||
@@ -78,6 +82,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=
|
||||
@@ -495,13 +501,17 @@ github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2
|
||||
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
|
||||
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
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=
|
||||
@@ -513,8 +523,11 @@ 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=
|
||||
@@ -535,6 +548,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=
|
||||
@@ -757,3 +774,4 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
||||
@@ -516,7 +516,7 @@ func (cfg *PackerConfig) getCoreBuildProvisioner(source SourceUseBlock, pb *Prov
|
||||
}
|
||||
}
|
||||
|
||||
if pb.PType == "hcp_sbom" {
|
||||
if pb.PType == "hcp-sbom" {
|
||||
provisioner = &packer.SBOMInternalProvisioner{
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
|
||||
+15
-9
@@ -51,13 +51,18 @@ type CoreBuild struct {
|
||||
l sync.Mutex
|
||||
prepareCalled bool
|
||||
|
||||
SBOMFilesCompressed [][]byte
|
||||
SBOMs []SBOM
|
||||
}
|
||||
|
||||
type SBOM struct {
|
||||
Format string
|
||||
CompressedData []byte
|
||||
}
|
||||
|
||||
type BuildMetadata struct {
|
||||
PackerVersion string
|
||||
Plugins map[string]PluginDetails
|
||||
SBOMs [][]byte
|
||||
SBOMs []SBOM
|
||||
}
|
||||
|
||||
func (b *CoreBuild) getPluginsMetadata() map[string]PluginDetails {
|
||||
@@ -91,7 +96,7 @@ func (b *CoreBuild) GetMetadata() BuildMetadata {
|
||||
metadata := BuildMetadata{
|
||||
PackerVersion: version.FormattedVersion(),
|
||||
Plugins: b.getPluginsMetadata(),
|
||||
SBOMs: b.SBOMFilesCompressed,
|
||||
SBOMs: b.SBOMs,
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
@@ -304,13 +309,14 @@ 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 {
|
||||
sbom := SBOM{
|
||||
Format: sbomInternalProvisioner.SBOMFormat,
|
||||
CompressedData: sbomInternalProvisioner.CompressedData,
|
||||
}
|
||||
b.SBOMFilesCompressed = append(b.SBOMFilesCompressed, sbomInternalProvisioner.CompressedData)
|
||||
b.SBOMs = append(b.SBOMs, sbom)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -297,7 +297,7 @@ func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName
|
||||
}
|
||||
}
|
||||
|
||||
if rawP.Type == "hcp_sbom" {
|
||||
if rawP.Type == "hcp-sbom" {
|
||||
provisioner = &SBOMInternalProvisioner{
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
|
||||
+41
-22
@@ -6,10 +6,11 @@ package packer
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
hcpSbomProvisioner "github.com/hashicorp/packer/provisioner/hcp-sbom"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
|
||||
"time"
|
||||
@@ -240,12 +241,14 @@ func (p *DebuggedProvisioner) Provision(ctx context.Context, ui packersdk.Ui, co
|
||||
return p.Provisioner.Provision(ctx, ui, comm, generatedData)
|
||||
}
|
||||
|
||||
// SBOMInternalProvisioner is a Provisioner implementation that waits until a key
|
||||
// press before the provisioner is actually run.
|
||||
// SBOMInternalProvisioner is a wrapper provisioner for the `hcp-sbom` provisioner
|
||||
// that sets the path for SBOM file download and, after the successful execution of
|
||||
// the `hcp-sbom` provisioner, compresses the SBOM and prepares the data for API
|
||||
// integration.
|
||||
type SBOMInternalProvisioner struct {
|
||||
Provisioner packersdk.Provisioner
|
||||
TempFileLoc string
|
||||
CompressedData []byte
|
||||
SBOMFormat string
|
||||
}
|
||||
|
||||
func (p *SBOMInternalProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.ConfigSpec() }
|
||||
@@ -258,61 +261,77 @@ func (p *SBOMInternalProvisioner) Provision(
|
||||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
generatedData map[string]interface{},
|
||||
) error {
|
||||
// Get the current working directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current working directory for Packer SBOM: %s", err)
|
||||
}
|
||||
|
||||
// Create a temporary file in the current working directory
|
||||
tmpFile, err := os.CreateTemp(cwd, "packer-sbom-*.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create internal temporary file for Packer SBOM: %s", err)
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
|
||||
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)
|
||||
sbomFormat, err := p.getSBOMFormat(tmpFile.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compressedData, err := p.compressFile(tmpFile.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.CompressedData = compressedData
|
||||
p.SBOMFormat = sbomFormat
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SBOMInternalProvisioner) compressFile(filePath string) ([]byte, error) {
|
||||
sourceFile, err := os.Open(filePath)
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
data, err := io.ReadAll(sourceFile)
|
||||
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
|
||||
}
|
||||
|
||||
func (p *SBOMInternalProvisioner) getSBOMFormat(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open SBOM file %s: %w", filePath, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
format, err := hcpSbomProvisioner.ValidateSBOM(file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to detect SBOM format: %w", err)
|
||||
}
|
||||
|
||||
return format, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate packer-sdc struct-markdown
|
||||
|
||||
package hcp_sbom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
// 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 {
|
||||
config Config
|
||||
}
|
||||
|
||||
func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec {
|
||||
return p.config.FlatMapstructure().HCL2Spec()
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
PluginType: "hcp-sbom",
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
if p.config.Source == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("source must be specified"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(
|
||||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
generatedData map[string]interface{},
|
||||
) error {
|
||||
log.Println("Starting to provision with `hcp-sbom` provisioner")
|
||||
|
||||
if generatedData == nil {
|
||||
generatedData = make(map[string]interface{})
|
||||
}
|
||||
p.config.ctx.Data = generatedData
|
||||
|
||||
downloadErr := p.downloadAndValidateSBOM(ui, comm, generatedData)
|
||||
if downloadErr != nil {
|
||||
return fmt.Errorf("failed to download SBOM file: %w", downloadErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadAndValidateSBOM handles downloading SBOM files for the User and Packer.
|
||||
func (p *Provisioner) downloadAndValidateSBOM(
|
||||
ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{},
|
||||
) error {
|
||||
src, err := interpolate.Render(p.config.Source, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error interpolating SBOM source: %s", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err = comm.Download(src, &buf); err != nil {
|
||||
ui.Errorf("download failed for SBOM file: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(buf.Bytes())
|
||||
if _, err = ValidateSBOM(reader); err != nil {
|
||||
ui.Errorf("validation failed for SBOM file: %s", err)
|
||||
return err
|
||||
}
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// SBOM for Packer
|
||||
pkrDst, err := p.getPackerDestination(generatedData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get Packer SBOM destination: %s", err)
|
||||
}
|
||||
|
||||
err = p.writeToFile(reader, pkrDst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download Packer SBOM: %s", err)
|
||||
}
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Packer SBOM file successfully downloaded to: %s\n", pkrDst)
|
||||
|
||||
// SBOM for User
|
||||
usrDst, err := p.getUserDestination()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine user SBOM destination: %s", err)
|
||||
}
|
||||
|
||||
if usrDst != "" {
|
||||
err = p.writeToFile(reader, usrDst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download User SBOM: %s", err)
|
||||
}
|
||||
log.Printf("User SBOM file successfully downloaded to: %s\n", usrDst)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUserDestination determines and returns the destination path for the user SBOM file.
|
||||
func (p *Provisioner) getUserDestination() (string, error) {
|
||||
dst := p.config.Destination
|
||||
if dst == "" {
|
||||
log.Println("skipped downloading user SBOM file because 'Destination' is not provided")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
dst, err := interpolate.Render(dst, &p.config.ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error interpolating SBOM file destination for user: %s", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("failed to stat destination for user SBOM: %s\n", err)
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// getPackerDestination retrieves the destination path for the Packer SBOM file.
|
||||
func (p *Provisioner) getPackerDestination(generatedData map[string]interface{}) (string, error) {
|
||||
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")
|
||||
}
|
||||
|
||||
// Ensure the destination directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return "", fmt.Errorf("failed to create destination directory for Packer SBOM: %w", err)
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) writeToFile(buf *bytes.Reader, dst string) error {
|
||||
// Open the destination file
|
||||
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open destination file for SBOM: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Write the buffer content to the destination file
|
||||
if _, err = buf.WriteTo(f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package hcp_sbom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
spdxjson "github.com/spdx/tools-golang/json"
|
||||
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrorType represents the type of validation error.
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
ParsingErr ErrorType = "parsing"
|
||||
ValidationErr ErrorType = "validation"
|
||||
)
|
||||
|
||||
// ValidationError represents an error encountered while validating an SBOM.
|
||||
type ValidationError struct {
|
||||
Type ErrorType
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf(" %s error: %v", e.Type, e.Err)
|
||||
}
|
||||
|
||||
func (e *ValidationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// ValidateCycloneDX is a validation for CycloneDX in JSON format.
|
||||
func ValidateCycloneDX(content io.Reader) error {
|
||||
decoder := cyclonedx.NewBOMDecoder(content, cyclonedx.BOMFileFormatJSON)
|
||||
bom := new(cyclonedx.BOM)
|
||||
if err := decoder.Decode(bom); err != nil {
|
||||
return &ValidationError{
|
||||
Type: ParsingErr,
|
||||
Err: fmt.Errorf("error parsing CycloneDX SBOM: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
if bom.BOMFormat != "CycloneDX" {
|
||||
return &ValidationError{
|
||||
Type: ValidationErr,
|
||||
Err: fmt.Errorf("invalid bomFormat: %s, expected CycloneDX", bom.BOMFormat),
|
||||
}
|
||||
}
|
||||
if bom.SpecVersion.String() == "" {
|
||||
return &ValidationError{
|
||||
Type: ValidationErr,
|
||||
Err: fmt.Errorf("specVersion is required"),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSPDX is a validation for SPDX in JSON format.
|
||||
func ValidateSPDX(content io.Reader) error {
|
||||
doc, err := spdxjson.Read(content)
|
||||
if err != nil {
|
||||
return &ValidationError{
|
||||
Type: ParsingErr,
|
||||
Err: fmt.Errorf("error parsing SPDX JSON file: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
if doc.SPDXVersion == "" {
|
||||
return &ValidationError{
|
||||
Type: ValidationErr,
|
||||
Err: fmt.Errorf("missing SPDXVersion"),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSBOM validates the SBOM file and returns the format of the SBOM.
|
||||
func ValidateSBOM(content io.Reader) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, content); err != nil {
|
||||
return "", fmt.Errorf("failed to copy content: %s", err)
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(buf.Bytes())
|
||||
|
||||
// Try validating as SPDX
|
||||
spdxErr := ValidateSPDX(reader)
|
||||
if spdxErr == nil {
|
||||
return "spdx", nil
|
||||
} else if vErr, ok := spdxErr.(*ValidationError); ok && vErr.Type == ValidationErr {
|
||||
return "", spdxErr
|
||||
}
|
||||
|
||||
// Reset the reader's position
|
||||
if _, err := reader.Seek(0, io.SeekStart); err != nil {
|
||||
return "", fmt.Errorf("failed to reset reader: %s", err)
|
||||
}
|
||||
|
||||
cycloneDxErr := ValidateCycloneDX(reader)
|
||||
if cycloneDxErr == nil {
|
||||
return "cyclonedx", nil
|
||||
} else if vErr, ok := cycloneDxErr.(*ValidationError); ok && vErr.Type == ValidationErr {
|
||||
return "", spdxErr
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("error validating SBOM file: invalid SBOM format")
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
//go:generate packer-sdc struct-markdown
|
||||
|
||||
package hcp_sbom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Source string `mapstructure:"source" required:"true"`
|
||||
Destination string `mapstructure:"destination"`
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec {
|
||||
return p.config.FlatMapstructure().HCL2Spec()
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
PluginType: "hcp-sbom",
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
if p.config.Source == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("source must be specified"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(
|
||||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
generatedData map[string]interface{},
|
||||
) error {
|
||||
ui.Say(
|
||||
fmt.Sprintf("Starting to provision with hcp-sbom using source: %s",
|
||||
p.config.Source,
|
||||
),
|
||||
)
|
||||
|
||||
if generatedData == nil {
|
||||
generatedData = make(map[string]interface{})
|
||||
}
|
||||
p.config.ctx.Data = generatedData
|
||||
|
||||
// Download the file for Packer
|
||||
destPath, downloadErr := p.downloadSBOMForPacker(ui, comm, generatedData)
|
||||
if downloadErr != nil {
|
||||
return fmt.Errorf("failed to download file: %w", downloadErr)
|
||||
}
|
||||
|
||||
// Download the file for user
|
||||
p.downloadSBOMForUser(ui, comm)
|
||||
|
||||
// Validate the file
|
||||
ui.Say(fmt.Sprintf("Validating SBOM file %s", destPath))
|
||||
validationErr := p.validateSBOM(ui, destPath)
|
||||
if validationErr != nil {
|
||||
return fmt.Errorf("failed to validate SBOM file: %w", validationErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadSBOMForPacker downloads SBOM from a specified source to a local
|
||||
// destination set by internal SBOM provisioner. It works with all communicators
|
||||
// from packersdk.
|
||||
func (p *Provisioner) downloadSBOMForPacker(
|
||||
ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{},
|
||||
) (string, error) {
|
||||
src, err := interpolate.Render(p.config.Source, &p.config.ctx)
|
||||
if err != nil {
|
||||
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 := 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
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 {
|
||||
ui.Error(fmt.Sprintf("download failed for Packer SBOM file: %s", err))
|
||||
return dst, err
|
||||
}
|
||||
|
||||
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.
|
||||
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
|
||||
dst := p.config.Destination
|
||||
if dst == "" {
|
||||
ui.Say("skipped downloading SBOM file for user because 'Destination' is not provided")
|
||||
return
|
||||
}
|
||||
|
||||
dst, err = interpolate.Render(dst, &p.config.ctx)
|
||||
if err != nil {
|
||||
ui.Say(fmt.Sprintf("error interpolating SBOM file destination: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
dst = tmpFile.Name()
|
||||
tmpFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
type SBOM struct {
|
||||
BomFormat string `json:"bomFormat"`
|
||||
SpecVersion string `json:"specVersion"`
|
||||
}
|
||||
|
||||
func (p *Provisioner) validateSBOM(ui packersdk.Ui, filePath string) error {
|
||||
sourceFile, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return 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)
|
||||
}
|
||||
|
||||
if sbom.BomFormat != "CycloneDX" {
|
||||
return fmt.Errorf("invalid bomFormat: %s", sbom.BomFormat)
|
||||
}
|
||||
|
||||
if sbom.SpecVersion == "" {
|
||||
return fmt.Errorf("specVersion is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
package hcp_sbom
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type MockUi struct {
|
||||
packer.Ui
|
||||
}
|
||||
|
||||
func (m *MockUi) Say(message string) {
|
||||
fmt.Println(message)
|
||||
}
|
||||
|
||||
func (m *MockUi) Error(message string) {
|
||||
fmt.Println("ERROR:", message)
|
||||
}
|
||||
|
||||
type MockCommunicator struct {
|
||||
packer.Communicator
|
||||
}
|
||||
|
||||
func (m *MockCommunicator) Download(src string, dst io.Writer) error {
|
||||
_, err := dst.Write([]byte("mock SBOM content"))
|
||||
return err
|
||||
}
|
||||
|
||||
func TestDownloadSBOMForPacker(t *testing.T) {
|
||||
ui := &MockUi{}
|
||||
comm := &MockCommunicator{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config Config
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Source is a dir, Dest is a dir",
|
||||
config: Config{
|
||||
Source: "mock-source/",
|
||||
Destination: "test-dir/",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Source is a json file, Destination is a dir",
|
||||
config: Config{
|
||||
Source: "mock-source/sbom.json",
|
||||
Destination: "test-dir/",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Source is a json file, Destination is a json file",
|
||||
config: Config{
|
||||
Source: "mock-source/sbom.json",
|
||||
Destination: "sbom.json",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Source is a json file, Destination is a json file in test-output-data",
|
||||
config: Config{
|
||||
Source: "mock-source/sbom.json",
|
||||
Destination: "test-output-data/sbom.json",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Source is a json file, Destination is test-output-data w/o /",
|
||||
config: Config{
|
||||
Source: "mock-source/sbom.json",
|
||||
Destination: "test-output-data",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Source is a json file, Destination is empty",
|
||||
config: Config{
|
||||
Source: "mock-source/sbom.json",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
provisioner := &Provisioner{
|
||||
config: tt.config,
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get current working directory for Packer SBOM: %s", err)
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp(cwd, "packer-sbom-*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create internal temporary file for Packer SBOM: %s", err)
|
||||
}
|
||||
generatedData := map[string]interface{}{
|
||||
"dst": tmpFile.Name(),
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
destPath, err := provisioner.downloadSBOMForPacker(ui, comm, generatedData)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(destPath); os.IsNotExist(err) {
|
||||
t.Fatalf("expected file to exist at %s", destPath)
|
||||
}
|
||||
|
||||
os.RemoveAll(destPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSBOM(t *testing.T) {
|
||||
provisioner := &Provisioner{}
|
||||
ui := &MockUi{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sbom SBOM
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "Valid SBOM",
|
||||
sbom: SBOM{
|
||||
BomFormat: "CycloneDX",
|
||||
SpecVersion: "1.0",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid BomFormat",
|
||||
sbom: SBOM{
|
||||
BomFormat: "InvalidFormat",
|
||||
SpecVersion: "1.0",
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "invalid bomFormat: InvalidFormat",
|
||||
},
|
||||
{
|
||||
name: "Empty SpecVersion",
|
||||
sbom: SBOM{
|
||||
BomFormat: "CycloneDX",
|
||||
SpecVersion: "",
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "specVersion is required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, _ := json.Marshal(tt.sbom)
|
||||
filePath := "test-sbom.json"
|
||||
os.WriteFile(filePath, data, 0644)
|
||||
defer os.Remove(filePath)
|
||||
|
||||
err := provisioner.validateSBOM(ui, filePath)
|
||||
if tt.expectError {
|
||||
if err == nil || err.Error() != tt.errorMsg {
|
||||
t.Fatalf("expected error %v, got %v", tt.errorMsg, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!-- Code generated from the comments of the Config struct in provisioner/hcp-sbom/provisioner.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `destination` (string) - 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.
|
||||
|
||||
<!-- End of code generated from the comments of the Config struct in provisioner/hcp-sbom/provisioner.go; -->
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- Code generated from the comments of the Config struct in provisioner/hcp-sbom/provisioner.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `source` (string) - 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.
|
||||
|
||||
<!-- End of code generated from the comments of the Config struct in provisioner/hcp-sbom/provisioner.go; -->
|
||||
@@ -1,5 +0,0 @@
|
||||
<!-- Code generated from the comments of the Config struct in provisioner/hcp_sbom/provisioner.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `destination` (string) - Destination
|
||||
|
||||
<!-- End of code generated from the comments of the Config struct in provisioner/hcp_sbom/provisioner.go; -->
|
||||
@@ -1,5 +0,0 @@
|
||||
<!-- Code generated from the comments of the Config struct in provisioner/hcp_sbom/provisioner.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `source` (string) - Source
|
||||
|
||||
<!-- End of code generated from the comments of the Config struct in provisioner/hcp_sbom/provisioner.go; -->
|
||||
Reference in New Issue
Block a user