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-22 10:58:09 -04:00
parent b1b57f6ba6
commit f180d1c537
20 changed files with 179 additions and 131 deletions
+27 -12
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"})
}
+23
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
}
+3 -17
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:"-"`
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()
}
+2 -16
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()
}
@@ -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 {
@@ -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
}
+8
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{
+6 -6
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)
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)
}
+9
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",