From 50a6f6e00454a5e86863fc970b890c09da1bb814 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 25 Aug 2019 23:19:29 -0700 Subject: [PATCH 1/7] Use reflection and tags for env variables. --- integrations/promunifi/unifipoller/config.go | 53 ++++++-------------- integrations/promunifi/unifipoller/start.go | 49 ++++++++++-------- integrations/promunifi/unifipoller/unifi.go | 3 +- 3 files changed, 45 insertions(+), 60 deletions(-) diff --git a/integrations/promunifi/unifipoller/config.go b/integrations/promunifi/unifipoller/config.go index 4e097b33..ad64a050 100644 --- a/integrations/promunifi/unifipoller/config.go +++ b/integrations/promunifi/unifipoller/config.go @@ -22,27 +22,6 @@ const ( defaultUnifURL = "https://127.0.0.1:8443" ) -// These are environment variables that can be used to override configuration. -// Useful for Docker users. -const ( - ENVConfigMode = "UP_POLLING_MODE" - ENVConfigInfluxDB = "UP_INFLUX_DB" - ENVConfigInfluxUser = "UP_INFLUX_USER" - ENVConfigInfluxPass = "UP_INFLUX_PASS" - ENVConfigInfluxURL = "UP_INFLUX_URL" - ENVConfigUnifiUser = "UP_UNIFI_USER" - ENVConfigUnifiPass = "UP_UNIFI_PASS" - ENVConfigUnifiBase = "UP_UNIFI_URL" - ENVConfigReAuth = "UP_REAUTHENTICATE" - ENVConfigVerifySSL = "UP_VERIFY_SSL" - ENVConfigCollectIDS = "UP_COLLECT_IDS" - ENVConfigQuiet = "UP_QUIET_MODE" - ENVConfigDebug = "UP_DEBUG_MODE" - ENVConfigInterval = "UP_POLLING_INTERVAL" - ENVConfigMaxErrors = "UP_MAX_ERRORS" - ENVConfigSites = "UP_POLL_SITES" -) - // UnifiPoller contains the application startup data, and auth info for UniFi & Influx. type UnifiPoller struct { Influx influx.Client @@ -74,22 +53,22 @@ type Metrics struct { // Config represents the data needed to poll a controller and report to influxdb. // This is all of the data stored in the config file. type Config struct { - MaxErrors int `json:"max_errors,_omitempty" toml:"max_errors,_omitempty" xml:"max_errors" yaml:"max_errors"` - Interval Duration `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval"` - Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug"` - Quiet bool `json:"quiet,_omitempty" toml:"quiet,_omitempty" xml:"quiet" yaml:"quiet"` - VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"` - CollectIDS bool `json:"collect_ids" toml:"collect_ids" xml:"collect_ids" yaml:"collect_ids"` - ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate"` - Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode"` - InfluxURL string `json:"influx_url,_omitempty" toml:"influx_url,_omitempty" xml:"influx_url" yaml:"influx_url"` - InfluxUser string `json:"influx_user,_omitempty" toml:"influx_user,_omitempty" xml:"influx_user" yaml:"influx_user"` - InfluxPass string `json:"influx_pass,_omitempty" toml:"influx_pass,_omitempty" xml:"influx_pass" yaml:"influx_pass"` - InfluxDB string `json:"influx_db,_omitempty" toml:"influx_db,_omitempty" xml:"influx_db" yaml:"influx_db"` - UnifiUser string `json:"unifi_user,_omitempty" toml:"unifi_user,_omitempty" xml:"unifi_user" yaml:"unifi_user"` - UnifiPass string `json:"unifi_pass,_omitempty" toml:"unifi_pass,_omitempty" xml:"unifi_pass" yaml:"unifi_pass"` - UnifiBase string `json:"unifi_url,_omitempty" toml:"unifi_url,_omitempty" xml:"unifi_url" yaml:"unifi_url"` - Sites []string `json:"sites,_omitempty" toml:"sites,_omitempty" xml:"sites" yaml:"sites"` + MaxErrors int `json:"max_errors,_omitempty" toml:"max_errors,_omitempty" xml:"max_errors" yaml:"max_errors" env:"UP_MAX_ERRORS"` + Interval Duration `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval" env:"UP_POLLING_INTERVAL"` + Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug" env:"UP_DEBUG_MODE"` + Quiet bool `json:"quiet,_omitempty" toml:"quiet,_omitempty" xml:"quiet" yaml:"quiet" env:"UP_QUIET_MODE"` + VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl" env:"UP_VERIFY_SSL"` + CollectIDS bool `json:"collect_ids" toml:"collect_ids" xml:"collect_ids" yaml:"collect_ids" env:"UP_COLLECT_IDS"` + ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate" env:"UP_REAUTHENTICATE"` + Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode" env:"UP_POLLING_MODE"` + InfluxURL string `json:"influx_url,_omitempty" toml:"influx_url,_omitempty" xml:"influx_url" yaml:"influx_url" env:"UP_INFLUX_URL"` + InfluxUser string `json:"influx_user,_omitempty" toml:"influx_user,_omitempty" xml:"influx_user" yaml:"influx_user" env:"UP_INFLUX_USER"` + InfluxPass string `json:"influx_pass,_omitempty" toml:"influx_pass,_omitempty" xml:"influx_pass" yaml:"influx_pass" env:"UP_INFLUX_PASS"` + InfluxDB string `json:"influx_db,_omitempty" toml:"influx_db,_omitempty" xml:"influx_db" yaml:"influx_db" env:"UP_INFLUX_DB"` + UnifiUser string `json:"unifi_user,_omitempty" toml:"unifi_user,_omitempty" xml:"unifi_user" yaml:"unifi_user" env:"UP_UNIFI_USER"` + UnifiPass string `json:"unifi_pass,_omitempty" toml:"unifi_pass,_omitempty" xml:"unifi_pass" yaml:"unifi_pass" env:"UP_UNIFI_PASS"` + UnifiBase string `json:"unifi_url,_omitempty" toml:"unifi_url,_omitempty" xml:"unifi_url" yaml:"unifi_url" env:"UP_UNIFI_URL"` + Sites []string `json:"sites,_omitempty" toml:"sites,_omitempty" xml:"sites" yaml:"sites" env:"UP_POLL_SITES"` } // Duration is used to UnmarshalTOML into a time.Duration value. diff --git a/integrations/promunifi/unifipoller/start.go b/integrations/promunifi/unifipoller/start.go index a16f105f..98316e3e 100644 --- a/integrations/promunifi/unifipoller/start.go +++ b/integrations/promunifi/unifipoller/start.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "log" "os" + "reflect" "strconv" "strings" "time" @@ -51,29 +52,33 @@ func (f *Flag) Parse(args []string) { // setEnvVarOptions copies environment variables into configuration values. // This is useful for Docker users that find it easier to pass ENV variables -// that a specific configuration file. +// than a specific configuration file. Uses reflection to find struct tags. func (u *UnifiPoller) setEnvVarOptions() { - u.Config.Mode = pick(os.Getenv(ENVConfigMode), u.Config.Mode) - u.Config.InfluxDB = pick(os.Getenv(ENVConfigInfluxDB), u.Config.InfluxDB) - u.Config.InfluxUser = pick(os.Getenv(ENVConfigInfluxUser), u.Config.InfluxUser) - u.Config.InfluxPass = pick(os.Getenv(ENVConfigInfluxPass), u.Config.InfluxPass) - u.Config.InfluxURL = pick(os.Getenv(ENVConfigInfluxURL), u.Config.InfluxURL) - u.Config.UnifiUser = pick(os.Getenv(ENVConfigUnifiUser), u.Config.UnifiUser) - u.Config.UnifiPass = pick(os.Getenv(ENVConfigUnifiPass), u.Config.UnifiPass) - u.Config.UnifiBase = pick(os.Getenv(ENVConfigUnifiBase), u.Config.UnifiBase) - u.Config.ReAuth = parseBool(os.Getenv(ENVConfigReAuth), u.Config.ReAuth) - u.Config.VerifySSL = parseBool(os.Getenv(ENVConfigVerifySSL), u.Config.VerifySSL) - u.Config.CollectIDS = parseBool(os.Getenv(ENVConfigCollectIDS), u.Config.CollectIDS) - u.Config.Quiet = parseBool(os.Getenv(ENVConfigQuiet), u.Config.Quiet) - u.Config.Debug = parseBool(os.Getenv(ENVConfigDebug), u.Config.Debug) - if e := os.Getenv(ENVConfigInterval); e != "" { - _ = u.Config.Interval.UnmarshalText([]byte(e)) - } - if e := os.Getenv(ENVConfigMaxErrors); e != "" { - u.Config.MaxErrors, _ = strconv.Atoi(e) - } - if e := os.Getenv(ENVConfigSites); e != "" { - u.Config.Sites = strings.Split(e, ",") + t := reflect.TypeOf(u.Config) // whole struct + // Loop each Config struct member and check for a reflect tag / env var setting. + for i := 0; i < t.NumField(); i++ { + tag := t.Field(i).Tag.Get("env") // struct member tag + env := os.Getenv(tag) // value of "tag" env variable + if tag == "" || env == "" { + continue + } + // Reflect and update the u.Config struct member at position i. + switch c := reflect.ValueOf(u.Config).Elem().Field(i); c.Type().String() { + // Handle each member type appropriately (differently). + case "string": + c.SetString(env) + case "int": + val, _ := strconv.Atoi(env) + c.Set(reflect.ValueOf(val)) + case "[]string": + c.Set(reflect.ValueOf(strings.Split(env, ","))) + case "Duration": + val, _ := time.ParseDuration(env) + c.Set(reflect.ValueOf(Duration{val})) + case "bool": + val, _ := strconv.ParseBool(env) + c.SetBool(val) + } } } diff --git a/integrations/promunifi/unifipoller/unifi.go b/integrations/promunifi/unifipoller/unifi.go index 3cf0171f..2ba78b35 100644 --- a/integrations/promunifi/unifipoller/unifi.go +++ b/integrations/promunifi/unifipoller/unifi.go @@ -62,7 +62,8 @@ func (u *UnifiPoller) PollController() error { _ = u.CollectAndReport() } if u.Config.MaxErrors >= 0 && u.errorCount > u.Config.MaxErrors { - return fmt.Errorf("reached maximum error count, stopping poller (%d > %d)", u.errorCount, u.Config.MaxErrors) + return fmt.Errorf("reached maximum error count, stopping poller (%d > %d)", + u.errorCount, u.Config.MaxErrors) } } return nil From 9a2c18d5c8d50d8400837e4c46d003044981fbd7 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 25 Aug 2019 23:31:49 -0700 Subject: [PATCH 2/7] Use actual struct with TypeOf --- integrations/promunifi/unifipoller/start.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/integrations/promunifi/unifipoller/start.go b/integrations/promunifi/unifipoller/start.go index 98316e3e..5e257806 100644 --- a/integrations/promunifi/unifipoller/start.go +++ b/integrations/promunifi/unifipoller/start.go @@ -54,18 +54,19 @@ func (f *Flag) Parse(args []string) { // This is useful for Docker users that find it easier to pass ENV variables // than a specific configuration file. Uses reflection to find struct tags. func (u *UnifiPoller) setEnvVarOptions() { - t := reflect.TypeOf(u.Config) // whole struct - // Loop each Config struct member and check for a reflect tag / env var setting. + t := reflect.TypeOf(Config{}) + // Loop each Config struct member; get reflect tag & env var value; update config. for i := 0; i < t.NumField(); i++ { - tag := t.Field(i).Tag.Get("env") // struct member tag - env := os.Getenv(tag) // value of "tag" env variable - if tag == "" || env == "" { + // Get the ENV variable name from "env" struct tag then pull value from OS. + env := os.Getenv(t.Field(i).Tag.Get("env")) + if env == "" { continue } // Reflect and update the u.Config struct member at position i. switch c := reflect.ValueOf(u.Config).Elem().Field(i); c.Type().String() { // Handle each member type appropriately (differently). case "string": + // This is a reflect package method to update a struct member by index. c.SetString(env) case "int": val, _ := strconv.Atoi(env) From 5e83811c5aafd7c340f85836d704b6303e2994b0 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 25 Aug 2019 23:37:58 -0700 Subject: [PATCH 3/7] fix tests --- integrations/promunifi/.metadata.sh | 2 +- integrations/promunifi/unifipoller/config.go | 4 ++-- integrations/promunifi/unifipoller/helpers.go | 23 ------------------- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/integrations/promunifi/.metadata.sh b/integrations/promunifi/.metadata.sh index 72f614d5..dd683d8c 100755 --- a/integrations/promunifi/.metadata.sh +++ b/integrations/promunifi/.metadata.sh @@ -11,7 +11,7 @@ HBREPO="golift/homebrew-mugs" MAINT="David Newhall II " VENDOR="Go Lift " DESC="Polls a UniFi controller and exports metrics to InfluxDB" -GOLANGCI_LINT_ARGS="--enable-all -D gochecknoglobals -e dupl -e G101" +GOLANGCI_LINT_ARGS="--enable-all -D gochecknoglobals -D dupl -D lll -e G101 " # Example must exist at examples/$CONFIG_FILE.example CONFIG_FILE="up.conf" LICENSE="MIT" diff --git a/integrations/promunifi/unifipoller/config.go b/integrations/promunifi/unifipoller/config.go index ad64a050..008caf7b 100644 --- a/integrations/promunifi/unifipoller/config.go +++ b/integrations/promunifi/unifipoller/config.go @@ -62,8 +62,8 @@ type Config struct { ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate" env:"UP_REAUTHENTICATE"` Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode" env:"UP_POLLING_MODE"` InfluxURL string `json:"influx_url,_omitempty" toml:"influx_url,_omitempty" xml:"influx_url" yaml:"influx_url" env:"UP_INFLUX_URL"` - InfluxUser string `json:"influx_user,_omitempty" toml:"influx_user,_omitempty" xml:"influx_user" yaml:"influx_user" env:"UP_INFLUX_USER"` - InfluxPass string `json:"influx_pass,_omitempty" toml:"influx_pass,_omitempty" xml:"influx_pass" yaml:"influx_pass" env:"UP_INFLUX_PASS"` + InfluxUser string `json:"influx_user,_omitempty" toml:"influx_user,_omitempty" xml:"influx_user" yaml:"influx_user" env:"INFLUX_USER"` + InfluxPass string `json:"influx_pass,_omitempty" toml:"influx_pass,_omitempty" xml:"influx_pass" yaml:"influx_pass" env:"INFLUX_PASS"` InfluxDB string `json:"influx_db,_omitempty" toml:"influx_db,_omitempty" xml:"influx_db" yaml:"influx_db" env:"UP_INFLUX_DB"` UnifiUser string `json:"unifi_user,_omitempty" toml:"unifi_user,_omitempty" xml:"unifi_user" yaml:"unifi_user" env:"UP_UNIFI_USER"` UnifiPass string `json:"unifi_pass,_omitempty" toml:"unifi_pass,_omitempty" xml:"unifi_pass" yaml:"unifi_pass" env:"UP_UNIFI_PASS"` diff --git a/integrations/promunifi/unifipoller/helpers.go b/integrations/promunifi/unifipoller/helpers.go index 5cb2041d..c2e0fce4 100644 --- a/integrations/promunifi/unifipoller/helpers.go +++ b/integrations/promunifi/unifipoller/helpers.go @@ -43,26 +43,3 @@ func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) { func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) { _ = log.Output(2, fmt.Sprintf("[ERROR] "+m, v...)) } - -// pick returns the first non empty string in a list. -// used in a few places around this library. -func pick(strings ...string) string { - for _, s := range strings { - if s != "" { - return s - } - } - return "" -} - -// parseBool returns true/false if the string is "true" or "false", otherwise returns e value. -func parseBool(s string, e bool) bool { - switch s { - case "true", "t": - return true - case "false", "f": - return false - default: - return e - } -} From 685b9b148ea481cc3494b7c9a13d1f1aa14639d0 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 25 Aug 2019 23:53:03 -0700 Subject: [PATCH 4/7] return error on bad parsed env variable --- integrations/promunifi/unifipoller/config.go | 33 +++++++++++--------- integrations/promunifi/unifipoller/start.go | 29 ++++++++++++----- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/integrations/promunifi/unifipoller/config.go b/integrations/promunifi/unifipoller/config.go index 008caf7b..89e7e821 100644 --- a/integrations/promunifi/unifipoller/config.go +++ b/integrations/promunifi/unifipoller/config.go @@ -22,6 +22,10 @@ const ( defaultUnifURL = "https://127.0.0.1:8443" ) +// ENVConfigPrefix is the prefix appended to an env variable tag +// name before retrieving the value from the OS. +const ENVConfigPrefix = "UP_" + // UnifiPoller contains the application startup data, and auth info for UniFi & Influx. type UnifiPoller struct { Influx influx.Client @@ -52,23 +56,24 @@ type Metrics struct { // Config represents the data needed to poll a controller and report to influxdb. // This is all of the data stored in the config file. +// Any with explicit defaults have _omitempty on json and toml tags. type Config struct { - MaxErrors int `json:"max_errors,_omitempty" toml:"max_errors,_omitempty" xml:"max_errors" yaml:"max_errors" env:"UP_MAX_ERRORS"` - Interval Duration `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval" env:"UP_POLLING_INTERVAL"` - Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug" env:"UP_DEBUG_MODE"` - Quiet bool `json:"quiet,_omitempty" toml:"quiet,_omitempty" xml:"quiet" yaml:"quiet" env:"UP_QUIET_MODE"` - VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl" env:"UP_VERIFY_SSL"` - CollectIDS bool `json:"collect_ids" toml:"collect_ids" xml:"collect_ids" yaml:"collect_ids" env:"UP_COLLECT_IDS"` - ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate" env:"UP_REAUTHENTICATE"` - Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode" env:"UP_POLLING_MODE"` - InfluxURL string `json:"influx_url,_omitempty" toml:"influx_url,_omitempty" xml:"influx_url" yaml:"influx_url" env:"UP_INFLUX_URL"` + MaxErrors int `json:"max_errors" toml:"max_errors" xml:"max_errors" yaml:"max_errors" env:"MAX_ERRORS"` + Interval Duration `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval" env:"POLLING_INTERVAL"` + Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug" env:"DEBUG_MODE"` + Quiet bool `json:"quiet,_omitempty" toml:"quiet,_omitempty" xml:"quiet" yaml:"quiet" env:"QUIET_MODE"` + VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl" env:"VERIFY_SSL"` + CollectIDS bool `json:"collect_ids" toml:"collect_ids" xml:"collect_ids" yaml:"collect_ids" env:"COLLECT_IDS"` + ReAuth bool `json:"reauthenticate" toml:"reauthenticate" xml:"reauthenticate" yaml:"reauthenticate" env:"REAUTHENTICATE"` + Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode" env:"POLLING_MODE"` + InfluxURL string `json:"influx_url,_omitempty" toml:"influx_url,_omitempty" xml:"influx_url" yaml:"influx_url" env:"INFLUX_URL"` InfluxUser string `json:"influx_user,_omitempty" toml:"influx_user,_omitempty" xml:"influx_user" yaml:"influx_user" env:"INFLUX_USER"` InfluxPass string `json:"influx_pass,_omitempty" toml:"influx_pass,_omitempty" xml:"influx_pass" yaml:"influx_pass" env:"INFLUX_PASS"` - InfluxDB string `json:"influx_db,_omitempty" toml:"influx_db,_omitempty" xml:"influx_db" yaml:"influx_db" env:"UP_INFLUX_DB"` - UnifiUser string `json:"unifi_user,_omitempty" toml:"unifi_user,_omitempty" xml:"unifi_user" yaml:"unifi_user" env:"UP_UNIFI_USER"` - UnifiPass string `json:"unifi_pass,_omitempty" toml:"unifi_pass,_omitempty" xml:"unifi_pass" yaml:"unifi_pass" env:"UP_UNIFI_PASS"` - UnifiBase string `json:"unifi_url,_omitempty" toml:"unifi_url,_omitempty" xml:"unifi_url" yaml:"unifi_url" env:"UP_UNIFI_URL"` - Sites []string `json:"sites,_omitempty" toml:"sites,_omitempty" xml:"sites" yaml:"sites" env:"UP_POLL_SITES"` + InfluxDB string `json:"influx_db,_omitempty" toml:"influx_db,_omitempty" xml:"influx_db" yaml:"influx_db" env:"INFLUX_DB"` + UnifiUser string `json:"unifi_user,_omitempty" toml:"unifi_user,_omitempty" xml:"unifi_user" yaml:"unifi_user" env:"UNIFI_USER"` + UnifiPass string `json:"unifi_pass,_omitempty" toml:"unifi_pass,_omitempty" xml:"unifi_pass" yaml:"unifi_pass" env:"UNIFI_PASS"` + UnifiBase string `json:"unifi_url,_omitempty" toml:"unifi_url,_omitempty" xml:"unifi_url" yaml:"unifi_url" env:"UNIFI_URL"` + Sites []string `json:"sites,_omitempty" toml:"sites,_omitempty" xml:"sites" yaml:"sites" env:"POLL_SITES"` } // Duration is used to UnmarshalTOML into a time.Duration value. diff --git a/integrations/promunifi/unifipoller/start.go b/integrations/promunifi/unifipoller/start.go index 5e257806..f1c2e49c 100644 --- a/integrations/promunifi/unifipoller/start.go +++ b/integrations/promunifi/unifipoller/start.go @@ -29,10 +29,15 @@ func Start() error { fmt.Printf("unifi-poller v%s\n", Version) return nil // don't run anything else w/ version request. } + // Parse config file. if err := up.GetConfig(); err != nil { up.Flag.Usage() return err } + // Update Config with ENV variable overrides. + if err := up.setEnvVarOptions(); err != nil { + return err + } return up.Run() } @@ -53,13 +58,14 @@ func (f *Flag) Parse(args []string) { // setEnvVarOptions copies environment variables into configuration values. // This is useful for Docker users that find it easier to pass ENV variables // than a specific configuration file. Uses reflection to find struct tags. -func (u *UnifiPoller) setEnvVarOptions() { +func (u *UnifiPoller) setEnvVarOptions() error { t := reflect.TypeOf(Config{}) // Loop each Config struct member; get reflect tag & env var value; update config. for i := 0; i < t.NumField(); i++ { // Get the ENV variable name from "env" struct tag then pull value from OS. - env := os.Getenv(t.Field(i).Tag.Get("env")) - if env == "" { + tag := t.Field(i).Tag.Get("env") + env := os.Getenv(ENVConfigPrefix + tag) + if tag == "" || env == "" { continue } // Reflect and update the u.Config struct member at position i. @@ -69,18 +75,28 @@ func (u *UnifiPoller) setEnvVarOptions() { // This is a reflect package method to update a struct member by index. c.SetString(env) case "int": - val, _ := strconv.Atoi(env) + val, err := strconv.Atoi(env) + if err != nil { + return err + } c.Set(reflect.ValueOf(val)) case "[]string": c.Set(reflect.ValueOf(strings.Split(env, ","))) case "Duration": - val, _ := time.ParseDuration(env) + val, err := time.ParseDuration(env) + if err != nil { + return err + } c.Set(reflect.ValueOf(Duration{val})) case "bool": - val, _ := strconv.ParseBool(env) + val, err := strconv.ParseBool(env) + if err != nil { + return err + } c.SetBool(val) } } + return nil } // GetConfig parses and returns our configuration data. @@ -99,7 +115,6 @@ func (u *UnifiPoller) GetConfig() error { Quiet: u.Flag.DumpJSON != "", //s uppress the following u.Logf line. } u.Logf("Loading Configuration File: %s", u.Flag.ConfigFile) - defer u.setEnvVarOptions() // Set env variable overrides when done here. switch buf, err := ioutil.ReadFile(u.Flag.ConfigFile); { case err != nil: return err From 779dff04bc323f70982879b3f79c32394f848bb8 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 26 Aug 2019 00:13:03 -0700 Subject: [PATCH 5/7] fix Duration types --- integrations/promunifi/unifipoller/start.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/integrations/promunifi/unifipoller/start.go b/integrations/promunifi/unifipoller/start.go index f1c2e49c..6b811719 100644 --- a/integrations/promunifi/unifipoller/start.go +++ b/integrations/promunifi/unifipoller/start.go @@ -35,7 +35,7 @@ func Start() error { return err } // Update Config with ENV variable overrides. - if err := up.setEnvVarOptions(); err != nil { + if err := up.ENVSetConfig(); err != nil { return err } return up.Run() @@ -49,16 +49,16 @@ func (f *Flag) Parse(args []string) { f.PrintDefaults() } f.StringVarP(&f.DumpJSON, "dumpjson", "j", "", - "This debug option prints a json payload and exits. See man page for more.") - f.StringVarP(&f.ConfigFile, "config", "c", DefaultConfFile, "Poller Config File (TOML Format)") - f.BoolVarP(&f.ShowVer, "version", "v", false, "Print the version and exit") + "This debug option prints a json payload and exits. See man page for more info.") + f.StringVarP(&f.ConfigFile, "config", "c", DefaultConfFile, "Poller config file path.") + f.BoolVarP(&f.ShowVer, "version", "v", false, "Print the version and exit.") _ = f.FlagSet.Parse(args) } -// setEnvVarOptions copies environment variables into configuration values. +// ENVSetConfig copies environment variables into configuration values. // This is useful for Docker users that find it easier to pass ENV variables // than a specific configuration file. Uses reflection to find struct tags. -func (u *UnifiPoller) setEnvVarOptions() error { +func (u *UnifiPoller) ENVSetConfig() error { t := reflect.TypeOf(Config{}) // Loop each Config struct member; get reflect tag & env var value; update config. for i := 0; i < t.NumField(); i++ { @@ -68,6 +68,7 @@ func (u *UnifiPoller) setEnvVarOptions() error { if tag == "" || env == "" { continue } + // Reflect and update the u.Config struct member at position i. switch c := reflect.ValueOf(u.Config).Elem().Field(i); c.Type().String() { // Handle each member type appropriately (differently). @@ -77,21 +78,21 @@ func (u *UnifiPoller) setEnvVarOptions() error { case "int": val, err := strconv.Atoi(env) if err != nil { - return err + return fmt.Errorf("%s: %v", tag, err) } c.Set(reflect.ValueOf(val)) case "[]string": c.Set(reflect.ValueOf(strings.Split(env, ","))) - case "Duration": + case "unifipoller.Duration": val, err := time.ParseDuration(env) if err != nil { - return err + return fmt.Errorf("%s: %v", tag, err) } c.Set(reflect.ValueOf(Duration{val})) case "bool": val, err := strconv.ParseBool(env) if err != nil { - return err + return fmt.Errorf("%s: %v", tag, err) } c.SetBool(val) } From 6472ff4307d355e95adad5a8c72b7f0bd14166ff Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 26 Aug 2019 00:24:25 -0700 Subject: [PATCH 6/7] get local types dynamically --- integrations/promunifi/unifipoller/start.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/integrations/promunifi/unifipoller/start.go b/integrations/promunifi/unifipoller/start.go index 6b811719..6dc45e11 100644 --- a/integrations/promunifi/unifipoller/start.go +++ b/integrations/promunifi/unifipoller/start.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "log" "os" + "path" "reflect" "strconv" "strings" @@ -59,14 +60,13 @@ func (f *Flag) Parse(args []string) { // This is useful for Docker users that find it easier to pass ENV variables // than a specific configuration file. Uses reflection to find struct tags. func (u *UnifiPoller) ENVSetConfig() error { - t := reflect.TypeOf(Config{}) + t := reflect.TypeOf(Config{}) // Get tag names from the Config struct. // Loop each Config struct member; get reflect tag & env var value; update config. for i := 0; i < t.NumField(); i++ { - // Get the ENV variable name from "env" struct tag then pull value from OS. - tag := t.Field(i).Tag.Get("env") - env := os.Getenv(ENVConfigPrefix + tag) + tag := t.Field(i).Tag.Get("env") // Get the ENV variable name from "env" struct tag + env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS. if tag == "" || env == "" { - continue + continue // Skip if either are empty. } // Reflect and update the u.Config struct member at position i. @@ -83,7 +83,7 @@ func (u *UnifiPoller) ENVSetConfig() error { c.Set(reflect.ValueOf(val)) case "[]string": c.Set(reflect.ValueOf(strings.Split(env, ","))) - case "unifipoller.Duration": + case path.Base(t.PkgPath()) + ".Duration": val, err := time.ParseDuration(env) if err != nil { return fmt.Errorf("%s: %v", tag, err) From 860e4f83cb8b3565d3f7ce601a6a2b118ea334c3 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 26 Aug 2019 01:06:35 -0700 Subject: [PATCH 7/7] move things --- integrations/promunifi/unifipoller/config.go | 71 ++++++++++++ integrations/promunifi/unifipoller/start.go | 108 ++++--------------- 2 files changed, 90 insertions(+), 89 deletions(-) diff --git a/integrations/promunifi/unifipoller/config.go b/integrations/promunifi/unifipoller/config.go index 89e7e821..09d20b83 100644 --- a/integrations/promunifi/unifipoller/config.go +++ b/integrations/promunifi/unifipoller/config.go @@ -1,11 +1,22 @@ package unifipoller import ( + "encoding/json" + "encoding/xml" + "fmt" + "io/ioutil" + "os" + "path" + "reflect" + "strconv" + "strings" "time" + "github.com/BurntSushi/toml" influx "github.com/influxdata/influxdb1-client/v2" "github.com/spf13/pflag" "golift.io/unifi" + "gopkg.in/yaml.v2" ) // Version is injected by the Makefile @@ -84,3 +95,63 @@ func (d *Duration) UnmarshalText(data []byte) (err error) { d.Duration, err = time.ParseDuration(string(data)) return } + +// ParseFile parses and returns our configuration data. +func (c *Config) ParseFile(configFile string) error { + switch buf, err := ioutil.ReadFile(configFile); { + case err != nil: + return err + case strings.Contains(configFile, ".json"): + return json.Unmarshal(buf, c) + case strings.Contains(configFile, ".xml"): + return xml.Unmarshal(buf, c) + case strings.Contains(configFile, ".yaml"): + return yaml.Unmarshal(buf, c) + default: + return toml.Unmarshal(buf, c) + } +} + +// ParseENV copies environment variables into configuration values. +// This is useful for Docker users that find it easier to pass ENV variables +// than a specific configuration file. Uses reflection to find struct tags. +func (c *Config) ParseENV() error { + t := reflect.TypeOf(Config{}) // Get tag names from the Config struct. + // Loop each Config struct member; get reflect tag & env var value; update config. + for i := 0; i < t.NumField(); i++ { + tag := t.Field(i).Tag.Get("env") // Get the ENV variable name from "env" struct tag + env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS. + if tag == "" || env == "" { + continue // Skip if either are empty. + } + + // Reflect and update the u.Config struct member at position i. + switch c := reflect.ValueOf(c).Elem().Field(i); c.Type().String() { + // Handle each member type appropriately (differently). + case "string": + // This is a reflect package method to update a struct member by index. + c.SetString(env) + case "int": + val, err := strconv.Atoi(env) + if err != nil { + return fmt.Errorf("%s: %v", tag, err) + } + c.Set(reflect.ValueOf(val)) + case "[]string": + c.Set(reflect.ValueOf(strings.Split(env, ","))) + case path.Base(t.PkgPath()) + ".Duration": + val, err := time.ParseDuration(env) + if err != nil { + return fmt.Errorf("%s: %v", tag, err) + } + c.Set(reflect.ValueOf(Duration{val})) + case "bool": + val, err := strconv.ParseBool(env) + if err != nil { + return fmt.Errorf("%s: %v", tag, err) + } + c.SetBool(val) + } + } + return nil +} diff --git a/integrations/promunifi/unifipoller/start.go b/integrations/promunifi/unifipoller/start.go index 6dc45e11..dbd2b44e 100644 --- a/integrations/promunifi/unifipoller/start.go +++ b/integrations/promunifi/unifipoller/start.go @@ -1,42 +1,49 @@ package unifipoller import ( - "encoding/json" - "encoding/xml" "fmt" - "io/ioutil" "log" "os" - "path" - "reflect" - "strconv" "strings" "time" - "github.com/BurntSushi/toml" influx "github.com/influxdata/influxdb1-client/v2" "github.com/spf13/pflag" "golift.io/unifi" - "gopkg.in/yaml.v2" ) // Start begins the application from a CLI. // Parses flags, parses config and executes Run(). func Start() error { log.SetFlags(log.LstdFlags) - up := &UnifiPoller{Flag: &Flag{}} + up := &UnifiPoller{Flag: &Flag{}, + Config: &Config{ + // Preload our defaults. + InfluxURL: defaultInfxURL, + InfluxUser: defaultInfxUser, + InfluxPass: defaultInfxPass, + InfluxDB: defaultInfxDb, + UnifiUser: defaultUnifUser, + UnifiPass: os.Getenv("UNIFI_PASSWORD"), // deprecated name. + UnifiBase: defaultUnifURL, + Interval: Duration{defaultInterval}, + Sites: []string{"all"}, + }} up.Flag.Parse(os.Args[1:]) if up.Flag.ShowVer { fmt.Printf("unifi-poller v%s\n", Version) return nil // don't run anything else w/ version request. } + if up.Flag.DumpJSON == "" { // do not print this when dumping JSON. + up.Logf("Loading Configuration File: %s", up.Flag.ConfigFile) + } // Parse config file. - if err := up.GetConfig(); err != nil { + if err := up.Config.ParseFile(up.Flag.ConfigFile); err != nil { up.Flag.Usage() return err } // Update Config with ENV variable overrides. - if err := up.ENVSetConfig(); err != nil { + if err := up.Config.ParseENV(); err != nil { return err } return up.Run() @@ -56,80 +63,6 @@ func (f *Flag) Parse(args []string) { _ = f.FlagSet.Parse(args) } -// ENVSetConfig copies environment variables into configuration values. -// This is useful for Docker users that find it easier to pass ENV variables -// than a specific configuration file. Uses reflection to find struct tags. -func (u *UnifiPoller) ENVSetConfig() error { - t := reflect.TypeOf(Config{}) // Get tag names from the Config struct. - // Loop each Config struct member; get reflect tag & env var value; update config. - for i := 0; i < t.NumField(); i++ { - tag := t.Field(i).Tag.Get("env") // Get the ENV variable name from "env" struct tag - env := os.Getenv(ENVConfigPrefix + tag) // Then pull value from OS. - if tag == "" || env == "" { - continue // Skip if either are empty. - } - - // Reflect and update the u.Config struct member at position i. - switch c := reflect.ValueOf(u.Config).Elem().Field(i); c.Type().String() { - // Handle each member type appropriately (differently). - case "string": - // This is a reflect package method to update a struct member by index. - c.SetString(env) - case "int": - val, err := strconv.Atoi(env) - if err != nil { - return fmt.Errorf("%s: %v", tag, err) - } - c.Set(reflect.ValueOf(val)) - case "[]string": - c.Set(reflect.ValueOf(strings.Split(env, ","))) - case path.Base(t.PkgPath()) + ".Duration": - val, err := time.ParseDuration(env) - if err != nil { - return fmt.Errorf("%s: %v", tag, err) - } - c.Set(reflect.ValueOf(Duration{val})) - case "bool": - val, err := strconv.ParseBool(env) - if err != nil { - return fmt.Errorf("%s: %v", tag, err) - } - c.SetBool(val) - } - } - return nil -} - -// GetConfig parses and returns our configuration data. -func (u *UnifiPoller) GetConfig() error { - // Preload our defaults. - u.Config = &Config{ - InfluxURL: defaultInfxURL, - InfluxUser: defaultInfxUser, - InfluxPass: defaultInfxPass, - InfluxDB: defaultInfxDb, - UnifiUser: defaultUnifUser, - UnifiPass: os.Getenv("UNIFI_PASSWORD"), // deprecated name. - UnifiBase: defaultUnifURL, - Interval: Duration{defaultInterval}, - Sites: []string{"default"}, - Quiet: u.Flag.DumpJSON != "", //s uppress the following u.Logf line. - } - u.Logf("Loading Configuration File: %s", u.Flag.ConfigFile) - switch buf, err := ioutil.ReadFile(u.Flag.ConfigFile); { - case err != nil: - return err - case strings.Contains(u.Flag.ConfigFile, ".json"): - return json.Unmarshal(buf, u.Config) - case strings.Contains(u.Flag.ConfigFile, ".xml"): - return xml.Unmarshal(buf, u.Config) - case strings.Contains(u.Flag.ConfigFile, ".yaml"): - return yaml.Unmarshal(buf, u.Config) - default: - return toml.Unmarshal(buf, u.Config) - } -} - // Run invokes all the application logic and routines. func (u *UnifiPoller) Run() (err error) { if u.Flag.DumpJSON != "" { @@ -186,8 +119,5 @@ func (u *UnifiPoller) GetUnifi() (err error) { if err != nil { return fmt.Errorf("unifi controller: %v", err) } - if err := u.CheckSites(); err != nil { - return err - } - return nil + return u.CheckSites() }