diff --git a/pkg/datadogunifi/datadog.go b/pkg/datadogunifi/datadog.go index e9811f7e..924a0d09 100644 --- a/pkg/datadogunifi/datadog.go +++ b/pkg/datadogunifi/datadog.go @@ -3,6 +3,7 @@ package datadogunifi import ( + "fmt" "reflect" "time" @@ -203,6 +204,22 @@ func (u *DatadogUnifi) Enabled() bool { return *u.Enable } +func (u *DatadogUnifi) DebugOutput(l poller.Logger) (bool, error) { + if u == nil { + return true, nil + } + if !u.Enabled() { + return true, nil + } + u.setConfigDefaults() + var err error + u.datadog, err = statsd.New(u.Address, u.options...) + if err != nil { + return false, fmt.Errorf("Error configuration Datadog agent reporting: %+v", err) + } + return true, nil +} + // Run runs a ticker to poll the unifi server and update Datadog. func (u *DatadogUnifi) Run(c poller.Collect) error { u.Collector = c diff --git a/pkg/influxunifi/influxdb.go b/pkg/influxunifi/influxdb.go index 9da3ab61..b0459438 100644 --- a/pkg/influxunifi/influxdb.go +++ b/pkg/influxunifi/influxdb.go @@ -16,6 +16,7 @@ import ( "github.com/unpoller/unifi" "github.com/unpoller/unpoller/pkg/poller" "github.com/unpoller/unpoller/pkg/webserver" + "golang.org/x/net/context" "golift.io/cnfg" ) @@ -142,6 +143,51 @@ func (u *InfluxUnifi) Enabled() bool { return !u.Disable } +func (u *InfluxUnifi) DebugOutput(l poller.Logger) (bool, error) { + if u == nil { + return true, nil + } + if !u.Enabled() { + return true, nil + } + u.setConfigDefaults() + _, err := url.Parse(u.Config.URL) + if err != nil { + return false, fmt.Errorf("invalid influx URL: %v", err) + } + + if u.IsVersion2 { + // we're a version 2 + tlsConfig := &tls.Config{InsecureSkipVerify: !u.VerifySSL} // nolint: gosec + serverOptions := influx.DefaultOptions().SetTLSConfig(tlsConfig).SetBatchSize(u.BatchSize) + u.influxV2 = influx.NewClientWithOptions(u.URL, u.AuthToken, serverOptions) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + ok, err := u.influxV2.Ping(ctx) + if err != nil { + return false, err + } + if !ok { + return false, fmt.Errorf("unsuccessful ping to influxdb2") + } + } else { + u.influxV1, err = influxV1.NewHTTPClient(influxV1.HTTPConfig{ + Addr: u.URL, + Username: u.User, + Password: u.Pass, + TLSConfig: &tls.Config{InsecureSkipVerify: !u.VerifySSL}, // nolint: gosec + }) + if err != nil { + return false, fmt.Errorf("making client: %w", err) + } + _, _, err = u.influxV1.Ping(time.Second * 2) + if err != nil { + return false, fmt.Errorf("unsuccessful ping to influxdb1") + } + } + return true, nil +} + // Run runs a ticker to poll the unifi server and update influxdb. func (u *InfluxUnifi) Run(c poller.Collect) error { u.Collector = c diff --git a/pkg/inputunifi/interface.go b/pkg/inputunifi/interface.go index 337814c7..1833bb48 100644 --- a/pkg/inputunifi/interface.go +++ b/pkg/inputunifi/interface.go @@ -57,6 +57,47 @@ func (u *InputUnifi) Initialize(l poller.Logger) error { return nil } +func (u *InputUnifi) DebugInputs(l poller.Logger) (bool, error) { + if u == nil || u.Config == nil { + return true, nil + } + if u.setDefaults(&u.Default); len(u.Controllers) == 0 && !u.Dynamic { + u.Controllers = []*Controller{&u.Default} + } + + if len(u.Controllers) == 0 { + u.Logf("No controllers configured. Polling dynamic controllers only! Defaults:") + u.logController(&u.Default) + } + + ok := true + var allErrors error + for i, c := range u.Controllers { + if err := u.getUnifi(u.setControllerDefaults(c)); err != nil { + u.LogErrorf("Controller %d of %d Auth or Connection Error, retrying: %v", i+1, len(u.Controllers), err) + ok = false + if allErrors != nil { + err = fmt.Errorf("%v: %w", err, allErrors) + } + continue + } + + if err := u.checkSites(c); err != nil { + u.LogErrorf("checking sites on %s: %v", c.URL, err) + ok = false + if allErrors != nil { + err = fmt.Errorf("%v: %w", err, allErrors) + } + continue + } + + u.Logf("Valid UniFi Controller %d of %d:", i+1, len(u.Controllers)) + u.logController(c) + } + + return ok, allErrors +} + func (u *InputUnifi) logController(c *Controller) { u.Logf(" => URL: %s (verify SSL: %v)", c.URL, *c.VerifySSL) diff --git a/pkg/lokiunifi/logger.go b/pkg/lokiunifi/logger.go index ff8f554e..294ea25a 100644 --- a/pkg/lokiunifi/logger.go +++ b/pkg/lokiunifi/logger.go @@ -14,7 +14,9 @@ func (l *Loki) Logf(msg string, v ...any) { Msg: fmt.Sprintf(msg, v...), Tags: map[string]string{"type": "info"}, }) - l.Collect.Logf(msg, v...) + if l.Collect != nil { + l.Collect.Logf(msg, v...) + } } // LogErrorf logs an error message. @@ -24,7 +26,9 @@ func (l *Loki) LogErrorf(msg string, v ...any) { Msg: fmt.Sprintf(msg, v...), Tags: map[string]string{"type": "error"}, }) - l.Collect.LogErrorf(msg, v...) + if l.Collect != nil { + l.Collect.LogErrorf(msg, v...) + } } // LogDebugf logs a debug message. @@ -34,5 +38,7 @@ func (l *Loki) LogDebugf(msg string, v ...any) { Msg: fmt.Sprintf(msg, v...), Tags: map[string]string{"type": "debug"}, }) - l.Collect.LogDebugf(msg, v...) + if l.Collect != nil { + l.Collect.LogDebugf(msg, v...) + } } diff --git a/pkg/lokiunifi/loki.go b/pkg/lokiunifi/loki.go index d02685e4..669a3711 100644 --- a/pkg/lokiunifi/loki.go +++ b/pkg/lokiunifi/loki.go @@ -76,6 +76,19 @@ func (l *Loki) Enabled() bool { return !l.Disable } +func (l *Loki) DebugOutput(logger poller.Logger) (bool, error) { + if l == nil { + return true, nil + } + if !l.Enabled() { + return true, nil + } + if err := l.ValidateConfig(); err != nil { + return false, err + } + return true, nil +} + // Run is fired from the poller library after the Config is unmarshalled. func (l *Loki) Run(collect poller.Collect) error { l.Collect = collect @@ -85,7 +98,10 @@ func (l *Loki) Run(collect poller.Collect) error { } l.Logf("Loki enabled") - l.ValidateConfig() + if err := l.ValidateConfig(); err != nil { + l.LogErrorf("invalid loki config") + return err + } fake := *l.Config fake.Password = strconv.FormatBool(fake.Password != "") @@ -99,7 +115,7 @@ func (l *Loki) Run(collect poller.Collect) error { // ValidateConfig sets initial "last" update time. Also creates an http client, // makes sure URL is sane, and sets interval within min/max limits. -func (l *Loki) ValidateConfig() { +func (l *Loki) ValidateConfig() error { if l.Interval.Duration > maxInterval { l.Interval.Duration = maxInterval } else if l.Interval.Duration < minInterval { @@ -110,6 +126,7 @@ func (l *Loki) ValidateConfig() { pass, err := os.ReadFile(strings.TrimPrefix(l.Password, "file://")) if err != nil { l.LogErrorf("Reading Loki Password File: %v", err) + return fmt.Errorf("error reading password file") } l.Password = strings.TrimSpace(string(pass)) @@ -118,6 +135,7 @@ func (l *Loki) ValidateConfig() { l.last = time.Now().Add(-l.Interval.Duration) l.client = l.httpClient() l.URL = strings.TrimRight(l.URL, "/") // gets a path appended to it later. + return nil } // PollController runs forever, polling UniFi for events and pushing them to Loki. diff --git a/pkg/lokiunifi/report_ids.go b/pkg/lokiunifi/report_ids.go index 5c2f4750..4629d253 100644 --- a/pkg/lokiunifi/report_ids.go +++ b/pkg/lokiunifi/report_ids.go @@ -15,7 +15,7 @@ func (r *Report) IDS(event *unifi.IDS, logs *Logs) { } r.Counts[typeIDS]++ // increase counter and append new log line. - + logs.Streams = append(logs.Streams, LogStream{ Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), event.Msg}}, Labels: CleanLabels(map[string]string{ diff --git a/pkg/mysqlunifi/main.go b/pkg/mysqlunifi/main.go index 7fbb2d76..74ce80a3 100644 --- a/pkg/mysqlunifi/main.go +++ b/pkg/mysqlunifi/main.go @@ -69,6 +69,19 @@ func (p *plugin) Enabled() bool { return !p.Disable } +func (p *plugin) DebugOutput(l poller.Logger) (bool, error) { + if p == nil { + return true, nil + } + if !p.Enabled() { + return true, nil + } + if err := p.validateConfig(); err != nil { + return false, err + } + return true, nil +} + // Run gets called by poller core code. Return when the plugin stops working or has an error. // In other words, don't run your code in a go routine, it already is. func (p *plugin) Run(c poller.Collect) error { diff --git a/pkg/poller/inputs.go b/pkg/poller/inputs.go index 1bb8bced..fea5a125 100644 --- a/pkg/poller/inputs.go +++ b/pkg/poller/inputs.go @@ -21,6 +21,7 @@ type Input interface { Metrics(*Filter) (*Metrics, error) // Called every time new metrics are requested. Events(*Filter) (*Events, error) // This is new. RawMetrics(*Filter) ([]byte, error) + DebugInputs(Logger) (bool, error) } // InputPlugin describes an input plugin's consumable interface. diff --git a/pkg/poller/outputs.go b/pkg/poller/outputs.go index 7785de7e..183b66e4 100644 --- a/pkg/poller/outputs.go +++ b/pkg/poller/outputs.go @@ -27,6 +27,7 @@ type Collect interface { type OutputPlugin interface { Run(Collect) error Enabled() bool + DebugOutput(Logger) (bool, error) } // Output defines the output data for a metric exporter like influx or prometheus. diff --git a/pkg/promunifi/collector.go b/pkg/promunifi/collector.go index e9414692..cfd070dc 100644 --- a/pkg/promunifi/collector.go +++ b/pkg/promunifi/collector.go @@ -3,7 +3,9 @@ package promunifi import ( "fmt" + "net" "net/http" + "net/url" "reflect" "strings" "sync" @@ -116,6 +118,29 @@ func init() { // nolint: gochecknoinits }) } +func (u *promUnifi) DebugOutput(l poller.Logger) (bool, error) { + if u == nil { + return true, nil + } + if !u.Enabled() { + return true, nil + } + if u.HTTPListen == "" { + return false, fmt.Errorf("invalid listen string") + } + // check the port + httpListenURL, err := url.Parse(u.HTTPListen) + if err != nil { + return false, err + } + ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", httpListenURL.Host, httpListenURL.Port())) + if err != nil { + return false, err + } + _ = ln.Close() + return false, nil +} + func (u *promUnifi) Enabled() bool { if u == nil { return false diff --git a/pkg/webserver/server.go b/pkg/webserver/server.go index dae24323..9d3be346 100644 --- a/pkg/webserver/server.go +++ b/pkg/webserver/server.go @@ -4,6 +4,7 @@ package webserver import ( "errors" "fmt" + "net" "net/http" "os" "path/filepath" @@ -94,6 +95,32 @@ func (s *Server) Run(c poller.Collect) error { return s.Start() } +func (s *Server) DebugOutput(l poller.Logger) (bool, error) { + if s == nil { + return true, nil + } + if !s.Enabled() { + return true, nil + } + if s.HTMLPath == "" { + return true, nil + } + + // check the html path + if _, err := os.Stat(s.HTMLPath); err != nil { + return false, fmt.Errorf("problem with HTML path: %w", err) + } + + // check the port + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port)) + if err != nil { + return false, err + } + _ = ln.Close() + + return true, nil +} + // Start gets the web server going. func (s *Server) Start() (err error) { s.server = &http.Server{