diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 2c26ca5f9..76b4f0cc7 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -361,4 +361,4 @@ jobs: path: snapshot - name: Run CLI Tests (Linux) - run: make cli-linux + run: make cli diff --git a/Makefile b/Makefile index fbcd38382..35d4cb09e 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,6 @@ SUCCESS := $(BOLD)$(GREEN) # the quality gate lower threshold for unit test total % coverage (by function statements) COVERAGE_THRESHOLD := 68 # CI cache busting values; change these if you want CI to not use previous stored cache -COMPARE_CACHE_BUSTER="f7e689d76a9" INTEGRATION_CACHE_BUSTER="23493ba738c3d2f" CLI_CACHE_BUSTER="789bacdf" BOOTSTRAP_CACHE="789bacdf" @@ -26,7 +25,13 @@ BOOTSTRAP_CACHE="789bacdf" DISTDIR=./dist SNAPSHOTDIR=./snapshot GITTREESTATE=$(if $(shell git status --porcelain),dirty,clean) -SNAPSHOT_CMD=$(shell realpath $(shell pwd)/$(SNAPSHOTDIR)/syft_linux_amd64/syft) +OS := $(shell uname) + +ifeq ($(OS),Darwin) + SNAPSHOT_CMD=$(shell realpath $(shell pwd)/$(SNAPSHOTDIR)/$(BIN)-macos_darwin_amd64/$(BIN)) +else + SNAPSHOT_CMD=$(shell realpath $(shell pwd)/$(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN)) +endif ifeq "$(strip $(VERSION))" "" override VERSION = $(shell git describe --always --tags --dirty) @@ -73,7 +78,7 @@ all: clean static-analysis test ## Run all linux-based checks (linting, license @printf '$(SUCCESS)All checks pass!$(RESET)\n' .PHONY: test -test: unit validate-cyclonedx-schema integration benchmark acceptance-linux ## Run all tests (currently unit, integration, and linux acceptance tests) +test: unit validate-cyclonedx-schema integration benchmark acceptance-linux cli ## Run all tests (currently unit, integration, linux acceptance, and mac cli tests) .PHONY: help help: @@ -215,20 +220,6 @@ acceptance-mac: $(RESULTSDIR) $(SNAPSHOTDIR) ## Run acceptance tests on build sn .PHONY: acceptance-linux acceptance-linux: acceptance-test-deb-package-install acceptance-test-rpm-package-install ## Run acceptance tests on build snapshot binaries and packages (Linux) -# note: this is used by CI to determine if the inline-scan report cache should be busted for the inline-compare tests -.PHONY: compare-fingerprint -compare-fingerprint: - find test/inline-compare/* -type f -exec md5sum {} + | grep -v '\-reports' | grep -v 'fingerprint' | awk '{print $1}' | sort | md5sum | tee test/inline-compare/inline-compare.fingerprint && echo "$(COMPARE_CACHE_BUSTER)" >> test/inline-compare/inline-compare.fingerprint - -.PHONY: compare-snapshot -compare-snapshot: $(SNAPSHOTDIR) ## Compare the reports of a run of a snapshot build of syft against inline-scan - chmod 755 $(SNAPSHOT_CMD) - @cd test/inline-compare && SYFT_CMD=$(SNAPSHOT_CMD) make - -.PHONY: compare -compare: ## Compare the reports of a run of a main-branch build of syft against inline-scan - @cd test/inline-compare && make - .PHONY: acceptance-test-deb-package-install acceptance-test-deb-package-install: $(RESULTSDIR) $(SNAPSHOTDIR) $(call title,Running acceptance test: DEB install) @@ -251,17 +242,11 @@ acceptance-test-rpm-package-install: $(RESULTSDIR) $(SNAPSHOTDIR) cli-fingerprint: find test/cli/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/cli/test-fixtures/cache.fingerprint && echo "$(CLI_CACHE_BUSTER)" >> test/cli/test-fixtures/cache.fingerprint -.PHONY: cli-linux -cli-linux: $(SNAPSHOTDIR) ## Run CLI tests for Linux executable - chmod 755 "$(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN)" - $(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN) version - SYFT_BINARY_LOCATION='$(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN)' \ - go test -count=1 -v ./test/cli - -.PHONY: cli-macos -cli-macos: $(SNAPSHOTDIR) ## Run CLI tests for macOS executable - $(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN) version - SYFT_BINARY_LOCATION='$(SNAPSHOTDIR)/$(BIN)-macos_darwin_amd64/$(BIN)' \ +.PHONY: cli +cli: $(SNAPSHOTDIR) ## Run CLI tests + chmod 755 "$(SNAPSHOT_CMD)" + $(SNAPSHOT_CMD) version + SYFT_BINARY_LOCATION='$(SNAPSHOT_CMD)' \ go test -count=1 -v ./test/cli .PHONY: changlog-release diff --git a/README.md b/README.md index 6210b6228..62123c14b 100644 --- a/README.md +++ b/README.md @@ -93,24 +93,26 @@ quiet: false check-for-app-update: true # cataloging packages is exposed through the packages and power-user subcommands -packages: +package: + cataloger: # enable/disable cataloging of packages - # SYFT_PACKAGES_CATALOGING_ENABLED env var - cataloging-enabled: true - + # SYFT_PACKAGE_CATALOGER_ENABLED env var + enabled: true + # the search space to look for packages (options: all-layers, squashed) - # same as -s ; SYFT_PACKAGES_SCOPE env var + # same as -s ; SYFT_PACKAGE_CATALOGER_SCOPE env var scope: "squashed" # cataloging file metadata is exposed through the power-user subcommand file-metadata: - # enable/disable cataloging of file metadata - # SYFT_FILE_METADATA_CATALOGING_ENABLED env var - cataloging-enabled: true - - # the search space to look for file metadata (options: all-layers, squashed) - # SYFT_FILE_METADATA_SCOPE env var - scope: "squashed" + cataloger: + # enable/disable cataloging of file metadata + # SYFT_FILE_METADATA_CATALOGER_ENABLED env var + enabled: true + + # the search space to look for file metadata (options: all-layers, squashed) + # SYFT_FILE_METADATA_CATALOGER_SCOPE env var + scope: "squashed" # the file digest algorithms to use when cataloging files (options: "sha256", "md5", "sha1") # SYFT_FILE_METADATA_DIGESTS env var diff --git a/cmd/cmd.go b/cmd/cmd.go index 450d3e469..9e0bda837 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -34,7 +34,7 @@ func init() { } // provided to disambiguate the root vs packages command, whichever is indicated by the cli args will be set here. -// TODO: when the root alias command is removed, this function (hack) can be removed +// TODO: when the root alias command is removed, this variable can be removed var activeCmd *cobra.Command func Execute() { @@ -44,23 +44,28 @@ func Execute() { } } +// we must setup the config-cli bindings first before the application configuration is parsed. However, this cannot +// be done without determining what the primary command that the config options should be bound to since there are +// shared concerns (the root-packages alias). func initCmdAliasBindings() { // TODO: when the root alias command is removed, this function (hack) can be removed + // map of all commands except for root + commands := make(map[string]*cobra.Command) + for _, c := range rootCmd.Commands() { + name := strings.Split(c.Use, " ")[0] + commands[name] = c + } + activeCmd = rootCmd for i, a := range os.Args { if i == 0 { // don't consider the bin continue } - if a == "packages" { - // this is positively the first subcommand directive, and is "packages" - activeCmd = packagesCmd - break - } - if !strings.HasPrefix("-", a) { - // this is the first non-switch provided and was not "packages" - break + // check to see if this argument may be a command + if c, exists := commands[a]; exists { + activeCmd = c } } @@ -69,14 +74,20 @@ func initCmdAliasBindings() { fmt.Fprintln(os.Stderr, color.New(color.Bold, color.Red).Sprintf("The root command is deprecated, please use the 'packages' subcommand")) } - // note: we need to lazily bind config options since they are shared between both the root command - // and the packages command. Otherwise there will be global viper state that is in contention. - // See for more details: https://github.com/spf13/viper/issues/233 . Additionally, the bindings must occur BEFORE - // reading the application configuration, which implies that it must be an initializer (or rewrite the command - // initialization structure against typical patterns used with cobra, which is somewhat extreme for a - // temporary alias) - if err := bindConfigOptions(activeCmd.Flags()); err != nil { - panic(err) + if activeCmd == packagesCmd || activeCmd == rootCmd { + // note: we need to lazily bind config options since they are shared between both the root command + // and the packages command. Otherwise there will be global viper state that is in contention. + // See for more details: https://github.com/spf13/viper/issues/233 . Additionally, the bindings must occur BEFORE + // reading the application configuration, which implies that it must be an initializer (or rewrite the command + // initialization structure against typical patterns used with cobra, which is somewhat extreme for a + // temporary alias) + if err := bindConfigOptions(activeCmd.Flags()); err != nil { + panic(err) + } + } else { + if err := bindConfigOptions(packagesCmd.Flags()); err != nil { + panic(err) + } } } diff --git a/cmd/packages.go b/cmd/packages.go index 1e95adec5..d0d205919 100644 --- a/cmd/packages.go +++ b/cmd/packages.go @@ -141,7 +141,7 @@ func setPackageFlags(flags *pflag.FlagSet) { func bindConfigOptions(flags *pflag.FlagSet) error { ///////// Formatting & Input options ////////////////////////////////////////////// - if err := viper.BindPFlag("packages.scope", flags.Lookup("scope")); err != nil { + if err := viper.BindPFlag("package.cataloger.scope", flags.Lookup("scope")); err != nil { return err } @@ -194,14 +194,14 @@ func packagesExecWorker(userInput string) <-chan error { } defer cleanup() - catalog, d, err := syft.CatalogPackages(src, appConfig.Packages.ScopeOpt) + catalog, d, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt) if err != nil { errs <- fmt.Errorf("failed to catalog input: %+v", err) return } if appConfig.Anchore.Host != "" { - if err := runPackageSbomUpload(src, src.Metadata, catalog, d, appConfig.Packages.ScopeOpt); err != nil { + if err := runPackageSbomUpload(src, src.Metadata, catalog, d, appConfig.Package.Cataloger.ScopeOpt); err != nil { errs <- err return } @@ -213,7 +213,7 @@ func packagesExecWorker(userInput string) <-chan error { SourceMetadata: src.Metadata, Catalog: catalog, Distro: d, - Scope: appConfig.Packages.ScopeOpt, + Scope: appConfig.Package.Cataloger.ScopeOpt, }), }) }() diff --git a/cmd/power_user.go b/cmd/power_user.go index 1f48459a6..2a15f5180 100644 --- a/cmd/power_user.go +++ b/cmd/power_user.go @@ -3,6 +3,8 @@ package cmd import ( "fmt" + "github.com/anchore/syft/internal" + "github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/presenter/poweruser" "github.com/anchore/syft/internal/ui" @@ -13,14 +15,24 @@ import ( "github.com/wagoodman/go-partybus" ) +const powerUserExample = ` {{.appName}} {{.command}} + + Only image sources are supported (e.g. docker: , docker-archive: , oci: , etc.), the directory source (dir:) is not supported. + + All behavior is controlled via application configuration and environment variables (see https://github.com/anchore/syft#configuration) +` + var powerUserOpts = struct { configPath string }{} var powerUserCmd = &cobra.Command{ - Use: "power-user [SOURCE]", - Short: "Run bulk operations on container images", - Example: ` {{.appName}} power-user `, + Use: "power-user [IMAGE]", + Short: "Run bulk operations on container images", + Example: internal.Tprintf(powerUserExample, map[string]interface{}{ + "appName": internal.ApplicationName, + "command": "power-user", + }), Args: cobra.ExactArgs(1), Hidden: true, SilenceUsage: true, diff --git a/cmd/power_user_tasks.go b/cmd/power_user_tasks.go index 0d5c21746..2800202f6 100644 --- a/cmd/power_user_tasks.go +++ b/cmd/power_user_tasks.go @@ -37,12 +37,12 @@ func powerUserTasks(src source.Source) ([]powerUserTask, error) { } func catalogPackagesTask(src source.Source) powerUserTask { - if !appConfig.Packages.CatalogingEnabled { + if !appConfig.Package.Cataloger.Enabled { return nil } task := func(results *poweruser.JSONDocumentConfig) error { - packageCatalog, theDistro, err := syft.CatalogPackages(src, appConfig.Packages.ScopeOpt) + packageCatalog, theDistro, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt) if err != nil { return err } @@ -57,11 +57,11 @@ func catalogPackagesTask(src source.Source) powerUserTask { } func catalogFileMetadataTask(src source.Source) (powerUserTask, error) { - if !appConfig.FileMetadata.CatalogingEnabled { + if !appConfig.FileMetadata.Cataloger.Enabled { return nil, nil } - resolver, err := src.FileResolver(appConfig.FileMetadata.ScopeOpt) + resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt) if err != nil { return nil, err } @@ -79,11 +79,11 @@ func catalogFileMetadataTask(src source.Source) (powerUserTask, error) { } func catalogFileDigestTask(src source.Source) (powerUserTask, error) { - if !appConfig.FileMetadata.CatalogingEnabled { + if !appConfig.FileMetadata.Cataloger.Enabled { return nil, nil } - resolver, err := src.FileResolver(appConfig.FileMetadata.ScopeOpt) + resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt) if err != nil { return nil, err } diff --git a/internal/config/application.go b/internal/config/application.go index 7ec137331..24c97d1a8 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "path" "strings" @@ -15,6 +16,8 @@ import ( "gopkg.in/yaml.v2" ) +var ErrApplicationConfigNotFound = fmt.Errorf("application config not found") + // Application is the main syft application configuration. type Application struct { ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading) @@ -25,7 +28,7 @@ type Application struct { Dev Development `yaml:"dev" json:"dev" mapstructure:"dev"` CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise - Packages Packages `yaml:"packages" json:"packages" mapstructure:"packages"` + Package Packages `yaml:"package" json:"package" mapstructure:"package"` FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"` } @@ -33,7 +36,9 @@ type Application struct { func LoadApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions) (*Application, error) { // the user may not have a config, and this is OK, we can use the default config + default cobra cli values instead setNonCliDefaultAppConfigValues(v) - _ = readConfig(v, cliOpts.ConfigPath) + if err := readConfig(v, cliOpts.ConfigPath); err != nil && !errors.Is(err, ErrApplicationConfigNotFound) { + return nil, err + } config := &Application{ CliOptions: cliOpts, @@ -88,7 +93,7 @@ func (cfg *Application) build() error { } for _, builder := range []func() error{ - cfg.Packages.build, + cfg.Package.build, cfg.FileMetadata.build, } { if err := builder(); err != nil { @@ -111,7 +116,9 @@ func (cfg Application) String() string { } // readConfig attempts to read the given config path from disk or discover an alternate store location +// nolint:funlen func readConfig(v *viper.Viper, configPath string) error { + var err error v.AutomaticEnv() v.SetEnvPrefix(internal.ApplicationName) // allow for nested options to be specified via environment variables @@ -132,16 +139,20 @@ func readConfig(v *viper.Viper, configPath string) error { // 1. look for ..yaml (in the current directory) v.AddConfigPath(".") - v.SetConfigName(internal.ApplicationName) - if err := v.ReadInConfig(); err == nil { + v.SetConfigName("." + internal.ApplicationName) + if err = v.ReadInConfig(); err == nil { return nil + } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { + return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) } // 2. look for ./config.yaml (in the current directory) v.AddConfigPath("." + internal.ApplicationName) v.SetConfigName("config") - if err := v.ReadInConfig(); err == nil { + if err = v.ReadInConfig(); err == nil { return nil + } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { + return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) } // 3. look for ~/..yaml @@ -149,8 +160,10 @@ func readConfig(v *viper.Viper, configPath string) error { if err == nil { v.AddConfigPath(home) v.SetConfigName("." + internal.ApplicationName) - if err := v.ReadInConfig(); err == nil { + if err = v.ReadInConfig(); err == nil { return nil + } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { + return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) } } @@ -160,11 +173,13 @@ func readConfig(v *viper.Viper, configPath string) error { v.AddConfigPath(path.Join(dir, internal.ApplicationName)) } v.SetConfigName("config") - if err := v.ReadInConfig(); err == nil { + if err = v.ReadInConfig(); err == nil { return nil + } else if !errors.As(err, &viper.ConfigFileNotFoundError{}) { + return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err) } - return fmt.Errorf("application config not found") + return ErrApplicationConfigNotFound } // setNonCliDefaultAppConfigValues ensures that there are sane defaults for values that do not have CLI equivalent options (where there would already be a default value) @@ -174,8 +189,8 @@ func setNonCliDefaultAppConfigValues(v *viper.Viper) { v.SetDefault("check-for-app-update", true) v.SetDefault("dev.profile-cpu", false) v.SetDefault("dev.profile-mem", false) - v.SetDefault("packages.cataloging-enabled", true) - v.SetDefault("file-metadata.cataloging-enabled", true) - v.SetDefault("file-metadata.scope", source.SquashedScope) + v.SetDefault("package.cataloger.enabled", true) + v.SetDefault("file-metadata.cataloger.enabled", true) + v.SetDefault("file-metadata.cataloger.scope", source.SquashedScope) v.SetDefault("file-metadata.digests", []string{"sha256"}) } diff --git a/internal/config/cataloger_options.go b/internal/config/cataloger_options.go new file mode 100644 index 000000000..72e4c0977 --- /dev/null +++ b/internal/config/cataloger_options.go @@ -0,0 +1,23 @@ +package config + +import ( + "fmt" + + "github.com/anchore/syft/syft/source" +) + +type catalogerOptions struct { + Enabled bool `yaml:"enabled" json:"enabled" mapstructure:"enabled"` + Scope string `yaml:"scope" json:"scope" mapstructure:"scope"` + ScopeOpt source.Scope `yaml:"-" json:"-"` +} + +func (cfg *catalogerOptions) build() error { + scopeOption := source.ParseScope(cfg.Scope) + if scopeOption == source.UnknownScope { + return fmt.Errorf("bad scope value %q", cfg.Scope) + } + cfg.ScopeOpt = scopeOption + + return nil +} diff --git a/internal/config/file_metadata.go b/internal/config/file_metadata.go index a58f6410c..274fce24b 100644 --- a/internal/config/file_metadata.go +++ b/internal/config/file_metadata.go @@ -1,24 +1,10 @@ package config -import ( - "fmt" - - "github.com/anchore/syft/syft/source" -) - type FileMetadata struct { - CatalogingEnabled bool `yaml:"cataloging-enabled" json:"cataloging-enabled" mapstructure:"cataloging-enabled"` - Scope string `yaml:"scope" json:"scope" mapstructure:"scope"` - ScopeOpt source.Scope `yaml:"-" json:"-"` - Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"` + Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"` + Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"` } func (cfg *FileMetadata) build() error { - scopeOption := source.ParseScope(cfg.Scope) - if scopeOption == source.UnknownScope { - return fmt.Errorf("bad scope value %q", cfg.Scope) - } - cfg.ScopeOpt = scopeOption - - return nil + return cfg.Cataloger.build() } diff --git a/internal/config/packages.go b/internal/config/packages.go index 63a1245f6..8193ab04c 100644 --- a/internal/config/packages.go +++ b/internal/config/packages.go @@ -1,23 +1,9 @@ package config -import ( - "fmt" - - "github.com/anchore/syft/syft/source" -) - type Packages struct { - CatalogingEnabled bool `yaml:"cataloging-enabled" json:"cataloging-enabled" mapstructure:"cataloging-enabled"` - Scope string `yaml:"scope" json:"scope" mapstructure:"scope"` - ScopeOpt source.Scope `yaml:"-" json:"-"` + Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"` } func (cfg *Packages) build() error { - scopeOption := source.ParseScope(cfg.Scope) - if scopeOption == source.UnknownScope { - return fmt.Errorf("bad scope value %q", cfg.Scope) - } - cfg.ScopeOpt = scopeOption - - return nil + return cfg.Cataloger.build() } diff --git a/internal/presenter/packages/json_package.go b/internal/presenter/packages/json_package.go index c3be8ff1f..a2ea899dd 100644 --- a/internal/presenter/packages/json_package.go +++ b/internal/presenter/packages/json_package.go @@ -23,6 +23,9 @@ type JSONPackage struct { func NewJSONPackages(catalog *pkg.Catalog) ([]JSONPackage, error) { artifacts := make([]JSONPackage, 0) + if catalog == nil { + return artifacts, nil + } for _, p := range catalog.Sorted() { art, err := NewJSONPackage(p) if err != nil { diff --git a/internal/presenter/poweruser/json_document.go b/internal/presenter/poweruser/json_document.go index d59b38c93..856090a80 100644 --- a/internal/presenter/poweruser/json_document.go +++ b/internal/presenter/poweruser/json_document.go @@ -15,7 +15,7 @@ type JSONDocument struct { // NewJSONDocument creates and populates a new JSON document struct from the given cataloging results. func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) { - pkgsDoc, err := packages.NewJSONDocument(config.PackageCatalog, config.SourceMetadata, config.Distro, config.ApplicationConfig.Packages.ScopeOpt, config.ApplicationConfig) + pkgsDoc, err := packages.NewJSONDocument(config.PackageCatalog, config.SourceMetadata, config.Distro, config.ApplicationConfig.Package.Cataloger.ScopeOpt, config.ApplicationConfig) if err != nil { return JSONDocument{}, err } diff --git a/internal/version/build.go b/internal/version/build.go index 3d6702c0c..1313fa9a3 100644 --- a/internal/version/build.go +++ b/internal/version/build.go @@ -6,6 +6,7 @@ package version import ( "fmt" "runtime" + "strings" ) const valueNotProvided = "[not provided]" @@ -28,6 +29,13 @@ type Version struct { Platform string `json:"platform"` // GOOS and GOARCH at build-time } +func (v Version) IsProductionBuild() bool { + if strings.Contains(v.Version, "SNAPSHOT") || strings.Contains(v.Version, valueNotProvided) { + return false + } + return true +} + // FromBuild provides all version details func FromBuild() Version { return Version{ diff --git a/internal/version/update.go b/internal/version/update.go index 3aa13ef3a..7fd791e1c 100644 --- a/internal/version/update.go +++ b/internal/version/update.go @@ -20,13 +20,13 @@ var latestAppVersionURL = struct { // IsUpdateAvailable indicates if there is a newer application version available, and if so, what the new version is. func IsUpdateAvailable() (bool, string, error) { - currentVersionStr := FromBuild().Version - currentVersion, err := hashiVersion.NewVersion(currentVersionStr) + currentBuildInfo := FromBuild() + if !currentBuildInfo.IsProductionBuild() { + // don't allow for non-production builds to check for a version. + return false, "", nil + } + currentVersion, err := hashiVersion.NewVersion(currentBuildInfo.Version) if err != nil { - if currentVersionStr == valueNotProvided { - // this is the default build arg and should be ignored (this is not an error case) - return false, "", nil - } return false, "", fmt.Errorf("failed to parse current application version: %w", err) } diff --git a/internal/version/update_test.go b/internal/version/update_test.go index 10c34b738..c79750540 100644 --- a/internal/version/update_test.go +++ b/internal/version/update_test.go @@ -81,6 +81,15 @@ func TestIsUpdateAvailable(t *testing.T) { newVersion: "", err: false, }, + { + name: "SnapshotBuildVersion", + buildVersion: "2.0.0-SHAPSHOT-a78bf9c", + latestVersion: "1.0.0", + code: 200, + isAvailable: false, + newVersion: "", + err: false, + }, { name: "BadUpdateValidVersion", buildVersion: "1.0.0", diff --git a/syft/pkg/ownership_by_files_relationship.go b/syft/pkg/ownership_by_files_relationship.go index 0151ee2b4..f235557a4 100644 --- a/syft/pkg/ownership_by_files_relationship.go +++ b/syft/pkg/ownership_by_files_relationship.go @@ -45,6 +45,10 @@ func ownershipByFilesRelationships(catalog *Catalog) []Relationship { func findOwnershipByFilesRelationships(catalog *Catalog) map[ID]map[ID]*strset.Set { var relationships = make(map[ID]map[ID]*strset.Set) + if catalog == nil { + return relationships + } + for _, candidateOwnerPkg := range catalog.Sorted() { if candidateOwnerPkg.Metadata == nil { continue diff --git a/test/cli/packages_cmd_test.go b/test/cli/packages_cmd_test.go index f5732330e..e5c28dfbb 100644 --- a/test/cli/packages_cmd_test.go +++ b/test/cli/packages_cmd_test.go @@ -71,7 +71,7 @@ func TestPackagesCmdFlags(t *testing.T) { { name: "packages-scope-env-binding", env: map[string]string{ - "SYFT_PACKAGES_SCOPE": "all-layers", + "SYFT_PACKAGE_CATALOGER_SCOPE": "all-layers", }, args: []string{"packages", "-o", "json", request}, assertions: []traitAssertion{ diff --git a/test/cli/power_user_cmd_test.go b/test/cli/power_user_cmd_test.go index 5bc6aa982..603c1a18d 100644 --- a/test/cli/power_user_cmd_test.go +++ b/test/cli/power_user_cmd_test.go @@ -25,6 +25,7 @@ func TestPowerUserCmdFlags(t *testing.T) { name: "default-results", args: []string{"power-user", request}, assertions: []traitAssertion{ + assertNotInOutput(" command is deprecated"), // only the root command should be deprecated assertInOutput(`"type": "regularFile"`), // proof of file-metadata data assertInOutput(`"algorithm": "sha256"`), // proof of file-metadata default digest algorithm of sha256 assertInOutput(`"metadataType": "ApkMetadata"`), // proof of package artifacts data diff --git a/test/cli/utils_test.go b/test/cli/utils_test.go index a2b6bf0c7..a12525d47 100644 --- a/test/cli/utils_test.go +++ b/test/cli/utils_test.go @@ -45,16 +45,19 @@ func getSyftCommand(t testing.TB, args ...string) *exec.Cmd { var binaryLocation string if os.Getenv("SYFT_BINARY_LOCATION") != "" { - // SYFT_BINARY_LOCATION is relative to the repository root. (e.g., "snapshot/syft-linux_amd64/syft") - // This value is transformed due to the CLI tests' need for a path relative to the test directory. - binaryLocation = path.Join(repoRoot(t), os.Getenv("SYFT_BINARY_LOCATION")) + // SYFT_BINARY_LOCATION is the absolute path to the snapshot binary + binaryLocation = os.Getenv("SYFT_BINARY_LOCATION") } else { - os := runtime.GOOS - if os == "darwin" { - os = "macos_darwin" + // note: there is a subtle - vs _ difference between these versions + switch runtime.GOOS { + case "darwin": + binaryLocation = path.Join(repoRoot(t), fmt.Sprintf("snapshot/syft-macos_darwin_%s/syft", runtime.GOARCH)) + case "linux": + binaryLocation = path.Join(repoRoot(t), fmt.Sprintf("snapshot/syft_linux_%s/syft", runtime.GOARCH)) + default: + t.Fatalf("unsupported OS: %s", runtime.GOOS) } - binaryLocation = path.Join(repoRoot(t), fmt.Sprintf("snapshot/syft-%s_%s/syft", os, runtime.GOARCH)) } return exec.Command(binaryLocation, args...) }