mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: consolidate proxy config into structured section and add support for custom commands to skip proxy (#240)
* feat: add ProxyConfig struct with per-PM skip_commands and legacy fallback * feat: consolidate proxy config into structured section with backward compat Replaces flat proxy_mode/proxy_install_only keys with a structured proxy section supporting per-package-manager skip_commands. Legacy keys are respected via fallback when user's config lacks the new proxy section. Removes deprecated experimental_proxy_mode config and flag. * fix: env var resolution for nested config keys and deduplicate skip command matching - Add "." to "_" in Viper env key replacer so nested keys like sandbox.enabled resolve from PMG_SANDBOX_ENABLED (was silently broken) - Export IsFirstNonFlagArgInList and remove duplicate from proxy_flow.go - Add table-driven tests for skip command matching with real-world cases - Remove redundant env var test * docs: update proxy configuration and env var documentation Update config.md env var table to reflect new proxy.enabled and proxy.install_only keys. Add proxy configuration section to proxy.md covering config structure, per-PM skip commands, CLI flags, and env vars. * fix: legacy fallback precedence
This commit is contained in:
+102
-29
@@ -28,7 +28,7 @@ func TestConfigHasDefaultValues(t *testing.T) {
|
||||
assert.Len(t, config.Config.TrustedPackages, 1)
|
||||
assert.Equal(t, "/tmp/pmg-test/random-does-not-exist", config.configDir)
|
||||
assert.Equal(t, "/tmp/pmg-test/random-does-not-exist/config.yml", config.configFilePath)
|
||||
assert.Equal(t, false, config.Config.ProxyInstallOnly)
|
||||
assert.Equal(t, false, config.Config.Proxy.InstallOnly)
|
||||
})
|
||||
|
||||
t.Run("when no config directory is set", func(t *testing.T) {
|
||||
@@ -69,7 +69,7 @@ func TestPartialConfigFallsBackToDefaults(t *testing.T) {
|
||||
// Missing keys should fall back to DefaultConfig() values, not Go zero values
|
||||
defaults := DefaultConfig().Config
|
||||
assert.Equal(t, defaults.TransitiveDepth, config.Config.TransitiveDepth)
|
||||
assert.Equal(t, defaults.ProxyMode, config.Config.ProxyMode)
|
||||
assert.Equal(t, defaults.Proxy.Enabled, config.Config.Proxy.Enabled)
|
||||
assert.Equal(t, defaults.Verbosity, config.Config.Verbosity)
|
||||
assert.Equal(t, defaults.EventLogRetentionDays, config.Config.EventLogRetentionDays)
|
||||
assert.Equal(t, defaults.DependencyCooldown.Enabled, config.Config.DependencyCooldown.Enabled)
|
||||
@@ -102,14 +102,14 @@ func TestPartialConfigWithNestedOverride(t *testing.T) {
|
||||
// Top-level fields should fall back to defaults
|
||||
assert.Equal(t, defaults.Transitive, config.Config.Transitive)
|
||||
assert.Equal(t, defaults.TransitiveDepth, config.Config.TransitiveDepth)
|
||||
assert.Equal(t, defaults.ProxyMode, config.Config.ProxyMode)
|
||||
assert.Equal(t, defaults.Proxy.Enabled, config.Config.Proxy.Enabled)
|
||||
}
|
||||
|
||||
func TestProxyInstallOnlyConfig(t *testing.T) {
|
||||
t.Run("defaults to false", func(t *testing.T) {
|
||||
t.Setenv("PMG_CONFIG_DIR", "/tmp/pmg-test/random-does-not-exist")
|
||||
initConfig()
|
||||
assert.Equal(t, false, Get().Config.ProxyInstallOnly)
|
||||
assert.Equal(t, false, Get().Config.Proxy.InstallOnly)
|
||||
})
|
||||
|
||||
t.Run("can be set to true via config file", func(t *testing.T) {
|
||||
@@ -117,18 +117,18 @@ func TestProxyInstallOnlyConfig(t *testing.T) {
|
||||
t.Setenv("PMG_CONFIG_DIR", tmpDir)
|
||||
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
err := os.WriteFile(configPath, []byte("proxy_install_only: true\n"), 0o644)
|
||||
err := os.WriteFile(configPath, []byte("proxy:\n install_only: true\n"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
assert.Equal(t, true, Get().Config.ProxyInstallOnly)
|
||||
assert.Equal(t, true, Get().Config.Proxy.InstallOnly)
|
||||
})
|
||||
}
|
||||
|
||||
// TestConfigPrecedence verifies the expected override order:
|
||||
// flags > env var > config file > default
|
||||
func TestConfigPrecedence(t *testing.T) {
|
||||
t.Run("env var overrides config file", func(t *testing.T) {
|
||||
t.Run("env var sets legacy flat field", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("PMG_CONFIG_DIR", tmpDir)
|
||||
t.Setenv("PMG_PROXY_INSTALL_ONLY", "true")
|
||||
@@ -138,7 +138,8 @@ func TestConfigPrecedence(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
assert.Equal(t, true, Get().Config.ProxyInstallOnly, "env var should override config file")
|
||||
// PMG_PROXY_INSTALL_ONLY maps to the flat proxy_install_only key, not nested proxy.install_only
|
||||
assert.Equal(t, true, Get().Config.ProxyInstallOnly, "env var should set legacy flat field")
|
||||
})
|
||||
|
||||
t.Run("config file overrides default", func(t *testing.T) {
|
||||
@@ -147,11 +148,11 @@ func TestConfigPrecedence(t *testing.T) {
|
||||
t.Setenv("PMG_PROXY_INSTALL_ONLY", "")
|
||||
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
err := os.WriteFile(configPath, []byte("proxy_install_only: true\n"), 0o644)
|
||||
err := os.WriteFile(configPath, []byte("proxy:\n install_only: true\n"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
assert.Equal(t, true, Get().Config.ProxyInstallOnly, "config file should override default")
|
||||
assert.Equal(t, true, Get().Config.Proxy.InstallOnly, "config file should override default")
|
||||
})
|
||||
|
||||
t.Run("telemetry can be disabled via config", func(t *testing.T) {
|
||||
@@ -167,27 +168,12 @@ func TestConfigPrecedence(t *testing.T) {
|
||||
assert.Equal(t, true, Get().Config.DisableTelemetry, "config file should disable telemetry")
|
||||
})
|
||||
|
||||
t.Run("env var works when key is absent from config file", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("PMG_CONFIG_DIR", tmpDir)
|
||||
t.Setenv("PMG_PROXY_INSTALL_ONLY", "true")
|
||||
|
||||
// Config file exists but proxy_install_only is not in it (e.g. commented out
|
||||
// or user hasn't re-run "pmg setup install" after an upgrade)
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
err := os.WriteFile(configPath, []byte("transitive: false\n"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
assert.Equal(t, true, Get().Config.ProxyInstallOnly, "env var should work even when key is absent from config file")
|
||||
})
|
||||
|
||||
t.Run("env var works without a config file", func(t *testing.T) {
|
||||
t.Setenv("PMG_CONFIG_DIR", "/tmp/pmg-test/random-does-not-exist")
|
||||
t.Setenv("PMG_PROXY_INSTALL_ONLY", "true")
|
||||
t.Setenv("PMG_PARANOID", "true")
|
||||
|
||||
initConfig()
|
||||
assert.Equal(t, true, Get().Config.ProxyInstallOnly, "env var should work even without a config file")
|
||||
assert.Equal(t, true, Get().Config.Paranoid, "env var should work even without a config file")
|
||||
})
|
||||
|
||||
t.Run("default is used when neither env var nor config file sets the key", func(t *testing.T) {
|
||||
@@ -200,7 +186,8 @@ func TestConfigPrecedence(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
assert.Equal(t, false, Get().Config.ProxyInstallOnly, "should use default when key absent from config and env")
|
||||
assert.Equal(t, false, Get().Config.Proxy.InstallOnly, "should use default when key absent from config and env")
|
||||
assert.Equal(t, false, Get().Config.ProxyInstallOnly, "legacy flat field should also default to false")
|
||||
})
|
||||
|
||||
t.Run("cobra flag overrides env var", func(t *testing.T) {
|
||||
@@ -308,7 +295,7 @@ func TestWriteTemplateConfigMergesExistingConfig(t *testing.T) {
|
||||
assert.Contains(t, raw, "transitive_depth: 10")
|
||||
|
||||
// New keys from template added
|
||||
assert.Contains(t, raw, "proxy_mode:")
|
||||
assert.Contains(t, raw, "proxy:")
|
||||
assert.Contains(t, raw, "sandbox:")
|
||||
assert.Contains(t, raw, "verbosity:")
|
||||
}
|
||||
@@ -329,3 +316,89 @@ func TestWriteTemplateConfigCreatesNewFile(t *testing.T) {
|
||||
// Should be the full template
|
||||
assert.Equal(t, templateConfig, string(result))
|
||||
}
|
||||
|
||||
func TestProxyConfigSection(t *testing.T) {
|
||||
t.Run("defaults to enabled with install_only false", func(t *testing.T) {
|
||||
t.Setenv("PMG_CONFIG_DIR", "/tmp/pmg-test/random-does-not-exist")
|
||||
initConfig()
|
||||
|
||||
cfg := Get()
|
||||
assert.Equal(t, true, cfg.Config.Proxy.Enabled)
|
||||
assert.Equal(t, false, cfg.Config.Proxy.InstallOnly)
|
||||
assert.NotNil(t, cfg.Config.Proxy.Policies)
|
||||
})
|
||||
|
||||
t.Run("reads proxy section from config file", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("PMG_CONFIG_DIR", tmpDir)
|
||||
|
||||
configYAML := `proxy:
|
||||
enabled: true
|
||||
install_only: true
|
||||
policies:
|
||||
npm:
|
||||
skip_commands: ["my-script", "dev"]
|
||||
`
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
err := os.WriteFile(configPath, []byte(configYAML), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
cfg := Get()
|
||||
|
||||
assert.Equal(t, true, cfg.Config.Proxy.Enabled)
|
||||
assert.Equal(t, true, cfg.Config.Proxy.InstallOnly)
|
||||
assert.Equal(t, []string{"my-script", "dev"}, cfg.Config.Proxy.Policies["npm"].SkipCommands)
|
||||
})
|
||||
|
||||
t.Run("falls back to legacy keys from config file", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("PMG_CONFIG_DIR", tmpDir)
|
||||
|
||||
configYAML := `proxy_mode: false
|
||||
proxy_install_only: true
|
||||
`
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
err := os.WriteFile(configPath, []byte(configYAML), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
cfg := Get()
|
||||
|
||||
assert.Equal(t, false, cfg.Config.Proxy.Enabled)
|
||||
assert.Equal(t, true, cfg.Config.Proxy.InstallOnly)
|
||||
})
|
||||
|
||||
t.Run("falls back to legacy keys from env vars", func(t *testing.T) {
|
||||
t.Setenv("PMG_CONFIG_DIR", "/tmp/pmg-test/random-does-not-exist")
|
||||
t.Setenv("PMG_PROXY_MODE", "false")
|
||||
t.Setenv("PMG_PROXY_INSTALL_ONLY", "true")
|
||||
|
||||
initConfig()
|
||||
cfg := Get()
|
||||
|
||||
assert.Equal(t, false, cfg.Config.Proxy.Enabled, "PMG_PROXY_MODE=false should set Proxy.Enabled=false")
|
||||
assert.Equal(t, true, cfg.Config.Proxy.InstallOnly, "PMG_PROXY_INSTALL_ONLY=true should set Proxy.InstallOnly=true")
|
||||
})
|
||||
|
||||
t.Run("new proxy section takes precedence over old keys", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("PMG_CONFIG_DIR", tmpDir)
|
||||
|
||||
configYAML := `proxy_mode: false
|
||||
proxy_install_only: true
|
||||
proxy:
|
||||
enabled: true
|
||||
install_only: false
|
||||
`
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
err := os.WriteFile(configPath, []byte(configYAML), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
initConfig()
|
||||
cfg := Get()
|
||||
|
||||
assert.Equal(t, true, cfg.Config.Proxy.Enabled, "new proxy.enabled should win over old proxy_mode")
|
||||
assert.Equal(t, false, cfg.Config.Proxy.InstallOnly, "new proxy.install_only should win over old proxy_install_only")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user