diff --git a/app/reaction.go b/app/reaction.go index 7ed5380..3b32176 100644 --- a/app/reaction.go +++ b/app/reaction.go @@ -42,16 +42,27 @@ func cmdStdout(commandline []string) chan *string { return lines } +func (p *Pattern) notAnIgnore(match *string) bool { + for _, ignore := range p.Ignore { + if ignore == *match { + return false + } + } + return true +} + // Whether one of the filter's regexes is matched on a line func (f *Filter) match(line *string) string { for _, regex := range f.compiledRegex { if matches := regex.FindStringSubmatch(*line); matches != nil { - match := matches[regex.SubexpIndex(f.patternName)] + match := matches[regex.SubexpIndex(f.pattern.name)] - log.Printf("INFO %s.%s: match [%v]\n", f.stream.name, f.name, match) - return match + if f.pattern.notAnIgnore(&match) { + log.Printf("INFO %s.%s: match [%v]\n", f.stream.name, f.name, match) + return match + } } } return "" @@ -94,7 +105,7 @@ func (a *Action) exec(match string, advance time.Duration) { if doExec { computedCommand := make([]string, 0, len(a.Cmd)) for _, item := range a.Cmd { - computedCommand = append(computedCommand, strings.ReplaceAll(item, a.filter.patternWithBraces, match)) + computedCommand = append(computedCommand, strings.ReplaceAll(item, a.filter.pattern.nameWithBraces, match)) } log.Printf("INFO %s.%s.%s: run %s\n", a.filter.stream.name, a.filter.name, a.name, computedCommand) diff --git a/app/startup.go b/app/startup.go index 6ead2cf..611d19b 100644 --- a/app/startup.go +++ b/app/startup.go @@ -15,8 +15,16 @@ import ( ) type Conf struct { - Patterns map[string]string `yaml:"patterns"` - Streams map[string]*Stream `yaml:"streams"` + Patterns map[string]*Pattern `yaml:"patterns"` + Streams map[string]*Stream `yaml:"streams"` +} + +type Pattern struct { + Regex string `yaml:"regex"` + Ignore []string `yaml:"ignore"` + + name string `yaml:"-"` + nameWithBraces string `yaml:"-"` } // Stream, Filter & Action structures must never be copied. @@ -33,10 +41,9 @@ type Filter struct { stream *Stream `yaml:"-"` name string `yaml:"-"` - Regex []string `yaml:"regex"` - compiledRegex []regexp.Regexp `yaml:"-"` - patternName string `yaml:"-"` - patternWithBraces string `yaml:"-"` + Regex []string `yaml:"regex"` + compiledRegex []regexp.Regexp `yaml:"-"` + pattern *Pattern `yaml:"-"` Retry int `yaml:"retry"` RetryPeriod string `yaml:"retry-period"` @@ -68,9 +75,28 @@ type LogEntry struct { } func (c *Conf) setup() { - for patternName, pattern := range c.Patterns { - c.Patterns[patternName] = fmt.Sprintf("(?P<%s>%s)", patternName, pattern) + + for patternName := range c.Patterns { + pattern := c.Patterns[patternName] + pattern.name = patternName + pattern.nameWithBraces = fmt.Sprintf("<%s>", pattern.name) + + if pattern.Regex == "" { + log.Fatalf("FATAL Bad configuration: pattern's regex %v is empty!", patternName) + } + + compiled, err := regexp.Compile(fmt.Sprintf("^%v$", pattern.Regex)) + if err != nil { + log.Fatalf("FATAL Bad configuration: pattern %v doesn't compile!", patternName) + } + c.Patterns[patternName].Regex = fmt.Sprintf("(?P<%s>%s)", patternName, pattern.Regex) + for _, ignore := range pattern.Ignore { + if !compiled.MatchString(ignore) { + log.Fatalf("FATAL Bad configuration: pattern ignore '%v' doesn't match pattern %v! It should be fixed or removed.", ignore, pattern.nameWithBraces) + } + } } + if len(c.Streams) == 0 { log.Fatalln("FATAL Bad configuration: no streams configured!") } @@ -109,24 +135,24 @@ func (c *Conf) setup() { // Look for Patterns inside Regexes for _, regex := range filter.Regex { for patternName, pattern := range c.Patterns { - if strings.Contains(regex, patternName) { + if strings.Contains(regex, pattern.nameWithBraces) { - switch filter.patternName { - case "": - filter.patternName = patternName - filter.patternWithBraces = fmt.Sprintf("<%s>", patternName) - case patternName: + if filter.pattern == nil { + filter.pattern = pattern + } else if filter.pattern == pattern { // no op - default: + } else { log.Fatalf( "Bad configuration: Can't mix different patterns (%s, %s) in same filter (%s.%s)\n", - filter.patternName, patternName, streamName, filterName, + filter.pattern.name, patternName, streamName, filterName, ) } - regex = strings.Replace(regex, fmt.Sprintf("<%s>", patternName), pattern, 1) + // FIXME should go in the `if filter.pattern == nil`? + regex = strings.Replace(regex, pattern.nameWithBraces, pattern.Regex, 1) } } + // TODO regexp.Compile and show proper message if it doesn't instead of panicing filter.compiledRegex = append(filter.compiledRegex, *regexp.MustCompile(regex)) } diff --git a/config/reaction.test.yml b/config/reaction.test.yml index 2892acf..862ebce 100644 --- a/config/reaction.test.yml +++ b/config/reaction.test.yml @@ -1,20 +1,19 @@ --- patterns: - ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})' + ip: + regex: '(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})' + ignore: + - 1.0.0.1 streams: tailDown: - cmd: [ "sh", "-c", "echo 'found 1.1.1.1' && sleep 2s && echo 'found 1.1.1.2' && sleep 2s && echo 'found 1.1.1.1' && sleep 10m" ] + cmd: [ "sh", "-c", "echo 'found 1.1.1.1' && sleep 2 && echo 'found 1.0.0.1' && sleep 10m" ] filters: findIP: regex: - - found + - '^found ' retry: 2 retry-period: 1m actions: damn: cmd: [ "echo", "" ] - sleepdamn: - cmd: [ "echo", "sleep", "" ] - after: 8m - onexit: true diff --git a/config/reaction.yml b/config/reaction.yml index 1c39c43..a3e6310 100644 --- a/config/reaction.yml +++ b/config/reaction.yml @@ -8,7 +8,11 @@ definitions: # patterns are substitued in regexes. # when a filter performs an action, it replaces the found pattern patterns: - ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})' + ip: + regex: '(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})' + ignore: + - 127.0.0.1 + - ::1 # streams are command that are run # their output will be used by one or more filters