Archived
Fix Dynu DNS record A/AAAA payload encoding
This commit is contained in:
@@ -202,7 +202,7 @@ func (c *Client) GetDNSRecord(ctx context.Context, domainID int64, recordID int6
|
|||||||
|
|
||||||
func (c *Client) CreateDNSRecord(ctx context.Context, domainID int64, req CreateDNSRecordRequest) (*DNSRecord, error) {
|
func (c *Client) CreateDNSRecord(ctx context.Context, domainID int64, req CreateDNSRecordRequest) (*DNSRecord, error) {
|
||||||
var resp getDNSRecordResponse
|
var resp getDNSRecordResponse
|
||||||
if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/dns/%d/record", domainID), req, &resp); err != nil {
|
if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/dns/%d/record", domainID), buildDNSRecordUpsertPayload(req.RecordType, req.NodeName, req.Content, req.TTL, req.State, req.Group, req.Host), &resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &resp.DNSRecord, nil
|
return &resp.DNSRecord, nil
|
||||||
@@ -210,7 +210,7 @@ func (c *Client) CreateDNSRecord(ctx context.Context, domainID int64, req Create
|
|||||||
|
|
||||||
func (c *Client) UpdateDNSRecord(ctx context.Context, domainID int64, recordID int64, req UpdateDNSRecordRequest) (*DNSRecord, error) {
|
func (c *Client) UpdateDNSRecord(ctx context.Context, domainID int64, recordID int64, req UpdateDNSRecordRequest) (*DNSRecord, error) {
|
||||||
var resp getDNSRecordResponse
|
var resp getDNSRecordResponse
|
||||||
if err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/dns/%d/record/%d", domainID, recordID), req, &resp); err != nil {
|
if err := c.doRequest(ctx, http.MethodPut, fmt.Sprintf("/dns/%d/record/%d", domainID, recordID), buildDNSRecordUpsertPayload(req.RecordType, req.NodeName, req.Content, req.TTL, req.State, req.Group, req.Host), &resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &resp.DNSRecord, nil
|
return &resp.DNSRecord, nil
|
||||||
@@ -277,13 +277,64 @@ func (c *Client) doRequest(ctx context.Context, method string, path string, requ
|
|||||||
|
|
||||||
func parseAPIException(payload []byte) error {
|
func parseAPIException(payload []byte) error {
|
||||||
apiResult := apiResponse{}
|
apiResult := apiResponse{}
|
||||||
if err := json.Unmarshal(payload, &apiResult); err != nil || apiResult.Exception == nil {
|
if err := json.Unmarshal(payload, &apiResult); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiResult.Exception != nil {
|
||||||
|
return &APIError{
|
||||||
|
StatusCode: apiResult.Exception.StatusCode,
|
||||||
|
Type: apiResult.Exception.Type,
|
||||||
|
Message: apiResult.Exception.Message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some Dynu responses surface API failures at the top level instead of under exception.
|
||||||
|
topLevel := apiException{}
|
||||||
|
if err := json.Unmarshal(payload, &topLevel); err != nil || topLevel.StatusCode == 0 || topLevel.Type == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &APIError{
|
return &APIError{
|
||||||
StatusCode: apiResult.Exception.StatusCode,
|
StatusCode: topLevel.StatusCode,
|
||||||
Type: apiResult.Exception.Type,
|
Type: topLevel.Type,
|
||||||
Message: apiResult.Exception.Message,
|
Message: topLevel.Message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dnsRecordUpsertPayload struct {
|
||||||
|
NodeName string `json:"nodeName,omitempty"`
|
||||||
|
RecordType string `json:"recordType"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
IPv4Address string `json:"ipv4Address,omitempty"`
|
||||||
|
IPv6Address string `json:"ipv6Address,omitempty"`
|
||||||
|
TTL int64 `json:"ttl,omitempty"`
|
||||||
|
State *bool `json:"state,omitempty"`
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDNSRecordUpsertPayload(recordType string, nodeName string, content string, ttl int64, state *bool, group string, host string) dnsRecordUpsertPayload {
|
||||||
|
payload := dnsRecordUpsertPayload{
|
||||||
|
NodeName: nodeName,
|
||||||
|
RecordType: recordType,
|
||||||
|
Content: content,
|
||||||
|
TTL: ttl,
|
||||||
|
State: state,
|
||||||
|
Group: group,
|
||||||
|
Host: host,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToUpper(strings.TrimSpace(recordType)) {
|
||||||
|
case "A":
|
||||||
|
payload.IPv4Address = content
|
||||||
|
case "AAAA":
|
||||||
|
payload.IPv6Address = content
|
||||||
|
case "CNAME":
|
||||||
|
if payload.Host == "" {
|
||||||
|
payload.Host = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package dynuclient_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -147,3 +150,55 @@ func TestClientDNSRecordWriteAPIError(t *testing.T) {
|
|||||||
t.Fatalf("expected validation API error, got %v", err)
|
t.Fatalf("expected validation API error, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientCreateDNSRecordSendsIPv4AddressForARecord(t *testing.T) {
|
||||||
|
var captured map[string]any
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost || r.URL.Path != "/dns/1001/record" {
|
||||||
|
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&captured); err != nil {
|
||||||
|
t.Fatalf("failed to decode payload: %v", err)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"statusCode": 200,
|
||||||
|
"id": 10,
|
||||||
|
"domainId": 1001,
|
||||||
|
"domainName": "example.com",
|
||||||
|
"nodeName": "www",
|
||||||
|
"hostname": "www.example.com",
|
||||||
|
"recordType": "A",
|
||||||
|
"content": "167.179.167.166",
|
||||||
|
"ttl": 300,
|
||||||
|
"state": true,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := dynuclient.New("test-key", dynuclient.WithBaseURL(server.URL), dynuclient.WithHTTPClient(server.Client()))
|
||||||
|
_, err := client.CreateDNSRecord(context.Background(), 1001, dynuclient.CreateDNSRecordRequest{
|
||||||
|
NodeName: "www",
|
||||||
|
RecordType: "A",
|
||||||
|
Content: "167.179.167.166",
|
||||||
|
TTL: 300,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateDNSRecord() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if captured["ipv4Address"] != "167.179.167.166" {
|
||||||
|
t.Fatalf("expected ipv4Address in payload, got %#v", captured)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientDoRequestTopLevelAPIExceptionPayload(t *testing.T) {
|
||||||
|
fake := fakedynu.NewServer()
|
||||||
|
defer fake.Close()
|
||||||
|
fake.SetRawResponse("/dns", 505, `{"statusCode":505,"type":"Validation Exception","message":"Invalid IP address."}`)
|
||||||
|
|
||||||
|
client := dynuclient.New("test-key", dynuclient.WithBaseURL(fake.BaseURL()), dynuclient.WithHTTPClient(fake.Client()))
|
||||||
|
_, err := client.ListDomains(context.Background())
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Invalid IP address.") {
|
||||||
|
t.Fatalf("expected top-level API error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user