reaction/app/conf.go
ppom bd6288dae0 Do not allow for empty conf, stream, filter, regex or action.
This means the minimal valid configuration contains
one stream, with one filter, with one regex and one action.
2023-04-12 09:53:44 +02:00

151 lines
3.6 KiB
Go

package app
import (
"fmt"
"log"
"os"
"regexp"
"strings"
"time"
"gopkg.in/yaml.v3"
)
type Conf struct {
Patterns map[string]string `yaml:"patterns"`
Streams map[string]*Stream `yaml:"streams"`
}
type Stream struct {
name string `yaml:"-"`
Cmd []string `yaml:"cmd"`
Filters map[string]*Filter `yaml:"filters"`
}
type Filter struct {
stream *Stream `yaml:"-"`
name string `yaml:"-"`
Regex []string `yaml:"regex"`
compiledRegex []regexp.Regexp `yaml:"-"`
patternName string `yaml:"-"`
patternWithBraces string `yaml:"-"`
Retry int `yaml:"retry"`
RetryPeriod string `yaml:"retry-period"`
retryDuration time.Duration `yaml:"-"`
Actions map[string]*Action `yaml:"actions"`
matches map[string][]time.Time `yaml:"-"`
}
type Action struct {
filter *Filter `yaml:"-"`
name string `yaml:"-"`
Cmd []string `yaml:"cmd"`
After string `yaml:"after"`
afterDuration time.Duration `yaml:"-"`
}
func (c *Conf) setup() {
for patternName, pattern := range c.Patterns {
c.Patterns[patternName] = fmt.Sprintf("(?P<%s>%s)", patternName, pattern)
}
if len(c.Streams) == 0 {
log.Fatalln("Bad configuration: no streams configured!")
}
for streamName := range c.Streams {
stream := c.Streams[streamName]
stream.name = streamName
if len(stream.Filters) == 0 {
log.Fatalln("Bad configuration: no filters configured in '%s'!", stream.name)
}
for filterName := range stream.Filters {
filter := stream.Filters[filterName]
filter.stream = stream
filter.name = filterName
filter.matches = make(map[string][]time.Time)
// Parse Duration
retryDuration, err := time.ParseDuration(filter.RetryPeriod)
if err != nil {
log.Fatalln("Failed to parse time in configuration file:", err)
}
filter.retryDuration = retryDuration
if len(filter.Regex) == 0 {
log.Fatalln("Bad configuration: no regexes configured in '%s.%s'!", stream.name, filter.name)
}
// Compute Regexes
// Look for Patterns inside Regexes
for _, regex := range filter.Regex {
for patternName, pattern := range c.Patterns {
if strings.Contains(regex, patternName) {
switch filter.patternName {
case "":
filter.patternName = patternName
filter.patternWithBraces = fmt.Sprintf("<%s>", patternName)
case patternName:
// no op
default:
log.Fatalf(
"ERROR Can't mix different patterns (%s, %s) in same filter (%s.%s)\n",
filter.patternName, patternName, streamName, filterName,
)
}
regex = strings.Replace(regex, fmt.Sprintf("<%s>", patternName), pattern, 1)
}
}
filter.compiledRegex = append(filter.compiledRegex, *regexp.MustCompile(regex))
}
if len(filter.Actions) == 0 {
log.Fatalln("Bad configuration: no actions configured in '%s.%s'!", stream.name, filter.name)
}
for actionName := range filter.Actions {
action := filter.Actions[actionName]
action.filter = filter
action.name = actionName
// Parse Duration
if action.After != "" {
afterDuration, err := time.ParseDuration(action.After)
if err != nil {
log.Fatalln("Failed to parse time in configuration file:", err)
}
action.afterDuration = afterDuration
}
}
}
}
}
func parseConf(filename string) *Conf {
data, err := os.ReadFile(filename)
if err != nil {
log.Fatalln("Failed to read configuration file:", err)
}
var conf Conf
err = yaml.Unmarshal(data, &conf)
if err != nil {
log.Fatalln("Failed to parse configuration file:", err)
}
conf.setup()
return &conf
}