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
- 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)
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

View File

@ -93,23 +93,25 @@ 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:
cataloger:
# enable/disable cataloging of file metadata
# SYFT_FILE_METADATA_CATALOGING_ENABLED env var
cataloging-enabled: true
# SYFT_FILE_METADATA_CATALOGER_ENABLED env var
enabled: true
# 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"
# the file digest algorithms to use when cataloging files (options: "sha256", "md5", "sha1")

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.
// 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,6 +74,7 @@ func initCmdAliasBindings() {
fmt.Fprintln(os.Stderr, color.New(color.Bold, color.Red).Sprintf("The root command is deprecated, please use the 'packages' subcommand"))
}
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
@ -78,6 +84,11 @@ func initCmdAliasBindings() {
if err := bindConfigOptions(activeCmd.Flags()); err != nil {
panic(err)
}
} else {
if err := bindConfigOptions(packagesCmd.Flags()); err != nil {
panic(err)
}
}
}
func initAppConfig() {

View File

@ -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,
}),
})
}()

View File

@ -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}} <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 {
configPath string
}{}
var powerUserCmd = &cobra.Command{
Use: "power-user [SOURCE]",
Use: "power-user [IMAGE]",
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),
Hidden: true,
SilenceUsage: true,

View File

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

View File

@ -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 .<appname>.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 .<appname>/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 ~/.<appname>.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"})
}

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
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:"-"`
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()
}

View File

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

View File

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

View File

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

View File

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

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.
func IsUpdateAvailable() (bool, string, error) {
currentVersionStr := FromBuild().Version
currentVersion, err := hashiVersion.NewVersion(currentVersionStr)
if err != nil {
if currentVersionStr == valueNotProvided {
// this is the default build arg and should be ignored (this is not an error case)
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 {
return false, "", fmt.Errorf("failed to parse current application version: %w", err)
}

View File

@ -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",

View File

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

View File

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

View File

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

View File

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