Reload provider file configuration on SIGHUP

This commit is contained in:
So Koide 2024-02-01 22:06:05 +09:00 committed by GitHub
parent 85039e0d54
commit d7ec0cedbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -6,8 +6,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/signal"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall"
"text/template" "text/template"
"github.com/Masterminds/sprig/v3" "github.com/Masterminds/sprig/v3"
@ -48,6 +50,8 @@ func (p *Provider) Init() error {
// Provide allows the file provider to provide configurations to traefik // Provide allows the file provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
logger := log.With().Str(logs.ProviderName, providerName).Logger()
if p.Watch { if p.Watch {
var watchItem string var watchItem string
@ -57,48 +61,44 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
case len(p.Filename) > 0: case len(p.Filename) > 0:
watchItem = filepath.Dir(p.Filename) watchItem = filepath.Dir(p.Filename)
default: default:
return errors.New("error using file configuration provider, neither filename or directory defined") return errors.New("error using file configuration provider, neither filename nor directory is defined")
} }
if err := p.addWatcher(pool, watchItem, configurationChan, p.watcherCallback); err != nil { if err := p.addWatcher(pool, watchItem, configurationChan, p.applyConfiguration); err != nil {
return err return err
} }
} }
configuration, err := p.BuildConfiguration() pool.GoCtx(func(ctx context.Context) {
if err != nil { signals := make(chan os.Signal, 1)
if p.Watch { signal.Notify(signals, syscall.SIGHUP)
log.Error().
Str(logs.ProviderName, providerName).
Err(err).
Msg("Error while building configuration (for the first time)")
for {
select {
case <-ctx.Done():
return
// signals only receives SIGHUP events.
case <-signals:
if err := p.applyConfiguration(configurationChan); err != nil {
logger.Error().Err(err).Msg("Error while building configuration")
}
}
}
})
if err := p.applyConfiguration(configurationChan); err != nil {
if p.Watch {
logger.Err(err).Msg("Error while building configuration (for the first time)")
return nil return nil
} }
return err return err
} }
sendConfigToChannel(configurationChan, configuration)
return nil return nil
} }
// BuildConfiguration loads configuration either from file or a directory func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message) error) error {
// specified by 'Filename'/'Directory' and returns a 'Configuration' object.
func (p *Provider) BuildConfiguration() (*dynamic.Configuration, error) {
ctx := log.With().Str(logs.ProviderName, providerName).Logger().WithContext(context.Background())
if len(p.Directory) > 0 {
return p.loadFileConfigFromDirectory(ctx, p.Directory, nil)
}
if len(p.Filename) > 0 {
return p.loadFileConfig(ctx, p.Filename, true)
}
return nil, errors.New("error using file configuration provider, neither filename or directory defined")
}
func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message, fsnotify.Event)) error {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
return fmt.Errorf("error creating file watcher: %w", err) return fmt.Errorf("error creating file watcher: %w", err)
@ -111,6 +111,7 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh
// Process events // Process events
pool.GoCtx(func(ctx context.Context) { pool.GoCtx(func(ctx context.Context) {
logger := log.With().Str(logs.ProviderName, providerName).Logger()
defer watcher.Close() defer watcher.Close()
for { for {
select { select {
@ -121,39 +122,50 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh
_, evtFileName := filepath.Split(evt.Name) _, evtFileName := filepath.Split(evt.Name)
_, confFileName := filepath.Split(p.Filename) _, confFileName := filepath.Split(p.Filename)
if evtFileName == confFileName { if evtFileName == confFileName {
callback(configurationChan, evt) err := callback(configurationChan)
if err != nil {
logger.Error().Err(err).Msg("Error occurred during watcher callback")
}
} }
} else { } else {
callback(configurationChan, evt) err := callback(configurationChan)
if err != nil {
logger.Error().Err(err).Msg("Error occurred during watcher callback")
}
} }
case err := <-watcher.Errors: case err := <-watcher.Errors:
log.Error().Str(logs.ProviderName, providerName).Err(err).Msg("Watcher event error") logger.Error().Err(err).Msg("Watcher event error")
} }
} }
}) })
return nil return nil
} }
func (p *Provider) watcherCallback(configurationChan chan<- dynamic.Message, event fsnotify.Event) { // applyConfiguration builds the configuration and sends it to the given configurationChan.
watchItem := p.Filename func (p *Provider) applyConfiguration(configurationChan chan<- dynamic.Message) error {
if len(p.Directory) > 0 { configuration, err := p.buildConfiguration()
watchItem = p.Directory
}
logger := log.With().Str(logs.ProviderName, providerName).Logger()
if _, err := os.Stat(watchItem); err != nil {
logger.Error().Err(err).Msgf("Unable to watch %s", watchItem)
return
}
configuration, err := p.BuildConfiguration()
if err != nil { if err != nil {
logger.Error().Err(err).Msg("Error occurred during watcher callback") return err
return
} }
sendConfigToChannel(configurationChan, configuration) sendConfigToChannel(configurationChan, configuration)
return nil
}
// buildConfiguration loads configuration either from file or a directory
// specified by 'Filename'/'Directory' and returns a 'Configuration' object.
func (p *Provider) buildConfiguration() (*dynamic.Configuration, error) {
ctx := log.With().Str(logs.ProviderName, providerName).Logger().WithContext(context.Background())
if len(p.Directory) > 0 {
return p.loadFileConfigFromDirectory(ctx, p.Directory, nil)
}
if len(p.Filename) > 0 {
return p.loadFileConfig(ctx, p.Filename, true)
}
return nil, errors.New("error using file configuration provider, neither filename nor directory is defined")
} }
func sendConfigToChannel(configurationChan chan<- dynamic.Message, configuration *dynamic.Configuration) { func sendConfigToChannel(configurationChan chan<- dynamic.Message, configuration *dynamic.Configuration) {