Skip to content

Commit 7d93e8f

Browse files
committed
add: config overlay example, more default paths
Signed-off-by: Christoph Hoopmann <[email protected]>
1 parent 835cd79 commit 7d93e8f

File tree

13 files changed

+233
-111
lines changed

13 files changed

+233
-111
lines changed

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"version": "os-provided"
2020
},
2121
"ghcr.io/devcontainers/features/go:1": {
22-
"version": "1.24"
22+
"version": "1.25"
2323
}
2424
},
2525
"customizations": {

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ By using stdfx as an application starter you benefit from:
2121

2222
See [examples/webserver](./examples/webserver/) to test and experience it in action.
2323

24+
It acts as a demo for every stdfx feature.
25+
2426
### Usage example
2527

26-
A complete usage might look like this:
28+
A minimal usage might look like this:
2729

2830
<!-- markdownlint-disable MD010 -->
2931
```golang

configfx/defaults.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,15 @@ func DefaultEnvironmentPrefix(configName string) string {
4444
// - $HOME/.config/
4545
// - $HOME/.<configName>/
4646
// - $HOME/.local/etc/
47+
// - $HOME/.local/etc/<configName>/
4748
// - $HOME/
4849
// - /opt/<configName>/
4950
// - /opt/<configName>/etc/
51+
// - /opt/<configName>/etc/<configName>/
5052
// - /usr/local/etc/
53+
// - /usr/local/etc/<configName>/
5154
// - /etc/
55+
// - /etc/<configName>/
5256
func DefaultFileSearchPaths(configName string) []string {
5357
// working dir
5458
paths := []string{
@@ -61,6 +65,7 @@ func DefaultFileSearchPaths(configName string) []string {
6165
filepath.Join(home, ".config", configName),
6266
filepath.Join(home, "."+configName),
6367
filepath.Join(home, ".local/etc"),
68+
filepath.Join(home, ".local/etc", configName),
6469
home,
6570
}...)
6671
}
@@ -69,8 +74,11 @@ func DefaultFileSearchPaths(configName string) []string {
6974
paths = append(paths, []string{
7075
filepath.Join("/opt", configName),
7176
filepath.Join("/opt", configName, "etc"),
77+
filepath.Join("/opt", configName, "etc", configName),
7278
"/usr/local/etc",
79+
filepath.Join("/usr/local/etc", configName),
7380
"/etc",
81+
filepath.Join("/etc", configName),
7482
}...)
7583

7684
return paths

configfx/overlay.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,25 @@ func (s *Overlay) applyTo(vip *viper.Viper, cfg any) error {
6363
}
6464

6565
// retrieve the from key
66-
from, ok := s.viper.AllSettings()[s.From]
67-
if !ok {
68-
return fmt.Errorf("referenced from field %q not found in overlay", s.From)
66+
fromPath := strings.Split(s.From, ".")
67+
fromSlice := s.viper.AllSettings()
68+
var from any
69+
for _, elem := range fromPath {
70+
// retrieve path element
71+
var ok bool
72+
from, ok = fromSlice[elem]
73+
if !ok {
74+
return fmt.Errorf("referenced from field %q in path %q not found in overlay %q", elem, s.From, s.Filename)
75+
}
76+
77+
// check if it is a map for next iter
78+
if cast, ok := from.(map[string]any); ok {
79+
fromSlice = cast
80+
}
81+
}
82+
// sanity check
83+
if from == nil {
84+
return fmt.Errorf("referenced from path %q is nil in overlay %q", s.From, s.Filename)
6985
}
7086

7187
for _, path := range s.To {

examples/webserver/cmd/webserver/main.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"sync"
21+
2022
"go.uber.org/fx"
23+
"k8s.io/utils/diff"
2124

2225
"github.com/choopm/stdfx"
2326
"github.com/choopm/stdfx/configfx"
2427
"github.com/choopm/stdfx/examples/webserver"
2528
"github.com/choopm/stdfx/loggingfx/zerologfx"
29+
"github.com/fsnotify/fsnotify"
2630
"github.com/rs/zerolog/log"
2731
"github.com/spf13/cobra"
2832
)
@@ -76,8 +80,66 @@ func serverCommand(
7680
}
7781
log.Logger = *logger
7882

83+
var server *webserver.Server
84+
85+
// build config options
86+
opts := []configfx.ConfigOption{
87+
configfx.WithOverlays(cfg.Config.Overlays...),
88+
}
89+
if cfg.Config.HotReload {
90+
cfgSwap := sync.Mutex{}
91+
92+
// callback for hot-reloading of config
93+
opts = append(opts, configfx.WithOnConfigChange(func(in fsnotify.Event) {
94+
// we only care for config writes
95+
if in.Op != fsnotify.Write {
96+
return
97+
}
98+
99+
// synchronize config swapping
100+
cfgSwap.Lock()
101+
defer cfgSwap.Unlock()
102+
103+
log.Debug().
104+
Msg("config file has changed on disk - reloading config")
105+
106+
// re-create config with opts (overlays, config change)
107+
newcfg, err := configProvider.Config(opts...)
108+
if err != nil {
109+
log.Error().Err(err).
110+
Msg("new config file can't be parsed")
111+
return
112+
}
113+
// check config
114+
err = newcfg.Validate()
115+
if err != nil {
116+
log.Error().Err(err).
117+
Msg("new config file has errors")
118+
return
119+
}
120+
121+
changelog := diff.ObjectReflectDiff(cfg, newcfg)
122+
*cfg = *newcfg // this replaces the config
123+
124+
log.Info().
125+
Msgf("updated config, changelog: %s", changelog)
126+
127+
log.Info().Msg("reconfiguring server...")
128+
err = server.Reconfigure(cfg)
129+
if err != nil {
130+
log.Panic().Err(err).Msg("failed to reconfiguring server")
131+
return
132+
}
133+
}))
134+
}
135+
// re-create config with opts (overlays, config change)
136+
cfg, err = configProvider.Config(opts...)
137+
if err != nil {
138+
return err
139+
}
140+
79141
// create server instance
80-
server, err := webserver.NewServer(cfg, logger)
142+
server, err = webserver.NewServer(cfg, logger)
81143
if err != nil {
82144
return err
83145
}

examples/webserver/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package webserver
1919
import (
2020
"fmt"
2121

22+
"github.com/choopm/stdfx/configfx"
2223
"github.com/choopm/stdfx/loggingfx"
2324
"github.com/go-viper/mapstructure/v2"
2425
)
@@ -27,13 +28,21 @@ import (
2728
type Config struct {
2829
Logging loggingfx.Config `mapstructure:"log"`
2930

31+
Config ConfigConfig `mapstructure:"config"`
32+
3033
// Webserver defines the http server config
3134
Webserver WebserverConfig `mapstructure:"webserver"`
3235

3336
// Routes defines the webserver routes
3437
Routes []*Route `mapstructure:"routes" default:"[]"`
3538
}
3639

40+
// ConfigConfig struct stores all config options
41+
type ConfigConfig struct {
42+
HotReload bool `mapstructure:"hot-reload" default:"false"`
43+
Overlays []*configfx.Overlay `mapstructure:"overlays" default:"[]"`
44+
}
45+
3746
// Validate validates the Config
3847
func (c *Config) Validate() error {
3948
if err := c.Webserver.Validate(); err != nil {

examples/webserver/go.mod

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@ toolchain go1.24.5
66

77
require (
88
github.com/choopm/stdfx v0.1.5
9+
github.com/fsnotify/fsnotify v1.9.0
910
github.com/go-viper/mapstructure/v2 v2.4.0
1011
github.com/rs/zerolog v1.34.0
1112
github.com/spf13/cobra v1.9.1
1213
go.uber.org/fx v1.24.0
1314
golang.org/x/sync v0.16.0
15+
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
1416
)
1517

1618
require (
1719
github.com/creasty/defaults v1.8.0 // indirect
1820
github.com/davecgh/go-spew v1.1.1 // indirect
1921
github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect
20-
github.com/fsnotify/fsnotify v1.9.0 // indirect
2122
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
2223
github.com/go-logr/logr v1.4.3 // indirect
23-
github.com/go-openapi/jsonpointer v0.21.1 // indirect
24+
github.com/go-openapi/jsonpointer v0.21.2 // indirect
2425
github.com/go-openapi/jsonreference v0.21.0 // indirect
2526
github.com/go-openapi/swag v0.23.1 // indirect
2627
github.com/gogo/protobuf v1.3.2 // indirect
@@ -32,13 +33,13 @@ require (
3233
github.com/mattn/go-colorable v0.1.14 // indirect
3334
github.com/mattn/go-isatty v0.0.20 // indirect
3435
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
35-
github.com/modern-go/reflect2 v1.0.2 // indirect
36+
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
3637
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
37-
github.com/sagikazarmark/locafero v0.9.0 // indirect
38+
github.com/sagikazarmark/locafero v0.10.0 // indirect
3839
github.com/samber/lo v1.51.0 // indirect
3940
github.com/samber/slog-common v0.19.0 // indirect
4041
github.com/samber/slog-zerolog/v2 v2.7.3 // indirect
41-
github.com/sourcegraph/conc v0.3.0 // indirect
42+
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
4243
github.com/spf13/afero v1.14.0 // indirect
4344
github.com/spf13/cast v1.9.2 // indirect
4445
github.com/spf13/pflag v1.0.7 // indirect
@@ -51,18 +52,17 @@ require (
5152
go.uber.org/zap v1.27.0 // indirect
5253
go.yaml.in/yaml/v2 v2.4.2 // indirect
5354
go.yaml.in/yaml/v3 v3.0.4 // indirect
54-
golang.org/x/net v0.42.0 // indirect
55-
golang.org/x/sys v0.34.0 // indirect
56-
golang.org/x/text v0.27.0 // indirect
57-
google.golang.org/protobuf v1.36.6 // indirect
55+
golang.org/x/net v0.43.0 // indirect
56+
golang.org/x/sys v0.35.0 // indirect
57+
golang.org/x/text v0.28.0 // indirect
58+
google.golang.org/protobuf v1.36.8 // indirect
5859
gopkg.in/inf.v0 v0.9.1 // indirect
5960
gopkg.in/yaml.v3 v3.0.1 // indirect
60-
k8s.io/apimachinery v0.33.3 // indirect
61+
k8s.io/apimachinery v0.34.0 // indirect
6162
k8s.io/klog/v2 v2.130.1 // indirect
62-
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
63-
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
64-
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
63+
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect
64+
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
6565
sigs.k8s.io/randfill v1.0.0 // indirect
66-
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
67-
sigs.k8s.io/yaml v1.5.0 // indirect
66+
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
67+
sigs.k8s.io/yaml v1.6.0 // indirect
6868
)

0 commit comments

Comments
 (0)