mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
refactor: Replace reflection-based Viper defaults with embedded template
Load the embedded config template as the Viper base so all keys are registered upfront, enabling PMG_* env vars to work regardless of whether a key exists in the user's config file.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
+7
-48
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user