From 96ec0019d3713f34b81a7bae90c3e102eb2c6e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= Date: Mon, 9 Jan 2023 17:30:00 +0100 Subject: [PATCH] wrapper.go: Use two viper instances to watch configs Also make sure that the config from the config file is merged, so we don't miss the custom resource state config file defined in the options config. --- internal/wrapper.go | 95 ++++++++++++++------------------------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/internal/wrapper.go b/internal/wrapper.go index dd282c7d9c..b83f74651a 100644 --- a/internal/wrapper.go +++ b/internal/wrapper.go @@ -19,12 +19,13 @@ package internal import ( "context" "errors" + "os" "path/filepath" - "sync" "time" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" + "gopkg.in/yaml.v3" "k8s.io/klog/v2" "k8s.io/kube-state-metrics/v2/pkg/app" @@ -43,9 +44,10 @@ func RunKubeStateMetricsWrapper(opts *options.Options) { ctx, cancel := context.WithCancel(context.Background()) if file := options.GetConfigFile(*opts); file != "" { - viper.SetConfigType("yaml") - viper.SetConfigFile(file) - if err := viper.ReadInConfig(); err != nil { + cfgViper := viper.New() + cfgViper.SetConfigType("yaml") + cfgViper.SetConfigFile(file) + if err := cfgViper.ReadInConfig(); err != nil { if errors.Is(err, viper.ConfigFileNotFoundError{}) { klog.ErrorS(err, "Options configuration file not found", "file", file) } else { @@ -53,7 +55,7 @@ func RunKubeStateMetricsWrapper(opts *options.Options) { } klog.FlushAndExit(klog.ExitFlushTimeout, 1) } - viper.OnConfigChange(func(e fsnotify.Event) { + cfgViper.OnConfigChange(func(e fsnotify.Event) { klog.Infof("Changes detected: %s\n", e.Name) cancel() // Wait for the ports to be released. @@ -61,11 +63,29 @@ func RunKubeStateMetricsWrapper(opts *options.Options) { ctx, cancel = context.WithCancel(context.Background()) go KSMRunOrDie(ctx) }) - viper.WatchConfig() - } + cfgViper.WatchConfig() + // Merge configFile values with opts so we get the CustomResourceConfigFile from config as well + configFile, err := os.ReadFile(filepath.Clean(file)) + if err != nil { + klog.ErrorS(err, "failed to read options configuration file", "file", file) + } + + yaml.Unmarshal(configFile, opts) + } if opts.CustomResourceConfigFile != "" { - WatchConfig(opts.CustomResourceConfigFile, func(e fsnotify.Event) { + crcViper := viper.New() + crcViper.SetConfigType("yaml") + crcViper.SetConfigFile(opts.CustomResourceConfigFile) + if err := crcViper.ReadInConfig(); err != nil { + if errors.Is(err, viper.ConfigFileNotFoundError{}) { + klog.ErrorS(err, "Custom resource configuration file not found", "file", opts.CustomResourceConfigFile) + } else { + klog.ErrorS(err, "Error reading Custom resource configuration file", "file", opts.CustomResourceConfigFile) + } + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + crcViper.OnConfigChange(func(e fsnotify.Event) { klog.Infof("Changes detected: %s\n", e.Name) cancel() // Wait for the ports to be released. @@ -73,66 +93,9 @@ func RunKubeStateMetricsWrapper(opts *options.Options) { ctx, cancel = context.WithCancel(context.Background()) go KSMRunOrDie(ctx) }) + crcViper.WatchConfig() } klog.Infoln("Starting kube-state-metrics") KSMRunOrDie(ctx) select {} } - -// WatchConfig is taken and adapted from https://github.com/spf13/viper/blob/b89e554a96abde447ad13a26dcc59fd00375e555/viper.go#L429 MIT-Licensed -func WatchConfig(filename string, onConfigChange func(fsnotify.Event)) { - initWG := sync.WaitGroup{} - initWG.Add(1) - go func() { - watcher, err := fsnotify.NewWatcher() - if err != nil { - klog.ErrorS(err, "Failed to create new watcher") - } - defer watcher.Close() - - // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way - configFile := filepath.Clean(filename) - configDir, _ := filepath.Split(configFile) - realConfigFile, _ := filepath.EvalSymlinks(filename) - - eventsWG := sync.WaitGroup{} - eventsWG.Add(1) - go func() { - for { - select { - case event, ok := <-watcher.Events: - if !ok { // 'Events' channel is closed - eventsWG.Done() - return - } - currentConfigFile, _ := filepath.EvalSymlinks(filename) - // we only care about the config file with the following cases: - // 1 - if the config file was modified or created - // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement) - if (filepath.Clean(event.Name) == configFile && - (event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) || - (currentConfigFile != "" && currentConfigFile != realConfigFile) { - realConfigFile = currentConfigFile - if onConfigChange != nil { - onConfigChange(event) - } - } else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) { - eventsWG.Done() - return - } - - case err, ok := <-watcher.Errors: - if ok { // 'Errors' channel is not closed - klog.ErrorS(err, "Watcher received an error") - } - eventsWG.Done() - return - } - } - }() - watcher.Add(configDir) - initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on... - eventsWG.Wait() // now, wait for event loop to end in this go-routine... - }() - initWG.Wait() // make sure that the go routine above fully ended before returning -}