Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate config.yaml as valid config source; Add unit regression for correct config paths #1640

Merged
merged 9 commits into from
Mar 20, 2023
37 changes: 27 additions & 10 deletions internal/config/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"errors"
"fmt"
"os"
"path"
"reflect"
"sort"
Expand Down Expand Up @@ -208,6 +209,7 @@ func (cfg Application) String() string {
return string(appaStr)
}

// nolint:funlen
func loadConfig(v *viper.Viper, configPath string) error {
var err error
// use explicitly the given user config
Expand All @@ -223,13 +225,26 @@ func loadConfig(v *viper.Viper, configPath string) error {

// start searching for valid configs in order...
// 1. look for .<appname>.yaml (in the current directory)
confFilePath := "." + internal.ApplicationName

// TODO: Remove this before v1.0.0
// See syft #1634
v.AddConfigPath(".")
v.SetConfigName("." + internal.ApplicationName)
if err = v.ReadInConfig(); err == nil {
v.Set("config", v.ConfigFileUsed())
return nil
} else if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err)
v.SetConfigName(confFilePath)

// check if config.yaml exists in the current directory
// DEPRECATED: this will be removed in v1.0.0
if _, err := os.Stat("config.yaml"); err == nil {
log.Warn("DEPRECATED: ./config.yaml as a configuration file is deprecated and will be removed as an option in v1.0.0, please rename to .syft.yaml")
}

if _, err := os.Stat(confFilePath + ".yaml"); err == nil {
if err = v.ReadInConfig(); err == nil {
v.Set("config", v.ConfigFileUsed())
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)
Expand All @@ -255,12 +270,14 @@ func loadConfig(v *viper.Viper, configPath string) error {
}
}

// 4. look for <appname>/config.yaml in xdg locations (starting with xdg home config dir, then moving upwards)
v.AddConfigPath(path.Join(xdg.ConfigHome, internal.ApplicationName))
// 4. look for .<appname>/config.yaml in xdg locations (starting with xdg home config dir, then moving upwards)

v.SetConfigName("config")
configPath = path.Join(xdg.ConfigHome, "."+internal.ApplicationName)
v.AddConfigPath(configPath)
for _, dir := range xdg.ConfigDirs {
v.AddConfigPath(path.Join(dir, internal.ApplicationName))
v.AddConfigPath(path.Join(dir, "."+internal.ApplicationName))
}
v.SetConfigName("config")
if err = v.ReadInConfig(); err == nil {
v.Set("config", v.ConfigFileUsed())
return nil
Expand Down
121 changes: 121 additions & 0 deletions internal/config/application_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package config

import (
"os"
"path"
"testing"

"github.com/adrg/xdg"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)

// TODO: set negative case when config.yaml is no longer a valid option
func TestApplicationConfig(t *testing.T) {
// config is picked up at desired configuration paths
// VALID: .syft.yaml, .syft/config.yaml, ~/.syft.yaml, <XDG_CONFIG_HOME>/syft/config.yaml
// DEPRECATED: config.yaml is currently supported by
tests := []struct {
name string
setup func(t *testing.T) string
assertions func(t *testing.T, app *Application)
Cleanup func(t *testing.T)
}{
{
name: "explicit config",
setup: func(t *testing.T) string {
return "./test-fixtures/.syft.yaml"
}, // no-op for explicit config
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-explicit-config", app.File)
},
Cleanup: func(t *testing.T) {},
},
{
name: "current working directory named config",
setup: func(t *testing.T) string {
err := os.Chdir("./test-fixtures/config-wd-file") // change application cwd to test-fixtures
if err != nil {
t.Fatalf("%s failed to change cwd: %+v", t.Name(), err)
}
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-wd-named-config", app.File)
},
Cleanup: func(t *testing.T) {},
},
{
name: "current working directory syft dir config",
setup: func(t *testing.T) string {
err := os.Chdir("./test-fixtures/config-dir-test") // change application cwd to test-fixtures
if err != nil {
t.Fatalf("%s failed to change cwd: %+v", t.Name(), err)
}
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-dir-config", app.File)
},
Cleanup: func(t *testing.T) {},
},
{
name: "home directory file config",
setup: func(t *testing.T) string {
// Because Setenv affects the whole process, it cannot be used in parallel tests or
// tests with parallel ancestors: see separate XDG test for consequence of this
t.Setenv("HOME", "./test-fixtures/config-home-test")
err := os.Link("./test-fixtures/config-home-test/config-file/.syft.yaml", "./test-fixtures/config-home-test/.syft.yaml")
if err != nil {
t.Fatalf("%s failed to link home config: %+v", t.Name(), err)
}
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-home-config", app.File)
},
Cleanup: func(t *testing.T) {
err := os.Remove("./test-fixtures/config-home-test/.syft.yaml") //
if err != nil {
t.Fatalf("%s failed to remove home config link: %+v", t.Name(), err)
}
},
},
{
name: "XDG file config",
setup: func(t *testing.T) string {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("%s: failed to get working directory: %+v", t.Name(), err)
}
configDir := path.Join(wd, "./test-fixtures/config-home-test") // set HOME to testdata
t.Setenv("XDG_CONFIG_DIRS", configDir)
xdg.Reload()
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-home-XDG-config", app.File)
},
Cleanup: func(t *testing.T) {},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
defer test.Cleanup(t)
wd, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get working directory: %+v", err)
}
defer os.Chdir(wd) // reset working directory after test
application := &Application{}
viperInstance := viper.New()

configPath := test.setup(t)
err = application.LoadAllValues(viperInstance, configPath)
if err != nil {
t.Fatalf("failed to load application config: %+v", err)
}
test.assertions(t, application)
})
}
}
7 changes: 7 additions & 0 deletions internal/config/test-fixtures/.syft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-explicit-config"
package:
cataloger:
scope: "squashed"

# same as --scope; limit the scope of the cataloger to only the specified types
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-dir-config"
package:
cataloger:
scope: "squashed"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-home-XDG-config"
package:
cataloger:
scope: "squashed"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-home-config"
package:
cataloger:
scope: "squashed"
5 changes: 5 additions & 0 deletions internal/config/test-fixtures/config-wd-file/.syft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-wd-named-config"
package:
cataloger:
scope: "squashed"