mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-12-09 21:55:57 +08:00
Fixes https://github.com/StackExchange/dnscontrol/issues/3379 Thanks to @SukkaW for adding this provider! Even though you claimed to be "not familiar with Go at all" the new code looks excellent! Great job!
171 lines
4.6 KiB
Go
171 lines
4.6 KiB
Go
package vercel
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
vercelClient "github.com/vercel/terraform-provider-vercel/client"
|
|
)
|
|
|
|
// DNSRecord is a helper struct to unmarshal the JSON response.
|
|
// It embeds vercelClient.DNSRecord to reuse the upstream type,
|
|
// but adds fields to handle API inconsistencies (type vs recordType, mxPriority).
|
|
type DNSRecord struct {
|
|
vercelClient.DNSRecord
|
|
Type string `json:"type"`
|
|
// Normally MXPriority would be uint16 type, but since vercelClient.DNSRecord uses int64, we'd better be consistent here
|
|
// Later in GetZoneRecords we do a `uint16OrZero` to ensure the type is correct
|
|
MXPriority int64 `json:"mxPriority"`
|
|
}
|
|
|
|
// pagination represents the pagination object in Vercel API responses.
|
|
type pagination struct {
|
|
Count int64 `json:"count"`
|
|
Next *int64 `json:"next"`
|
|
Prev *int64 `json:"prev"`
|
|
}
|
|
|
|
// listResponse represents the response from the Vercel List DNS Records API.
|
|
type listResponse struct {
|
|
Records []DNSRecord `json:"records"`
|
|
Pagination pagination `json:"pagination"`
|
|
}
|
|
|
|
// Vercel API limit is max 100
|
|
const vercelAPIPaginationLimit = 100
|
|
|
|
// ListDNSRecords retrieves all DNS records for a domain, handling pagination.
|
|
func (c *vercelProvider) ListDNSRecords(ctx context.Context, domain string) ([]DNSRecord, error) {
|
|
var allRecords []DNSRecord
|
|
var nextTimestamp int64
|
|
|
|
for {
|
|
url := fmt.Sprintf("https://api.vercel.com/v4/domains/%s/records?limit=%d", domain, vercelAPIPaginationLimit)
|
|
if c.teamID != "" {
|
|
url += fmt.Sprintf("&teamId=%s", c.teamID)
|
|
}
|
|
if nextTimestamp != 0 {
|
|
url += fmt.Sprintf("&until=%d", nextTimestamp)
|
|
}
|
|
|
|
var result listResponse
|
|
err := c.doRequest(clientRequest{
|
|
ctx: ctx,
|
|
method: http.MethodGet,
|
|
url: url,
|
|
}, &result, c.listLimiter)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list DNS records: %w", err)
|
|
}
|
|
|
|
for _, r := range result.Records {
|
|
// The official SDK expects 'recordType' but the API returns 'type'.
|
|
// We explicitly map it here to fix the discrepancy.
|
|
r.RecordType = r.Type
|
|
// Ensure Domain field is set (it might not be in the record object itself)
|
|
if r.Domain == "" {
|
|
r.Domain = domain
|
|
}
|
|
if r.TeamID == "" {
|
|
r.TeamID = c.teamID
|
|
}
|
|
|
|
allRecords = append(allRecords, r)
|
|
}
|
|
|
|
if result.Pagination.Next == nil {
|
|
break
|
|
}
|
|
nextTimestamp = *result.Pagination.Next
|
|
}
|
|
|
|
return allRecords, nil
|
|
}
|
|
|
|
// httpsRecord structure for Vercel API
|
|
type httpsRecord struct {
|
|
Priority int64 `json:"priority"`
|
|
Target string `json:"target"`
|
|
Params string `json:"params,omitempty"`
|
|
}
|
|
|
|
// createDNSRecordRequest embeds the official SDK request but adds HTTPS support
|
|
type createDNSRecordRequest struct {
|
|
vercelClient.CreateDNSRecordRequest
|
|
HTTPS *httpsRecord `json:"https,omitempty"`
|
|
}
|
|
|
|
// CreateDNSRecord creates a DNS record.
|
|
func (c *vercelProvider) CreateDNSRecord(ctx context.Context, req createDNSRecordRequest) (*vercelClient.DNSRecord, error) {
|
|
url := fmt.Sprintf("https://api.vercel.com/v4/domains/%s/records", req.Domain)
|
|
if c.teamID != "" {
|
|
url += "?teamId=" + c.teamID
|
|
}
|
|
|
|
var response struct {
|
|
RecordID string `json:"uid"`
|
|
}
|
|
|
|
payloadJSON, err := json.Marshal(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = c.doRequest(clientRequest{
|
|
ctx: ctx,
|
|
method: http.MethodPost,
|
|
url: url,
|
|
body: string(payloadJSON),
|
|
}, &response, c.createLimiter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &vercelClient.DNSRecord{ID: response.RecordID}, nil
|
|
}
|
|
|
|
// updateDNSRecordRequest embeds the official SDK request but adds HTTPS support
|
|
type updateDNSRecordRequest struct {
|
|
vercelClient.UpdateDNSRecordRequest
|
|
HTTPS *httpsRecord `json:"https,omitempty"`
|
|
}
|
|
|
|
// UpdateDNSRecord updates a DNS record.
|
|
func (c *vercelProvider) UpdateDNSRecord(ctx context.Context, recordID string, req updateDNSRecordRequest) (*vercelClient.DNSRecord, error) {
|
|
url := fmt.Sprintf("https://api.vercel.com/v4/domains/records/%s", recordID)
|
|
if c.teamID != "" {
|
|
url += "?teamId=" + c.teamID
|
|
}
|
|
|
|
payloadJSON, err := json.Marshal(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result vercelClient.DNSRecord
|
|
err = c.doRequest(clientRequest{
|
|
ctx: ctx,
|
|
method: http.MethodPatch,
|
|
url: url,
|
|
body: string(payloadJSON),
|
|
}, &result, c.updateLimiter)
|
|
|
|
return &result, err
|
|
}
|
|
|
|
// DeleteDNSRecord deletes a DNS record.
|
|
func (c *vercelProvider) DeleteDNSRecord(ctx context.Context, domain string, recordID string) error {
|
|
url := fmt.Sprintf("https://api.vercel.com/v2/domains/%s/records/%s", domain, recordID)
|
|
if c.teamID != "" {
|
|
url += "?teamId=" + c.teamID
|
|
}
|
|
|
|
return c.doRequest(clientRequest{
|
|
ctx: ctx,
|
|
method: http.MethodDelete,
|
|
url: url,
|
|
}, nil, c.deleteLimiter)
|
|
}
|