mirror of https://github.com/hashicorp/packer
parent
342fc1c554
commit
cc75d71745
@ -0,0 +1,119 @@
|
||||
package command
|
||||
|
||||
//Types to define SBOM generation command
|
||||
// - Command struct (e.g., SBOMGenerateCommand)
|
||||
// - Flags for the command (e.g., --format, --output)
|
||||
// - Method to execute the command (e.g., Run)
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/internal/sbom"
|
||||
)
|
||||
|
||||
type SBOMGenerateCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (cmd *SBOMGenerateCommand) Run(args []string) int {
|
||||
ctx, cleanup := handleTermInterrupt(cmd.Ui)
|
||||
defer cleanup()
|
||||
|
||||
cfg, ret := cmd.ParseArgs(args)
|
||||
if ret != 0 {
|
||||
return ret
|
||||
}
|
||||
return cmd.RunContext(ctx, cfg)
|
||||
}
|
||||
|
||||
func (cmd *SBOMGenerateCommand) ParseArgs(args []string) (*sbom.Config, int) {
|
||||
cfg := &sbom.Config{
|
||||
ScanPath: "/",
|
||||
Format: sbom.FormatCycloneDX, // default format
|
||||
Parallelism: 4, // default parallelism
|
||||
}
|
||||
|
||||
//Parse Syft Style args
|
||||
// Parse Syft-style arguments
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
|
||||
switch arg {
|
||||
case "-o", "--output":
|
||||
// Next arg is format
|
||||
if i+1 >= len(args) {
|
||||
cmd.Ui.Error("Missing value for -o flag")
|
||||
return cfg, 1
|
||||
}
|
||||
i++
|
||||
formatStr := args[i]
|
||||
|
||||
// Parse format string
|
||||
format, err := sbom.ParseFormatFromArgs(formatStr)
|
||||
if err != nil {
|
||||
cmd.Ui.Error(err.Error())
|
||||
return cfg, 1
|
||||
}
|
||||
cfg.Format = format
|
||||
|
||||
default:
|
||||
// Assume it's the scan path (positional argument)
|
||||
if !strings.HasPrefix(arg, "-") {
|
||||
cfg.ScanPath = arg
|
||||
}
|
||||
}
|
||||
}
|
||||
return cfg, 0
|
||||
}
|
||||
|
||||
func (cmd *SBOMGenerateCommand) RunContext(ctx context.Context, cfg *sbom.Config) int {
|
||||
fmt.Fprintf(os.Stderr, "Generating %s SBOM for %s...\n", cfg.Format, cfg.ScanPath)
|
||||
|
||||
// Create generator
|
||||
generator := sbom.NewGenerator(*cfg)
|
||||
|
||||
// Generate SBOM
|
||||
sbomData, err := generator.Generate(ctx)
|
||||
if err != nil {
|
||||
cmd.Ui.Error(fmt.Sprintf("SBOM generation failed: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Write to stdout (will be redirected to file via > operator)
|
||||
_, err = os.Stdout.Write(sbomData)
|
||||
if err != nil {
|
||||
cmd.Ui.Error(fmt.Sprintf("Failed to write SBOM: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "✓ SBOM generation completed")
|
||||
|
||||
return 0
|
||||
|
||||
}
|
||||
func (c *SBOMGenerateCommand) Synopsis() string {
|
||||
return "Generate SBOM for the local system (internal use)"
|
||||
}
|
||||
func (c *SBOMGenerateCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: packer sbom-generate [options] <path>
|
||||
Generate a Software Bill of Materials (SBOM) for the local filesystem.
|
||||
This command is typically invoked internally by the hcp-sbom provisioner.
|
||||
Options:
|
||||
-o <format> Output format: cyclonedx-json, spdx-json (default: cyclonedx-json)
|
||||
Arguments:
|
||||
<path> Path to scan (default: /)
|
||||
Examples:
|
||||
# Generate CycloneDX SBOM for root filesystem
|
||||
packer sbom-generate -o cyclonedx-json / > sbom.json
|
||||
# Generate SPDX SBOM
|
||||
packer sbom-generate -o spdx-json / > sbom.json
|
||||
# Scan specific directory
|
||||
packer sbom-generate -o cyclonedx-json /opt/app > app-sbom.json
|
||||
Note: Output is written to stdout. Use shell redirection (>) to save to file.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package sbom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format represents the SBOM output format
|
||||
type Format string
|
||||
|
||||
const (
|
||||
FormatCycloneDX Format = "cyclonedx"
|
||||
FormatSPDX Format = "spdx"
|
||||
)
|
||||
|
||||
// Config holds configuration for SBOM generation
|
||||
type Config struct {
|
||||
ScanPath string // Path to scan (e.g., "/", "/opt/app")
|
||||
Format Format // Output format (cyclonedx or spdx)
|
||||
Parallelism int // Number of parallel catalogers (0 = auto-detect)
|
||||
}
|
||||
|
||||
// Generator generates SBOMs using embedded Syft SDK
|
||||
type Generator struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// NewGenerator creates a new SBOM generator with the given configuration
|
||||
func NewGenerator(cfg Config) *Generator {
|
||||
// Set defaults
|
||||
if cfg.ScanPath == "" {
|
||||
cfg.ScanPath = "/"
|
||||
}
|
||||
if cfg.Format == "" {
|
||||
cfg.Format = FormatCycloneDX
|
||||
}
|
||||
if cfg.Parallelism == 0 {
|
||||
cfg.Parallelism = 4
|
||||
}
|
||||
|
||||
return &Generator{config: cfg}
|
||||
}
|
||||
|
||||
// ParseFormatFromArgs parses Syft-style format argument
|
||||
// This is a PACKAGE-LEVEL EXPORTED FUNCTION
|
||||
func ParseFormatFromArgs(formatArg string) (Format, error) {
|
||||
formatArg = strings.ToLower(formatArg)
|
||||
|
||||
if strings.Contains(formatArg, "cyclonedx") {
|
||||
return FormatCycloneDX, nil
|
||||
}
|
||||
if strings.Contains(formatArg, "spdx") {
|
||||
return FormatSPDX, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unsupported format: %s", formatArg)
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
// Copyright IBM Corp. 2013, 2025
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build !netbsd && !openbsd && !solaris
|
||||
|
||||
package sbom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/format/spdxjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Generate creates an SBOM for the configured scan path and returns the encoded result.
|
||||
func (g *Generator) Generate(ctx context.Context) ([]byte, error) {
|
||||
|
||||
sourceInput := g.config.ScanPath
|
||||
src, err := syft.GetSource(ctx, sourceInput, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get source: %w", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
sbomCfg := syft.DefaultCreateSBOMConfig().
|
||||
WithSearchConfig(cataloging.SearchConfig{
|
||||
Scope: source.SquashedScope,
|
||||
}).
|
||||
WithParallelism(g.config.Parallelism)
|
||||
|
||||
sbomResult, err := syft.CreateSBOM(ctx, src, sbomCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SBOM: %w", err)
|
||||
}
|
||||
|
||||
return g.encodeToFormat(sbomResult)
|
||||
}
|
||||
|
||||
// encodeToFormat encodes the SBOM to the requested format.
|
||||
func (g *Generator) encodeToFormat(sbomData *sbom.SBOM) ([]byte, error) {
|
||||
switch g.config.Format {
|
||||
case FormatCycloneDX:
|
||||
encoder, err := cyclonedxjson.NewFormatEncoderWithConfig(
|
||||
cyclonedxjson.EncoderConfig{
|
||||
Version: "1.5",
|
||||
Pretty: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create CycloneDX encoder: %w", err)
|
||||
}
|
||||
return format.Encode(*sbomData, encoder)
|
||||
|
||||
case FormatSPDX:
|
||||
encoder, err := spdxjson.NewFormatEncoderWithConfig(
|
||||
spdxjson.EncoderConfig{
|
||||
Version: "2.3",
|
||||
Pretty: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create SPDX encoder: %w", err)
|
||||
}
|
||||
return format.Encode(*sbomData, encoder)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format: %s (supported: cyclonedx, spdx)", g.config.Format)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
// Copyright IBM Corp. 2013, 2025
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build netbsd || openbsd || solaris
|
||||
|
||||
package sbom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Generate returns an error on platforms where the Syft SDK cannot be built.
|
||||
func (g *Generator) Generate(ctx context.Context) ([]byte, error) {
|
||||
_ = ctx
|
||||
return nil, fmt.Errorf("sbom generation is not supported on %s builds", runtime.GOOS)
|
||||
}
|
||||
@ -1 +1 @@
|
||||
1.15.3
|
||||
1.15.3-dev
|
||||
|
||||
Loading…
Reference in new issue