From 79a955b1a9aac71fbf58db3602d988035182f68b Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Mon, 5 Jun 2023 10:36:34 -0400 Subject: [PATCH] feat: source-version flag (#1859) --- cmd/syft/cli/attest/attest.go | 2 +- cmd/syft/cli/options/packages.go | 20 ++++++++- cmd/syft/cli/packages/packages.go | 2 +- cmd/syft/cli/poweruser/poweruser.go | 2 +- internal/config/application.go | 9 ++++ syft/source/metadata.go | 1 + syft/source/source.go | 69 +++++++++++++++++++++-------- syft/source/source_test.go | 2 +- 8 files changed, 83 insertions(+), 24 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 05e17867933..997f3307de2 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error { // could be an image or a directory, with or without a scheme // TODO: validate that source is image userInput := args[0] - si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource) + si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource) if err != nil { return fmt.Errorf("could not generate source input for packages command: %w", err) } diff --git a/cmd/syft/cli/options/packages.go b/cmd/syft/cli/options/packages.go index 7ab3b1fc23f..f6992a948c2 100644 --- a/cmd/syft/cli/options/packages.go +++ b/cmd/syft/cli/options/packages.go @@ -21,7 +21,8 @@ type PackagesOptions struct { Platform string Exclude []string Catalogers []string - Name string + SourceName string + SourceVersion string } var _ Interface = (*PackagesOptions)(nil) @@ -48,7 +49,14 @@ 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", "", "", + cmd.Flags().StringVarP(&o.SourceName, "name", "", "", + "set the name of the target being analyzed") + cmd.Flags().Lookup("name").Deprecated = "use: source-name" + + cmd.Flags().StringVarP(&o.SourceName, "source-name", "", "", + "set the name of the target being analyzed") + + cmd.Flags().StringVarP(&o.SourceVersion, "source-version", "", "", "set the name of the target being analyzed") return bindPackageConfigOptions(cmd.Flags(), v) @@ -78,6 +86,14 @@ func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { return err } + if err := v.BindPFlag("source-name", flags.Lookup("source-name")); err != nil { + return err + } + + if err := v.BindPFlag("source-version", flags.Lookup("source-version")); 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 1f3acd13e56..12695e4f086 100644 --- a/cmd/syft/cli/packages/packages.go +++ b/cmd/syft/cli/packages/packages.go @@ -42,7 +42,7 @@ func Run(_ 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.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource) + si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource) 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 b6fae72fef1..b4e524feadd 100644 --- a/cmd/syft/cli/poweruser/poweruser.go +++ b/cmd/syft/cli/poweruser/poweruser.go @@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error { }() userInput := args[0] - si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource) + si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource) 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 1a37594f30b..9f3274265fa 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -61,6 +61,8 @@ type Application struct { Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"` Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` Name string `yaml:"name" json:"name" mapstructure:"name"` + SourceName string `yaml:"source-name" json:"source-name" mapstructure:"source-name"` + SourceVersion string `yaml:"source-version" json:"source-version" mapstructure:"source-version"` Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source } @@ -143,6 +145,13 @@ func (cfg *Application) parseConfigValues() error { return err } + if cfg.Name != "" { + log.Warnf("name parameter is deprecated. please use: source-name. name will be removed in a future version") + if cfg.SourceName == "" { + cfg.SourceName = cfg.Name + } + } + // check for valid default source options // parse nested config options // for each field in the configuration struct, see if the field implements the parser interface diff --git a/syft/source/metadata.go b/syft/source/metadata.go index 1d29973b4ec..ecbad4f1dd8 100644 --- a/syft/source/metadata.go +++ b/syft/source/metadata.go @@ -8,4 +8,5 @@ type Metadata struct { Path string // the root path to be cataloged (directory only) Base string // the base path to be cataloged (directory only) Name string + Version string } diff --git a/syft/source/source.go b/syft/source/source.go index bc59b306d7a..4ff747ae297 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -48,6 +48,7 @@ type Input struct { Location string Platform string Name string + Version string } // ParseInput generates a source Input that can be used as an argument to generate a new source @@ -59,6 +60,12 @@ func ParseInput(userInput string, platform string) (*Input, error) { // 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, name, defaultImageSource string) (*Input, error) { + return ParseInputWithNameVersion(userInput, platform, name, "", defaultImageSource) +} + +// ParseInputWithNameVersion 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 and version. +func ParseInputWithNameVersion(userInput, platform, name, version, defaultImageSource string) (*Input, error) { fs := afero.NewOsFs() scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput) if err != nil { @@ -97,6 +104,7 @@ func ParseInputWithName(userInput string, platform, name, defaultImageSource str Location: location, Platform: platform, Name: name, + Version: version, }, nil } @@ -154,7 +162,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 := NewFromImageWithName(img, in.Location, in.Name) + s, err := NewFromImageWithNameVersion(img, in.Location, in.Name, in.Version) if err != nil { return nil, cleanup, fmt.Errorf("could not populate source with image: %w", err) } @@ -251,7 +259,7 @@ func generateDirectorySource(fs afero.Fs, in Input) (*Source, func(), error) { return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err) } - s, err := NewFromDirectoryWithName(in.Location, in.Name) + s, err := NewFromDirectoryWithNameVersion(in.Location, in.Name, in.Version) if err != nil { return nil, func() {}, fmt.Errorf("could not populate source from path=%q: %w", in.Location, err) } @@ -269,7 +277,7 @@ func generateFileSource(fs afero.Fs, in Input) (*Source, func(), error) { return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err) } - s, cleanupFn := NewFromFileWithName(in.Location, in.Name) + s, cleanupFn := NewFromFileWithNameVersion(in.Location, in.Name, in.Version) return &s, cleanupFn, nil } @@ -279,19 +287,20 @@ func NewFromDirectory(path string) (Source, error) { return NewFromDirectoryWithName(path, "") } -// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively. -func NewFromDirectoryRoot(path string) (Source, error) { - return NewFromDirectoryRootWithName(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) { + return NewFromDirectoryWithNameVersion(path, name, "") +} + +// NewFromDirectoryWithNameVersion creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. +func NewFromDirectoryWithNameVersion(path string, name string, version string) (Source, error) { s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ - Name: name, - Scheme: DirectoryScheme, - Path: path, + Name: name, + Version: version, + Scheme: DirectoryScheme, + Path: path, }, path: path, } @@ -299,15 +308,26 @@ func NewFromDirectoryWithName(path string, name string) (Source, error) { return s, nil } +// NewFromDirectoryRoot creates a new source object tailored to catalog a given filesystem directory recursively. +func NewFromDirectoryRoot(path string) (Source, error) { + return NewFromDirectoryRootWithName(path, "") +} + // NewFromDirectoryRootWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. func NewFromDirectoryRootWithName(path string, name string) (Source, error) { + return NewFromDirectoryRootWithNameVersion(path, name, "") +} + +// NewFromDirectoryRootWithNameVersion creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. +func NewFromDirectoryRootWithNameVersion(path string, name string, version string) (Source, error) { s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ - Name: name, - Scheme: DirectoryScheme, - Path: path, - Base: path, + Name: name, + Version: version, + Scheme: DirectoryScheme, + Path: path, + Base: path, }, path: path, base: path, @@ -323,14 +343,20 @@ func NewFromFile(path string) (Source, func()) { // NewFromFileWithName creates a new source object tailored to catalog a file, with an explicitly provided name. func NewFromFileWithName(path string, name string) (Source, func()) { + return NewFromFileWithNameVersion(path, name, "") +} + +// NewFromFileWithNameVersion creates a new source object tailored to catalog a file, with an explicitly provided name and version. +func NewFromFileWithNameVersion(path string, name string, version string) (Source, func()) { analysisPath, cleanupFn := fileAnalysisPath(path) s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ - Name: name, - Scheme: FileScheme, - Path: path, + Name: name, + Version: version, + Scheme: FileScheme, + Path: path, }, path: analysisPath, } @@ -380,6 +406,12 @@ func NewFromImage(img *image.Image, userImageStr string) (Source, error) { // 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) { + return NewFromImageWithNameVersion(img, userImageStr, name, "") +} + +// NewFromImageWithNameVersion 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 and version. +func NewFromImageWithNameVersion(img *image.Image, userImageStr string, name string, version string) (Source, error) { if img == nil { return Source{}, fmt.Errorf("no image given") } @@ -388,6 +420,7 @@ func NewFromImageWithName(img *image.Image, userImageStr string, name string) (S Image: img, Metadata: Metadata{ Name: name, + Version: version, Scheme: ImageScheme, ImageMetadata: NewImageMetadata(img, userImageStr), }, diff --git a/syft/source/source_test.go b/syft/source/source_test.go index 32af0c05202..66f3dc955ef 100644 --- a/syft/source/source_test.go +++ b/syft/source/source_test.go @@ -125,7 +125,7 @@ func TestSetID(t *testing.T) { Path: "test-fixtures/image-simple", }, }, - expected: artifact.ID("1b0dc351e6577b01"), + expected: artifact.ID("9ee9e786412d6ae5"), }, }