Files
pmg/config/viper.go
T

94 lines
2.9 KiB
Go
Raw Normal View History

2026-01-01 12:33:52 +05:30
package config
import (
"fmt"
"os"
"strings"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
2026-01-01 12:33:52 +05:30
)
// loadViperConfig loads the configuration using Viper.
// Precedence (highest to lowest): cobra flags > env vars > config file > defaults.
// Cobra flags write directly to the config struct after this function runs.
func loadViperConfig() error {
2026-05-21 14:32:30 +05:30
// Use the active config path resolved by initConfig (globally managed file
// when present, otherwise the per-user file).
configPath := globalConfig.configFilePath
2026-01-01 12:33:52 +05:30
v := viper.New()
v.SetConfigType("yaml")
v.SetEnvPrefix("PMG")
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
2026-01-01 12:33:52 +05:30
// 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 user config on top if it exists.
if _, statErr := os.Stat(configPath); statErr == nil {
v.SetConfigFile(configPath)
if err := v.MergeInConfig(); err != nil {
return fmt.Errorf("failed to read config file %s: %w", configPath, err)
}
2026-01-01 12:33:52 +05:30
}
merged := globalConfig.Config
if err := v.Unmarshal(&merged); err != nil {
return fmt.Errorf("failed to unmarshal config: %w", err)
2026-01-01 12:33:52 +05:30
}
globalConfig.Config = merged
globalConfig.viper = v
// Resolve proxy config: new proxy section > legacy flat keys.
// Viper can't distinguish "value from template" vs "value from user config"
// (v.IsSet is always true for template keys), so we check the raw user file.
if !hasProxySectionInFile(configPath) {
applyProxyLegacyFallback(v)
}
return nil
2026-01-01 12:33:52 +05:30
}
// hasProxySectionInFile checks whether the user's config file contains a
// top-level "proxy" key. Returns false if the file doesn't exist or can't
// be parsed.
func hasProxySectionInFile(path string) bool {
data, err := os.ReadFile(path)
if err != nil {
return false
}
var raw map[string]any
if err := yaml.Unmarshal(data, &raw); err != nil {
return false
}
_, ok := raw["proxy"]
return ok
}
// applyProxyLegacyFallback populates the new Proxy struct from deprecated
// flat keys when the user's config file does not have a proxy: section.
// New env vars (PMG_PROXY_ENABLED, PMG_PROXY_INSTALL_ONLY) take precedence
// over legacy config file keys to respect the documented precedence order.
func applyProxyLegacyFallback(v *viper.Viper) {
if os.Getenv("PMG_PROXY_ENABLED") == "" && v.IsSet("proxy_mode") {
val := v.GetBool("proxy_mode")
globalConfig.Config.Proxy.Enabled = val
v.Set("proxy.enabled", val)
}
if os.Getenv("PMG_PROXY_INSTALL_ONLY") == "" && v.IsSet("proxy_install_only") {
val := v.GetBool("proxy_install_only")
globalConfig.Config.Proxy.InstallOnly = val
v.Set("proxy.install_only", val)
}
}