mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 10:36:45 +01:00
improve config parsing + fix command deprecation warning
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
b1b57f6ba6
commit
f180d1c537
2
.github/workflows/validations.yaml
vendored
2
.github/workflows/validations.yaml
vendored
@ -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
|
||||||
|
|||||||
41
Makefile
41
Makefile
@ -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
|
||||||
|
|||||||
22
README.md
22
README.md
@ -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
|
||||||
|
|||||||
45
cmd/cmd.go
45
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.
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"})
|
||||||
}
|
}
|
||||||
|
|||||||
23
internal/config/cataloger_options.go
Normal file
23
internal/config/cataloger_options.go
Normal 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
|
||||||
|
}
|
||||||
@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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{
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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{
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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...)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user