fix(11-04): keep earlier overrides when compass Persist saves
Persist rewrote overrides.yaml with only this process's runtime values, so saving one key (for example websockets:generate-vapid-keys --update) dropped every key persisted earlier. It now starts from the saved file and lets runtime values win.
This commit is contained in:
@@ -19,7 +19,7 @@ Layered YAML configuration with per-environment directories, `SUMMER_` environme
|
||||
6. In-memory values stored with `compass.Config.Set`.
|
||||
- Typed getters with zero-value defaults: `compass.Config.String`, `compass.Config.Int`, `compass.Config.Bool`, plus `compass.Config.Lookup` and `compass.Config.Has` to tell a missing key from a zero value.
|
||||
- `compass.Config.LoadSection` unmarshals a whole subtree into a struct using `koanf` struct tags.
|
||||
- Runtime overrides: `compass.Config.Set` changes a value in memory, `compass.Config.Persist` saves all runtime values atomically to the environment's `overrides.yaml` (directory mode 0700, file mode 0600, refusing any path outside the config directory), and `compass.Config.Reload` rereads every source and discards unsaved runtime values.
|
||||
- Runtime overrides: `compass.Config.Set` changes a value in memory, `compass.Config.Persist` saves all runtime values atomically to the environment's `overrides.yaml`, keeping the keys already saved there and replacing the saved value of any key set at runtime (directory mode 0700, file mode 0600, refusing any path outside the config directory), and `compass.Config.Reload` rereads every source and discards unsaved runtime values.
|
||||
- `compass.Config.Environment` reports the active environment name, which must consist of letters, digits, `-` and `_`.
|
||||
- Safe for concurrent reads and writes.
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ func (c *Config) Set(path string, value any) error {
|
||||
}
|
||||
|
||||
// Persist writes runtime overrides to config/env/<env>/overrides.yaml
|
||||
// using an atomic replace and restrictive file permissions.
|
||||
// using an atomic replace and restrictive file permissions. Keys already in
|
||||
// the file are kept; a runtime value replaces the saved value of its key.
|
||||
func (c *Config) Persist() error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("compass: config is nil")
|
||||
@@ -48,9 +49,20 @@ func (c *Config) Persist() error {
|
||||
if err := os.MkdirAll(destDir, 0o700); err != nil {
|
||||
return fmt.Errorf("compass: create overrides dir: %w", err)
|
||||
}
|
||||
raw := []byte("{}\n")
|
||||
// Start from the values persisted earlier, so saving one runtime value
|
||||
// never drops another key from the file; runtime values win.
|
||||
merged := koanf.New(".")
|
||||
if err := loadOverrides(merged, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.runtime != nil {
|
||||
body, err := c.runtime.Marshal(yaml.Parser())
|
||||
if err := merged.Merge(c.runtime); err != nil {
|
||||
return fmt.Errorf("compass: merge overrides: %w", err)
|
||||
}
|
||||
}
|
||||
raw := []byte("{}\n")
|
||||
if len(merged.Keys()) > 0 {
|
||||
body, err := merged.Marshal(yaml.Parser())
|
||||
if err != nil {
|
||||
return fmt.Errorf("compass: marshal overrides: %w", err)
|
||||
}
|
||||
|
||||
@@ -115,3 +115,44 @@ func TestPersistRejectsInvalidEnvName(t *testing.T) {
|
||||
t.Fatalf("error should not echo unsanitized env path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistKeepsEarlierOverrides(t *testing.T) {
|
||||
dir := writeConfigTree(t, map[string]string{
|
||||
"app.yaml": "name: base\n",
|
||||
"env/development/overrides.yaml": "app:\n name: persisted\n",
|
||||
})
|
||||
cfg, err := Open(Options{Dir: dir, Env: "development", Environ: []string{}})
|
||||
if err != nil {
|
||||
t.Fatalf("Open: %v", err)
|
||||
}
|
||||
if err := cfg.Set("push.public_key", "saved-key"); err != nil {
|
||||
t.Fatalf("Set: %v", err)
|
||||
}
|
||||
if err := cfg.Persist(); err != nil {
|
||||
t.Fatalf("Persist: %v", err)
|
||||
}
|
||||
again, err := Open(Options{Dir: dir, Env: "development", Environ: []string{}})
|
||||
if err != nil {
|
||||
t.Fatalf("reopen: %v", err)
|
||||
}
|
||||
if got := again.String("push.public_key"); got != "saved-key" {
|
||||
t.Fatalf("push.public_key = %q, want saved-key", got)
|
||||
}
|
||||
if got := again.String("app.name"); got != "persisted" {
|
||||
t.Fatalf("app.name = %q, want the earlier persisted value", got)
|
||||
}
|
||||
// A runtime value replaces the persisted value of the same key.
|
||||
if err := again.Set("app.name", "runtime"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := again.Persist(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
third, err := Open(Options{Dir: dir, Env: "development", Environ: []string{}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if third.String("app.name") != "runtime" || third.String("push.public_key") != "saved-key" {
|
||||
t.Fatalf("after second Persist: app.name=%q push.public_key=%q", third.String("app.name"), third.String("push.public_key"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user