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