Support a config file alongside env vars in containers #10
@@ -83,8 +83,15 @@ Each backend applies `order_by`/`limit`/`offset` to its own slice only, so
|
||||
|
||||
Precedence (lowest → highest): **defaults < config file < env vars (`PDBMUX_*`) < flags**.
|
||||
|
||||
Config file: `$XDG_CONFIG_HOME/pdbmux/config.yaml`. In a container there is no
|
||||
config file — everything comes from `PDBMUX_*` env vars.
|
||||
The config file is optional; a file, env vars, or both work equally well,
|
||||
including in a container.
|
||||
|
||||
Which file is read: `--config <path>`, else `PDBMUX_CONFIG`, else the first that
|
||||
exists of `$XDG_CONFIG_HOME/pdbmux/config.yaml` (or `$HOME/.config/pdbmux/config.yaml`),
|
||||
then `/etc/pdbmux/config.yaml`. A path given via `--config`/`PDBMUX_CONFIG` **must**
|
||||
exist — pdbmux fails rather than silently falling back — while a missing file on
|
||||
the default search path is fine. `pdbmux config show` prints the file it loaded,
|
||||
or the paths it searched.
|
||||
|
||||
```yaml
|
||||
listen: ":8080"
|
||||
@@ -105,6 +112,7 @@ the `/pdb/query/v4/...` path per request.
|
||||
|
||||
| Env var | Overrides |
|
||||
|---|---|
|
||||
| `PDBMUX_CONFIG` | config file path (not a file key) |
|
||||
| `PDBMUX_LISTEN` | `listen` |
|
||||
| `PDBMUX_PRIMARY` | `primary` |
|
||||
| `PDBMUX_MERGE` | `merge` |
|
||||
@@ -113,7 +121,10 @@ the `/pdb/query/v4/...` path per request.
|
||||
| `PDBMUX_FRESHNESS_TTL` | `freshness_ttl` |
|
||||
| `PDBMUX_BACKENDS` | whole backend list, as `name=url,name=url` |
|
||||
|
||||
Flags: `--listen`, `--primary`, `--merge`.
|
||||
Flags: `--config`, `--listen`, `--primary`, `--merge`.
|
||||
|
||||
`config init` writes to `--config`/`PDBMUX_CONFIG` when set, else to
|
||||
`$XDG_CONFIG_HOME/pdbmux/config.yaml`.
|
||||
|
||||
## Running
|
||||
|
||||
@@ -137,7 +148,9 @@ Container image only — no OS package. Every `v*` tag builds and pushes the ima
|
||||
(`.woodpecker/docker.yaml`); registry and repository are pipeline settings. Tag
|
||||
with `make patch` / `minor` / `major`.
|
||||
|
||||
A static (`CGO_ENABLED=0`) binary on a distroless base, configured entirely via
|
||||
`PDBMUX_*` env vars; a container needs at minimum `PDBMUX_BACKENDS`. Stateless,
|
||||
so run as many replicas as you like; use `/healthz` for liveness/readiness
|
||||
probes.
|
||||
A static (`CGO_ENABLED=0`) binary on a distroless base. Configure it with
|
||||
`PDBMUX_*` env vars (at minimum `PDBMUX_BACKENDS`), or mount a config file — a
|
||||
configmap at `/etc/pdbmux/config.yaml` is picked up with no env var at all, and
|
||||
any other mount path works via `PDBMUX_CONFIG`. Env vars still override file
|
||||
values, so the two mix. Stateless, so run as many replicas as you like; use
|
||||
`/healthz` for liveness/readiness probes.
|
||||
|
||||
@@ -15,6 +15,11 @@ const (
|
||||
appName = "pdbmux"
|
||||
configFileName = "config.yaml"
|
||||
envPrefix = "PDBMUX_"
|
||||
envConfigPath = envPrefix + "CONFIG"
|
||||
|
||||
// systemConfigDir is the last resort in the search order, and the path a
|
||||
// container mount (configmap, secret) is expected to land on.
|
||||
systemConfigDir = "/etc/" + appName
|
||||
|
||||
defaultListen = ":8080"
|
||||
|
||||
@@ -40,8 +45,13 @@ type Config struct {
|
||||
Prefer string `yaml:"prefer"` // wins under static merge, and breaks ties under freshness merge
|
||||
Timeout time.Duration `yaml:"timeout"`
|
||||
FreshnessTTL time.Duration `yaml:"freshness_ttl"`
|
||||
|
||||
sourcePath string // file this config was read from, empty if none was found
|
||||
}
|
||||
|
||||
// SourcePath returns the config file Load read, or "" when none was loaded.
|
||||
func (c Config) SourcePath() string { return c.sourcePath }
|
||||
|
||||
const (
|
||||
mergeFreshness = "freshness"
|
||||
mergeStatic = "static"
|
||||
@@ -68,6 +78,9 @@ func ConfigDir() string {
|
||||
base := os.Getenv("XDG_CONFIG_HOME")
|
||||
if base == "" {
|
||||
home, _ := os.UserHomeDir()
|
||||
if home == "" {
|
||||
return systemConfigDir
|
||||
}
|
||||
base = filepath.Join(home, ".config")
|
||||
}
|
||||
return filepath.Join(base, appName)
|
||||
@@ -77,19 +90,56 @@ func ConfigPath() string {
|
||||
return filepath.Join(ConfigDir(), configFileName)
|
||||
}
|
||||
|
||||
// configSearchPaths lists the default config locations, highest priority first.
|
||||
func configSearchPaths() []string {
|
||||
paths := []string{ConfigPath()}
|
||||
if system := filepath.Join(systemConfigDir, configFileName); system != paths[0] {
|
||||
paths = append(paths, system)
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
// explicitConfigPath returns the config path named by --config or PDBMUX_CONFIG,
|
||||
// or "" when neither is set.
|
||||
func explicitConfigPath(flagPath string) string {
|
||||
if flagPath != "" {
|
||||
return flagPath
|
||||
}
|
||||
return os.Getenv(envConfigPath)
|
||||
}
|
||||
|
||||
// resolveConfigPath picks the config file to read: --config, else PDBMUX_CONFIG,
|
||||
// else the first existing default search path. explicit reports whether the path
|
||||
// was named outright, in which case a missing file is an error.
|
||||
func resolveConfigPath(flagPath string) (path string, explicit bool) {
|
||||
if p := explicitConfigPath(flagPath); p != "" {
|
||||
return p, true
|
||||
}
|
||||
paths := configSearchPaths()
|
||||
for _, p := range paths {
|
||||
if st, err := os.Stat(p); err == nil && !st.IsDir() {
|
||||
return p, false
|
||||
}
|
||||
}
|
||||
return paths[0], false
|
||||
}
|
||||
|
||||
// Precedence: defaults < config file < env vars < flags, and flags are applied by the caller.
|
||||
func Load() (Config, error) {
|
||||
func Load(flagPath string) (Config, error) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
path := ConfigPath()
|
||||
path, explicit := resolveConfigPath(flagPath)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return cfg, fmt.Errorf("reading config %s: %w", path, err)
|
||||
}
|
||||
if err == nil {
|
||||
switch {
|
||||
case err == nil:
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return cfg, fmt.Errorf("parsing config %s: %w", path, err)
|
||||
}
|
||||
cfg.sourcePath = path
|
||||
case os.IsNotExist(err) && !explicit:
|
||||
// No config file anywhere on the search path: defaults + env only.
|
||||
default:
|
||||
return cfg, fmt.Errorf("reading config %s: %w", path, err)
|
||||
}
|
||||
|
||||
applyEnv(&cfg, os.Getenv)
|
||||
@@ -157,10 +207,19 @@ func (c *Config) normalize() {
|
||||
}
|
||||
}
|
||||
|
||||
// configHint names the file a user should edit: the one actually loaded, else
|
||||
// the default write target.
|
||||
func (c Config) configHint() string {
|
||||
if c.sourcePath != "" {
|
||||
return c.sourcePath
|
||||
}
|
||||
return ConfigPath()
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if len(c.Backends) == 0 {
|
||||
return fmt.Errorf("no backends configured: set %sBACKENDS to \"name=url,name=url\" or add a backends list to %s",
|
||||
envPrefix, ConfigPath())
|
||||
envPrefix, c.configHint())
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, b := range c.Backends {
|
||||
@@ -198,12 +257,10 @@ func (c Config) PrimaryBackend() Backend {
|
||||
return c.Backends[0]
|
||||
}
|
||||
|
||||
func writeDefaultConfig() error {
|
||||
dir := ConfigDir()
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
func writeDefaultConfig(path string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return fmt.Errorf("creating config dir: %w", err)
|
||||
}
|
||||
path := ConfigPath()
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return fmt.Errorf("config already exists at %s", path)
|
||||
}
|
||||
|
||||
+229
-4
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -42,7 +43,7 @@ func TestLoad_NoBackendsLoadsButFailsValidation(t *testing.T) {
|
||||
clearEnv(t)
|
||||
|
||||
// Load itself must succeed so `config init` / `version` work unconfigured.
|
||||
cfg, err := Load()
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
@@ -60,7 +61,7 @@ func TestLoad_PrimaryDefaultsToFirstBackend(t *testing.T) {
|
||||
clearEnv(t)
|
||||
t.Setenv(envPrefix+"BACKENDS", "a=http://localhost:18080,b=http://localhost:18081")
|
||||
|
||||
cfg, err := Load()
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,7 +88,7 @@ func TestLoad_FileAndEnvOverride(t *testing.T) {
|
||||
// env beats file for listen.
|
||||
t.Setenv(envPrefix+"LISTEN", "127.0.0.1:1234")
|
||||
|
||||
cfg, err := Load()
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
@@ -174,9 +175,233 @@ func TestExampleConfig_IsValidAndNeutral(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const testConfigBody = "listen: \":9999\"\nmerge: static\nprimary: old\nprefer: old\n" +
|
||||
"backends:\n - name: old\n url: http://localhost:18080\n - name: new\n url: http://localhost:18081\n"
|
||||
|
||||
func writeConfigFile(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(testConfigBody), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_FileOnly(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", dir)
|
||||
clearEnv(t)
|
||||
path := filepath.Join(dir, appName, configFileName)
|
||||
writeConfigFile(t, path)
|
||||
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if cfg.Listen != ":9999" || cfg.Merge != mergeStatic || len(cfg.Backends) != 2 {
|
||||
t.Errorf("file values not applied: %+v", cfg)
|
||||
}
|
||||
if cfg.SourcePath() != path {
|
||||
t.Errorf("source path = %q, want %q", cfg.SourcePath(), path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_EnvOnly_DefaultPathMissing(t *testing.T) {
|
||||
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
||||
clearEnv(t)
|
||||
t.Setenv(envPrefix+"BACKENDS", "a=http://localhost:18080")
|
||||
t.Setenv(envPrefix+"LISTEN", "127.0.0.1:1234")
|
||||
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("a missing default config file must not be an error: %v", err)
|
||||
}
|
||||
if cfg.SourcePath() != "" {
|
||||
t.Errorf("no file was loaded, source path should be empty, got %q", cfg.SourcePath())
|
||||
}
|
||||
if cfg.Listen != "127.0.0.1:1234" || len(cfg.Backends) != 1 {
|
||||
t.Errorf("env values not applied: %+v", cfg)
|
||||
}
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Errorf("env-only config should validate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A mounted config file must load with neither HOME nor XDG_CONFIG_HOME set.
|
||||
func TestLoad_ExplicitPath(t *testing.T) {
|
||||
mounted := filepath.Join(t.TempDir(), "mounted.yaml")
|
||||
writeConfigFile(t, mounted)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
flag string
|
||||
env string
|
||||
}{
|
||||
{"flag", mounted, ""},
|
||||
{"env", "", mounted},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("XDG_CONFIG_HOME", "")
|
||||
t.Setenv("HOME", "")
|
||||
clearEnv(t)
|
||||
t.Setenv(envConfigPath, tc.env)
|
||||
|
||||
cfg, err := Load(tc.flag)
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if cfg.SourcePath() != mounted {
|
||||
t.Errorf("source path = %q, want %q", cfg.SourcePath(), mounted)
|
||||
}
|
||||
if cfg.Listen != ":9999" {
|
||||
t.Errorf("listen = %q, want :9999", cfg.Listen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_ExplicitPathMissingIsError(t *testing.T) {
|
||||
missing := filepath.Join(t.TempDir(), "typo.yaml")
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
flag string
|
||||
env string
|
||||
}{
|
||||
{"flag", missing, ""},
|
||||
{"env", "", missing},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
||||
clearEnv(t)
|
||||
t.Setenv(envConfigPath, tc.env)
|
||||
|
||||
_, err := Load(tc.flag)
|
||||
if err == nil {
|
||||
t.Fatal("an explicitly named config file that does not exist must fail loudly")
|
||||
}
|
||||
if !strings.Contains(err.Error(), missing) {
|
||||
t.Errorf("error should name the missing path, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_ExplicitFileStillLosesToEnv(t *testing.T) {
|
||||
mounted := filepath.Join(t.TempDir(), "mounted.yaml")
|
||||
writeConfigFile(t, mounted)
|
||||
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
||||
clearEnv(t)
|
||||
t.Setenv(envConfigPath, mounted)
|
||||
t.Setenv(envPrefix+"LISTEN", "127.0.0.1:1234")
|
||||
t.Setenv(envPrefix+"MERGE", mergeFreshness)
|
||||
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if cfg.Listen != "127.0.0.1:1234" || cfg.Merge != mergeFreshness {
|
||||
t.Errorf("env must beat the file: %+v", cfg)
|
||||
}
|
||||
if len(cfg.Backends) != 2 {
|
||||
t.Errorf("unset env must leave file backends alone: %+v", cfg.Backends)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveConfigPath_Precedence(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", dir)
|
||||
clearEnv(t)
|
||||
defaultPath := filepath.Join(dir, appName, configFileName)
|
||||
|
||||
if got, explicit := resolveConfigPath(""); got != defaultPath || explicit {
|
||||
t.Errorf("no file anywhere: got %q explicit=%v, want %q false", got, explicit, defaultPath)
|
||||
}
|
||||
|
||||
writeConfigFile(t, defaultPath)
|
||||
if got, explicit := resolveConfigPath(""); got != defaultPath || explicit {
|
||||
t.Errorf("default search: got %q explicit=%v, want %q false", got, explicit, defaultPath)
|
||||
}
|
||||
|
||||
t.Setenv(envConfigPath, "/from/env.yaml")
|
||||
if got, explicit := resolveConfigPath(""); got != "/from/env.yaml" || !explicit {
|
||||
t.Errorf("env should beat the search path: got %q explicit=%v", got, explicit)
|
||||
}
|
||||
if got, explicit := resolveConfigPath("/from/flag.yaml"); got != "/from/flag.yaml" || !explicit {
|
||||
t.Errorf("flag should beat env: got %q explicit=%v", got, explicit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigSearchPaths_EndsAtSystemDir(t *testing.T) {
|
||||
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
||||
paths := configSearchPaths()
|
||||
want := filepath.Join(systemConfigDir, configFileName)
|
||||
if len(paths) != 2 || paths[1] != want {
|
||||
t.Errorf("search paths = %v, want the system path %q last", paths, want)
|
||||
}
|
||||
|
||||
t.Setenv("XDG_CONFIG_HOME", "")
|
||||
t.Setenv("HOME", "")
|
||||
if paths := configSearchPaths(); len(paths) != 1 || paths[0] != want {
|
||||
t.Errorf("without HOME/XDG the search path should be just %q, got %v", want, paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintConfig_ReportsSource(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", dir)
|
||||
clearEnv(t)
|
||||
|
||||
out := captureStdout(t, func() {
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
printConfig(cfg)
|
||||
})
|
||||
if !strings.Contains(out, "none loaded") || !strings.Contains(out, filepath.Join(dir, appName, configFileName)) {
|
||||
t.Errorf("config show should report nothing was loaded and what it searched, got:\n%s", out)
|
||||
}
|
||||
|
||||
path := filepath.Join(dir, appName, configFileName)
|
||||
writeConfigFile(t, path)
|
||||
out = captureStdout(t, func() {
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
printConfig(cfg)
|
||||
})
|
||||
if !strings.Contains(out, path+" (loaded)") {
|
||||
t.Errorf("config show should name the loaded file, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func captureStdout(t *testing.T, f func()) string {
|
||||
t.Helper()
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
orig := os.Stdout
|
||||
os.Stdout = w
|
||||
defer func() { os.Stdout = orig }()
|
||||
|
||||
f()
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err := buf.ReadFrom(r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func clearEnv(t *testing.T) {
|
||||
t.Helper()
|
||||
for _, k := range []string{"LISTEN", "PRIMARY", "MERGE", "PREFER", "TIMEOUT", "FRESHNESS_TTL", "BACKENDS"} {
|
||||
for _, k := range []string{"CONFIG", "LISTEN", "PRIMARY", "MERGE", "PREFER", "TIMEOUT", "FRESHNESS_TTL", "BACKENDS"} {
|
||||
t.Setenv(envPrefix+k, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -18,19 +19,28 @@ import (
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "config error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var (
|
||||
listen string
|
||||
primary string
|
||||
merge string
|
||||
cfg Config
|
||||
configPath string
|
||||
listen string
|
||||
primary string
|
||||
merge string
|
||||
)
|
||||
|
||||
// Loaded lazily: --config is only known once cobra has parsed flags.
|
||||
loadConfig := func() error {
|
||||
c, err := Load(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg = c
|
||||
return nil
|
||||
}
|
||||
|
||||
serve := func(cmd *cobra.Command) error {
|
||||
if err := loadConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.Flags().Changed("listen") {
|
||||
cfg.Listen = listen
|
||||
}
|
||||
@@ -58,9 +68,10 @@ func main() {
|
||||
}
|
||||
|
||||
pf := root.PersistentFlags()
|
||||
pf.StringVar(&listen, "listen", cfg.Listen, "HTTP listen address (overrides config and PDBMUX_LISTEN)")
|
||||
pf.StringVar(&primary, "primary", cfg.Primary, "Primary backend name for non-merged pass-through")
|
||||
pf.StringVar(&merge, "merge", cfg.Merge, "Facts merge strategy: freshness or static")
|
||||
pf.StringVar(&configPath, "config", "", "Config file path (overrides PDBMUX_CONFIG and the default search path)")
|
||||
pf.StringVar(&listen, "listen", defaultListen, "HTTP listen address (overrides config and PDBMUX_LISTEN)")
|
||||
pf.StringVar(&primary, "primary", "", "Primary backend name for non-merged pass-through")
|
||||
pf.StringVar(&merge, "merge", mergeFreshness, "Facts merge strategy: freshness or static")
|
||||
|
||||
serveCmd := &cobra.Command{
|
||||
Use: "serve",
|
||||
@@ -73,15 +84,24 @@ func main() {
|
||||
configCmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Write a default config file to " + ConfigPath(),
|
||||
Short: "Write a default config file (--config path, else " + ConfigPath() + ")",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error { return writeDefaultConfig() },
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
path := explicitConfigPath(configPath)
|
||||
if path == "" {
|
||||
path = ConfigPath()
|
||||
}
|
||||
return writeDefaultConfig(path)
|
||||
},
|
||||
},
|
||||
&cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Print the active configuration",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := loadConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
printConfig(cfg)
|
||||
return nil
|
||||
},
|
||||
@@ -137,7 +157,11 @@ func runServer(cfg Config) error {
|
||||
}
|
||||
|
||||
func printConfig(cfg Config) {
|
||||
fmt.Printf("config file : %s\n", ConfigPath())
|
||||
if p := cfg.SourcePath(); p != "" {
|
||||
fmt.Printf("config file : %s (loaded)\n", p)
|
||||
} else {
|
||||
fmt.Printf("config file : none loaded (searched %s)\n", strings.Join(configSearchPaths(), ", "))
|
||||
}
|
||||
fmt.Printf("listen : %s\n", cfg.Listen)
|
||||
fmt.Printf("primary : %s\n", cfg.Primary)
|
||||
fmt.Printf("merge : %s\n", cfg.Merge)
|
||||
|
||||
Reference in New Issue
Block a user