config: Support "include" directive

With this directive other configuration files can be imported into the
main config. Can be useful for keeping secrets out of the latter.

Closes: https://todo.xenrox.net/~xenrox/ntfy-alertmanager/23
This commit is contained in:
Thorben Günther 2024-09-11 21:53:26 +02:00
parent 8ea629264d
commit 6c2521eeca
No known key found for this signature in database
GPG key ID: 415CD778D8C5AFED
2 changed files with 326 additions and 294 deletions

View file

@ -1,3 +1,8 @@
# Absolute path to another scfg configuration file which will be included.
# This directive can be specified multiple times in the main configuration,
# but only the last occurrence of a setting will be used. Settings from
# the main configuration will take precedence.
include /etc/ntfy-alertmanager/ntfy.scfg
# Public facing base URL of the service (e.g. https://ntfy-alertmanager.xenrox.net) # Public facing base URL of the service (e.g. https://ntfy-alertmanager.xenrox.net)
# This setting is required for the "Silence" feature. # This setting is required for the "Silence" feature.
base-url https://ntfy-alertmanager.xenrox.net base-url https://ntfy-alertmanager.xenrox.net

View file

@ -21,6 +21,7 @@ const (
// Config is the configuration of the bridge. // Config is the configuration of the bridge.
type Config struct { type Config struct {
Include string
BaseURL string BaseURL string
HTTPAddress string HTTPAddress string
LogLevel string LogLevel string
@ -82,6 +83,308 @@ type resolvedConfig struct {
Priority string Priority string
} }
func parseBlock(block scfg.Block, config *Config) error {
d := block.Get("log-level")
if d != nil {
if err := d.ParseParams(&config.LogLevel); err != nil {
return err
}
}
d = block.Get("log-format")
if d != nil {
if err := d.ParseParams(&config.LogFormat); err != nil {
return err
}
}
d = block.Get("http-address")
if d != nil {
if err := d.ParseParams(&config.HTTPAddress); err != nil {
return err
}
}
d = block.Get("base-url")
if d != nil {
if err := d.ParseParams(&config.BaseURL); err != nil {
return err
}
}
d = block.Get("alert-mode")
if d != nil {
var mode string
if err := d.ParseParams(&mode); err != nil {
return err
}
switch strings.ToLower(mode) {
case "single":
config.AlertMode = Single
case "multi":
config.AlertMode = Multi
default:
return fmt.Errorf("%q directive: illegal mode %q", d.Name, mode)
}
}
d = block.Get("user")
if d != nil {
if err := d.ParseParams(&config.User); err != nil {
return err
}
}
d = block.Get("password")
if d != nil {
if err := d.ParseParams(&config.Password); err != nil {
return err
}
}
labelsDir := block.Get("labels")
if labelsDir != nil {
d = labelsDir.Children.Get("order")
if d != nil {
var order string
if err := d.ParseParams(&order); err != nil {
return err
}
config.Labels.Order = strings.Split(order, ",")
}
labels := make(map[string]labelConfig)
for _, labelName := range config.Labels.Order {
for _, labelDir := range labelsDir.Children.GetAll(labelName) {
labelConfig := new(labelConfig)
var name string
if err := labelDir.ParseParams(&name); err != nil {
return err
}
d = labelDir.Children.Get("priority")
if d != nil {
if err := d.ParseParams(&labelConfig.Priority); err != nil {
return err
}
}
d = labelDir.Children.Get("tags")
if d != nil {
var tags string
if err := d.ParseParams(&tags); err != nil {
return err
}
labelConfig.Tags = strings.Split(tags, ",")
}
d = labelDir.Children.Get("icon")
if d != nil {
if err := d.ParseParams(&labelConfig.Icon); err != nil {
return err
}
}
d = labelDir.Children.Get("email-address")
if d != nil {
if err := d.ParseParams(&labelConfig.EmailAddress); err != nil {
return err
}
}
d = labelDir.Children.Get("call")
if d != nil {
if err := d.ParseParams(&labelConfig.Call); err != nil {
return err
}
}
labels[fmt.Sprintf("%s:%s", labelName, name)] = *labelConfig
}
}
config.Labels.Label = labels
}
ntfyDir := block.Get("ntfy")
if ntfyDir != nil {
d = ntfyDir.Children.Get("topic")
if d != nil {
if err := d.ParseParams(&config.Ntfy.Topic); err != nil {
return err
}
}
d = ntfyDir.Children.Get("user")
if d != nil {
if err := d.ParseParams(&config.Ntfy.User); err != nil {
return err
}
}
d = ntfyDir.Children.Get("password")
if d != nil {
if err := d.ParseParams(&config.Ntfy.Password); err != nil {
return err
}
}
d = ntfyDir.Children.Get("access-token")
if d != nil {
if err := d.ParseParams(&config.Ntfy.AccessToken); err != nil {
return err
}
}
d = ntfyDir.Children.Get("certificate-fingerprint")
if d != nil {
if err := d.ParseParams(&config.Ntfy.CertFingerprint); err != nil {
return err
}
// hex.EncodeToString outputs a lower case string
config.Ntfy.CertFingerprint = strings.ToLower(strings.ReplaceAll(config.Ntfy.CertFingerprint, ":", ""))
}
d = ntfyDir.Children.Get("email-address")
if d != nil {
if err := d.ParseParams(&config.Ntfy.EmailAddress); err != nil {
return err
}
}
d = ntfyDir.Children.Get("call")
if d != nil {
if err := d.ParseParams(&config.Ntfy.Call); err != nil {
return err
}
}
}
cacheDir := block.Get("cache")
if cacheDir != nil {
d = cacheDir.Children.Get("type")
if d != nil {
if err := d.ParseParams(&config.Cache.Type); err != nil {
return err
}
}
var durationString string
d = cacheDir.Children.Get("duration")
if d != nil {
if err := d.ParseParams(&durationString); err != nil {
return err
}
duration, err := time.ParseDuration(durationString)
if err != nil {
return err
}
config.Cache.Duration = duration
}
// memory
var cleanupIntervalString string
d = cacheDir.Children.Get("cleanup-interval")
if d != nil {
if err := d.ParseParams(&cleanupIntervalString); err != nil {
return err
}
interval, err := time.ParseDuration(cleanupIntervalString)
if err != nil {
return err
}
config.Cache.CleanupInterval = interval
}
// redis
d = cacheDir.Children.Get("redis-url")
if d != nil {
if err := d.ParseParams(&config.Cache.RedisURL); err != nil {
return err
}
}
}
amDir := block.Get("alertmanager")
if amDir != nil {
var durationString string
d = amDir.Children.Get("silence-duration")
if d != nil {
if err := d.ParseParams(&durationString); err != nil {
return err
}
duration, err := time.ParseDuration(durationString)
if err != nil {
return err
}
config.Am.SilenceDuration = duration
}
d = amDir.Children.Get("user")
if d != nil {
if err := d.ParseParams(&config.Am.User); err != nil {
return err
}
}
d = amDir.Children.Get("password")
if d != nil {
if err := d.ParseParams(&config.Am.Password); err != nil {
return err
}
}
d = amDir.Children.Get("url")
if d != nil {
if err := d.ParseParams(&config.Am.URL); err != nil {
return err
}
}
}
resolvedDir := block.Get("resolved")
if resolvedDir != nil {
d = resolvedDir.Children.Get("tags")
if d != nil {
var tags string
if err := d.ParseParams(&tags); err != nil {
return err
}
config.Resolved.Tags = strings.Split(tags, ",")
}
d = resolvedDir.Children.Get("icon")
if d != nil {
if err := d.ParseParams(&config.Resolved.Icon); err != nil {
return err
}
}
d = resolvedDir.Children.Get("priority")
if d != nil {
if err := d.ParseParams(&config.Resolved.Priority); err != nil {
return err
}
}
}
return nil
}
// ReadConfig reads an scfg formatted file and returns the configuration struct. // ReadConfig reads an scfg formatted file and returns the configuration struct.
func ReadConfig(path string) (*Config, error) { func ReadConfig(path string) (*Config, error) {
cfg, err := scfg.Load(path) cfg, err := scfg.Load(path)
@ -103,326 +406,50 @@ func ReadConfig(path string) (*Config, error) {
// redis // redis
config.Cache.RedisURL = "redis://localhost:6379" config.Cache.RedisURL = "redis://localhost:6379"
d := cfg.Get("log-level") includeDirs := cfg.GetAll("include")
if d != nil { for _, d := range includeDirs {
if err := d.ParseParams(&config.LogLevel); err != nil { var includePath string
return nil, err if err := d.ParseParams(&includePath); err != nil {
}
}
d = cfg.Get("log-format")
if d != nil {
if err := d.ParseParams(&config.LogFormat); err != nil {
return nil, err
}
}
d = cfg.Get("http-address")
if d != nil {
if err := d.ParseParams(&config.HTTPAddress); err != nil {
return nil, err
}
}
d = cfg.Get("base-url")
if d != nil {
if err := d.ParseParams(&config.BaseURL); err != nil {
return nil, err
}
}
d = cfg.Get("alert-mode")
if d != nil {
var mode string
if err := d.ParseParams(&mode); err != nil {
return nil, err return nil, err
} }
switch strings.ToLower(mode) { block, err := scfg.Load(includePath)
case "single": if err != nil {
config.AlertMode = Single return nil, fmt.Errorf("cannot load included config file %q: %v", includePath, err)
}
case "multi": if err := parseBlock(block, config); err != nil {
config.AlertMode = Multi return nil, fmt.Errorf("cannot parse included config file %q: %v", includePath, err)
default:
return nil, fmt.Errorf("%q directive: illegal mode %q", d.Name, mode)
} }
} }
d = cfg.Get("user") err = parseBlock(cfg, config)
if d != nil { if err != nil {
if err := d.ParseParams(&config.User); err != nil { return nil, err
return nil, err
}
}
d = cfg.Get("password")
if d != nil {
if err := d.ParseParams(&config.Password); err != nil {
return nil, err
}
} }
// Check settings
if (config.Password != "" && config.User == "") || if (config.Password != "" && config.User == "") ||
(config.Password == "" && config.User != "") { (config.Password == "" && config.User != "") {
return nil, errors.New("user and password have to be set together") return nil, errors.New("user and password have to be set together")
} }
labelsDir := cfg.Get("labels") if config.Ntfy.Topic == "" {
if labelsDir != nil {
d = labelsDir.Children.Get("order")
if d != nil {
var order string
if err := d.ParseParams(&order); err != nil {
return nil, err
}
config.Labels.Order = strings.Split(order, ",")
}
labels := make(map[string]labelConfig)
for _, labelName := range config.Labels.Order {
for _, labelDir := range labelsDir.Children.GetAll(labelName) {
labelConfig := new(labelConfig)
var name string
if err := labelDir.ParseParams(&name); err != nil {
return nil, err
}
d = labelDir.Children.Get("priority")
if d != nil {
if err := d.ParseParams(&labelConfig.Priority); err != nil {
return nil, err
}
}
d = labelDir.Children.Get("tags")
if d != nil {
var tags string
if err := d.ParseParams(&tags); err != nil {
return nil, err
}
labelConfig.Tags = strings.Split(tags, ",")
}
d = labelDir.Children.Get("icon")
if d != nil {
if err := d.ParseParams(&labelConfig.Icon); err != nil {
return nil, err
}
}
d = labelDir.Children.Get("email-address")
if d != nil {
if err := d.ParseParams(&labelConfig.EmailAddress); err != nil {
return nil, err
}
}
d = labelDir.Children.Get("call")
if d != nil {
if err := d.ParseParams(&labelConfig.Call); err != nil {
return nil, err
}
}
labels[fmt.Sprintf("%s:%s", labelName, name)] = *labelConfig
}
}
config.Labels.Label = labels
}
ntfyDir := cfg.Get("ntfy")
if ntfyDir == nil {
return nil, fmt.Errorf("%q directive missing", "ntfy")
}
d = ntfyDir.Children.Get("topic")
if d == nil {
return nil, fmt.Errorf("%q missing from %q directive", "topic", "ntfy") return nil, fmt.Errorf("%q missing from %q directive", "topic", "ntfy")
} }
if err := d.ParseParams(&config.Ntfy.Topic); err != nil {
return nil, err
}
d = ntfyDir.Children.Get("user")
if d != nil {
if err := d.ParseParams(&config.Ntfy.User); err != nil {
return nil, err
}
}
d = ntfyDir.Children.Get("password")
if d != nil {
if err := d.ParseParams(&config.Ntfy.Password); err != nil {
return nil, err
}
}
if (config.Ntfy.Password != "" && config.Ntfy.User == "") || if (config.Ntfy.Password != "" && config.Ntfy.User == "") ||
(config.Ntfy.Password == "" && config.Ntfy.User != "") { (config.Ntfy.Password == "" && config.Ntfy.User != "") {
return nil, errors.New("ntfy: user and password have to be set together") return nil, errors.New("ntfy: user and password have to be set together")
} }
d = ntfyDir.Children.Get("access-token")
if d != nil {
if err := d.ParseParams(&config.Ntfy.AccessToken); err != nil {
return nil, err
}
}
if config.Ntfy.User != "" && config.Ntfy.AccessToken != "" { if config.Ntfy.User != "" && config.Ntfy.AccessToken != "" {
return nil, errors.New("ntfy: cannot use both an access-token and a user/password at the same time") return nil, errors.New("ntfy: cannot use both an access-token and a user/password at the same time")
} }
d = ntfyDir.Children.Get("certificate-fingerprint") if (config.Am.Password != "" && config.Am.User == "") ||
if d != nil { (config.Am.Password == "" && config.Am.User != "") {
if err := d.ParseParams(&config.Ntfy.CertFingerprint); err != nil { return nil, errors.New("alertmanager: user and password have to be set together")
return nil, err
}
// hex.EncodeToString outputs a lower case string
config.Ntfy.CertFingerprint = strings.ToLower(strings.ReplaceAll(config.Ntfy.CertFingerprint, ":", ""))
}
d = ntfyDir.Children.Get("email-address")
if d != nil {
if err := d.ParseParams(&config.Ntfy.EmailAddress); err != nil {
return nil, err
}
}
d = ntfyDir.Children.Get("call")
if d != nil {
if err := d.ParseParams(&config.Ntfy.Call); err != nil {
return nil, err
}
}
cacheDir := cfg.Get("cache")
if cacheDir != nil {
d = cacheDir.Children.Get("type")
if d != nil {
if err := d.ParseParams(&config.Cache.Type); err != nil {
return nil, err
}
}
var durationString string
d = cacheDir.Children.Get("duration")
if d != nil {
if err := d.ParseParams(&durationString); err != nil {
return nil, err
}
duration, err := time.ParseDuration(durationString)
if err != nil {
return nil, err
}
config.Cache.Duration = duration
}
// memory
var cleanupIntervalString string
d = cacheDir.Children.Get("cleanup-interval")
if d != nil {
if err := d.ParseParams(&cleanupIntervalString); err != nil {
return nil, err
}
interval, err := time.ParseDuration(cleanupIntervalString)
if err != nil {
return nil, err
}
config.Cache.CleanupInterval = interval
}
// redis
d = cacheDir.Children.Get("redis-url")
if d != nil {
if err := d.ParseParams(&config.Cache.RedisURL); err != nil {
return nil, err
}
}
}
amDir := cfg.Get("alertmanager")
if amDir != nil {
var durationString string
d = amDir.Children.Get("silence-duration")
if d != nil {
if err := d.ParseParams(&durationString); err != nil {
return nil, err
}
duration, err := time.ParseDuration(durationString)
if err != nil {
return nil, err
}
config.Am.SilenceDuration = duration
}
d = amDir.Children.Get("user")
if d != nil {
if err := d.ParseParams(&config.Am.User); err != nil {
return nil, err
}
}
d = amDir.Children.Get("password")
if d != nil {
if err := d.ParseParams(&config.Am.Password); err != nil {
return nil, err
}
}
if (config.Am.Password != "" && config.Am.User == "") ||
(config.Am.Password == "" && config.Am.User != "") {
return nil, errors.New("alertmanager: user and password have to be set together")
}
d = amDir.Children.Get("url")
if d != nil {
if err := d.ParseParams(&config.Am.URL); err != nil {
return nil, err
}
}
}
resolvedDir := cfg.Get("resolved")
if resolvedDir != nil {
d = resolvedDir.Children.Get("tags")
if d != nil {
var tags string
if err := d.ParseParams(&tags); err != nil {
return nil, err
}
config.Resolved.Tags = strings.Split(tags, ",")
}
d = resolvedDir.Children.Get("icon")
if d != nil {
if err := d.ParseParams(&config.Resolved.Icon); err != nil {
return nil, err
}
}
d = resolvedDir.Children.Get("priority")
if d != nil {
if err := d.ParseParams(&config.Resolved.Priority); err != nil {
return nil, err
}
}
} }
return config, nil return config, nil