176 lines
4.1 KiB
Go
176 lines
4.1 KiB
Go
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"text/template"
|
|
|
|
"golang.org/x/exp/slices"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"git.st8l.com/luxolus/kdnotify/schema/notify"
|
|
"git.st8l.com/luxolus/kdnotify/schema/notify/state"
|
|
"git.st8l.com/luxolus/kdnotify/schema/notify/ty"
|
|
)
|
|
|
|
// Configuration for WatchHandlers, providing them an evaluation context
|
|
// and rules for handling notify events
|
|
type WatchConfig struct {
|
|
// Arbitrary user input, accessible in WatchMatch.Exec templates
|
|
Context any `yaml:"context"`
|
|
// List of rules to use when handling notify events
|
|
Rules []WatchRule `yaml:"rules"`
|
|
}
|
|
|
|
// Parse the given yaml, looking for the top level 'watch:' key, returning
|
|
// the unmarshaled WatchHandle configuration
|
|
func WatchConfigFromYAML(in []byte) (WatchConfig, error) {
|
|
var visitor visitWatchConfig
|
|
|
|
err := yaml.Unmarshal(in, &visitor)
|
|
if err != nil {
|
|
return WatchConfig{}, fmt.Errorf("while parsing 'watch': %w", err)
|
|
}
|
|
|
|
return visitor.Watch, nil
|
|
}
|
|
|
|
// A single rule to evaluate a notify event on.
|
|
//
|
|
// Each condition field (State, Type, Instance) is a logical OR
|
|
// while the fields together are a logical AND. Demonstrated:
|
|
//
|
|
// if State.Contains($state) && Type.Contains($type) && Instance.Contains($instance) {
|
|
// os.Exec(Template)
|
|
// }
|
|
//
|
|
// The one exception is if any check is empty, it is ignored
|
|
type WatchRule struct {
|
|
// List of states that apply this rule, ignored if empty
|
|
State state.State
|
|
// List of types that apply this rule, ignored if empty
|
|
Type ty.Type
|
|
// List of instances that apply this rule, ignored if empty
|
|
Instance []string
|
|
// Template to evaluate and execute if the conditions match
|
|
Exec *template.Template
|
|
}
|
|
|
|
// Check if the given message matches this WatchRule
|
|
func (w *WatchRule) Match(msg *notify.VrrpMessage) bool {
|
|
if w.Type != ty.NULL && !w.Type.Has(msg.Type) {
|
|
return false
|
|
}
|
|
if w.State != state.NULL && !w.State.Has(msg.State) {
|
|
return false
|
|
}
|
|
if len(w.Instance) > 0 && !slices.Contains(w.Instance, msg.Instance) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (w *WatchRule) UnmarshalYAML(value *yaml.Node) error {
|
|
var visitor visitWatchRule
|
|
|
|
switch value.Kind {
|
|
case yaml.AliasNode:
|
|
return w.UnmarshalYAML(value.Alias)
|
|
default:
|
|
err := value.Decode(&visitor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
rule, err := visitor.IntoWatchRule()
|
|
if err != nil {
|
|
return fmt.Errorf("while parsing watch rule: %w", err)
|
|
}
|
|
*w = rule
|
|
|
|
return nil
|
|
}
|
|
|
|
type visitWatchConfig struct {
|
|
Watch WatchConfig `yaml:"watch"`
|
|
}
|
|
|
|
type visitWatchRule struct {
|
|
State sslice `yaml:"state,omitempty"`
|
|
Type sslice `yaml:"type,omitempty"`
|
|
Instance sslice `yaml:"instance,omitempty"`
|
|
Exec string `yaml:"exec,omitempty"`
|
|
}
|
|
|
|
func (v visitWatchRule) IntoWatchRule() (WatchRule, error) {
|
|
var rule WatchRule
|
|
|
|
for _, val := range v.Type.Value {
|
|
v := ty.ParseType(val)
|
|
if v == ty.UNKNOWN {
|
|
return rule, fmt.Errorf("unknown type: '%s'", val)
|
|
}
|
|
|
|
rule.Type |= v
|
|
}
|
|
|
|
for _, val := range v.State.Value {
|
|
v := state.ParseState(val)
|
|
if v == state.UNKNOWN {
|
|
return rule, fmt.Errorf("unknown state: '%s'", val)
|
|
}
|
|
|
|
rule.State |= v
|
|
}
|
|
|
|
exec, err := template.New("WatchRule").Parse(v.Exec)
|
|
if err != nil {
|
|
return rule, fmt.Errorf("unable to parse exec template: %w", err)
|
|
}
|
|
rule.Exec = exec
|
|
|
|
rule.Instance = v.Instance.Value
|
|
|
|
return rule, nil
|
|
}
|
|
|
|
// Visitor for string or []string Yaml productions
|
|
type sslice struct {
|
|
Value []string
|
|
}
|
|
|
|
func (v *sslice) UnmarshalYAML(value *yaml.Node) error {
|
|
|
|
switch value.Kind {
|
|
case yaml.AliasNode:
|
|
return v.UnmarshalYAML(value.Alias)
|
|
case yaml.SequenceNode:
|
|
return value.Decode(&v.Value)
|
|
case yaml.ScalarNode:
|
|
var single string
|
|
|
|
err := value.Decode(&single)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v.Value = append(v.Value, single)
|
|
|
|
return nil
|
|
default:
|
|
error := fmt.Sprintf(
|
|
"unable to parse string or string array at %d:%d from '%s'",
|
|
value.Line, value.Column, value.Value,
|
|
)
|
|
|
|
return &yaml.TypeError{Errors: []string{error}}
|
|
}
|
|
}
|