diff --git a/integrations/influxunifi/Godeps/Godeps.json b/integrations/influxunifi/Godeps/Godeps.json deleted file mode 100644 index 2cd05fd2..00000000 --- a/integrations/influxunifi/Godeps/Godeps.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "ImportPath": "github.com/davidnewhall/unifi-poller", - "GoVersion": "go1.11", - "GodepVersion": "v79", - "Packages": [ - "./..." - ], - "Deps": [ - { - "ImportPath": "github.com/davecgh/go-spew/spew", - "Comment": "v1.1.0", - "Rev": "346938d642f2ec3594ed81d874461961cd0faa76" - }, - { - "ImportPath": "github.com/influxdata/influxdb/client/v2", - "Comment": "v1.5.0-149-g14dcc5d6e", - "Rev": "14dcc5d6e7a6b15e17aba7b104b8ad0ca6c91ad2" - }, - { - "ImportPath": "github.com/influxdata/influxdb/models", - "Comment": "v1.5.0-149-g14dcc5d6e", - "Rev": "14dcc5d6e7a6b15e17aba7b104b8ad0ca6c91ad2" - }, - { - "ImportPath": "github.com/influxdata/influxdb/pkg/escape", - "Comment": "v1.5.0-149-g14dcc5d6e", - "Rev": "14dcc5d6e7a6b15e17aba7b104b8ad0ca6c91ad2" - }, - { - "ImportPath": "github.com/naoina/go-stringutil", - "Comment": "v0.1.0", - "Rev": "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b" - }, - { - "ImportPath": "github.com/naoina/toml", - "Comment": "v0.1.1-2-g9fafd69", - "Rev": "9fafd69674167c06933b1787ae235618431ce87f" - }, - { - "ImportPath": "github.com/naoina/toml/ast", - "Comment": "v0.1.1-2-g9fafd69", - "Rev": "9fafd69674167c06933b1787ae235618431ce87f" - }, - { - "ImportPath": "github.com/ogier/pflag", - "Comment": "v0.0.1-7-g45c278a", - "Rev": "45c278ab3607870051a2ea9040bb85fcb8557481" - }, - { - "ImportPath": "github.com/pkg/errors", - "Comment": "v0.8.0-6-g2b3a18b", - "Rev": "2b3a18b5f0fb6b4f9190549597d3f962c02bc5eb" - }, - { - "ImportPath": "github.com/pmezard/go-difflib/difflib", - "Comment": "v1.0.0", - "Rev": "792786c7400a136282c1664665ae0a8db921c6c2" - }, - { - "ImportPath": "github.com/stretchr/testify/assert", - "Comment": "v1.1.4-27-g4d4bfba", - "Rev": "4d4bfba8f1d1027c4fdbe371823030df51419987" - }, - { - "ImportPath": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew", - "Comment": "v1.1.4-27-g4d4bfba", - "Rev": "4d4bfba8f1d1027c4fdbe371823030df51419987" - }, - { - "ImportPath": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib", - "Comment": "v1.1.4-27-g4d4bfba", - "Rev": "4d4bfba8f1d1027c4fdbe371823030df51419987" - } - ] -} diff --git a/integrations/influxunifi/Godeps/Readme b/integrations/influxunifi/Godeps/Readme deleted file mode 100644 index 4cdaa53d..00000000 --- a/integrations/influxunifi/Godeps/Readme +++ /dev/null @@ -1,5 +0,0 @@ -This directory tree is generated automatically by godep. - -Please do not edit. - -See https://github.com/tools/godep for more information. diff --git a/integrations/influxunifi/Gopkg.lock b/integrations/influxunifi/Gopkg.lock new file mode 100644 index 00000000..7275c78e --- /dev/null +++ b/integrations/influxunifi/Gopkg.lock @@ -0,0 +1,73 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + name = "github.com/influxdata/influxdb" + packages = [ + "client/v2", + "models", + "pkg/escape" + ] + revision = "76f907b0fada2f16931e37471da695349fcdf8c6" + version = "v1.7.2" + +[[projects]] + branch = "master" + name = "github.com/influxdata/platform" + packages = [ + "models", + "pkg/escape" + ] + revision = "98469bf07613ffae6f025893761c1e7a5e96e4aa" + +[[projects]] + name = "github.com/naoina/go-stringutil" + packages = ["."] + revision = "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b" + version = "v0.1.0" + +[[projects]] + name = "github.com/naoina/toml" + packages = [ + ".", + "ast" + ] + revision = "e6f5723bf2a66af014955e0888881314cf294129" + version = "v0.1.1" + +[[projects]] + name = "github.com/ogier/pflag" + packages = ["."] + revision = "32a05c62658bd1d7c7e75cbc8195de5d585fde0f" + version = "v0.0.1" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" + version = "v0.8.1" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" + version = "v1.3.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "3b1d8ae8346fc10f0725163478eab3fa466a2064f4d44077102c5f7651625dc4" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/integrations/influxunifi/Gopkg.toml b/integrations/influxunifi/Gopkg.toml new file mode 100644 index 00000000..d951dd2c --- /dev/null +++ b/integrations/influxunifi/Gopkg.toml @@ -0,0 +1,50 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/influxdata/influxdb" + version = "1.7.2" + +[[constraint]] + name = "github.com/naoina/toml" + version = "0.1.1" + +[[constraint]] + name = "github.com/ogier/pflag" + version = "0.0.1" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.1" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.3.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/integrations/influxunifi/Makefile b/integrations/influxunifi/Makefile index ad40a80e..7abb9b4b 100644 --- a/integrations/influxunifi/Makefile +++ b/integrations/influxunifi/Makefile @@ -43,6 +43,4 @@ man: script/build_manpages.sh ./ deps: - rm -rf Godeps vendor - godep save ./... - godep update ./... + dep ensure -update diff --git a/integrations/influxunifi/cmd/unifi-poller/README.md b/integrations/influxunifi/cmd/unifi-poller/README.md index 98d70e30..95dd06ee 100644 --- a/integrations/influxunifi/cmd/unifi-poller/README.md +++ b/integrations/influxunifi/cmd/unifi-poller/README.md @@ -12,11 +12,22 @@ unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB ## OPTIONS -`unifi-poller [-c ] [-h] [-v]` +`unifi-poller [-c ] [-D] [-q] [-s] [-h] [-v]` -c, --config Provide a configuration file (instead of the default). + -D, --debug + Turns on line numbers, microsecond logging, and a per-device log. + + -q, --quiet + Turns off per-device log and per-interval log. Logs only errors. + Recommend using -D with this setting for better error logging. + + -s, --verify-ssl + If your Unifi controller has a valid SSL certificate, you can enable + this option to validate it. Otherwise, any SSL certificate is valid. + -v, --version Display version and exit. diff --git a/integrations/influxunifi/cmd/unifi-poller/main.go b/integrations/influxunifi/cmd/unifi-poller/main.go index fa280076..8c3ced1f 100644 --- a/integrations/influxunifi/cmd/unifi-poller/main.go +++ b/integrations/influxunifi/cmd/unifi-poller/main.go @@ -21,12 +21,15 @@ func main() { } configFile := flg.StringP("config", "c", defaultConfFile, "Poller Config File (TOML Format)") flg.BoolVarP(&unidev.Debug, "debug", "D", false, "Turn on the Spam (default false)") - version := flg.BoolP("version", "v", false, "Print the version and exit.") + quiet := flg.BoolP("quiet", "q", false, "Do not print logs on every poll, only errors") + version := flg.BoolP("version", "v", false, "Print the version and exit") + verifySSL := flg.BoolP("verify-ssl", "s", false, "If your controller has a valid SSL cert, require it with this flag") if flg.Parse(); *version { fmt.Println("unifi-poller version:", Version) os.Exit(0) // don't run anything else. } + log.Println("Unifi-Poller Starting Up! PID:", os.Getpid()) if log.SetFlags(0); unidev.Debug { log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate) log.Println("Debug Logging Enabled") @@ -38,12 +41,13 @@ func main() { } log.Println("Loaded Configuration:", *configFile) // Create an authenticated session to the Unifi Controller. - unifi, err := unidev.AuthController(config.UnifiUser, config.UnifiPass, config.UnifiBase) + unifi, err := unidev.AuthController(config.UnifiUser, config.UnifiPass, config.UnifiBase, *verifySSL) if err != nil { log.Fatalln("Unifi Controller Error:", err) } - log.Println("Authenticated to Unifi Controller @", config.UnifiBase, "as user", config.UnifiUser) - + if !*quiet { + log.Println("Authenticated to Unifi Controller @", config.UnifiBase, "as user", config.UnifiUser) + } infdb, err := influx.NewHTTPClient(influx.HTTPConfig{ Addr: config.InfluxURL, Username: config.InfluxUser, @@ -52,9 +56,15 @@ func main() { if err != nil { log.Fatalln("InfluxDB Error:", err) } - log.Println("Logging Unifi Metrics to InfluXDB @", config.InfluxURL, "as user", config.InfluxUser) - log.Println("Polling Unifi Controller, interval:", config.Interval.value) - config.PollUnifiController(infdb, unifi) + if *quiet { + // Do it this way allows debug error logs (line numbers, etc) + unidev.Debug = false + } else { + log.Println("Logging Unifi Metrics to InfluXDB @", config.InfluxURL, "as user", config.InfluxUser) + log.Println("Polling Unifi Controller, interval:", config.Interval.value) + } + log.Println("Everyting checks out! Beginning Poller Routine.") + config.PollUnifiController(infdb, unifi, *quiet) } // GetConfig parses and returns our configuration data. @@ -80,7 +90,7 @@ func GetConfig(configFile string) (Config, error) { } // PollUnifiController runs forever, polling and pushing. -func (c *Config) PollUnifiController(infdb influx.Client, unifi *unidev.AuthedReq) { +func (c *Config) PollUnifiController(infdb influx.Client, unifi *unidev.AuthedReq, quiet bool) { ticker := time.NewTicker(c.Interval.value) for range ticker.C { var clients, devices []unidev.Asset @@ -107,6 +117,8 @@ func (c *Config) PollUnifiController(infdb influx.Client, unifi *unidev.AuthedRe log.Println("ERROR infdb.Write(bp):", err) continue } - log.Println("Logged client state. Clients:", len(clients), "- Devices:", len(devices)) + if !quiet { + log.Println("Logged client state. Clients:", len(clients), "- Devices:", len(devices)) + } } } diff --git a/integrations/influxunifi/unidev/unidev.go b/integrations/influxunifi/unidev/unidev.go index 32a20b7b..34f08254 100644 --- a/integrations/influxunifi/unidev/unidev.go +++ b/integrations/influxunifi/unidev/unidev.go @@ -61,23 +61,23 @@ func (f *FlexInt) UnmarshalJSON(b []byte) error { // AuthController creates a http.Client with authenticated cookies. // Used to make additional, authenticated requests to the APIs. -func AuthController(user, pass, url string) (*AuthedReq, error) { +func AuthController(user, pass, url string, verifySSL bool) (*AuthedReq, error) { json := `{"username": "` + user + `","password": "` + pass + `"}` jar, err := cookiejar.New(nil) if err != nil { return nil, errors.Wrap(err, "cookiejar.New(nil)") } - authReq := &AuthedReq{&http.Client{ - Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, + a := &AuthedReq{&http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifySSL}}, Jar: jar, }, url} - req, err := authReq.UniReq(LoginPath, json) + req, err := a.UniReq(LoginPath, json) if err != nil { - return nil, errors.Wrap(err, "UniReq(LoginPath, json)") + return a, errors.Wrap(err, "UniReq(LoginPath, json)") } - resp, err := authReq.Do(req) + resp, err := a.Do(req) if err != nil { - return nil, errors.Wrap(err, "authReq.Do(req)") + return a, errors.Wrap(err, "authReq.Do(req)") } defer func() { if err := resp.Body.Close(); err != nil { @@ -85,18 +85,18 @@ func AuthController(user, pass, url string) (*AuthedReq, error) { } }() if resp.StatusCode != http.StatusOK { - return nil, errors.Errorf("authentication failed (%v): %v (status: %v/%v)", + return a, errors.Errorf("authentication failed (%v): %v (status: %v/%v)", user, url+LoginPath, resp.StatusCode, resp.Status) } - return authReq, nil + return a, nil } // UniReq is a small helper function that adds an Accept header. -func (c AuthedReq) UniReq(apiURL string, params string) (req *http.Request, err error) { +func (a AuthedReq) UniReq(apiPath string, params string) (req *http.Request, err error) { if params != "" { - req, err = http.NewRequest("POST", c.baseURL+apiURL, bytes.NewBufferString(params)) + req, err = http.NewRequest("POST", a.baseURL+apiPath, bytes.NewBufferString(params)) } else { - req, err = http.NewRequest("GET", c.baseURL+apiURL, nil) + req, err = http.NewRequest("GET", a.baseURL+apiPath, nil) } if err == nil { req.Header.Add("Accept", "application/json") diff --git a/integrations/influxunifi/unidev/unidev_test.go b/integrations/influxunifi/unidev/unidev_test.go index 210e4099..0557934a 100644 --- a/integrations/influxunifi/unidev/unidev_test.go +++ b/integrations/influxunifi/unidev/unidev_test.go @@ -2,6 +2,8 @@ package unidev import ( "encoding/json" + "io/ioutil" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -11,13 +13,15 @@ func TestFlexInt(t *testing.T) { t.Parallel() a := assert.New(t) type testReply struct { - Five FlexInt `json:"five"` - Seven FlexInt `json:"seven"` - Auto FlexInt `json:"auto"` + Five FlexInt `json:"five"` + Seven FlexInt `json:"seven"` + Auto FlexInt `json:"auto"` + Channel FlexInt `json:"channel"` } var r testReply // test unmarshalling the custom type three times with different values. a.Nil(json.Unmarshal([]byte(`{"five": "5", "seven": 7, "auto": "auto"}`), &r)) + // test number in string. a.EqualValues(5, r.Five.Number) a.EqualValues("5", r.Five.String) @@ -25,8 +29,54 @@ func TestFlexInt(t *testing.T) { a.EqualValues(7, r.Seven.Number) a.EqualValues("7", r.Seven.String) // test string. - a.Nil(json.Unmarshal([]byte(`{"channel": "auto"}`), &r), - "a regular string must not produce an unmarshal error") a.EqualValues(0, r.Auto.Number) a.EqualValues("auto", r.Auto.String) + // test (error) struct. + a.NotNil(json.Unmarshal([]byte(`{"channel": {}}`), &r), + "a non-string and non-number must produce an error.") + a.EqualValues(0, r.Channel.Number) +} + +func TestUniReq(t *testing.T) { + t.Parallel() + a := assert.New(t) + u := "/test/path" + url := "http://some.url:8443" + // Test empty parameters. + authReq := &AuthedReq{&http.Client{}, url} + r, err := authReq.UniReq(u, "") + a.Nil(err, "newrequest must not produce an error") + a.EqualValues(u, r.URL.Path, + "the provided apiPath was not added to http request") + a.EqualValues(url, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") + a.EqualValues("GET", r.Method, "without parameters the method must be GET") + a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") + + // Test with parameters + p := "key1=value9&key2=value7" + authReq = &AuthedReq{&http.Client{}, "http://some.url:8443"} + r, err = authReq.UniReq(u, p) + a.Nil(err, "newrequest must not produce an error") + a.EqualValues(u, r.URL.Path, + "the provided apiPath was not added to http request") + a.EqualValues(url, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") + a.EqualValues("POST", r.Method, "with parameters the method must be POST") + a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") + // Check the parameters. + d, err := ioutil.ReadAll(r.Body) + a.Nil(err, "problem reading request body, POST parameters may be malformed") + a.EqualValues(p, string(d), "POST parameters improperly encoded") +} + +func TestAuthController(t *testing.T) { + t.Parallel() + a := assert.New(t) + url := "http://127.0.0.1:64431" + authReq, err := AuthController("user1", "pass2", url, false) + a.NotNil(err) + a.EqualValues(url, authReq.baseURL) + a.Contains(err.Error(), "authReq.Do(req):", "an invalid destination should product a .Do(req) error.") + /* TODO: OPEN web server, check parameters posted, more. This test is incomplete. + a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params), "user/pass json parameters improperly encoded") + */ } diff --git a/integrations/influxunifi/up.conf.example b/integrations/influxunifi/up.conf.example index eddce77f..8cdc6583 100644 --- a/integrations/influxunifi/up.conf.example +++ b/integrations/influxunifi/up.conf.example @@ -1,4 +1,8 @@ -# The Unifi Controller only updates traffic stats about every 30 seconds. +# unifi-poller primary configuration file. # +# copy this file to: /usr/local/etc/unifi-poller/up.conf # +########################################################## + +# The Unifi Controller only updates traffic stats about every 30 seconds. # Setting this to something lower may lead to "zeros" in your data. You've been warned. interval = "30s"