Modify external provisioner

This commit is contained in:
Devashish
2024-11-11 17:04:00 -05:00
committed by Lucas Bajolet
parent c73ea49408
commit 4d341df7d3
2 changed files with 83 additions and 117 deletions
+81 -70
View File
@@ -16,7 +16,6 @@ import (
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/klauspost/compress/zstd"
"io"
"os"
"path/filepath"
@@ -64,7 +63,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
func (p *Provisioner) Provision(
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{},
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",
@@ -77,13 +77,15 @@ func (p *Provisioner) Provision(
}
p.config.ctx.Data = generatedData
// Download the file
destPath, downloadErr := p.downloadSBOM(ui, comm)
// defer os.Remove(destPath)
// 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)
@@ -91,29 +93,21 @@ func (p *Provisioner) Provision(
return fmt.Errorf("failed to validate SBOM file: %w", validationErr)
}
// Compress the file
ui.Say(fmt.Sprintf("Compressing SBOM file %s", destPath))
_, compessionErr := p.compressFile(ui, destPath)
if compessionErr != nil {
return fmt.Errorf("failed to compress file: %w", compessionErr)
}
// Future: send compressedData to the internal API as per RFC
// ...
return nil
}
// downloadSBOM downloads a Software Bill of Materials (SBOM) from a specified
// source to a local destination. It works with all communicators from packersdk.
// The method returns the path to the downloaded file or an error if any issues
// occur during the download process.
func (p *Provisioner) downloadSBOM(ui packersdk.Ui, comm packersdk.Communicator) (string, error) {
// 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(
@@ -121,40 +115,13 @@ func (p *Provisioner) downloadSBOM(ui packersdk.Ui, comm packersdk.Communicator)
)
}
// Determine the destination path
dst := p.config.Destination
if dst == "" {
tmpFile, err := os.CreateTemp("", "packer-sbom-*.json")
if err != nil {
return dst, fmt.Errorf(
"failed to create file for Packer SBOM: %s", err,
)
}
dst = tmpFile.Name()
tmpFile.Close()
} else {
dst, err = interpolate.Render(dst, &p.config.ctx)
if err != nil {
return dst, fmt.Errorf("error interpolating Packer SBOM destination: %s", err)
}
if strings.HasSuffix(dst, "/") {
info, err := os.Stat(dst)
if err != nil {
return dst, fmt.Errorf("failed to stat destination for Packer SBOM: %s", err)
}
if info.IsDir() {
tmpFile, err := os.CreateTemp(dst, "packer-sbom-*.json")
if err != nil {
return dst, fmt.Errorf("failed to create file for Packer SBOM: %s", err)
}
dst = tmpFile.Name()
tmpFile.Close()
}
}
// 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 {
@@ -172,37 +139,81 @@ func (p *Provisioner) downloadSBOM(ui packersdk.Ui, comm packersdk.Communicator)
pf := io.MultiWriter(f)
// Download the file
ui.Say(fmt.Sprintf("Downloading SBOM file %s => %s", src, dst))
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 SBOM file: %s", err))
ui.Error(fmt.Sprintf("download failed for Packer SBOM file: %s", err))
return dst, err
}
return dst, nil
}
func (p *Provisioner) compressFile(ui packersdk.Ui, filePath string) ([]byte, error) {
sourceFile, err := os.Open(filePath)
// 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 {
return nil, err
}
defer sourceFile.Close()
data, err := io.ReadAll(sourceFile)
if err != nil {
return nil, err
ui.Say(fmt.Sprintf("error interpolating source: %s", err))
return
}
encoder, err := zstd.NewWriter(nil)
if err != nil {
return nil, err
// Determine the destination path
dst := p.config.Destination
if dst == "" {
ui.Say("skipped downloading SBOM file for user because 'Destination' is not provided")
return
}
defer encoder.Close()
compressedData := encoder.EncodeAll(data, nil)
dst, err = interpolate.Render(dst, &p.config.ctx)
if err != nil {
ui.Say(fmt.Sprintf("error interpolating SBOM file destination: %s", err))
return
}
ui.Say(fmt.Sprintf("SBOM file compressed successfully. Size: %d bytes", len(compressedData)))
return compressedData, nil
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 {
+2 -47
View File
@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/klauspost/compress/zstd"
"io"
"os"
"testing"
@@ -94,8 +93,8 @@ func TestDownloadSBOM(t *testing.T) {
provisioner := &Provisioner{
config: tt.config,
}
destPath, err := provisioner.downloadSBOM(ui, comm)
generatedData := map[string]interface{}{}
destPath, err := provisioner.downloadSBOMForPacker(ui, comm, generatedData)
if tt.expectError {
if err == nil {
t.Fatalf("expected error, got none")
@@ -173,47 +172,3 @@ func TestValidateSBOM(t *testing.T) {
})
}
}
func TestCompressFile(t *testing.T) {
ui := &MockUi{}
provisioner := &Provisioner{}
validSBOM := SBOM{
BomFormat: "CycloneDX",
SpecVersion: "1.0",
}
data, _ := json.Marshal(validSBOM)
filePath := "data.json"
//os.WriteFile(filePath, data, 0644)
//defer os.Remove(filePath)
sourceFile, err := os.Open(filePath)
if err != nil {
t.Fatalf("expected no error:%v", err)
}
defer sourceFile.Close()
data, err = io.ReadAll(sourceFile)
if err != nil {
t.Fatalf("expected no error:%v", err)
}
compressedData, err := provisioner.compressFile(ui, filePath)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
decoder, err := zstd.NewReader(nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
defer decoder.Close()
decompressedData, err := decoder.DecodeAll(compressedData, nil)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if string(decompressedData) != string(data) {
t.Fatalf("expected decompressed data to be '%s', got %s", data, decompressedData)
}
}