diff --git a/config/config.template.yml b/config/config.template.yml index 763baf9..7b3135f 100644 --- a/config/config.template.yml +++ b/config/config.template.yml @@ -55,9 +55,7 @@ proxy_install_only: false # # The purl is the package identifier and the reason is the reason for trusting the package. # PURL specification: https://github.com/package-url/purl-spec -trusted_packages: - - purl: pkg:npm/@safedep/pmg - reason: "PMG is a trusted package for PMG" +trusted_packages: [] # Sandbox configuration (EXPERIMENTAL) # When enabled, package managers run in sandbox environments with restricted diff --git a/config/config_template_test.go b/config/config_template_test.go index d442785..e32ed1b 100644 --- a/config/config_template_test.go +++ b/config/config_template_test.go @@ -32,7 +32,7 @@ func TestTemplateParsesAsYAML(t *testing.T) { assert.False(t, false, cfg.Paranoid, "expected Paranoid false") assert.False(t, false, cfg.SkipEventLogging, "expected SkipEventLogging false") assert.Equal(t, 7, cfg.EventLogRetentionDays, "expected EventLogRetentionDays 7") - assert.Len(t, cfg.TrustedPackages, 1) + assert.Empty(t, cfg.TrustedPackages) } func TestTemplateMatchesDefaults(t *testing.T) { @@ -56,11 +56,7 @@ func TestTemplateMatchesDefaults(t *testing.T) { assert.Equal(t, def.EventLogRetentionDays, parsed.EventLogRetentionDays, "event_log_retention_days mismatch") assert.Equal(t, def.Verbosity, parsed.Verbosity, "verbosity mismatch") - assert.NotEmpty(t, parsed.TrustedPackages, "expected at least one trusted_packages entry") - - first := parsed.TrustedPackages[0] - assert.NotEmpty(t, first.Purl, "first trusted package has empty purl") - assert.NotEmpty(t, first.Reason, "first trusted package has empty reason") + assert.Equal(t, def.TrustedPackages, parsed.TrustedPackages, "trusted_packages mismatch") assert.Equal(t, def.DependencyCooldown.Enabled, parsed.DependencyCooldown.Enabled, "dependency_cooldown.enabled mismatch") assert.Equal(t, def.DependencyCooldown.Days, parsed.DependencyCooldown.Days, "dependency_cooldown.days mismatch") diff --git a/config/viper.go b/config/viper.go index d7c74df..a96310c 100644 --- a/config/viper.go +++ b/config/viper.go @@ -3,7 +3,6 @@ package config import ( "fmt" "os" - "reflect" "strings" "github.com/spf13/viper" @@ -24,13 +23,14 @@ func loadViperConfig() error { v.AutomaticEnv() v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) - // Register all config struct fields as Viper defaults so that env vars work - // for any key Viper wouldn't otherwise know about — either because there is no - // config file, or because the key is absent from the file (e.g. commented out, - // or a new key added after the user last ran "pmg setup install"). - registerViperDefaults(v, globalConfig.Config, "") + // Load the embedded template as the base so Viper knows all keys and their + // defaults. This is required for AutomaticEnv to resolve PMG_* env vars for + // keys that are absent from or newer than the user's config file. + if err := v.ReadConfig(strings.NewReader(templateConfig)); err != nil { + return fmt.Errorf("failed to load default config: %w", err) + } - // Merge the user config file on top if it exists. + // Merge user config on top if it exists. if _, statErr := os.Stat(configPath); statErr == nil { v.SetConfigFile(configPath) if err := v.MergeInConfig(); err != nil { @@ -38,8 +38,6 @@ func loadViperConfig() error { } } - // Unmarshal into a copy of the current defaults so that keys absent from - // both the env and the user config file retain their Go defaults. merged := globalConfig.Config if err := v.Unmarshal(&merged); err != nil { return fmt.Errorf("failed to unmarshal config: %w", err) @@ -48,42 +46,3 @@ func loadViperConfig() error { globalConfig.Config = merged return nil } - -// registerViperDefaults walks cfg (a struct) recursively via reflection and registers -// each field as a Viper default using its mapstructure tag as the key. This is the -// minimum required for AutomaticEnv to resolve env vars for those keys. -func registerViperDefaults(v *viper.Viper, cfg any, prefix string) { - t := reflect.TypeOf(cfg) - val := reflect.ValueOf(cfg) - - if t.Kind() == reflect.Pointer { - t = t.Elem() - val = val.Elem() - } - - if t.Kind() != reflect.Struct { - return - } - - for i := range t.NumField() { - field := t.Field(i) - fieldVal := val.Field(i) - - tag := field.Tag.Get("mapstructure") - if tag == "" || tag == "-" { - continue - } - - // Strip options like ",squash" or ",omitempty" - key := strings.SplitN(tag, ",", 2)[0] - if prefix != "" { - key = prefix + "." + key - } - - if field.Type.Kind() == reflect.Struct { - registerViperDefaults(v, fieldVal.Interface(), key) - } else { - v.SetDefault(key, fieldVal.Interface()) - } - } -}