Skip to content

Commit 1100cf3

Browse files
committed
add: config options for overlays
Signed-off-by: Christoph Hoopmann <[email protected]>
1 parent e10fff1 commit 1100cf3

File tree

7 files changed

+407
-59
lines changed

7 files changed

+407
-59
lines changed

configfx/options.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
Copyright 2025 Christoph Hoopmann
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package configfx
18+
19+
import "github.com/fsnotify/fsnotify"
20+
21+
// configOptions stores options for With*() funcs
22+
type configOptions struct {
23+
readInConfig bool
24+
overlays []*Overlay
25+
onConfigChange func(in fsnotify.Event)
26+
}
27+
28+
// ConfigOption is a func to adjust options of *configOptions for later
29+
// usage during [Config].
30+
type ConfigOption func(*configOptions)
31+
32+
// defaultConfigOptions returns the default *configOptions
33+
func defaultConfigOptions() *configOptions {
34+
opts := &configOptions{
35+
overlays: make([]*Overlay, 0),
36+
onConfigChange: nil,
37+
}
38+
39+
WithReadInConfig(true)(opts)
40+
41+
return opts
42+
}
43+
44+
// WithReadInConfig will use viper.ReadInConfig during [Config] invocation.
45+
//
46+
// Turning this off is useful when unmarshalling a config for the second time
47+
// after merging anything into the backing viper config
48+
// (otherwise the MergedInConfig would be lost).
49+
func WithReadInConfig(value bool) ConfigOption {
50+
return func(o *configOptions) {
51+
o.readInConfig = value
52+
}
53+
}
54+
55+
// WithOverlays adds the given overlays which will be injected
56+
// during configuration parsing.
57+
// This allows to split a config file into multiple files.
58+
func WithOverlays(overlays ...*Overlay) ConfigOption {
59+
return func(o *configOptions) {
60+
o.overlays = append(o.overlays, overlays...)
61+
}
62+
}
63+
64+
// WithOnConfigChange adds the callback to all viper instances.
65+
// This callback will be invoked whenever there is a config change.
66+
func WithOnConfigChange(callback func(in fsnotify.Event)) ConfigOption {
67+
return func(o *configOptions) {
68+
o.onConfigChange = callback
69+
}
70+
}

configfx/overlay.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
Copyright 2025 Christoph Hoopmann
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package configfx
18+
19+
import (
20+
"fmt"
21+
"path/filepath"
22+
"strings"
23+
"sync"
24+
25+
"github.com/spf13/viper"
26+
"k8s.io/apimachinery/pkg/util/strategicpatch"
27+
)
28+
29+
// Overlay defines a configuration overlay
30+
type Overlay struct {
31+
// Filename is the full filepath to the overlay config
32+
Filename string `mapstructure:"filename" default:""`
33+
34+
// From is the mapstructure path to the element which shall be used
35+
From string `mapstructure:"from" default:""`
36+
37+
// To defines mapstructure paths where the [From] element gets injected
38+
To []string `mapstructure:"to" default:"[]"`
39+
40+
// viper is used internally to read and parse the overlay config file
41+
viper *viper.Viper
42+
43+
// viperWatchOnce is used to only start one watcher
44+
viperWatchOnce sync.Once
45+
}
46+
47+
// ApplyTo loads the overlay from the filesystem and
48+
// merges it with vip *Viper and cfg or error.
49+
// Overlay config files are searched using full- and relative to main config file path.
50+
func (s *Overlay) applyTo(vip *viper.Viper, cfg any) error {
51+
// remove file extension
52+
extension := filepath.Ext(s.Filename)
53+
filename := s.Filename[0 : len(s.Filename)-len(extension)]
54+
55+
// fresh viper to read in overlay
56+
s.viper = viper.New()
57+
s.viper.SetConfigName(filename)
58+
s.viper.AddConfigPath(filepath.Dir(vip.ConfigFileUsed()))
59+
s.viper.AddConfigPath(".")
60+
err := s.viper.ReadInConfig()
61+
if err != nil {
62+
return fmt.Errorf("reading overlay config %q failed: %s", s.Filename, err)
63+
}
64+
65+
// 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)
69+
}
70+
71+
for _, path := range s.To {
72+
// forge a config from values inside overlay by adding the desired path in front
73+
forged := from
74+
apath := strings.Split(path, ".")
75+
for i := len(apath) - 1; i >= 0; i-- {
76+
key := apath[i]
77+
if strings.Contains(key, "[") {
78+
// whenever we encounter [] operator we need to parse it
79+
// this allows for syntax like this:
80+
// to:
81+
// - "policy.rules.[name=replace-subject].match.header.regex.[name=test].value"
82+
trimmed := strings.TrimRight(strings.TrimLeft(key, "["), "]")
83+
a, b, ok := strings.Cut(trimmed, "=")
84+
if a != "name" {
85+
return fmt.Errorf("[] operator in %q can only be used against name field", s.Filename)
86+
}
87+
if ok {
88+
// [a=b]
89+
v, ok := forged.(map[string]any)
90+
if !ok {
91+
return fmt.Errorf("[] operator in %q can only be used on map types", s.Filename)
92+
}
93+
94+
// add the name=selector to existing map and wrap it inside a slice
95+
v[a] = b
96+
forged = []any{
97+
v,
98+
}
99+
}
100+
101+
} else {
102+
// otherwise we can easily add it as a map path
103+
forged = map[string]any{
104+
key: forged,
105+
}
106+
}
107+
}
108+
mforged, ok := forged.(map[string]any)
109+
if !ok {
110+
return fmt.Errorf("merging overlay config %q failed due to map cast", s.Filename)
111+
}
112+
113+
// using Kubernetes strategic merge patch from forged patch documents
114+
patch, err := strategicpatch.StrategicMergeMapPatch(
115+
vip.AllSettings(), mforged, cfg)
116+
if err != nil {
117+
return fmt.Errorf("building patch of overlay config %q failed: %s", s.Filename, err)
118+
}
119+
120+
// merge into current viper configuration
121+
err = vip.MergeConfigMap(patch)
122+
if err != nil {
123+
return fmt.Errorf("merging overlay config %q failed: %s", s.Filename, err)
124+
}
125+
}
126+
127+
return nil
128+
}

configfx/provider.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
// Provider defines an interface for abstract config providers
3131
type Provider[T any] interface {
3232
// Config shall return the generic config or error
33-
Config() (*T, error)
33+
Config(opts ...ConfigOption) (*T, error)
3434
// Viper shall return the viper instance
3535
Viper() *viper.Viper
3636
}
@@ -42,6 +42,8 @@ type providerImpl[T any] struct {
4242

4343
viper *viper.Viper
4444
viperMutex sync.Mutex
45+
46+
viperWatchOnce sync.Once
4547
}
4648

4749
// ensure providerImpl[T] implements Provider[T]
@@ -64,7 +66,13 @@ func NewProvider[T any](
6466
// Config decoding can be tuned by implementing [CustomConfigDecoder].
6567
// Internally it requests a Viper instance from the ConfigSource[T]
6668
// to then unmarshall it onto *T using mapstructure and default tags.
67-
func (s *providerImpl[T]) Config() (*T, error) {
69+
func (s *providerImpl[T]) Config(opts ...ConfigOption) (*T, error) {
70+
// apply any given opts
71+
cOpts := defaultConfigOptions()
72+
for _, option := range opts {
73+
option(cOpts)
74+
}
75+
6876
// create fresh generic config
6977
t := new(T)
7078

@@ -87,11 +95,28 @@ func (s *providerImpl[T]) Config() (*T, error) {
8795

8896
// get viper instance
8997
v := s.Viper()
98+
if cOpts.onConfigChange != nil {
99+
v.OnConfigChange(cOpts.onConfigChange)
100+
s.viperWatchOnce.Do(v.WatchConfig)
101+
}
90102

91-
// let viper read the config from source
92-
if err := v.ReadInConfig(); err != nil {
93-
s.releaseViper()
94-
return nil, fmt.Errorf("read config: %s", err)
103+
if cOpts.readInConfig {
104+
// let viper read the config from source
105+
if err := v.ReadInConfig(); err != nil {
106+
s.releaseViper()
107+
return nil, fmt.Errorf("read config: %s", err)
108+
}
109+
}
110+
111+
// apply any overlays
112+
for _, overlay := range cOpts.overlays {
113+
if err := overlay.applyTo(v, t); err != nil {
114+
return nil, fmt.Errorf("apply overlay: %s", err)
115+
}
116+
if cOpts.onConfigChange != nil {
117+
overlay.viper.OnConfigChange(cOpts.onConfigChange)
118+
overlay.viperWatchOnce.Do(overlay.viper.WatchConfig)
119+
}
95120
}
96121

97122
// decode config using viper and struct tags `mapstructure:""`

examples/webserver/go.mod

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ module github.com/choopm/stdfx/examples/webserver
33
go 1.24
44

55
require (
6-
github.com/choopm/stdfx v0.1.3
7-
github.com/go-viper/mapstructure/v2 v2.2.1
6+
github.com/choopm/stdfx v0.1.4
7+
github.com/go-viper/mapstructure/v2 v2.4.0
88
github.com/rs/zerolog v1.34.0
99
github.com/spf13/cobra v1.9.1
1010
go.uber.org/fx v1.24.0
11-
golang.org/x/sync v0.15.0
11+
golang.org/x/sync v0.16.0
1212
)
1313

1414
require (
@@ -22,20 +22,21 @@ require (
2222
github.com/rogpeppe/go-internal v1.13.1 // indirect
2323
github.com/sagikazarmark/locafero v0.9.0 // indirect
2424
github.com/samber/lo v1.51.0 // indirect
25-
github.com/samber/slog-common v0.18.1 // indirect
25+
github.com/samber/slog-common v0.19.0 // indirect
2626
github.com/samber/slog-zerolog/v2 v2.7.3 // indirect
2727
github.com/sourcegraph/conc v0.3.0 // indirect
2828
github.com/spf13/afero v1.14.0 // indirect
2929
github.com/spf13/cast v1.9.2 // indirect
30-
github.com/spf13/pflag v1.0.6 // indirect
30+
github.com/spf13/pflag v1.0.7 // indirect
3131
github.com/spf13/viper v1.20.1 // indirect
3232
github.com/subosito/gotenv v1.6.0 // indirect
3333
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
3434
go.uber.org/dig v1.19.0 // indirect
3535
go.uber.org/multierr v1.11.0 // indirect
3636
go.uber.org/zap v1.27.0 // indirect
37-
golang.org/x/sys v0.33.0 // indirect
38-
golang.org/x/text v0.26.0 // indirect
37+
go.yaml.in/yaml/v2 v2.4.2 // indirect
38+
golang.org/x/sys v0.34.0 // indirect
39+
golang.org/x/text v0.27.0 // indirect
3940
gopkg.in/yaml.v3 v3.0.1 // indirect
40-
sigs.k8s.io/yaml v1.4.0 // indirect
41+
sigs.k8s.io/yaml v1.5.0 // indirect
4142
)

examples/webserver/go.sum

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
github.com/choopm/stdfx v0.1.3 h1:NOOyO87PzA8zA364EgutBlq4yXgdaQmPwal51mIh9K4=
2-
github.com/choopm/stdfx v0.1.3/go.mod h1:vct0xEmLH88xfWACk/23aZL0aQHodjsrP5ezcRCj9ak=
1+
github.com/choopm/stdfx v0.1.4 h1:dQAzMMbXFpay6YfRqm97cBooPeaDKXDmuhC8yUtNg2Y=
2+
github.com/choopm/stdfx v0.1.4/go.mod h1:zCLTG4WV24T+wsSVBAb0/J7Joh25QpKJuzk4XxgkFT4=
33
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
44
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
55
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
@@ -12,10 +12,9 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
1212
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
1313
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
1414
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
15-
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
16-
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
15+
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
16+
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
1717
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
18-
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1918
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2019
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
2120
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -46,8 +45,8 @@ github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFT
4645
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
4746
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
4847
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
49-
github.com/samber/slog-common v0.18.1 h1:c0EipD/nVY9HG5shgm/XAs67mgpWDMF+MmtptdJNCkQ=
50-
github.com/samber/slog-common v0.18.1/go.mod h1:QNZiNGKakvrfbJ2YglQXLCZauzkI9xZBjOhWFKS3IKk=
48+
github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
49+
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
5150
github.com/samber/slog-zerolog/v2 v2.7.3 h1:/MkPDl/tJhijN2GvB1MWwBn2FU8RiL3rQ8gpXkQm2EY=
5251
github.com/samber/slog-zerolog/v2 v2.7.3/go.mod h1:oWU7WHof4Xp8VguiNO02r1a4VzkgoOyOZhY5CuRke60=
5352
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -58,8 +57,9 @@ github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
5857
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
5958
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
6059
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
61-
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
6260
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
61+
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
62+
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
6363
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
6464
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
6565
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -78,19 +78,23 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
7878
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
7979
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
8080
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
81-
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
82-
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
81+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
82+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
83+
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
84+
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
85+
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
86+
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
8387
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8488
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8589
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
86-
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
87-
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
88-
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
89-
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
90+
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
91+
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
92+
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
93+
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
9094
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9195
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
9296
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
9397
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
9498
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
95-
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
96-
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
99+
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
100+
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

0 commit comments

Comments
 (0)