Skip to content

Commit

Permalink
wrapper.go: Use two viper instances to watch configs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mrueg committed Jan 9, 2023
1 parent bd04681 commit 96ec001
Showing 1 changed file with 29 additions and 66 deletions.
95 changes: 29 additions & 66 deletions internal/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -43,96 +44,58 @@ 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 {
klog.ErrorS(err, "Error reading options configuration file", "file", file)
}
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.
<-time.After(3 * time.Second)
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.
<-time.After(3 * time.Second)
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
}

0 comments on commit 96ec001

Please sign in to comment.