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"
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"text/template"
"github.com/Masterminds/sprig/v3"
@ -48,6 +50,8 @@ func (p *Provider) Init() error {
// Provide allows the file provider to provide configurations to traefik
// using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
logger := log.With().Str(logs.ProviderName, providerName).Logger()
if p.Watch {
var watchItem string
@ -57,48 +61,44 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
case len(p.Filename) > 0:
watchItem = filepath.Dir(p.Filename)
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
}
}
configuration, err := p.BuildConfiguration()
if err != nil {
if p.Watch {
log.Error().
Str(logs.ProviderName, providerName).
Err(err).
Msg("Error while building configuration (for the first time)")
pool.GoCtx(func(ctx context.Context) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGHUP)
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 err
}
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 or directory defined")
}
func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message, fsnotify.Event)) error {
func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message) error) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
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
pool.GoCtx(func(ctx context.Context) {
logger := log.With().Str(logs.ProviderName, providerName).Logger()
defer watcher.Close()
for {
select {
@ -121,39 +122,50 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh
_, evtFileName := filepath.Split(evt.Name)
_, confFileName := filepath.Split(p.Filename)
if evtFileName == confFileName {
callback(configurationChan, evt)
err := callback(configurationChan)
if err != nil {
logger.Error().Err(err).Msg("Error occurred during watcher callback")
}
}
} else {
callback(configurationChan, evt)
err := callback(configurationChan)
if err != nil {
logger.Error().Err(err).Msg("Error occurred during watcher callback")
}
}
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
}
func (p *Provider) watcherCallback(configurationChan chan<- dynamic.Message, event fsnotify.Event) {
watchItem := p.Filename
if len(p.Directory) > 0 {
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()
// applyConfiguration builds the configuration and sends it to the given configurationChan.
func (p *Provider) applyConfiguration(configurationChan chan<- dynamic.Message) error {
configuration, err := p.buildConfiguration()
if err != nil {
logger.Error().Err(err).Msg("Error occurred during watcher callback")
return
return err
}
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) {