From 6257a3068301ac3a9c2c22bb81787745b5414907 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 10 Jul 2024 14:56:17 +0200 Subject: [PATCH] Use CLI flags parser from Icinga Go Library --- cmd/icinga-notifications/main.go | 33 +---------- go.mod | 4 +- go.sum | 4 +- internal/daemon/config.go | 97 ++++++++++++++++++-------------- 4 files changed, 60 insertions(+), 78 deletions(-) diff --git a/cmd/icinga-notifications/main.go b/cmd/icinga-notifications/main.go index 680f0b79..36d88459 100644 --- a/cmd/icinga-notifications/main.go +++ b/cmd/icinga-notifications/main.go @@ -2,8 +2,6 @@ package main import ( "context" - "flag" - "fmt" "github.com/icinga/icinga-go-library/database" "github.com/icinga/icinga-go-library/logging" "github.com/icinga/icinga-go-library/utils" @@ -16,45 +14,18 @@ import ( "github.com/icinga/icinga-notifications/internal/listener" "github.com/icinga/icinga-notifications/internal/object" "github.com/okzk/sdnotify" - "os" "os/signal" - "runtime" "syscall" "time" ) func main() { - var configPath string - var showVersion bool - - flag.StringVar(&configPath, "config", internal.SysConfDir+"/icinga-notifications/config.yml", "path to config file") - flag.BoolVar(&showVersion, "version", false, "print version") - flag.Parse() - - if showVersion { - // reuse internal.Version.print() once the project name is configurable - fmt.Println("Icinga Notifications version:", internal.Version.Version) - fmt.Println() - - fmt.Println("Build information:") - fmt.Printf(" Go version: %s (%s, %s)\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) - if internal.Version.Commit != "" { - fmt.Println(" Git commit:", internal.Version.Commit) - } - return - } - - err := daemon.LoadConfig(configPath) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, "cannot load config:", err) - os.Exit(1) - } - + daemon.ParseFlagsAndConfig() conf := daemon.Config() logs, err := logging.NewLoggingFromConfig("icinga-notifications", conf.Logging) if err != nil { - utils.PrintErrorThenExit(err, 1) + utils.PrintErrorThenExit(err, daemon.ExitFailure) } logger := logs.GetLogger() diff --git a/go.mod b/go.mod index 2705b05d..b6aa5660 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,9 @@ require ( github.com/creasty/defaults v1.7.0 github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 github.com/emersion/go-smtp v0.21.3 - github.com/goccy/go-yaml v1.11.3 github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/icinga/icinga-go-library v0.2.0 + github.com/icinga/icinga-go-library v0.3.0 github.com/jhillyerd/enmime v1.2.0 github.com/jmoiron/sqlx v1.4.0 github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd @@ -27,6 +26,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-yaml v1.11.3 // indirect github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect diff --git a/go.sum b/go.sum index 0f0f6a9b..4f2300d3 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/icinga/icinga-go-library v0.2.0 h1:1or5s3KMEJGdhFbMzlN8NPw1NCd/3ntsKLw5et4/9XI= -github.com/icinga/icinga-go-library v0.2.0/go.mod h1:YN7XJN3W0FodD+j4kirO89zk2tgvanXWt1RMV8UgOLo= +github.com/icinga/icinga-go-library v0.3.0 h1:BeoomAiQC5RTRWCNqNkgbdTGxQ7ZFfkruR4HCSn5e0k= +github.com/icinga/icinga-go-library v0.3.0/go.mod h1:YN7XJN3W0FodD+j4kirO89zk2tgvanXWt1RMV8UgOLo= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= diff --git a/internal/daemon/config.go b/internal/daemon/config.go index e930ec84..72e9742d 100644 --- a/internal/daemon/config.go +++ b/internal/daemon/config.go @@ -3,14 +3,20 @@ package daemon import ( "errors" "github.com/creasty/defaults" - "github.com/goccy/go-yaml" + "github.com/icinga/icinga-go-library/config" "github.com/icinga/icinga-go-library/database" "github.com/icinga/icinga-go-library/logging" + "github.com/icinga/icinga-go-library/utils" "github.com/icinga/icinga-notifications/internal" "os" "time" ) +const ( + ExitSuccess = 0 + ExitFailure = 1 +) + type ConfigFile struct { Listen string `yaml:"listen" default:"localhost:5680"` DebugPassword string `yaml:"debug-password"` @@ -28,65 +34,70 @@ func (c *ConfigFile) SetDefaults() { } } -// Assert interface compliance. -var _ defaults.Setter = (*ConfigFile)(nil) - -// config holds the configuration state as a singleton. It is used from LoadConfig and Config -var config *ConfigFile - -// LoadConfig loads the daemon config from given path. Call it only once when starting the daemon. -func LoadConfig(path string) error { - if config != nil { - return errors.New("config already set") +// Validate implements the config.Validator interface. +// Validates the entire daemon configuration on daemon startup. +func (c *ConfigFile) Validate() error { + if err := c.Database.Validate(); err != nil { + return err } - - cfg, err := fromFile(path) - if err != nil { + if err := c.Logging.Validate(); err != nil { return err } - config = cfg - return nil } -// Config returns the config that was loaded while starting the daemon -func Config() *ConfigFile { - return config +// Assert interface compliance. +var ( + _ defaults.Setter = (*ConfigFile)(nil) + _ config.Validator = (*ConfigFile)(nil) +) + +// Flags defines the CLI flags supported by Icinga Notifications. +type Flags struct { + // Version decides whether to just print the version and exit. + Version bool `long:"version" description:"print version and exit"` + // Config is the path to the config file + Config string `short:"c" long:"config" description:"path to config file"` } -func fromFile(path string) (*ConfigFile, error) { - f, err := os.Open(path) - if err != nil { - return nil, err +// daemonConfig holds the configuration state as a singleton. +// It is initialised by the ParseFlagsAndConfig func and exposed through the Config function. +var daemonConfig *ConfigFile + +// Config returns the config that was loaded while starting the daemon. +// Panics when ParseFlagsAndConfig was not called earlier. +func Config() *ConfigFile { + if daemonConfig == nil { + panic("ERROR: daemon.Config() called before daemon.ParseFlagsAndConfig()") } - defer func() { _ = f.Close() }() - var c ConfigFile + return daemonConfig +} - if err := defaults.Set(&c); err != nil { - return nil, err - } +// ParseFlagsAndConfig parses the CLI flags provided to the executable and tries to load the config from the YAML file. +// Prints any error during parsing or config loading to os.Stderr and exits. +func ParseFlagsAndConfig() { + flags := Flags{Config: internal.SysConfDir + "/icinga-notifications/config.yml"} + if err := config.ParseFlags(&flags); err != nil { + if errors.Is(err, config.ErrInvalidArgument) { + panic(err) + } - d := yaml.NewDecoder(f) - if err := d.Decode(&c); err != nil { - return nil, err + utils.PrintErrorThenExit(err, ExitFailure) } - if err := c.Validate(); err != nil { - return nil, err + if flags.Version { + internal.Version.Print("Icinga Notifications") + os.Exit(ExitSuccess) } - return &c, nil -} + daemonConfig = new(ConfigFile) + if err := config.FromYAMLFile(flags.Config, daemonConfig); err != nil { + if errors.Is(err, config.ErrInvalidArgument) { + panic(err) + } -func (c *ConfigFile) Validate() error { - if err := c.Database.Validate(); err != nil { - return err + utils.PrintErrorThenExit(err, ExitFailure) } - if err := c.Logging.Validate(); err != nil { - return err - } - - return nil }