cloudflare/cloudflared
Publicmirrored from https://github.com/cloudflare/cloudflaredAvailable
config/manager.go
112lines · modecode
| 1 | package config |
| 2 | |
| 3 | import ( |
| 4 | "io" |
| 5 | "os" |
| 6 | |
| 7 | "github.com/pkg/errors" |
| 8 | "github.com/rs/zerolog" |
| 9 | yaml "gopkg.in/yaml.v3" |
| 10 | |
| 11 | "github.com/cloudflare/cloudflared/watcher" |
| 12 | ) |
| 13 | |
| 14 | // Notifier sends out config updates |
| 15 | type Notifier interface { |
| 16 | ConfigDidUpdate(Root) |
| 17 | } |
| 18 | |
| 19 | // Manager is the base functions of the config manager |
| 20 | type Manager interface { |
| 21 | Start(Notifier) error |
| 22 | Shutdown() |
| 23 | } |
| 24 | |
| 25 | // FileManager watches the yaml config for changes |
| 26 | // sends updates to the service to reconfigure to match the updated config |
| 27 | type FileManager struct { |
| 28 | watcher watcher.Notifier |
| 29 | notifier Notifier |
| 30 | configPath string |
| 31 | log *zerolog.Logger |
| 32 | ReadConfig func(string, *zerolog.Logger) (Root, error) |
| 33 | } |
| 34 | |
| 35 | // NewFileManager creates a config manager |
| 36 | func NewFileManager(watcher watcher.Notifier, configPath string, log *zerolog.Logger) (*FileManager, error) { |
| 37 | m := &FileManager{ |
| 38 | watcher: watcher, |
| 39 | configPath: configPath, |
| 40 | log: log, |
| 41 | ReadConfig: readConfigFromPath, |
| 42 | } |
| 43 | err := watcher.Add(configPath) |
| 44 | return m, err |
| 45 | } |
| 46 | |
| 47 | // Start starts the runloop to watch for config changes |
| 48 | func (m *FileManager) Start(notifier Notifier) error { |
| 49 | m.notifier = notifier |
| 50 | |
| 51 | // update the notifier with a fresh config on start |
| 52 | config, err := m.GetConfig() |
| 53 | if err != nil { |
| 54 | return err |
| 55 | } |
| 56 | notifier.ConfigDidUpdate(config) |
| 57 | |
| 58 | m.watcher.Start(m) |
| 59 | return nil |
| 60 | } |
| 61 | |
| 62 | // GetConfig reads the yaml file from the disk |
| 63 | func (m *FileManager) GetConfig() (Root, error) { |
| 64 | return m.ReadConfig(m.configPath, m.log) |
| 65 | } |
| 66 | |
| 67 | // Shutdown stops the watcher |
| 68 | func (m *FileManager) Shutdown() { |
| 69 | m.watcher.Shutdown() |
| 70 | } |
| 71 | |
| 72 | func readConfigFromPath(configPath string, log *zerolog.Logger) (Root, error) { |
| 73 | if configPath == "" { |
| 74 | return Root{}, errors.New("unable to find config file") |
| 75 | } |
| 76 | |
| 77 | file, err := os.Open(configPath) |
| 78 | if err != nil { |
| 79 | return Root{}, err |
| 80 | } |
| 81 | defer file.Close() |
| 82 | |
| 83 | var config Root |
| 84 | if err := yaml.NewDecoder(file).Decode(&config); err != nil { |
| 85 | if err == io.EOF { |
| 86 | log.Error().Msgf("Configuration file %s was empty", configPath) |
| 87 | return Root{}, nil |
| 88 | } |
| 89 | return Root{}, errors.Wrap(err, "error parsing YAML in config file at "+configPath) |
| 90 | } |
| 91 | |
| 92 | return config, nil |
| 93 | } |
| 94 | |
| 95 | // File change notifications from the watcher |
| 96 | |
| 97 | // WatcherItemDidChange triggers when the yaml config is updated |
| 98 | // sends the updated config to the service to reload its state |
| 99 | func (m *FileManager) WatcherItemDidChange(filepath string) { |
| 100 | config, err := m.GetConfig() |
| 101 | if err != nil { |
| 102 | m.log.Err(err).Msg("Failed to read new config") |
| 103 | return |
| 104 | } |
| 105 | m.log.Info().Msg("Config file has been updated") |
| 106 | m.notifier.ConfigDidUpdate(config) |
| 107 | } |
| 108 | |
| 109 | // WatcherDidError notifies of errors with the file watcher |
| 110 | func (m *FileManager) WatcherDidError(err error) { |
| 111 | m.log.Err(err).Msg("Config watcher encountered an error") |
| 112 | } |
| 113 | |