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.
|
# 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
|
# PURL specification: https://github.com/package-url/purl-spec
|
||||||
trusted_packages:
|
trusted_packages: []
|
||||||
- purl: pkg:npm/@safedep/pmg
|
|
||||||
reason: "PMG is a trusted package for PMG"
|
|
||||||
|
|
||||||
# Sandbox configuration (EXPERIMENTAL)
|
# Sandbox configuration (EXPERIMENTAL)
|
||||||
# When enabled, package managers run in sandbox environments with restricted
|
# 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.Paranoid, "expected Paranoid false")
|
||||||
assert.False(t, false, cfg.SkipEventLogging, "expected SkipEventLogging false")
|
assert.False(t, false, cfg.SkipEventLogging, "expected SkipEventLogging false")
|
||||||
assert.Equal(t, 7, cfg.EventLogRetentionDays, "expected EventLogRetentionDays 7")
|
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) {
|
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.EventLogRetentionDays, parsed.EventLogRetentionDays, "event_log_retention_days mismatch")
|
||||||
assert.Equal(t, def.Verbosity, parsed.Verbosity, "verbosity mismatch")
|
assert.Equal(t, def.Verbosity, parsed.Verbosity, "verbosity mismatch")
|
||||||
|
|
||||||
assert.NotEmpty(t, parsed.TrustedPackages, "expected at least one trusted_packages entry")
|
assert.Equal(t, def.TrustedPackages, parsed.TrustedPackages, "trusted_packages mismatch")
|
||||||
|
|
||||||
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.DependencyCooldown.Enabled, parsed.DependencyCooldown.Enabled, "dependency_cooldown.enabled 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")
|
assert.Equal(t, def.DependencyCooldown.Days, parsed.DependencyCooldown.Days, "dependency_cooldown.days mismatch")
|
||||||
|
|||||||
+7
-48
@@ -3,7 +3,6 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@@ -24,13 +23,14 @@ func loadViperConfig() error {
|
|||||||
v.AutomaticEnv()
|
v.AutomaticEnv()
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||||
|
|
||||||
// Register all config struct fields as Viper defaults so that env vars work
|
// Load the embedded template as the base so Viper knows all keys and their
|
||||||
// for any key Viper wouldn't otherwise know about — either because there is no
|
// defaults. This is required for AutomaticEnv to resolve PMG_* env vars for
|
||||||
// config file, or because the key is absent from the file (e.g. commented out,
|
// keys that are absent from or newer than the user's config file.
|
||||||
// or a new key added after the user last ran "pmg setup install").
|
if err := v.ReadConfig(strings.NewReader(templateConfig)); err != nil {
|
||||||
registerViperDefaults(v, globalConfig.Config, "")
|
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 {
|
if _, statErr := os.Stat(configPath); statErr == nil {
|
||||||
v.SetConfigFile(configPath)
|
v.SetConfigFile(configPath)
|
||||||
if err := v.MergeInConfig(); err != nil {
|
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
|
merged := globalConfig.Config
|
||||||
if err := v.Unmarshal(&merged); err != nil {
|
if err := v.Unmarshal(&merged); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal config: %w", err)
|
return fmt.Errorf("failed to unmarshal config: %w", err)
|
||||||
@@ -48,42 +46,3 @@ func loadViperConfig() error {
|
|||||||
globalConfig.Config = merged
|
globalConfig.Config = merged
|
||||||
return nil
|
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