Merge branch 'master' of ../poller into merge-them-all
This commit is contained in:
commit
c475ed8d5b
|
|
@ -0,0 +1,9 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.16.x
|
||||
before_install:
|
||||
# download super-linter: golangci-lint
|
||||
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
|
||||
script:
|
||||
- go test ./...
|
||||
- golangci-lint run --enable-all -D exhaustivestruct,nlreturn,forbidigo
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT LICENSE.
|
||||
Copyright (c) 2018-2020 David Newhall II
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# poller
|
||||
|
||||
## UniFi Poller Core
|
||||
|
||||
This module ties the inputs together with the outputs.
|
||||
|
||||
Aggregates metrics on request. Provides CLI app and args parsing.
|
||||
|
||||
|
||||
# Ideal
|
||||
|
||||
This library has no notion of "UniFi" or controllers, or Influx, or Prometheus.
|
||||
This library simply provides an input interface and an output interface.
|
||||
Each interface uses an `[]interface{}` type, so any type of data can be used.
|
||||
That is to say, you could write input and output plugins that work with, say,
|
||||
Cisco gear, or any other network (or even non-network) data. The existing plugins
|
||||
should provide ample example of how to use this library, but at some point the
|
||||
godoc will improve.
|
||||
|
||||
# Features
|
||||
|
||||
- Automatically unmarshal's plugin config structs from config file and/or env variables.
|
||||
- Initializes all "imported" plugins on startup.
|
||||
- Provides input plugins a Logger, requires an interface for Metrics and Events retrieval.
|
||||
- Provides Output plugins an interface to retrieve Metrics and Events, and a Logger.
|
||||
- Provides automatic aggregation of Metrics and Events from multiple sources.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// +build darwin freebsd netbsd openbsd
|
||||
|
||||
package poller
|
||||
|
||||
// DefaultConfFile is where to find config if --config is not prvided.
|
||||
const DefaultConfFile = "/etc/unifi-poller/up.conf,/usr/local/etc/unifi-poller/up.conf"
|
||||
|
||||
// DefaultObjPath is the path to look for shared object libraries (plugins).
|
||||
const DefaultObjPath = "/usr/local/lib/unifi-poller"
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// +build !windows,!darwin,!freebsd
|
||||
|
||||
package poller
|
||||
|
||||
// DefaultConfFile is where to find config if --config is not prvided.
|
||||
const DefaultConfFile = "/config/unifi-poller.conf,/etc/unifi-poller/up.conf"
|
||||
|
||||
// DefaultObjPath is the path to look for shared object libraries (plugins).
|
||||
const DefaultObjPath = "/usr/lib/unifi-poller"
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// +build windows
|
||||
|
||||
package poller
|
||||
|
||||
// DefaultConfFile is where to find config if --config is not prvided.
|
||||
const DefaultConfFile = `C:\ProgramData\unifi-poller\up.conf`
|
||||
|
||||
// DefaultObjPath is useless in this context. Bummer.
|
||||
const DefaultObjPath = "PLUGINS_DO_NOT_WORK_ON_WINDOWS_SOWWWWWY"
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// PrintRawMetrics prints raw json from the UniFi Controller. This is currently
|
||||
// tied into the -j CLI arg, and is probably not very useful outside that context.
|
||||
func (u *UnifiPoller) PrintRawMetrics() (err error) {
|
||||
split := strings.SplitN(u.Flags.DumpJSON, " ", 2)
|
||||
filter := &Filter{Kind: split[0]}
|
||||
|
||||
// Allows you to grab a controller other than 0 from config.
|
||||
if split2 := strings.Split(filter.Kind, ":"); len(split2) > 1 {
|
||||
filter.Kind = split2[0]
|
||||
filter.Unit, _ = strconv.Atoi(split2[1])
|
||||
}
|
||||
|
||||
// Used with "other"
|
||||
if len(split) > 1 {
|
||||
filter.Path = split[1]
|
||||
}
|
||||
|
||||
// As of now we only have one input plugin, so target that [0].
|
||||
m, err := inputs[0].RawMetrics(filter)
|
||||
fmt.Println(string(m))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// PrintPasswordHash prints a bcrypt'd password. Useful for the web server.
|
||||
func (u *UnifiPoller) PrintPasswordHash() (err error) {
|
||||
pwd := []byte(u.Flags.HashPW)
|
||||
|
||||
if u.Flags.HashPW == "-" {
|
||||
fmt.Print("Enter Password: ")
|
||||
|
||||
pwd, err = term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading stdin: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println() // print a newline.
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)
|
||||
fmt.Println(string(hash))
|
||||
|
||||
return err //nolint:wrapcheck
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"plugin"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"golift.io/cnfg"
|
||||
"golift.io/cnfg/cnfgfile"
|
||||
)
|
||||
|
||||
const (
|
||||
// AppName is the name of the application.
|
||||
AppName = "unpoller"
|
||||
// ENVConfigPrefix is the prefix appended to an env variable tag name.
|
||||
ENVConfigPrefix = "UP"
|
||||
)
|
||||
|
||||
// UnifiPoller contains the application startup data, and auth info for UniFi & Influx.
|
||||
type UnifiPoller struct {
|
||||
Flags *Flags
|
||||
*Config
|
||||
}
|
||||
|
||||
// Flags represents the CLI args available and their settings.
|
||||
type Flags struct {
|
||||
ConfigFile string
|
||||
DumpJSON string
|
||||
HashPW string
|
||||
ShowVer bool
|
||||
*pflag.FlagSet
|
||||
}
|
||||
|
||||
// Metrics is a type shared by the exporting and reporting packages.
|
||||
type Metrics struct {
|
||||
TS time.Time
|
||||
Sites []interface{}
|
||||
Clients []interface{}
|
||||
SitesDPI []interface{}
|
||||
ClientsDPI []interface{}
|
||||
Devices []interface{}
|
||||
RogueAPs []interface{}
|
||||
}
|
||||
|
||||
// Events defines the type for log entries.
|
||||
type Events struct {
|
||||
Logs []interface{}
|
||||
}
|
||||
|
||||
// Config represents the core library input data.
|
||||
type Config struct {
|
||||
*Poller `json:"poller" toml:"poller" xml:"poller" yaml:"poller"`
|
||||
}
|
||||
|
||||
// Poller is the global config values.
|
||||
type Poller struct {
|
||||
Plugins []string `json:"plugins" toml:"plugins" xml:"plugin" yaml:"plugins"`
|
||||
Debug bool `json:"debug" toml:"debug" xml:"debug,attr" yaml:"debug"`
|
||||
Quiet bool `json:"quiet" toml:"quiet" xml:"quiet,attr" yaml:"quiet"`
|
||||
}
|
||||
|
||||
// LoadPlugins reads-in dynamic shared libraries.
|
||||
// Not used very often, if at all.
|
||||
func (u *UnifiPoller) LoadPlugins() error {
|
||||
for _, p := range u.Plugins {
|
||||
name := strings.TrimSuffix(p, ".so") + ".so"
|
||||
|
||||
if name == ".so" {
|
||||
continue // Just ignore it. uhg.
|
||||
}
|
||||
|
||||
if _, err := os.Stat(name); os.IsNotExist(err) {
|
||||
name = path.Join(DefaultObjPath, name)
|
||||
}
|
||||
|
||||
u.Logf("Loading Dynamic Plugin: %s", name)
|
||||
|
||||
if _, err := plugin.Open(name); err != nil {
|
||||
return fmt.Errorf("opening plugin: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseConfigs parses the poller config and the config for each registered output plugin.
|
||||
func (u *UnifiPoller) ParseConfigs() error {
|
||||
// Parse core config.
|
||||
if err := u.parseInterface(u.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load dynamic plugins.
|
||||
if err := u.LoadPlugins(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.parseInputs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.parseOutputs()
|
||||
}
|
||||
|
||||
// getFirstFile returns the first file that exists and is "reachable".
|
||||
func getFirstFile(files []string) (string, error) {
|
||||
var err error
|
||||
|
||||
for _, f := range files {
|
||||
if _, err = os.Stat(f); err == nil {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("finding file: %w", err)
|
||||
}
|
||||
|
||||
// parseInterface parses the config file and environment variables into the provided interface.
|
||||
func (u *UnifiPoller) parseInterface(i interface{}) error {
|
||||
// Parse config file into provided interface.
|
||||
if err := cnfgfile.Unmarshal(i, u.Flags.ConfigFile); err != nil {
|
||||
return fmt.Errorf("cnfg unmarshal: %w", err)
|
||||
}
|
||||
|
||||
// Parse environment variables into provided interface.
|
||||
if _, err := cnfg.UnmarshalENV(i, ENVConfigPrefix); err != nil {
|
||||
return fmt.Errorf("env unmarshal: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse input plugin configs.
|
||||
func (u *UnifiPoller) parseInputs() error {
|
||||
inputSync.Lock()
|
||||
defer inputSync.Unlock()
|
||||
|
||||
for _, i := range inputs {
|
||||
if err := u.parseInterface(i.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse output plugin configs.
|
||||
func (u *UnifiPoller) parseOutputs() error {
|
||||
outputSync.Lock()
|
||||
defer outputSync.Unlock()
|
||||
|
||||
for _, o := range outputs {
|
||||
if err := u.parseInterface(o.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module github.com/unpoller/poller
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
golift.io/cnfg v0.0.7
|
||||
golift.io/version v0.0.2
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c h1:zqmyTlQyufRC65JnImJ6H1Sf7BDj8bG31EV919NVEQc=
|
||||
github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golift.io/cnfg v0.0.7 h1:qkNpP5Bq+5Gtoc6HcI8kapMD5zFOVan6qguxqBQF3OY=
|
||||
golift.io/cnfg v0.0.7/go.mod h1:AsB0DJe7nv0bizKaoy3e3MjjOF7upTpMOMvsfv4CNNk=
|
||||
golift.io/version v0.0.2 h1:i0gXRuSDHKs4O0sVDUg4+vNIuOxYoXhaxspftu2FRTE=
|
||||
golift.io/version v0.0.2/go.mod h1:76aHNz8/Pm7CbuxIsDi97jABL5Zui3f2uZxDm4vB6hU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// These are used ot keep track of loaded input plugins.
|
||||
inputs []*InputPlugin // nolint: gochecknoglobals
|
||||
inputSync sync.RWMutex // nolint: gochecknoglobals
|
||||
)
|
||||
|
||||
// Input plugins must implement this interface.
|
||||
type Input interface {
|
||||
Initialize(Logger) error // Called once on startup to initialize the plugin.
|
||||
Metrics(*Filter) (*Metrics, error) // Called every time new metrics are requested.
|
||||
Events(*Filter) (*Events, error) // This is new.
|
||||
RawMetrics(*Filter) ([]byte, error)
|
||||
}
|
||||
|
||||
// InputPlugin describes an input plugin's consumable interface.
|
||||
type InputPlugin struct {
|
||||
Name string
|
||||
Config interface{} // Each config is passed into an unmarshaller later.
|
||||
Input
|
||||
}
|
||||
|
||||
// Filter is used for metrics filters. Many fields for lots of expansion.
|
||||
type Filter struct {
|
||||
Type string
|
||||
Term string
|
||||
Name string
|
||||
Role string
|
||||
Kind string
|
||||
Path string
|
||||
Text string
|
||||
Unit int
|
||||
Pass bool
|
||||
Skip bool
|
||||
Time time.Time
|
||||
Dur time.Duration
|
||||
}
|
||||
|
||||
// NewInput creates a metric input. This should be called by input plugins
|
||||
// init() functions.
|
||||
func NewInput(i *InputPlugin) {
|
||||
inputSync.Lock()
|
||||
defer inputSync.Unlock()
|
||||
|
||||
if i == nil || i.Input == nil {
|
||||
panic("nil output or method passed to poller.NewOutput")
|
||||
}
|
||||
|
||||
inputs = append(inputs, i)
|
||||
}
|
||||
|
||||
// InitializeInputs runs the passed-in initializer method for each input plugin.
|
||||
func (u *UnifiPoller) InitializeInputs() error {
|
||||
inputSync.RLock()
|
||||
defer inputSync.RUnlock()
|
||||
|
||||
for _, input := range inputs {
|
||||
// This must return, or the app locks up here.
|
||||
if err := input.Initialize(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Events aggregates log messages (events) from one or more sources.
|
||||
func (u *UnifiPoller) Events(filter *Filter) (*Events, error) {
|
||||
inputSync.RLock()
|
||||
defer inputSync.RUnlock()
|
||||
|
||||
events := Events{}
|
||||
|
||||
for _, input := range inputs {
|
||||
if filter != nil &&
|
||||
filter.Name != "" &&
|
||||
!strings.EqualFold(input.Name, filter.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
e, err := input.Events(filter)
|
||||
if err != nil {
|
||||
return &events, err
|
||||
}
|
||||
|
||||
// Logs is the only member to extend at this time.
|
||||
events.Logs = append(events.Logs, e.Logs...)
|
||||
}
|
||||
|
||||
return &events, nil
|
||||
}
|
||||
|
||||
// Metrics aggregates all the measurements from filtered inputs and returns them.
|
||||
// Passing a null filter returns everything!
|
||||
func (u *UnifiPoller) Metrics(filter *Filter) (*Metrics, error) {
|
||||
inputSync.RLock()
|
||||
defer inputSync.RUnlock()
|
||||
|
||||
metrics := &Metrics{}
|
||||
|
||||
for _, input := range inputs {
|
||||
if filter != nil &&
|
||||
filter.Name != "" &&
|
||||
!strings.EqualFold(input.Name, filter.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := input.Metrics(filter)
|
||||
if err != nil {
|
||||
return metrics, err
|
||||
}
|
||||
|
||||
metrics = AppendMetrics(metrics, m)
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// AppendMetrics combines the metrics from two sources.
|
||||
func AppendMetrics(existing *Metrics, m *Metrics) *Metrics {
|
||||
if existing == nil {
|
||||
return m
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
return existing
|
||||
}
|
||||
|
||||
existing.SitesDPI = append(existing.SitesDPI, m.SitesDPI...)
|
||||
existing.Sites = append(existing.Sites, m.Sites...)
|
||||
existing.ClientsDPI = append(existing.ClientsDPI, m.ClientsDPI...)
|
||||
existing.RogueAPs = append(existing.RogueAPs, m.RogueAPs...)
|
||||
existing.Clients = append(existing.Clients, m.Clients...)
|
||||
existing.Devices = append(existing.Devices, m.Devices...)
|
||||
|
||||
return existing
|
||||
}
|
||||
|
||||
// Inputs allows output plugins to see the list of loaded input plugins.
|
||||
func (u *UnifiPoller) Inputs() (names []string) {
|
||||
inputSync.RLock()
|
||||
defer inputSync.RUnlock()
|
||||
|
||||
for i := range inputs {
|
||||
names = append(names, inputs[i].Name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Log the command that called these commands.
|
||||
const callDepth = 2
|
||||
|
||||
// Logger is passed into input packages so they may write logs.
|
||||
type Logger interface {
|
||||
Logf(m string, v ...interface{})
|
||||
LogErrorf(m string, v ...interface{})
|
||||
LogDebugf(m string, v ...interface{})
|
||||
}
|
||||
|
||||
// Logf prints a log entry if quiet is false.
|
||||
func (u *UnifiPoller) Logf(m string, v ...interface{}) {
|
||||
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.Debug && !u.Quiet {
|
||||
_ = log.Output(callDepth, fmt.Sprintf("[DEBUG] "+m, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// LogErrorf prints an error log entry.
|
||||
func (u *UnifiPoller) LogErrorf(m string, v ...interface{}) {
|
||||
_ = log.Output(callDepth, fmt.Sprintf("[ERROR] "+m, v...))
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// These are used to keep track of loaded output plugins.
|
||||
outputs []*Output // nolint: gochecknoglobals
|
||||
outputSync sync.RWMutex // nolint: gochecknoglobals
|
||||
errNoOutputPlugins = fmt.Errorf("no output plugins imported")
|
||||
errAllOutputStopped = fmt.Errorf("all output plugins have stopped, or none enabled")
|
||||
)
|
||||
|
||||
// Collect is passed into output packages so they may collect metrics to output.
|
||||
type Collect interface {
|
||||
Logger
|
||||
Metrics(*Filter) (*Metrics, error)
|
||||
Events(*Filter) (*Events, error)
|
||||
// These get used by the webserver output plugin.
|
||||
Poller() Poller
|
||||
Inputs() []string
|
||||
Outputs() []string
|
||||
}
|
||||
|
||||
// Output defines the output data for a metric exporter like influx or prometheus.
|
||||
// Output packages should call NewOutput with this struct in init().
|
||||
type Output struct {
|
||||
Name string
|
||||
Config interface{} // Each config is passed into an unmarshaller later.
|
||||
Method func(Collect) error // Called on startup for each configured output.
|
||||
}
|
||||
|
||||
// NewOutput should be called by each output package's init function.
|
||||
func NewOutput(o *Output) {
|
||||
outputSync.Lock()
|
||||
defer outputSync.Unlock()
|
||||
|
||||
if o == nil || o.Method == nil {
|
||||
panic("nil output or method passed to poller.NewOutput")
|
||||
}
|
||||
|
||||
outputs = append(outputs, o)
|
||||
}
|
||||
|
||||
// Poller returns the poller config.
|
||||
func (u *UnifiPoller) Poller() Poller {
|
||||
return *u.Config.Poller
|
||||
}
|
||||
|
||||
// InitializeOutputs runs all the configured output plugins.
|
||||
// If none exist, or they all exit an error is returned.
|
||||
func (u *UnifiPoller) InitializeOutputs() error {
|
||||
count, errChan := u.runOutputMethods()
|
||||
defer close(errChan)
|
||||
|
||||
if count == 0 {
|
||||
return errNoOutputPlugins
|
||||
}
|
||||
|
||||
// Wait for and return an error from any output plugin.
|
||||
for err := range errChan {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count--; count == 0 {
|
||||
return errAllOutputStopped
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UnifiPoller) runOutputMethods() (int, chan error) {
|
||||
// Output plugin errors go into this channel.
|
||||
err := make(chan error)
|
||||
|
||||
outputSync.RLock()
|
||||
defer outputSync.RUnlock()
|
||||
|
||||
for _, o := range outputs {
|
||||
go func(o *Output) {
|
||||
err <- o.Method(u) // Run each output plugin
|
||||
}(o)
|
||||
}
|
||||
|
||||
return len(outputs), err
|
||||
}
|
||||
|
||||
// Outputs allows other output plugins to see the list of loaded output plugins.
|
||||
func (u *UnifiPoller) Outputs() (names []string) {
|
||||
outputSync.RLock()
|
||||
defer outputSync.RUnlock()
|
||||
|
||||
for i := range outputs {
|
||||
names = append(names, outputs[i].Name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
// Package poller provides the CLI interface to setup unifi-poller.
|
||||
package poller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"golift.io/version"
|
||||
)
|
||||
|
||||
// New returns a new poller struct.
|
||||
func New() *UnifiPoller {
|
||||
return &UnifiPoller{Config: &Config{Poller: &Poller{}}, Flags: &Flags{}}
|
||||
}
|
||||
|
||||
// Start begins the application from a CLI.
|
||||
// Parses cli flags, parses config file, parses env vars, sets up logging, then:
|
||||
// - dumps a json payload OR - executes Run().
|
||||
func (u *UnifiPoller) Start() error {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetFlags(log.LstdFlags)
|
||||
u.Flags.Parse(os.Args[1:])
|
||||
|
||||
if u.Flags.ShowVer {
|
||||
fmt.Println(version.Print(AppName))
|
||||
return nil // don't run anything else w/ version request.
|
||||
}
|
||||
|
||||
if u.Flags.HashPW != "" {
|
||||
return u.PrintPasswordHash()
|
||||
}
|
||||
|
||||
cfile, err := getFirstFile(strings.Split(u.Flags.ConfigFile, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Flags.ConfigFile = cfile
|
||||
if u.Flags.DumpJSON == "" { // do not print this when dumping JSON.
|
||||
u.Logf("Loading Configuration File: %s", u.Flags.ConfigFile)
|
||||
}
|
||||
|
||||
// Parse config file and ENV variables.
|
||||
if err := u.ParseConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.Run()
|
||||
}
|
||||
|
||||
// Parse turns CLI arguments into data structures. Called by Start() on startup.
|
||||
func (f *Flags) Parse(args []string) {
|
||||
f.FlagSet = pflag.NewFlagSet(AppName, pflag.ExitOnError)
|
||||
f.Usage = func() {
|
||||
fmt.Printf("Usage: %s [--config=/path/to/up.conf] [--version]", AppName)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
f.StringVarP(&f.HashPW, "encrypt", "e", "",
|
||||
"This option bcrypts a provided string. Useful for the webserver password. Use - to be prompted.")
|
||||
f.StringVarP(&f.DumpJSON, "dumpjson", "j", "",
|
||||
"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. Separating multiple paths with a comma will load the first config file found.")
|
||||
f.BoolVarP(&f.ShowVer, "version", "v", false, "Print the version and exit.")
|
||||
_ = f.FlagSet.Parse(args) // pflag.ExitOnError means this will never return error.
|
||||
}
|
||||
|
||||
// Run picks a mode and executes the associated functions. This will do one of three things:
|
||||
// 1. Start the collector routine that polls unifi and reports to influx on an interval. (default)
|
||||
// 2. Run the collector one time and report the metrics to influxdb. (lambda)
|
||||
// 3. Start a web server and wait for Prometheus to poll the application for metrics.
|
||||
func (u *UnifiPoller) Run() error {
|
||||
if u.Flags.DumpJSON != "" {
|
||||
u.Config.Quiet = true
|
||||
if err := u.InitializeInputs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.PrintRawMetrics()
|
||||
}
|
||||
|
||||
if u.Debug {
|
||||
log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
|
||||
u.LogDebugf("Debug Logging Enabled")
|
||||
}
|
||||
|
||||
log.Printf("[INFO] UniFi Poller v%v Starting Up! PID: %d", version.Version, os.Getpid())
|
||||
|
||||
if err := u.InitializeInputs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.InitializeOutputs()
|
||||
}
|
||||
Loading…
Reference in New Issue