diff --git a/core/unifi/.travis.yml b/core/unifi/.travis.yml index 654efb02..b8e3b955 100644 --- a/core/unifi/.travis.yml +++ b/core/unifi/.travis.yml @@ -1,9 +1,9 @@ language: go go: -- 1.14.x +- 1.15.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: -- golangci-lint run --enable-all +- golangci-lint run --enable-all -D exhaustivestruct,nlreturn - go test ./... diff --git a/core/unifi/devices.go b/core/unifi/devices.go index 505d2743..13a5bac0 100644 --- a/core/unifi/devices.go +++ b/core/unifi/devices.go @@ -36,48 +36,27 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices { for _, r := range data { // Loop each item in the raw JSON message, detect its type and unmarshal it. - assetType := "" - - if o := make(map[string]interface{}); u.unmarshalDevice("map", r, &o) != nil { + o := make(map[string]interface{}) + if u.unmarshalDevice("map", r, &o) != nil { + u.ErrorLog("unknown asset type - cannot find asset type in payload - skipping") continue - } else if t, ok := o["type"].(string); ok { - assetType = t } + assetType, _ := o["type"].(string) u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, siteName) // Choose which type to unmarshal into based on the "type" json key. switch assetType { // Unmarshal again into the correct type.. case "uap": - dev := &UAP{SiteName: siteName, SourceName: u.URL} - if u.unmarshalDevice(assetType, r, dev) == nil { - dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) - devices.UAPs = append(devices.UAPs, dev) - } + u.unmarshallUAP(siteName, r, devices) case "ugw", "usg": // in case they ever fix the name in the api. - dev := &USG{SiteName: siteName, SourceName: u.URL} - if u.unmarshalDevice(assetType, r, dev) == nil { - dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) - devices.USGs = append(devices.USGs, dev) - } + u.unmarshallUSG(siteName, r, devices) case "usw": - dev := &USW{SiteName: siteName, SourceName: u.URL} - if u.unmarshalDevice(assetType, r, dev) == nil { - dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) - devices.USWs = append(devices.USWs, dev) - } + u.unmarshallUSW(siteName, r, devices) case "udm": - dev := &UDM{SiteName: siteName, SourceName: u.URL} - if u.unmarshalDevice(assetType, r, dev) == nil { - dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) - devices.UDMs = append(devices.UDMs, dev) - } + u.unmarshallUDM(siteName, r, devices) case "uxg": - dev := &UXG{SiteName: siteName, SourceName: u.URL} - if u.unmarshalDevice(assetType, r, dev) == nil { - dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) - devices.UXGs = append(devices.UXGs, dev) - } + u.unmarshallUXG(siteName, r, devices) default: u.ErrorLog("unknown asset type - %v - skipping", assetType) } @@ -86,6 +65,46 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices { return devices } +func (u *Unifi) unmarshallUAP(siteName string, payload json.RawMessage, devices *Devices) { + dev := &UAP{SiteName: siteName, SourceName: u.URL} + if u.unmarshalDevice("uap", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + devices.UAPs = append(devices.UAPs, dev) + } +} + +func (u *Unifi) unmarshallUSG(siteName string, payload json.RawMessage, devices *Devices) { + dev := &USG{SiteName: siteName, SourceName: u.URL} + if u.unmarshalDevice("ugw", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + devices.USGs = append(devices.USGs, dev) + } +} + +func (u *Unifi) unmarshallUSW(siteName string, payload json.RawMessage, devices *Devices) { + dev := &USW{SiteName: siteName, SourceName: u.URL} + if u.unmarshalDevice("usw", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + devices.USWs = append(devices.USWs, dev) + } +} + +func (u *Unifi) unmarshallUXG(siteName string, payload json.RawMessage, devices *Devices) { + dev := &UXG{SiteName: siteName, SourceName: u.URL} + if u.unmarshalDevice("uxg", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + devices.UXGs = append(devices.UXGs, dev) + } +} + +func (u *Unifi) unmarshallUDM(siteName string, payload json.RawMessage, devices *Devices) { + dev := &UDM{SiteName: siteName, SourceName: u.URL} + if u.unmarshalDevice("udm", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + devices.UDMs = append(devices.UDMs, dev) + } +} + // unmarshalDevice handles logging for the unmarshal operations in parseDevices(). func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) (err error) { if err = json.Unmarshal(data, v); err != nil { @@ -98,7 +117,11 @@ func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) u.DebugLog("==- https://github.com/unifi-poller/unifi/issues/new -==") } - return err + if err != nil { + return fmt.Errorf("json unmarshal: %w", err) + } + + return nil } // pick returns the first non empty string in a list. diff --git a/core/unifi/events.go b/core/unifi/events.go index d716daee..d43cf4f9 100644 --- a/core/unifi/events.go +++ b/core/unifi/events.go @@ -196,5 +196,9 @@ func (v *IPGeo) UnmarshalJSON(data []byte) error { v.CountryName = g.CountryName v.Organization = g.Organization - return err + if err != nil { + return fmt.Errorf("json unmarshal: %w", err) + } + + return nil } diff --git a/core/unifi/go.mod b/core/unifi/go.mod index 219ef9dc..9aade56e 100644 --- a/core/unifi/go.mod +++ b/core/unifi/go.mod @@ -1,10 +1,10 @@ module github.com/unifi-poller/unifi -go 1.14 +go 1.15 require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.4.0 - golang.org/x/net v0.0.0-20200625001655-4c5254603344 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 ) diff --git a/core/unifi/go.sum b/core/unifi/go.sum index 52fd963e..d2c30146 100644 --- a/core/unifi/go.sum +++ b/core/unifi/go.sum @@ -13,10 +13,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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= 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.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/core/unifi/networks.go b/core/unifi/networks.go index 05915432..f0e4a876 100644 --- a/core/unifi/networks.go +++ b/core/unifi/networks.go @@ -38,7 +38,7 @@ func (u *Unifi) parseNetwork(data json.RawMessage, siteName string) (*Network, e return network, u.unmarshalDevice(siteName, data, network) } -// Network is metadata about a network managed by a UniFi controller +// Network is metadata about a network managed by a UniFi controller. type Network struct { DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"` DhcpdEnabled FlexBool `json:"dhcpd_enabled"` diff --git a/core/unifi/types.go b/core/unifi/types.go index b3ce54a8..1413402a 100644 --- a/core/unifi/types.go +++ b/core/unifi/types.go @@ -7,13 +7,9 @@ import ( "strconv" "strings" "time" - - "github.com/pkg/errors" ) -var ( - errCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt") -) +var ErrCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt") // This is a list of unifi API paths. // The %s in each string must be replaced with a Site.Name. @@ -28,17 +24,17 @@ const ( APISiteDPI string = "/api/s/%s/stat/sitedpi" // APISiteDPI is site DPI data. APIClientDPI string = "/api/s/%s/stat/stadpi" - // APIClientPath is Unifi Clients API Path + // APIClientPath is Unifi Clients API Path. APIClientPath string = "/api/s/%s/stat/sta" // APINetworkPath is where we get data about Unifi networks. APINetworkPath string = "/api/s/%s/rest/networkconf" // APIDevicePath is where we get data about Unifi devices. APIDevicePath string = "/api/s/%s/stat/device" - // APILoginPath is Unifi Controller Login API Path + // APILoginPath is Unifi Controller Login API Path. APILoginPath string = "/api/login" - // APILoginPathNew is how we log into UDM 5.12.55+ + // APILoginPathNew is how we log into UDM 5.12.55+. APILoginPathNew string = "/api/auth/login" - // APIEventPathIDS returns Intrusion Detection/Prevention Systems Events + // APIEventPathIDS returns Intrusion Detection/Prevention Systems Events. APIEventPathIDS string = "/api/s/%s/stat/ips/event" // APIEventPathAlarms contains the site alarms. APIEventPathAlarms string = "/api/s/%s/list/alarm" @@ -127,7 +123,7 @@ func (f *FlexInt) UnmarshalJSON(b []byte) error { var unk interface{} if err := json.Unmarshal(b, &unk); err != nil { - return err + return fmt.Errorf("json unmarshal: %w", err) } switch i := unk.(type) { @@ -141,7 +137,7 @@ func (f *FlexInt) UnmarshalJSON(b []byte) error { f.Txt = "0" f.Val = 0 default: - return errors.Wrapf(errCannotUnmarshalFlexInt, "%v", b) + return fmt.Errorf("%v: %w", b, ErrCannotUnmarshalFlexInt) } return nil diff --git a/core/unifi/unifi.go b/core/unifi/unifi.go index b5e8aa21..4f05463b 100644 --- a/core/unifi/unifi.go +++ b/core/unifi/unifi.go @@ -34,7 +34,7 @@ var ( func NewUnifi(config *Config) (*Unifi, error) { jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { - return nil, err + return nil, fmt.Errorf("creating cookiejar: %w", err) } config.URL = strings.TrimRight(config.URL, "/") @@ -85,7 +85,7 @@ func (u *Unifi) Login() error { resp, err := u.Do(req) if err != nil { - return err + return fmt.Errorf("making request: %w", err) } defer resp.Body.Close() // we need no data here. @@ -106,11 +106,21 @@ func (u *Unifi) Login() error { // check if this is a newer controller or not. If it is, we set new to true. // Setting new to true makes the path() method return different (new) paths. func (u *Unifi) checkNewStyleAPI() error { + var ( + ctx = context.Background() + cancel func() + ) + + if u.Config.Timeout != 0 { + ctx, cancel = context.WithTimeout(ctx, u.Config.Timeout) + defer cancel() + } + u.DebugLog("Requesting %s/ to determine API paths", u.URL) - req, err := http.NewRequest("GET", u.URL+"/", nil) + req, err := http.NewRequestWithContext(ctx, "GET", u.URL+"/", nil) if err != nil { - return err + return fmt.Errorf("creating request: %w", err) } // We can't share these cookies with other requests, so make a new client. @@ -126,7 +136,7 @@ func (u *Unifi) checkNewStyleAPI() error { resp, err := client.Do(req) if err != nil { - return err + return fmt.Errorf("making request: %w", err) } defer resp.Body.Close() // we need no data here. @@ -210,7 +220,7 @@ func (u *Unifi) UniReq(apiPath string, params string) (*http.Request, error) { return req, nil } -// UniReqPut is the Put call equivalent to UniReq +// UniReqPut is the Put call equivalent to UniReq. func (u *Unifi) UniReqPut(apiPath string, params string) (*http.Request, error) { if params == "" { return nil, ErrNoParams @@ -239,7 +249,7 @@ func (u *Unifi) GetJSON(apiPath string, params ...string) ([]byte, error) { } // PutJSON uses a PUT call and returns the raw JSON in the same way as GetData -// Use this if you want to change data via the REST API +// Use this if you want to change data via the REST API. func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) { req, err := u.UniReqPut(apiPath, strings.Join(params, " ")) if err != nil { @@ -262,14 +272,14 @@ func (u *Unifi) do(req *http.Request) ([]byte, error) { resp, err := u.Do(req.WithContext(ctx)) if err != nil { - return []byte{}, err + return []byte{}, fmt.Errorf("making request: %w", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - return body, err + return body, fmt.Errorf("reading response: %w", err) } // Save the returned CSRF header.