diff --git a/Gopkg.lock b/Gopkg.lock
index 83212a0b..aa24b10e 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -107,11 +107,11 @@
[[projects]]
branch = "master"
- digest = "1:d54a8d89f95a4d2a5a24ce63cb1835ccdff337fde7776c87ceacb6fdbe4349ae"
+ digest = "1:7a90fad47972b5ae06013d4685eb2f3007e7c92609a1399d2adf59fe04cd9b63"
name = "golift.io/config"
packages = ["."]
pruneopts = "UT"
- revision = "fd8ffb02173aad2183e5555a03b1d1f909aca930"
+ revision = "fe642c8392dc00d72ddcc47f05a06096bd5d054b"
[[projects]]
digest = "1:2883cea734f2766f41ff9c9d4aefccccc53e3d44f5c8b08893b9c218cf666722"
diff --git a/examples/up.xml.example b/examples/up.xml.example
index ec98169d..14b2aa09 100644
--- a/examples/up.xml.example
+++ b/examples/up.xml.example
@@ -24,7 +24,7 @@
false
-
+
all
diff --git a/examples/up.yaml.example b/examples/up.yaml.example
index 2be57012..bb8a4aa1 100644
--- a/examples/up.yaml.example
+++ b/examples/up.yaml.example
@@ -24,6 +24,7 @@ influxdb:
verify_ssl: false
unifi:
+ disable: false
controllers:
- name: ""
user: "influx"
diff --git a/pkg/influxunifi/influxdb.go b/pkg/influxunifi/influxdb.go
index 199ea594..9bbe9c84 100644
--- a/pkg/influxunifi/influxdb.go
+++ b/pkg/influxunifi/influxdb.go
@@ -24,7 +24,7 @@ const (
// Config defines the data needed to store metrics in InfluxDB
type Config struct {
Interval config.Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"`
- Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
+ Disable bool `json:"disable" toml:"disable" xml:"disable,attr" yaml:"disable"`
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
URL string `json:"url,omitempty" toml:"url,omitempty" xml:"url" yaml:"url"`
User string `json:"user,omitempty" toml:"user,omitempty" xml:"user" yaml:"user"`
diff --git a/pkg/inputunifi/collector.go b/pkg/inputunifi/collector.go
index 20aeafac..c9c67dcb 100644
--- a/pkg/inputunifi/collector.go
+++ b/pkg/inputunifi/collector.go
@@ -8,14 +8,14 @@ import (
"golift.io/unifi"
)
-func (u *InputUnifi) isNill(c Controller) bool {
+func (u *InputUnifi) isNill(c *Controller) bool {
u.Config.RLock()
defer u.Config.RUnlock()
return c.Unifi == nil
}
-func (u *InputUnifi) collectController(c Controller) (*poller.Metrics, error) {
+func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) {
if u.isNill(c) {
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
@@ -32,7 +32,7 @@ func (u *InputUnifi) collectController(c Controller) (*poller.Metrics, error) {
return u.pollController(c)
}
-func (u *InputUnifi) pollController(c Controller) (*poller.Metrics, error) {
+func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) {
var err error
u.Config.RLock()
@@ -67,7 +67,7 @@ func (u *InputUnifi) pollController(c Controller) (*poller.Metrics, error) {
// augmentMetrics is our middleware layer between collecting metrics and writing them.
// This is where we can manipuate the returned data or make arbitrary decisions.
// This function currently adds parent device names to client metrics.
-func (u *InputUnifi) augmentMetrics(c Controller, metrics *poller.Metrics) *poller.Metrics {
+func (u *InputUnifi) augmentMetrics(c *Controller, metrics *poller.Metrics) *poller.Metrics {
if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
return metrics
}
@@ -113,22 +113,22 @@ func (u *InputUnifi) augmentMetrics(c Controller, metrics *poller.Metrics) *poll
// getFilteredSites returns a list of sites to fetch data for.
// Omits requested but unconfigured sites. Grabs the full list from the
// controller and returns the sites provided in the config file.
-func (u *InputUnifi) getFilteredSites(c Controller) (unifi.Sites, error) {
+func (u *InputUnifi) getFilteredSites(c *Controller) (unifi.Sites, error) {
u.Config.RLock()
defer u.Config.RUnlock()
sites, err := c.Unifi.GetSites()
if err != nil {
return nil, err
- } else if len(c.Sites) < 1 || poller.StringInSlice("all", c.Sites) {
+ } else if len(c.Sites) < 1 || StringInSlice("all", c.Sites) {
return sites, nil
}
- var i int
+ i := 0
for _, s := range sites {
// Only include valid sites in the request filter.
- if poller.StringInSlice(s.Name, c.Sites) {
+ if StringInSlice(s.Name, c.Sites) {
sites[i] = s
i++
}
diff --git a/pkg/inputunifi/input.go b/pkg/inputunifi/input.go
index 15a027dd..9a4877f9 100644
--- a/pkg/inputunifi/input.go
+++ b/pkg/inputunifi/input.go
@@ -4,6 +4,8 @@ package inputunifi
import (
"fmt"
+ "os"
+ "strings"
"sync"
@@ -13,7 +15,7 @@ import (
// InputUnifi contains the running data.
type InputUnifi struct {
- Config Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"`
+ Config *Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"`
poller.Logger
}
@@ -33,9 +35,9 @@ type Controller struct {
// Config contains our configuration data
type Config struct {
- sync.RWMutex // locks the Unifi struct member when re-authing to unifi.
- Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
- Controllers []Controller `json:"controllers" toml:"controller" xml:"controller" yaml:"controllers"`
+ sync.RWMutex // locks the Unifi struct member when re-authing to unifi.
+ Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
+ Controllers []*Controller `json:"controllers" toml:"controller" xml:"controller" yaml:"controllers"`
}
func init() {
@@ -48,7 +50,7 @@ func init() {
}
// getUnifi (re-)authenticates to a unifi controller.
-func (u *InputUnifi) getUnifi(c Controller) error {
+func (u *InputUnifi) getUnifi(c *Controller) error {
var err error
u.Config.Lock()
@@ -76,3 +78,74 @@ func (u *InputUnifi) getUnifi(c Controller) error {
return nil
}
+
+// checkSites makes sure the list of provided sites exists on the controller.
+// This only runs once during initialization.
+func (u *InputUnifi) checkSites(c *Controller) error {
+ u.Config.RLock()
+ defer u.Config.RUnlock()
+
+ if len(c.Sites) < 1 || c.Sites[0] == "" {
+ c.Sites = []string{"all"}
+ }
+
+ u.LogDebugf("Checking Controller Sites List")
+
+ sites, err := c.Unifi.GetSites()
+ if err != nil {
+ return err
+ }
+
+ msg := []string{}
+ for _, site := range sites {
+ msg = append(msg, site.Name+" ("+site.Desc+")")
+ }
+
+ u.Logf("Found %d site(s) on controller %s: %v", len(msg), c.Name, strings.Join(msg, ", "))
+
+ if StringInSlice("all", c.Sites) {
+ c.Sites = []string{"all"}
+ return nil
+ }
+
+FIRST:
+ for _, s := range c.Sites {
+ for _, site := range sites {
+ if s == site.Name {
+ continue FIRST
+ }
+ }
+ return fmt.Errorf("configured site not found on controller: %v", s)
+ }
+
+ return nil
+}
+
+func (u *InputUnifi) dumpSitesJSON(c *Controller, path, name string, sites unifi.Sites) ([]byte, error) {
+ allJSON := []byte{}
+
+ for _, s := range sites {
+ apiPath := fmt.Sprintf(path, s.Name)
+ _, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping %s: '%s' JSON for site: %s (%s):\n", name, apiPath, s.Desc, s.Name)
+
+ body, err := c.Unifi.GetJSON(apiPath)
+ if err != nil {
+ return allJSON, err
+ }
+
+ allJSON = append(allJSON, body...)
+ }
+
+ return allJSON, nil
+}
+
+// StringInSlice returns true if a string is in a slice.
+func StringInSlice(str string, slice []string) bool {
+ for _, s := range slice {
+ if strings.EqualFold(s, str) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/pkg/inputunifi/interface.go b/pkg/inputunifi/interface.go
index 1db32906..f9b437de 100644
--- a/pkg/inputunifi/interface.go
+++ b/pkg/inputunifi/interface.go
@@ -1,13 +1,51 @@
package inputunifi
+/* This file contains the three poller.Input interface methods. */
+
import (
"fmt"
+ "os"
"strings"
"github.com/davidnewhall/unifi-poller/pkg/poller"
"golift.io/unifi"
)
+// Initialize gets called one time when starting up.
+// Satisfies poller.Input interface.
+func (u *InputUnifi) Initialize(l poller.Logger) error {
+ if u.Config.Disable {
+ l.Logf("unifi input disabled")
+ return nil
+ }
+
+ if len(u.Config.Controllers) < 1 {
+ return fmt.Errorf("no unifi controllers defined for unifi input")
+ }
+
+ u.Logger = l
+
+ for i, c := range u.Config.Controllers {
+ if c.Name == "" {
+ u.Config.Controllers[i].Name = c.URL
+ }
+
+ switch err := u.getUnifi(c); err {
+ case nil:
+ if err := u.checkSites(c); err != nil {
+ u.LogErrorf("checking sites on %s: %v", c.Name, err)
+ }
+
+ u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
+ c.URL, c.Unifi.ServerVersion, c.User, c.Sites)
+ default:
+ u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Name, err)
+ }
+ }
+
+ return nil
+}
+
// Metrics grabs all the measurements from a UniFi controller and returns them.
func (u *InputUnifi) Metrics() (*poller.Metrics, error) {
if u.Config.Disable {
@@ -52,75 +90,35 @@ func (u *InputUnifi) Metrics() (*poller.Metrics, error) {
return metrics, nil
}
-// Initialize gets called one time when starting up.
-// Satisfies poller.Input interface.
-func (u *InputUnifi) Initialize(l poller.Logger) error {
- if u.Config.Disable {
- l.Logf("unifi input disabled")
- return nil
- }
+// RawMetrics returns API output from the first configured unifi controller.
+func (u *InputUnifi) RawMetrics(filter poller.Filter) ([]byte, error) {
+ c := u.Config.Controllers[0] // We could pull the controller number from the filter.
+ if u.isNill(c) {
+ u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
- if len(u.Config.Controllers) < 1 {
- return fmt.Errorf("no unifi controllers defined for unifi input")
- }
-
- u.Logger = l
-
- for i, c := range u.Config.Controllers {
- if c.Name == "" {
- u.Config.Controllers[i].Name = c.URL
- }
-
- switch err := u.getUnifi(c); err {
- case nil:
- if err := u.checkSites(c); err != nil {
- u.LogErrorf("checking sites on %s: %v", c.Name, err)
- }
-
- u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
- c.URL, c.Unifi.ServerVersion, c.User, c.Sites)
- default:
- u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Name, err)
+ if err := u.getUnifi(c); err != nil {
+ return nil, fmt.Errorf("re-authenticating to %s: %v", c.Name, err)
}
}
- return nil
-}
+ if err := u.checkSites(c); err != nil {
+ return nil, err
+ }
-// checkSites makes sure the list of provided sites exists on the controller.
-// This only runs once during initialization.
-func (u *InputUnifi) checkSites(c Controller) error {
- u.Config.RLock()
- defer u.Config.RUnlock()
- u.LogDebugf("Checking Controller Sites List")
-
- sites, err := c.Unifi.GetSites()
+ sites, err := u.getFilteredSites(c)
if err != nil {
- return err
+ return nil, err
}
- msg := []string{}
-
- for _, site := range sites {
- msg = append(msg, site.Name+" ("+site.Desc+")")
+ switch filter.Type {
+ case "d", "device", "devices":
+ return u.dumpSitesJSON(c, unifi.APIDevicePath, "Devices", sites)
+ case "client", "clients", "c":
+ return u.dumpSitesJSON(c, unifi.APIClientPath, "Clients", sites)
+ case "other", "o":
+ _, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", filter.Term)
+ return c.Unifi.GetJSON(filter.Term)
+ default:
+ return []byte{}, fmt.Errorf("must provide filter: devices, clients, other")
}
-
- u.Logf("Found %d site(s) on controller: %v", len(msg), strings.Join(msg, ", "))
-
- if poller.StringInSlice("all", c.Sites) {
- c.Sites = []string{"all"}
- return nil
- }
-
-FIRST:
- for _, s := range c.Sites {
- for _, site := range sites {
- if s == site.Name {
- continue FIRST
- }
- }
- return fmt.Errorf("configured site not found on controller: %v", s)
- }
-
- return nil
}
diff --git a/pkg/poller/dumper.go b/pkg/poller/dumper.go
index 18abe901..2a2b0dab 100644
--- a/pkg/poller/dumper.go
+++ b/pkg/poller/dumper.go
@@ -1,85 +1,28 @@
package poller
import (
+ "fmt"
"strings"
)
// DumpJSONPayload prints raw json from the UniFi Controller.
// This only works with controller 0 (first one) in the config.
func (u *UnifiPoller) DumpJSONPayload() (err error) {
- if true {
- return nil
+ u.Config.Quiet = true
+
+ split := strings.SplitN(u.Flags.DumpJSON, " ", 2)
+ filter := Filter{Type: split[0]}
+
+ if len(split) > 1 {
+ filter.Term = split[1]
}
- /*
- u.Config.Quiet = true
- config := u.Config.Controllers[0]
- config.Unifi, err = unifi.NewUnifi(&unifi.Config{
- User: config.User,
- Pass: config.Pass,
- URL: config.URL,
- VerifySSL: config.VerifySSL,
- })
- if err != nil {
- return err
- }
+ m, err := inputs[0].RawMetrics(filter)
+ if err != nil {
+ return err
+ }
- fmt.Fprintf(os.Stderr, "[INFO] Authenticated to UniFi Controller @ %v as user %v", config.URL, config.User)
+ fmt.Println(string(m))
- if err := u.CheckSites(config); err != nil {
- return err
- }
-
- config.Unifi.ErrorLog = func(m string, v ...interface{}) {
- fmt.Fprintf(os.Stderr, "[ERROR] "+m, v...)
- } // Log all errors to stderr.
-
- switch sites, err := u.GetFilteredSites(config); {
- case err != nil:
- return err
- case StringInSlice(u.Flags.DumpJSON, []string{"d", "device", "devices"}):
- return u.dumpSitesJSON(config, unifi.APIDevicePath, "Devices", sites)
- case StringInSlice(u.Flags.DumpJSON, []string{"client", "clients", "c"}):
- return u.dumpSitesJSON(config, unifi.APIClientPath, "Clients", sites)
- case strings.HasPrefix(u.Flags.DumpJSON, "other "):
- apiPath := strings.SplitN(u.Flags.DumpJSON, " ", 2)[1]
- _, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", apiPath)
- return u.PrintRawAPIJSON(config, apiPath)
- default:
- return fmt.Errorf("must provide filter: devices, clients, other")
- }
- */
return nil
}
-
-/*
-func (u *UnifiPoller) dumpSitesJSON(c Controller, path, name string, sites unifi.Sites) error {
- for _, s := range sites {
- apiPath := fmt.Sprintf(path, s.Name)
- _, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping %s: '%s' JSON for site: %s (%s):\n",
- name, apiPath, s.Desc, s.Name)
- if err := u.PrintRawAPIJSON(c, apiPath); err != nil {
- return err
- }
- }
- return nil
-}
-
-// PrintRawAPIJSON prints the raw json for a user-provided path on a UniFi Controller.
-func (u *UnifiPoller) PrintRawAPIJSON(c Controller, apiPath string) error {
- body, err := c.Unifi.GetJSON(apiPath)
- fmt.Println(string(body))
- return err
-}
-*/
-
-// StringInSlice returns true if a string is in a slice.
-func StringInSlice(str string, slice []string) bool {
- for _, s := range slice {
- if strings.EqualFold(s, str) {
- return true
- }
- }
-
- return false
-}
diff --git a/pkg/poller/inputs.go b/pkg/poller/inputs.go
index c2140b01..f2d64efb 100644
--- a/pkg/poller/inputs.go
+++ b/pkg/poller/inputs.go
@@ -17,6 +17,7 @@ var (
type Input interface {
Initialize(Logger) error // Called once on startup to initialize the plugin.
Metrics() (*Metrics, error) // Called every time new metrics are requested.
+ RawMetrics(Filter) ([]byte, error)
}
// InputPlugin describes an input plugin's consumable interface.
@@ -25,6 +26,12 @@ type InputPlugin struct {
Input
}
+// Filter is used for raw metrics filters.
+type Filter struct {
+ Type string
+ Term string
+}
+
// NewInput creates a metric input. This should be called by input plugins
// init() functions.
func NewInput(i *InputPlugin) {
diff --git a/pkg/poller/logger.go b/pkg/poller/logger.go
index b498a9b5..fa983e5f 100644
--- a/pkg/poller/logger.go
+++ b/pkg/poller/logger.go
@@ -16,14 +16,14 @@ type Logger interface {
// Logf prints a log entry if quiet is false.
func (u *UnifiPoller) Logf(m string, v ...interface{}) {
- if !u.Config.Quiet {
+ if !u.Quiet {
_ = log.Output(callDepth, fmt.Sprintf("[INFO] "+m, v...))
}
}
// LogDebugf prints a debug log entry if debug is true and quite is false
func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) {
- if u.Config.Debug && !u.Config.Quiet {
+ if u.Debug && !u.Quiet {
_ = log.Output(callDepth, fmt.Sprintf("[DEBUG] "+m, v...))
}
}
diff --git a/pkg/poller/start.go b/pkg/poller/start.go
index 9f6bedc8..e88daf24 100644
--- a/pkg/poller/start.go
+++ b/pkg/poller/start.go
@@ -61,10 +61,14 @@ func (f *Flags) Parse(args []string) {
// 3. Start a web server and wait for Prometheus to poll the application for metrics.
func (u *UnifiPoller) Run() error {
if u.Flags.DumpJSON != "" {
+ if err := u.InitializeInputs(); err != nil {
+ return err
+ }
+
return u.DumpJSONPayload()
}
- if u.Config.Debug {
+ if u.Debug {
log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
u.LogDebugf("Debug Logging Enabled")
}