Skip to content

Commit

Permalink
Modify external provisioner
Browse files Browse the repository at this point in the history
  • Loading branch information
devashish-patel committed Sep 25, 2024
1 parent 62eaca5 commit f9c29ce
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 113 deletions.
143 changes: 77 additions & 66 deletions provisioner/hcp_sbom/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand All @@ -77,84 +77,51 @@ 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)
if validationErr != nil {
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(
"packer SBOM source file is not a JSON file: %s", src,
)
}

// 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 {
Expand All @@ -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
ui.Say(fmt.Sprintf("error interpolating source: %s", err))
return
}
defer sourceFile.Close()

data, err := io.ReadAll(sourceFile)
// 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 {
return nil, err
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
}

encoder, err := zstd.NewWriter(nil)
// Open the destination file
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return nil, err
ui.Say(fmt.Sprintf("failed to open destination file: %s", err))
return
}
defer encoder.Close()
defer f.Close()

compressedData := encoder.EncodeAll(data, nil)
// Create MultiWriter for the current progress
pf := io.MultiWriter(f)

ui.Say(fmt.Sprintf("SBOM file compressed successfully. Size: %d bytes", len(compressedData)))
return compressedData, nil
// 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 {
Expand Down
49 changes: 2 additions & 47 deletions provisioner/hcp_sbom/provisioner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/klauspost/compress/zstd"
"io"
"os"
"testing"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
}
}

0 comments on commit f9c29ce

Please sign in to comment.