// Package config defines the configuration file. package config import ( "errors" "fmt" "strings" "time" "git.sr.ht/~emersion/go-scfg" ) // AlertMode determines if alerts grouped by Alertmanager are being kept together. type AlertMode int // The different modes for AlertMode. const ( Single AlertMode = iota Multi ) // Config is the configuration of the bridge. type Config struct { Include string BaseURL string HTTPAddress string LogLevel string LogFormat string AlertMode AlertMode User string Password string Ntfy ntfyConfig Labels labels Cache CacheConfig Am alertmanagerConfig Resolved resolvedConfig } type ntfyConfig struct { Topic string User string Password string AccessToken string CertFingerprint string EmailAddress string Call string } type labels struct { Order []string Label map[string]labelConfig } type labelConfig struct { Priority string Tags []string Icon string EmailAddress string Call string } // CacheConfig is the configuration of the cache. type CacheConfig struct { // shared settings Type string Duration time.Duration // memory settings CleanupInterval time.Duration // redis settings RedisURL string } type alertmanagerConfig struct { User string Password string SilenceDuration time.Duration URL string } type resolvedConfig struct { Tags []string Icon 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 } // Read reads an scfg formatted file and returns the configuration struct. func Read(path string) (*Config, error) { cfg, err := scfg.Load(path) if err != nil { return nil, err } config := new(Config) // Set default values config.HTTPAddress = "127.0.0.1:8080" config.LogLevel = "info" config.LogFormat = "text" config.AlertMode = Multi config.Cache.Type = "disabled" config.Cache.Duration = time.Hour * 24 // memory config.Cache.CleanupInterval = time.Hour // redis config.Cache.RedisURL = "redis://localhost:6379" includeDirs := cfg.GetAll("include") for _, d := range includeDirs { var includePath string if err := d.ParseParams(&includePath); err != nil { return nil, err } block, err := scfg.Load(includePath) if err != nil { return nil, fmt.Errorf("cannot load included config file %q: %v", includePath, err) } if err := parseBlock(block, config); err != nil { return nil, fmt.Errorf("cannot parse included config file %q: %v", includePath, err) } } err = parseBlock(cfg, config) if err != nil { return nil, err } // Check settings if (config.Password != "" && config.User == "") || (config.Password == "" && config.User != "") { return nil, errors.New("user and password have to be set together") } if config.Ntfy.Topic == "" { return nil, fmt.Errorf("%q missing from %q directive", "topic", "ntfy") } if (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") } 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") } 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") } return config, nil }