diff --git a/providers/unifi/api.go b/providers/unifi/api.go index bc0663b707..8e7a9d0eec 100644 --- a/providers/unifi/api.go +++ b/providers/unifi/api.go @@ -245,26 +245,40 @@ func (c *unifiClient) getSiteID() (string, error) { return "", fmt.Errorf("site '%s' not found", c.site) } -// getRecordsNew fetches all DNS policy records using the NEW API. +// dnsPoliciesPageLimit is the number of records to request per page when listing DNS policies. +const dnsPoliciesPageLimit = 200 + +// getRecordsNew fetches all DNS policy records using the NEW API, paginating as needed. func (c *unifiClient) getRecordsNew() ([]dnsPolicyRecord, error) { siteID, err := c.getSiteID() if err != nil { return nil, err } - path := fmt.Sprintf("/integration/v1/sites/%s/dns/policies", siteID) + var allRecords []dnsPolicyRecord + offset := 0 + for { + path := fmt.Sprintf("/integration/v1/sites/%s/dns/policies?offset=%d&limit=%d", siteID, offset, dnsPoliciesPageLimit) - respBytes, err := c.do("GET", path, nil) - if err != nil { - return nil, fmt.Errorf("failed to fetch records: %w", err) - } + respBytes, err := c.do("GET", path, nil) + if err != nil { + return nil, fmt.Errorf("failed to fetch records: %w", err) + } - var response dnsPolicyResponse - if err := json.Unmarshal(respBytes, &response); err != nil { - return nil, fmt.Errorf("failed to parse records: %w", err) + var response dnsPolicyResponse + if err := json.Unmarshal(respBytes, &response); err != nil { + return nil, fmt.Errorf("failed to parse records: %w", err) + } + + allRecords = append(allRecords, response.Data...) + + if len(response.Data) < dnsPoliciesPageLimit { + break + } + offset += dnsPoliciesPageLimit } - return response.Data, nil + return allRecords, nil } // createRecordNew creates a new DNS record using the NEW API. @@ -298,8 +312,8 @@ func (c *unifiClient) updateRecordNew(id string, r *dnsPolicyRecord) (*dnsPolicy path := fmt.Sprintf("/integration/v1/sites/%s/dns/policies/%s", siteID, id) - // Ensure the ID is set in the record - r.ID = id + // The ID is in the URL path; the API rejects it if included in the request body. + r.ID = "" respBytes, err := c.do("PUT", path, r) if err != nil { @@ -346,7 +360,7 @@ func (c *unifiClient) detectAPIAvailability() { if _, err := c.getSiteID(); err == nil { // Site ID fetched successfully, try to get records siteID := c.siteID - path := fmt.Sprintf("/integration/v1/sites/%s/dns/policies", siteID) + path := fmt.Sprintf("/integration/v1/sites/%s/dns/policies?limit=1", siteID) if _, err := c.do("GET", path, nil); err == nil { newAvailable = true } diff --git a/providers/unifi/types.go b/providers/unifi/types.go index 9f16ace343..53fbaf16a5 100644 --- a/providers/unifi/types.go +++ b/providers/unifi/types.go @@ -40,14 +40,14 @@ type dnsPolicyMetadata struct { // This API is available in UniFi Network 10.1+. // The record is polymorphic - different fields are used depending on the type. type dnsPolicyRecord struct { - Type string `json:"type"` // A_RECORD, AAAA_RECORD, CNAME_RECORD, MX_RECORD, TXT_RECORD, SRV_RECORD - ID string `json:"id,omitempty"` // UUID - Enabled bool `json:"enabled"` // Whether the record is enabled - Metadata dnsPolicyMetadata `json:"metadata"` // Metadata (origin) - Domain string `json:"domain"` // FQDN (e.g., "test.example.com") + Type string `json:"type"` // A_RECORD, AAAA_RECORD, CNAME_RECORD, MX_RECORD, TXT_RECORD, SRV_RECORD + ID string `json:"id,omitempty"` // UUID + Enabled bool `json:"enabled"` // Whether the record is enabled + Metadata *dnsPolicyMetadata `json:"metadata,omitempty"` // Metadata (origin, read-only in API responses) + Domain string `json:"domain"` // FQDN (e.g., "test.example.com") - // TTL (optional, 0 = default) - TTLSeconds int `json:"ttlSeconds,omitempty"` + // TTL in seconds (required by the API, always sent) + TTLSeconds int `json:"ttlSeconds"` // Type-specific fields IPv4Address string `json:"ipv4Address,omitempty"` // A record @@ -289,14 +289,13 @@ func recordToNew(rc *models.RecordConfig) (*dnsPolicyRecord, error) { r := &dnsPolicyRecord{ Enabled: true, Domain: rc.NameFQDN, - Metadata: dnsPolicyMetadata{ - Origin: "USER_DEFINED", - }, } - // Set TTL if non-default - if rc.TTL > 0 && rc.TTL != 300 { + // Always send TTL; the API requires ttlSeconds to be non-null. + if rc.TTL > 0 { r.TTLSeconds = int(rc.TTL) + } else { + r.TTLSeconds = 300 } switch rc.Type {