improve config parsing + fix command deprecation warning

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2021-03-20 07:33:13 -04:00
parent b1b57f6ba6
commit f180d1c537
No known key found for this signature in database
GPG Key ID: 5CB45AE22BAB7EA7
20 changed files with 179 additions and 131 deletions

View File

@ -361,4 +361,4 @@ jobs:
path: snapshot path: snapshot
- name: Run CLI Tests (Linux) - name: Run CLI Tests (Linux)
run: make cli-linux run: make cli

View File

@ -17,7 +17,6 @@ SUCCESS := $(BOLD)$(GREEN)
# the quality gate lower threshold for unit test total % coverage (by function statements) # the quality gate lower threshold for unit test total % coverage (by function statements)
COVERAGE_THRESHOLD := 68 COVERAGE_THRESHOLD := 68
# CI cache busting values; change these if you want CI to not use previous stored cache # CI cache busting values; change these if you want CI to not use previous stored cache
COMPARE_CACHE_BUSTER="f7e689d76a9"
INTEGRATION_CACHE_BUSTER="23493ba738c3d2f" INTEGRATION_CACHE_BUSTER="23493ba738c3d2f"
CLI_CACHE_BUSTER="789bacdf" CLI_CACHE_BUSTER="789bacdf"
BOOTSTRAP_CACHE="789bacdf" BOOTSTRAP_CACHE="789bacdf"
@ -26,7 +25,13 @@ BOOTSTRAP_CACHE="789bacdf"
DISTDIR=./dist DISTDIR=./dist
SNAPSHOTDIR=./snapshot SNAPSHOTDIR=./snapshot
GITTREESTATE=$(if $(shell git status --porcelain),dirty,clean) 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))" "" ifeq "$(strip $(VERSION))" ""
override VERSION = $(shell git describe --always --tags --dirty) 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' @printf '$(SUCCESS)All checks pass!$(RESET)\n'
.PHONY: test .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 .PHONY: help
help: help:
@ -215,20 +220,6 @@ acceptance-mac: $(RESULTSDIR) $(SNAPSHOTDIR) ## Run acceptance tests on build sn
.PHONY: acceptance-linux .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) 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 .PHONY: acceptance-test-deb-package-install
acceptance-test-deb-package-install: $(RESULTSDIR) $(SNAPSHOTDIR) acceptance-test-deb-package-install: $(RESULTSDIR) $(SNAPSHOTDIR)
$(call title,Running acceptance test: DEB install) $(call title,Running acceptance test: DEB install)
@ -251,17 +242,11 @@ acceptance-test-rpm-package-install: $(RESULTSDIR) $(SNAPSHOTDIR)
cli-fingerprint: 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 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 .PHONY: cli
cli-linux: $(SNAPSHOTDIR) ## Run CLI tests for Linux executable cli: $(SNAPSHOTDIR) ## Run CLI tests
chmod 755 "$(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN)" chmod 755 "$(SNAPSHOT_CMD)"
$(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN) version $(SNAPSHOT_CMD) version
SYFT_BINARY_LOCATION='$(SNAPSHOTDIR)/$(BIN)_linux_amd64/$(BIN)' \ SYFT_BINARY_LOCATION='$(SNAPSHOT_CMD)' \
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)' \
go test -count=1 -v ./test/cli go test -count=1 -v ./test/cli
.PHONY: changlog-release .PHONY: changlog-release

View File

@ -93,24 +93,26 @@ quiet: false
check-for-app-update: true check-for-app-update: true
# cataloging packages is exposed through the packages and power-user subcommands # cataloging packages is exposed through the packages and power-user subcommands
packages: package:
cataloger:
# enable/disable cataloging of packages # enable/disable cataloging of packages
# SYFT_PACKAGES_CATALOGING_ENABLED env var # SYFT_PACKAGE_CATALOGER_ENABLED env var
cataloging-enabled: true enabled: true
# the search space to look for packages (options: all-layers, squashed) # 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" scope: "squashed"
# cataloging file metadata is exposed through the power-user subcommand # cataloging file metadata is exposed through the power-user subcommand
file-metadata: file-metadata:
# enable/disable cataloging of file metadata cataloger:
# SYFT_FILE_METADATA_CATALOGING_ENABLED env var # enable/disable cataloging of file metadata
cataloging-enabled: true # SYFT_FILE_METADATA_CATALOGER_ENABLED env var
enabled: true
# the search space to look for file metadata (options: all-layers, squashed) # the search space to look for file metadata (options: all-layers, squashed)
# SYFT_FILE_METADATA_SCOPE env var # SYFT_FILE_METADATA_CATALOGER_SCOPE env var
scope: "squashed" scope: "squashed"
# the file digest algorithms to use when cataloging files (options: "sha256", "md5", "sha1") # the file digest algorithms to use when cataloging files (options: "sha256", "md5", "sha1")
# SYFT_FILE_METADATA_DIGESTS env var # SYFT_FILE_METADATA_DIGESTS env var

View File

@ -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. // 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 var activeCmd *cobra.Command
func Execute() { 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() { func initCmdAliasBindings() {
// TODO: when the root alias command is removed, this function (hack) can be removed // 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 activeCmd = rootCmd
for i, a := range os.Args { for i, a := range os.Args {
if i == 0 { if i == 0 {
// don't consider the bin // don't consider the bin
continue continue
} }
if a == "packages" { // check to see if this argument may be a command
// this is positively the first subcommand directive, and is "packages" if c, exists := commands[a]; exists {
activeCmd = packagesCmd activeCmd = c
break
}
if !strings.HasPrefix("-", a) {
// this is the first non-switch provided and was not "packages"
break
} }
} }
@ -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")) 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 if activeCmd == packagesCmd || activeCmd == rootCmd {
// and the packages command. Otherwise there will be global viper state that is in contention. // note: we need to lazily bind config options since they are shared between both the root command
// See for more details: https://github.com/spf13/viper/issues/233 . Additionally, the bindings must occur BEFORE // and the packages command. Otherwise there will be global viper state that is in contention.
// reading the application configuration, which implies that it must be an initializer (or rewrite the command // See for more details: https://github.com/spf13/viper/issues/233 . Additionally, the bindings must occur BEFORE
// initialization structure against typical patterns used with cobra, which is somewhat extreme for a // reading the application configuration, which implies that it must be an initializer (or rewrite the command
// temporary alias) // initialization structure against typical patterns used with cobra, which is somewhat extreme for a
if err := bindConfigOptions(activeCmd.Flags()); err != nil { // temporary alias)
panic(err) if err := bindConfigOptions(activeCmd.Flags()); err != nil {
panic(err)
}
} else {
if err := bindConfigOptions(packagesCmd.Flags()); err != nil {
panic(err)
}
} }
} }

View File

@ -141,7 +141,7 @@ func setPackageFlags(flags *pflag.FlagSet) {
func bindConfigOptions(flags *pflag.FlagSet) error { func bindConfigOptions(flags *pflag.FlagSet) error {
///////// Formatting & Input options ////////////////////////////////////////////// ///////// 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 return err
} }
@ -194,14 +194,14 @@ func packagesExecWorker(userInput string) <-chan error {
} }
defer cleanup() defer cleanup()
catalog, d, err := syft.CatalogPackages(src, appConfig.Packages.ScopeOpt) catalog, d, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt)
if err != nil { if err != nil {
errs <- fmt.Errorf("failed to catalog input: %+v", err) errs <- fmt.Errorf("failed to catalog input: %+v", err)
return return
} }
if appConfig.Anchore.Host != "" { 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 errs <- err
return return
} }
@ -213,7 +213,7 @@ func packagesExecWorker(userInput string) <-chan error {
SourceMetadata: src.Metadata, SourceMetadata: src.Metadata,
Catalog: catalog, Catalog: catalog,
Distro: d, Distro: d,
Scope: appConfig.Packages.ScopeOpt, Scope: appConfig.Package.Cataloger.ScopeOpt,
}), }),
}) })
}() }()

View File

@ -3,6 +3,8 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/presenter/poweruser" "github.com/anchore/syft/internal/presenter/poweruser"
"github.com/anchore/syft/internal/ui" "github.com/anchore/syft/internal/ui"
@ -13,14 +15,24 @@ import (
"github.com/wagoodman/go-partybus" "github.com/wagoodman/go-partybus"
) )
const powerUserExample = ` {{.appName}} {{.command}} <image>
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 { var powerUserOpts = struct {
configPath string configPath string
}{} }{}
var powerUserCmd = &cobra.Command{ var powerUserCmd = &cobra.Command{
Use: "power-user [SOURCE]", Use: "power-user [IMAGE]",
Short: "Run bulk operations on container images", Short: "Run bulk operations on container images",
Example: ` {{.appName}} power-user <image>`, Example: internal.Tprintf(powerUserExample, map[string]interface{}{
"appName": internal.ApplicationName,
"command": "power-user",
}),
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Hidden: true, Hidden: true,
SilenceUsage: true, SilenceUsage: true,

View File

@ -37,12 +37,12 @@ func powerUserTasks(src source.Source) ([]powerUserTask, error) {
} }
func catalogPackagesTask(src source.Source) powerUserTask { func catalogPackagesTask(src source.Source) powerUserTask {
if !appConfig.Packages.CatalogingEnabled { if !appConfig.Package.Cataloger.Enabled {
return nil return nil
} }
task := func(results *poweruser.JSONDocumentConfig) error { 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 { if err != nil {
return err return err
} }
@ -57,11 +57,11 @@ func catalogPackagesTask(src source.Source) powerUserTask {
} }
func catalogFileMetadataTask(src source.Source) (powerUserTask, error) { func catalogFileMetadataTask(src source.Source) (powerUserTask, error) {
if !appConfig.FileMetadata.CatalogingEnabled { if !appConfig.FileMetadata.Cataloger.Enabled {
return nil, nil return nil, nil
} }
resolver, err := src.FileResolver(appConfig.FileMetadata.ScopeOpt) resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -79,11 +79,11 @@ func catalogFileMetadataTask(src source.Source) (powerUserTask, error) {
} }
func catalogFileDigestTask(src source.Source) (powerUserTask, error) { func catalogFileDigestTask(src source.Source) (powerUserTask, error) {
if !appConfig.FileMetadata.CatalogingEnabled { if !appConfig.FileMetadata.Cataloger.Enabled {
return nil, nil return nil, nil
} }
resolver, err := src.FileResolver(appConfig.FileMetadata.ScopeOpt) resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"errors"
"fmt" "fmt"
"path" "path"
"strings" "strings"
@ -15,6 +16,8 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
var ErrApplicationConfigNotFound = fmt.Errorf("application config not found")
// Application is the main syft application configuration. // Application is the main syft application configuration.
type Application struct { 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) 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"` 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 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 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"` 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) { 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 // the user may not have a config, and this is OK, we can use the default config + default cobra cli values instead
setNonCliDefaultAppConfigValues(v) setNonCliDefaultAppConfigValues(v)
_ = readConfig(v, cliOpts.ConfigPath) if err := readConfig(v, cliOpts.ConfigPath); err != nil && !errors.Is(err, ErrApplicationConfigNotFound) {
return nil, err
}
config := &Application{ config := &Application{
CliOptions: cliOpts, CliOptions: cliOpts,
@ -88,7 +93,7 @@ func (cfg *Application) build() error {
} }
for _, builder := range []func() error{ for _, builder := range []func() error{
cfg.Packages.build, cfg.Package.build,
cfg.FileMetadata.build, cfg.FileMetadata.build,
} { } {
if err := builder(); err != nil { 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 // 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 { func readConfig(v *viper.Viper, configPath string) error {
var err error
v.AutomaticEnv() v.AutomaticEnv()
v.SetEnvPrefix(internal.ApplicationName) v.SetEnvPrefix(internal.ApplicationName)
// allow for nested options to be specified via environment variables // 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 .<appname>.yaml (in the current directory) // 1. look for .<appname>.yaml (in the current directory)
v.AddConfigPath(".") v.AddConfigPath(".")
v.SetConfigName(internal.ApplicationName) v.SetConfigName("." + internal.ApplicationName)
if err := v.ReadInConfig(); err == nil { if err = v.ReadInConfig(); err == nil {
return 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 .<appname>/config.yaml (in the current directory) // 2. look for .<appname>/config.yaml (in the current directory)
v.AddConfigPath("." + internal.ApplicationName) v.AddConfigPath("." + internal.ApplicationName)
v.SetConfigName("config") v.SetConfigName("config")
if err := v.ReadInConfig(); err == nil { if err = v.ReadInConfig(); err == nil {
return 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 ~/.<appname>.yaml // 3. look for ~/.<appname>.yaml
@ -149,8 +160,10 @@ func readConfig(v *viper.Viper, configPath string) error {
if err == nil { if err == nil {
v.AddConfigPath(home) v.AddConfigPath(home)
v.SetConfigName("." + internal.ApplicationName) v.SetConfigName("." + internal.ApplicationName)
if err := v.ReadInConfig(); err == nil { if err = v.ReadInConfig(); err == nil {
return 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.AddConfigPath(path.Join(dir, internal.ApplicationName))
} }
v.SetConfigName("config") v.SetConfigName("config")
if err := v.ReadInConfig(); err == nil { if err = v.ReadInConfig(); err == nil {
return 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) // 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("check-for-app-update", true)
v.SetDefault("dev.profile-cpu", false) v.SetDefault("dev.profile-cpu", false)
v.SetDefault("dev.profile-mem", false) v.SetDefault("dev.profile-mem", false)
v.SetDefault("packages.cataloging-enabled", true) v.SetDefault("package.cataloger.enabled", true)
v.SetDefault("file-metadata.cataloging-enabled", true) v.SetDefault("file-metadata.cataloger.enabled", true)
v.SetDefault("file-metadata.scope", source.SquashedScope) v.SetDefault("file-metadata.cataloger.scope", source.SquashedScope)
v.SetDefault("file-metadata.digests", []string{"sha256"}) v.SetDefault("file-metadata.digests", []string{"sha256"})
} }

View File

@ -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
}

View File

@ -1,24 +1,10 @@
package config package config
import (
"fmt"
"github.com/anchore/syft/syft/source"
)
type FileMetadata struct { type FileMetadata struct {
CatalogingEnabled bool `yaml:"cataloging-enabled" json:"cataloging-enabled" mapstructure:"cataloging-enabled"` Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"` Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
ScopeOpt source.Scope `yaml:"-" json:"-"`
Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
} }
func (cfg *FileMetadata) build() error { func (cfg *FileMetadata) build() error {
scopeOption := source.ParseScope(cfg.Scope) return cfg.Cataloger.build()
if scopeOption == source.UnknownScope {
return fmt.Errorf("bad scope value %q", cfg.Scope)
}
cfg.ScopeOpt = scopeOption
return nil
} }

View File

@ -1,23 +1,9 @@
package config package config
import (
"fmt"
"github.com/anchore/syft/syft/source"
)
type Packages struct { type Packages struct {
CatalogingEnabled bool `yaml:"cataloging-enabled" json:"cataloging-enabled" mapstructure:"cataloging-enabled"` Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
ScopeOpt source.Scope `yaml:"-" json:"-"`
} }
func (cfg *Packages) build() error { func (cfg *Packages) build() error {
scopeOption := source.ParseScope(cfg.Scope) return cfg.Cataloger.build()
if scopeOption == source.UnknownScope {
return fmt.Errorf("bad scope value %q", cfg.Scope)
}
cfg.ScopeOpt = scopeOption
return nil
} }

View File

@ -23,6 +23,9 @@ type JSONPackage struct {
func NewJSONPackages(catalog *pkg.Catalog) ([]JSONPackage, error) { func NewJSONPackages(catalog *pkg.Catalog) ([]JSONPackage, error) {
artifacts := make([]JSONPackage, 0) artifacts := make([]JSONPackage, 0)
if catalog == nil {
return artifacts, nil
}
for _, p := range catalog.Sorted() { for _, p := range catalog.Sorted() {
art, err := NewJSONPackage(p) art, err := NewJSONPackage(p)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ type JSONDocument struct {
// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results. // NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) { 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 { if err != nil {
return JSONDocument{}, err return JSONDocument{}, err
} }

View File

@ -6,6 +6,7 @@ package version
import ( import (
"fmt" "fmt"
"runtime" "runtime"
"strings"
) )
const valueNotProvided = "[not provided]" const valueNotProvided = "[not provided]"
@ -28,6 +29,13 @@ type Version struct {
Platform string `json:"platform"` // GOOS and GOARCH at build-time 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 // FromBuild provides all version details
func FromBuild() Version { func FromBuild() Version {
return Version{ return Version{

View File

@ -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. // IsUpdateAvailable indicates if there is a newer application version available, and if so, what the new version is.
func IsUpdateAvailable() (bool, string, error) { func IsUpdateAvailable() (bool, string, error) {
currentVersionStr := FromBuild().Version currentBuildInfo := FromBuild()
currentVersion, err := hashiVersion.NewVersion(currentVersionStr) 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 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) return false, "", fmt.Errorf("failed to parse current application version: %w", err)
} }

View File

@ -81,6 +81,15 @@ func TestIsUpdateAvailable(t *testing.T) {
newVersion: "", newVersion: "",
err: false, err: false,
}, },
{
name: "SnapshotBuildVersion",
buildVersion: "2.0.0-SHAPSHOT-a78bf9c",
latestVersion: "1.0.0",
code: 200,
isAvailable: false,
newVersion: "",
err: false,
},
{ {
name: "BadUpdateValidVersion", name: "BadUpdateValidVersion",
buildVersion: "1.0.0", buildVersion: "1.0.0",

View File

@ -45,6 +45,10 @@ func ownershipByFilesRelationships(catalog *Catalog) []Relationship {
func findOwnershipByFilesRelationships(catalog *Catalog) map[ID]map[ID]*strset.Set { func findOwnershipByFilesRelationships(catalog *Catalog) map[ID]map[ID]*strset.Set {
var relationships = make(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() { for _, candidateOwnerPkg := range catalog.Sorted() {
if candidateOwnerPkg.Metadata == nil { if candidateOwnerPkg.Metadata == nil {
continue continue

View File

@ -71,7 +71,7 @@ func TestPackagesCmdFlags(t *testing.T) {
{ {
name: "packages-scope-env-binding", name: "packages-scope-env-binding",
env: map[string]string{ env: map[string]string{
"SYFT_PACKAGES_SCOPE": "all-layers", "SYFT_PACKAGE_CATALOGER_SCOPE": "all-layers",
}, },
args: []string{"packages", "-o", "json", request}, args: []string{"packages", "-o", "json", request},
assertions: []traitAssertion{ assertions: []traitAssertion{

View File

@ -25,6 +25,7 @@ func TestPowerUserCmdFlags(t *testing.T) {
name: "default-results", name: "default-results",
args: []string{"power-user", request}, args: []string{"power-user", request},
assertions: []traitAssertion{ assertions: []traitAssertion{
assertNotInOutput(" command is deprecated"), // only the root command should be deprecated
assertInOutput(`"type": "regularFile"`), // proof of file-metadata data assertInOutput(`"type": "regularFile"`), // proof of file-metadata data
assertInOutput(`"algorithm": "sha256"`), // proof of file-metadata default digest algorithm of sha256 assertInOutput(`"algorithm": "sha256"`), // proof of file-metadata default digest algorithm of sha256
assertInOutput(`"metadataType": "ApkMetadata"`), // proof of package artifacts data assertInOutput(`"metadataType": "ApkMetadata"`), // proof of package artifacts data

View File

@ -45,16 +45,19 @@ func getSyftCommand(t testing.TB, args ...string) *exec.Cmd {
var binaryLocation string var binaryLocation string
if os.Getenv("SYFT_BINARY_LOCATION") != "" { if os.Getenv("SYFT_BINARY_LOCATION") != "" {
// SYFT_BINARY_LOCATION is relative to the repository root. (e.g., "snapshot/syft-linux_amd64/syft") // SYFT_BINARY_LOCATION is the absolute path to the snapshot binary
// This value is transformed due to the CLI tests' need for a path relative to the test directory. binaryLocation = os.Getenv("SYFT_BINARY_LOCATION")
binaryLocation = path.Join(repoRoot(t), os.Getenv("SYFT_BINARY_LOCATION"))
} else { } else {
os := runtime.GOOS // note: there is a subtle - vs _ difference between these versions
if os == "darwin" { switch runtime.GOOS {
os = "macos_darwin" 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...) return exec.Command(binaryLocation, args...)
} }