mirror of
https://github.com/unpoller/unpoller.git
synced 2026-03-31 06:24:19 -04:00
move remote.go to use unifi library functions
This commit is contained in:
4
go.mod
4
go.mod
@@ -23,7 +23,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.49.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -48,4 +48,4 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// for local iterative development only
|
// for local iterative development only
|
||||||
// replace github.com/unpoller/unifi/v5 => ../unifi
|
replace github.com/unpoller/unifi/v5 => ../unifi
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -77,8 +77,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/unpoller/unifi/v5 v5.5.0 h1:MxSbCYBxgg1NN4R0w6h+ES2R4QNM1vhmwjQhJVwVDp0=
|
|
||||||
github.com/unpoller/unifi/v5 v5.5.0/go.mod h1:pa6zv4Oyb1nFEm4qu/8CUv8Q25hQof04Wh2D0RXcTYc=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
@@ -92,8 +90,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|||||||
@@ -1,228 +1,29 @@
|
|||||||
package inputunifi
|
package inputunifi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
"github.com/unpoller/unifi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// discoverRemoteControllers discovers all controllers via remote API and creates Controller entries.
|
||||||
remoteAPIBaseURL = "https://api.ui.com"
|
func (u *InputUnifi) discoverRemoteControllers(apiKey string) ([]*Controller, error) {
|
||||||
remoteAPIVersion = "v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Console represents a UniFi console from the remote API.
|
|
||||||
type Console struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
IPAddress string `json:"ipAddress"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Owner bool `json:"owner"`
|
|
||||||
IsBlocked bool `json:"isBlocked"`
|
|
||||||
ReportedState struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Hostname string `json:"hostname"`
|
|
||||||
IP string `json:"ip"`
|
|
||||||
State string `json:"state"`
|
|
||||||
Mac string `json:"mac"`
|
|
||||||
} `json:"reportedState"`
|
|
||||||
ConsoleName string // Derived field: name from reportedState
|
|
||||||
}
|
|
||||||
|
|
||||||
// HostsResponse represents the response from /v1/hosts endpoint.
|
|
||||||
type HostsResponse struct {
|
|
||||||
Data []Console `json:"data"`
|
|
||||||
HTTPStatusCode int `json:"httpStatusCode"`
|
|
||||||
TraceID string `json:"traceId"`
|
|
||||||
NextToken string `json:"nextToken,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Site represents a site from the remote API.
|
|
||||||
type RemoteSite struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SitesResponse represents the response from the sites endpoint.
|
|
||||||
type SitesResponse struct {
|
|
||||||
Data []RemoteSite `json:"data"`
|
|
||||||
HTTPStatusCode int `json:"httpStatusCode"`
|
|
||||||
TraceID string `json:"traceId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// remoteAPIClient handles HTTP requests to the remote UniFi API.
|
|
||||||
type remoteAPIClient struct {
|
|
||||||
apiKey string
|
|
||||||
baseURL string
|
|
||||||
client *http.Client
|
|
||||||
logError func(string, ...any)
|
|
||||||
logDebug func(string, ...any)
|
|
||||||
log func(string, ...any)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newRemoteAPIClient creates a new remote API client.
|
|
||||||
func (u *InputUnifi) newRemoteAPIClient(apiKey string) *remoteAPIClient {
|
|
||||||
if apiKey == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle file:// prefix for API key
|
// Handle file:// prefix for API key
|
||||||
if strings.HasPrefix(apiKey, "file://") {
|
if strings.HasPrefix(apiKey, "file://") {
|
||||||
apiKey = u.getPassFromFile(strings.TrimPrefix(apiKey, "file://"))
|
apiKey = u.getPassFromFile(strings.TrimPrefix(apiKey, "file://"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &remoteAPIClient{
|
if apiKey == "" {
|
||||||
apiKey: apiKey,
|
|
||||||
baseURL: remoteAPIBaseURL,
|
|
||||||
client: &http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logError: u.LogErrorf,
|
|
||||||
logDebug: u.LogDebugf,
|
|
||||||
log: u.Logf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeRequest makes an HTTP request to the remote API.
|
|
||||||
func (c *remoteAPIClient) makeRequest(method, path string, queryParams map[string]string) ([]byte, error) {
|
|
||||||
fullURL := c.baseURL + path
|
|
||||||
|
|
||||||
if len(queryParams) > 0 {
|
|
||||||
u, err := url.Parse(fullURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing URL: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
q := u.Query()
|
|
||||||
for k, v := range queryParams {
|
|
||||||
q.Set(k, v)
|
|
||||||
}
|
|
||||||
u.RawQuery = q.Encode()
|
|
||||||
fullURL = u.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.logDebug("Making %s request to: %s", method, fullURL)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(method, fullURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
req.Header.Set("X-API-Key", c.apiKey)
|
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("making request: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("reading response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
||||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// discoverConsoles discovers all consoles available via the remote API.
|
|
||||||
func (c *remoteAPIClient) discoverConsoles() ([]Console, error) {
|
|
||||||
// Start with first page
|
|
||||||
queryParams := map[string]string{
|
|
||||||
"pageSize": "10",
|
|
||||||
}
|
|
||||||
|
|
||||||
var allConsoles []Console
|
|
||||||
nextToken := ""
|
|
||||||
|
|
||||||
for {
|
|
||||||
if nextToken != "" {
|
|
||||||
queryParams["nextToken"] = nextToken
|
|
||||||
} else {
|
|
||||||
// Remove nextToken from params for first request
|
|
||||||
delete(queryParams, "nextToken")
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := c.makeRequest("GET", "/v1/hosts", queryParams)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fetching consoles: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var response HostsResponse
|
|
||||||
if err := json.Unmarshal(body, &response); err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing consoles response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter for console type only
|
|
||||||
for _, console := range response.Data {
|
|
||||||
if console.Type == "console" && !console.IsBlocked {
|
|
||||||
// Extract the console name from reportedState
|
|
||||||
console.ConsoleName = console.ReportedState.Name
|
|
||||||
if console.ConsoleName == "" {
|
|
||||||
console.ConsoleName = console.ReportedState.Hostname
|
|
||||||
}
|
|
||||||
allConsoles = append(allConsoles, console)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there's a nextToken to continue pagination
|
|
||||||
if response.NextToken == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
nextToken = response.NextToken
|
|
||||||
c.logDebug("Fetching next page of consoles with nextToken: %s", nextToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
return allConsoles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// discoverSites discovers all sites for a given console ID.
|
|
||||||
func (c *remoteAPIClient) discoverSites(consoleID string) ([]RemoteSite, error) {
|
|
||||||
path := fmt.Sprintf("/v1/connector/consoles/%s/proxy/network/integration/v1/sites", consoleID)
|
|
||||||
|
|
||||||
queryParams := map[string]string{
|
|
||||||
"offset": "0",
|
|
||||||
"limit": "100",
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := c.makeRequest("GET", path, queryParams)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fetching sites for console %s: %w", consoleID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var response SitesResponse
|
|
||||||
if err := json.Unmarshal(body, &response); err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing sites response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// discoverRemoteControllers discovers all controllers via remote API and creates Controller entries.
|
|
||||||
func (u *InputUnifi) discoverRemoteControllers(apiKey string) ([]*Controller, error) {
|
|
||||||
client := u.newRemoteAPIClient(apiKey)
|
|
||||||
if client == nil {
|
|
||||||
return nil, fmt.Errorf("remote API key not provided")
|
return nil, fmt.Errorf("remote API key not provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use library client
|
||||||
|
client := unifi.NewRemoteAPIClient(apiKey, u.LogErrorf, u.LogDebugf, u.Logf)
|
||||||
|
|
||||||
u.Logf("Discovering remote UniFi consoles...")
|
u.Logf("Discovering remote UniFi consoles...")
|
||||||
|
|
||||||
consoles, err := client.discoverConsoles()
|
consoles, err := client.DiscoverConsoles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("discovering consoles: %w", err)
|
return nil, fmt.Errorf("discovering consoles: %w", err)
|
||||||
}
|
}
|
||||||
@@ -246,7 +47,7 @@ func (u *InputUnifi) discoverRemoteControllers(apiKey string) ([]*Controller, er
|
|||||||
}
|
}
|
||||||
u.LogDebugf("Discovering sites for console: %s (%s)", console.ID, consoleName)
|
u.LogDebugf("Discovering sites for console: %s (%s)", console.ID, consoleName)
|
||||||
|
|
||||||
sites, err := client.discoverSites(console.ID)
|
sites, err := client.DiscoverSites(console.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.LogErrorf("Failed to discover sites for console %s: %v", console.ID, err)
|
u.LogErrorf("Failed to discover sites for console %s: %v", console.ID, err)
|
||||||
continue
|
continue
|
||||||
@@ -268,7 +69,7 @@ func (u *InputUnifi) discoverRemoteControllers(apiKey string) ([]*Controller, er
|
|||||||
// Set URL to connector base - the library appends /proxy/network/status
|
// Set URL to connector base - the library appends /proxy/network/status
|
||||||
// But for integration API we need /proxy/network/integration/v1/...
|
// But for integration API we need /proxy/network/integration/v1/...
|
||||||
// This may require library updates, but try connector base first
|
// This may require library updates, but try connector base first
|
||||||
URL: fmt.Sprintf("%s/v1/connector/consoles/%s", remoteAPIBaseURL, console.ID),
|
URL: fmt.Sprintf("%s/v1/connector/consoles/%s", unifi.RemoteAPIBaseURL, console.ID),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure defaults are set before calling setControllerDefaults
|
// Ensure defaults are set before calling setControllerDefaults
|
||||||
|
|||||||
Reference in New Issue
Block a user