From 296c7b8d5ebdf514f097556c8f6b7eb2a8fa40c9 Mon Sep 17 00:00:00 2001 From: Bazaah Date: Fri, 9 Dec 2022 12:40:46 +0000 Subject: [PATCH] cmd: add CLI the only command added is 'watch', which serves the only currently expected functionality of the binary for this lib. --- cmd/root.go | 56 +++++++++++++++++++++++ cmd/watch.go | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 cmd/root.go create mode 100644 cmd/watch.go diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..5e146d7 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,56 @@ +/* + * 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 cmd + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "git.st8l.com/luxolus/kdnotify/buildinfo" +) + +const ( + rootHelp = "A program for responding to Keepalived's VRRP notify events" + rootHelpLong = rootHelp + ` + +This is meant to be used with the 'vrrp_notify ' option, see +man:keepalived.conf(5) for more.` +) + +func Execute() error { + return rootCmd().Execute() +} + +func rootCmd() *cobra.Command { + root := &cobra.Command{ + Use: "kdnotify ", + Short: rootHelp, + Long: rootHelpLong, + Version: fullVersion(), + } + + root.AddCommand(watchCmd()) + + root.PersistentFlags().String("log-level", "INFO", "Set program log level. ERROR|WARN|INFO|DEBUG") + + return root +} + +func fullVersion() string { + v := build.Version + hash := build.ShortHash() + features := build.FeaturesList() + + vstr := fmt.Sprintf("%s sha=%s", v, hash) + if len(features) > 0 { + vstr = vstr + "features=" + strings.Join(features, ",") + } + + return vstr +} diff --git a/cmd/watch.go b/cmd/watch.go new file mode 100644 index 0000000..295dafc --- /dev/null +++ b/cmd/watch.go @@ -0,0 +1,126 @@ +/* + * 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 cmd + +import ( + "os" + + "github.com/spf13/cobra" + "go.uber.org/zap" + + "git.st8l.com/luxolus/kdnotify/config" + "git.st8l.com/luxolus/kdnotify/handler" + "git.st8l.com/luxolus/kdnotify/watcher" +) + +const ( + watchHelp = "Start a watch on the given PIPE, using the provided CONFIG" + watchHelpLong = watchHelp + ` + +This command creates an IPC FIFO channel at the provided PIPE path. + +It is expected that you hand this named PIPE into Keepalived's +vrrp_notify conf option, and arrange for this program to run before +Keepalived. + +The configuration file can be used to match events Keepalived produces. +There are four fields: TYPE, STATE, INSTANCE and PRIORITY, with the last +being optional. You may provide any combination of the above and an action +to run when a match occurs. + +A sample configuration: + +--- +watch: + context: # Arbitrary structure you can reference via the 'Cxt' key + instance1: + ip: 192.168.0.1 + dev: eth0 + rules: # List of rules to match on + - state: [BACKUP, STOP] # Possible actions: MASTER, BACKUP, STOP + instance: instance1 # Refers to the name of a vrrp_instance or vrrp_group in Keepalived + exec: /bin/ip addr delete {{.Cxt.instance1.ip}} dev {{.Cxt.instance1.dev}} + - state: [MASTER] + exec: /bin/logger {{.Event.Instance}} is now MASTER with priority: {{.Event.Priority}} + - type: [INSTANCE] # Possible types: INSTANCE, GROUP + state: [STOP] + exec: /bin/logger {{- vip := index .Cxt .Event.Instance -}}{{vip.ip}}%{{vip.dev}} shutdown` +) + +type watchCommand struct { + log *zap.SugaredLogger + pipe string + conffile string + config *config.WatchConfig + cxt *config.LibCxt +} + +func (c *watchCommand) PreRunE(cmd *cobra.Command, args []string) error { + ll, _ := cmd.Flags().GetString("log-level") + cfg, _ := cmd.Flags().GetString("config") + + cxt := config.NewLibCxt(ll) + log := cxt.Logger.Named("cli.watch").Sugar() + + log.Debugw("reading config file", "config", cfg) + conf, err := os.ReadFile(cfg) + if err != nil { + return err + } + + log.Debug("loading watcher rules and context") + config, err := config.WatchConfigFromYAML(conf) + if err != nil { + return err + } + + c.cxt = cxt + c.log = log + c.conffile = cfg + c.config = &config + c.pipe = args[0] + + log.Debugw("config loaded", + "fifo", c.pipe, + "config", c.conffile, + "rules", len(c.config.Rules), + ) + return nil +} + +func (c *watchCommand) RunE(cmd *cobra.Command, _ []string) error { + h := handler.NewVrrp(c.cxt, c.config) + w, err := watcher.NewWatcherFifo(c.cxt, c.pipe) + if err != nil { + c.log.Errorf("unable to initialize watcher", "error", err) + return err + } + + c.log.Infow("starting watch", + "fifo", c.pipe, + "config", c.conffile, + "rules", len(c.config.Rules), + ) + return w.Watch(h) +} + +func watchCmd() *cobra.Command { + cmd := &watchCommand{} + + watch := &cobra.Command{ + Use: "watch [-f CONFIG] ", + Short: watchHelp, + Long: watchHelpLong, + Args: cobra.ExactArgs(1), + PreRunE: cmd.PreRunE, + RunE: cmd.RunE, + } + + watch.Flags().StringP("config", "f", "/etc/kdnotify/config.yaml", "Configuration file, see --help for more") + + return watch +}