From 10f43d75e0585ecb09bbb1fca629bd071375e015 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 10 Nov 2022 19:03:23 +0000 Subject: [PATCH] feat: Add `--name` option to override name in output (#1269) --- cmd/syft/cli/attest/attest.go | 2 +- cmd/syft/cli/options/packages.go | 8 +++ cmd/syft/cli/packages/packages.go | 2 +- cmd/syft/cli/poweruser/poweruser.go | 2 +- internal/config/application.go | 1 + .../formats/common/cyclonedxhelpers/format.go | 11 +++- .../common/spdxhelpers/document_name.go | 4 ++ syft/source/metadata.go | 1 + syft/source/source.go | 55 ++++++++++++++----- syft/source/source_test.go | 2 +- 10 files changed, 68 insertions(+), 20 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index fa789789a..446287e9d 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -124,7 +124,7 @@ func parseAttestationOutput(outputs []string) (format string) { } func parseImageSource(userInput string, app *config.Application) (s *source.Input, err error) { - si, err := source.ParseInput(userInput, app.Platform, false) + si, err := source.ParseInputWithName(userInput, app.Platform, false, app.Name) if err != nil { return nil, fmt.Errorf("could not generate source input for attest command: %w", err) } diff --git a/cmd/syft/cli/options/packages.go b/cmd/syft/cli/options/packages.go index 08ff362b2..0acdcdc2d 100644 --- a/cmd/syft/cli/options/packages.go +++ b/cmd/syft/cli/options/packages.go @@ -21,6 +21,7 @@ type PackagesOptions struct { Platform string Exclude []string Catalogers []string + Name string } var _ Interface = (*PackagesOptions)(nil) @@ -47,6 +48,9 @@ func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { cmd.Flags().StringArrayVarP(&o.Catalogers, "catalogers", "", nil, "enable one or more package catalogers") + cmd.Flags().StringVarP(&o.Name, "name", "", "", + "set the name of the target being analyzed") + return bindPackageConfigOptions(cmd.Flags(), v) } @@ -69,6 +73,10 @@ func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { return err } + if err := v.BindPFlag("name", flags.Lookup("name")); err != nil { + return err + } + if err := v.BindPFlag("output", flags.Lookup("output")); err != nil { return err } diff --git a/cmd/syft/cli/packages/packages.go b/cmd/syft/cli/packages/packages.go index 72bea0c78..3519fa8a8 100644 --- a/cmd/syft/cli/packages/packages.go +++ b/cmd/syft/cli/packages/packages.go @@ -42,7 +42,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error { // could be an image or a directory, with or without a scheme userInput := args[0] - si, err := source.ParseInput(userInput, app.Platform, true) + si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name) if err != nil { return fmt.Errorf("could not generate source input for packages command: %w", err) } diff --git a/cmd/syft/cli/poweruser/poweruser.go b/cmd/syft/cli/poweruser/poweruser.go index 9c7735964..d989857b5 100644 --- a/cmd/syft/cli/poweruser/poweruser.go +++ b/cmd/syft/cli/poweruser/poweruser.go @@ -46,7 +46,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error { }() userInput := args[0] - si, err := source.ParseInput(userInput, app.Platform, true) + si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name) if err != nil { return fmt.Errorf("could not generate source input for packages command: %w", err) } diff --git a/internal/config/application.go b/internal/config/application.go index bd98777ab..d9cacb3a8 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -55,6 +55,7 @@ type Application struct { Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"` Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"` Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` + Name string `yaml:"name" json:"name" mapstructure:"name"` } func (cfg Application) ToCatalogerConfig() cataloger.Config { diff --git a/syft/formats/common/cyclonedxhelpers/format.go b/syft/formats/common/cyclonedxhelpers/format.go index b9f50abb1..4050188b1 100644 --- a/syft/formats/common/cyclonedxhelpers/format.go +++ b/syft/formats/common/cyclonedxhelpers/format.go @@ -159,8 +159,12 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc } func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component { + name := srcMetadata.Name switch srcMetadata.Scheme { case source.ImageScheme: + if name == "" { + name = srcMetadata.ImageMetadata.UserInput + } bomRef, err := artifact.IDByHash(srcMetadata.ImageMetadata.ID) if err != nil { log.Warnf("unable to get fingerprint of image metadata=%s: %+v", srcMetadata.ImageMetadata.ID, err) @@ -168,10 +172,13 @@ func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component return &cyclonedx.Component{ BOMRef: string(bomRef), Type: cyclonedx.ComponentTypeContainer, - Name: srcMetadata.ImageMetadata.UserInput, + Name: name, Version: srcMetadata.ImageMetadata.ManifestDigest, } case source.DirectoryScheme, source.FileScheme: + if name == "" { + name = srcMetadata.Path + } bomRef, err := artifact.IDByHash(srcMetadata.Path) if err != nil { log.Warnf("unable to get fingerprint of source metadata path=%s: %+v", srcMetadata.Path, err) @@ -179,7 +186,7 @@ func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component return &cyclonedx.Component{ BOMRef: string(bomRef), Type: cyclonedx.ComponentTypeFile, - Name: srcMetadata.Path, + Name: name, } } diff --git a/syft/formats/common/spdxhelpers/document_name.go b/syft/formats/common/spdxhelpers/document_name.go index 4e896427a..2545b14f9 100644 --- a/syft/formats/common/spdxhelpers/document_name.go +++ b/syft/formats/common/spdxhelpers/document_name.go @@ -8,6 +8,10 @@ import ( ) func DocumentName(srcMetadata source.Metadata) string { + if srcMetadata.Name != "" { + return cleanName(srcMetadata.Name) + } + switch srcMetadata.Scheme { case source.ImageScheme: return cleanName(srcMetadata.ImageMetadata.UserInput) diff --git a/syft/source/metadata.go b/syft/source/metadata.go index 42981c221..feafba7bc 100644 --- a/syft/source/metadata.go +++ b/syft/source/metadata.go @@ -6,4 +6,5 @@ type Metadata struct { Scheme Scheme // the source data scheme type (directory or image) ImageMetadata ImageMetadata // all image info (image only) Path string // the root path to be cataloged (directory only) + Name string } diff --git a/syft/source/source.go b/syft/source/source.go index d4be9cc03..d2fb31807 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -44,12 +44,19 @@ type Input struct { ImageSource image.Source Location string Platform string + Name string autoDetectAvailableImageSources bool } // ParseInput generates a source Input that can be used as an argument to generate a new source // from specific providers including a registry. func ParseInput(userInput string, platform string, detectAvailableImageSources bool) (*Input, error) { + return ParseInputWithName(userInput, platform, detectAvailableImageSources, "") +} + +// ParseInputWithName generates a source Input that can be used as an argument to generate a new source +// from specific providers including a registry, with an explicit name. +func ParseInputWithName(userInput string, platform string, detectAvailableImageSources bool, name string) (*Input, error) { fs := afero.NewOsFs() scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput) if err != nil { @@ -86,6 +93,7 @@ func ParseInput(userInput string, platform string, detectAvailableImageSources b ImageSource: source, Location: location, Platform: platform, + Name: name, autoDetectAvailableImageSources: detectAvailableImageSources, }, nil } @@ -109,9 +117,9 @@ func New(in Input, registryOptions *image.RegistryOptions, exclusions []string) switch in.Scheme { case FileScheme: - source, cleanupFn, err = generateFileSource(fs, in.Location) + source, cleanupFn, err = generateFileSource(fs, in) case DirectoryScheme: - source, cleanupFn, err = generateDirectorySource(fs, in.Location) + source, cleanupFn, err = generateDirectorySource(fs, in) case ImageScheme: source, cleanupFn, err = generateImageSource(in, registryOptions) default: @@ -131,7 +139,7 @@ func generateImageSource(in Input, registryOptions *image.RegistryOptions) (*Sou return nil, cleanup, fmt.Errorf("could not fetch image %q: %w", in.Location, err) } - s, err := NewFromImage(img, in.Location) + s, err := NewFromImageWithName(img, in.Location, in.Name) if err != nil { return nil, cleanup, fmt.Errorf("could not populate source with image: %w", err) } @@ -206,44 +214,50 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions) return img, cleanup, err } -func generateDirectorySource(fs afero.Fs, location string) (*Source, func(), error) { - fileMeta, err := fs.Stat(location) +func generateDirectorySource(fs afero.Fs, in Input) (*Source, func(), error) { + fileMeta, err := fs.Stat(in.Location) if err != nil { - return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) + return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", in.Location, err) } if !fileMeta.IsDir() { - return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) + return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err) } - s, err := NewFromDirectory(location) + s, err := NewFromDirectoryWithName(in.Location, in.Name) if err != nil { - return nil, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err) + return nil, func() {}, fmt.Errorf("could not populate source from path=%q: %w", in.Location, err) } return &s, func() {}, nil } -func generateFileSource(fs afero.Fs, location string) (*Source, func(), error) { - fileMeta, err := fs.Stat(location) +func generateFileSource(fs afero.Fs, in Input) (*Source, func(), error) { + fileMeta, err := fs.Stat(in.Location) if err != nil { - return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) + return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", in.Location, err) } if fileMeta.IsDir() { - return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) + return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err) } - s, cleanupFn := NewFromFile(location) + s, cleanupFn := NewFromFileWithName(in.Location, in.Name) return &s, cleanupFn, nil } // NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively. func NewFromDirectory(path string) (Source, error) { + return NewFromDirectoryWithName(path, "") +} + +// NewFromDirectoryWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. +func NewFromDirectoryWithName(path string, name string) (Source, error) { s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ + Name: name, Scheme: DirectoryScheme, Path: path, }, @@ -255,11 +269,17 @@ func NewFromDirectory(path string) (Source, error) { // NewFromFile creates a new source object tailored to catalog a file. func NewFromFile(path string) (Source, func()) { + return NewFromFileWithName(path, "") +} + +// NewFromFileWithName creates a new source object tailored to catalog a file, with an explicitly provided name. +func NewFromFileWithName(path string, name string) (Source, func()) { analysisPath, cleanupFn := fileAnalysisPath(path) s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ + Name: name, Scheme: FileScheme, Path: path, }, @@ -299,6 +319,12 @@ func fileAnalysisPath(path string) (string, func()) { // NewFromImage creates a new source object tailored to catalog a given container image, relative to the // option given (e.g. all-layers, squashed, etc) func NewFromImage(img *image.Image, userImageStr string) (Source, error) { + return NewFromImageWithName(img, userImageStr, "") +} + +// NewFromImageWithName creates a new source object tailored to catalog a given container image, relative to the +// option given (e.g. all-layers, squashed, etc), with an explicit name. +func NewFromImageWithName(img *image.Image, userImageStr string, name string) (Source, error) { if img == nil { return Source{}, fmt.Errorf("no image given") } @@ -306,6 +332,7 @@ func NewFromImage(img *image.Image, userImageStr string) (Source, error) { s := Source{ Image: img, Metadata: Metadata{ + Name: name, Scheme: ImageScheme, ImageMetadata: NewImageMetadata(img, userImageStr), }, diff --git a/syft/source/source_test.go b/syft/source/source_test.go index ca9818774..b1b7b538e 100644 --- a/syft/source/source_test.go +++ b/syft/source/source_test.go @@ -120,7 +120,7 @@ func TestSetID(t *testing.T) { Path: "test-fixtures/image-simple", }, }, - expected: artifact.ID("26e8f4daad203793"), + expected: artifact.ID("14b60020c4f9955"), }, }