Archived
Fix CNAME updates to use content as target host
This commit is contained in:
@@ -343,7 +343,7 @@ func buildDNSRecordUpsertPayload(recordType string, nodeName string, content *st
|
|||||||
payload.IPv6Address = *normalizedContent
|
payload.IPv6Address = *normalizedContent
|
||||||
}
|
}
|
||||||
case "CNAME":
|
case "CNAME":
|
||||||
if payload.Host == "" && normalizedContent != nil {
|
if normalizedContent != nil {
|
||||||
payload.Host = *normalizedContent
|
payload.Host = *normalizedContent
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -290,6 +290,34 @@ func TestClientCreateDNSRecordSendsHostForCNAMERecord(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientUpdateDNSRecordCNAMEContentOverridesHost(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/11" {
|
||||||
|
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": 11, "domainId": 1001, "recordType": "CNAME", "host": "example.co"})
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := dynuclient.New("test-key", dynuclient.WithBaseURL(server.URL), dynuclient.WithHTTPClient(server.Client()))
|
||||||
|
_, err := client.UpdateDNSRecord(context.Background(), 1001, 11, dynuclient.UpdateDNSRecordRequest{
|
||||||
|
NodeName: "alias",
|
||||||
|
RecordType: "CNAME",
|
||||||
|
Content: stringPointer("example.co"),
|
||||||
|
Host: "example.com",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateDNSRecord() error = %v", err)
|
||||||
|
}
|
||||||
|
if captured["host"] != "example.co" {
|
||||||
|
t.Fatalf("expected host to be overridden by content, got %#v", captured["host"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientCreateDNSRecordSendsContentForTXTRecord(t *testing.T) {
|
func TestClientCreateDNSRecordSendsContentForTXTRecord(t *testing.T) {
|
||||||
var captured map[string]any
|
var captured map[string]any
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateReque
|
|||||||
Group: stringFromOptional(plan.Group),
|
Group: stringFromOptional(plan.Group),
|
||||||
Host: stringFromOptional(plan.Host),
|
Host: stringFromOptional(plan.Host),
|
||||||
}
|
}
|
||||||
|
createReq = normalizeDNSRecordCreateRequestForType(createReq)
|
||||||
if !validateDNSRecordContentForType(createReq.RecordType, createReq.Content, dynamicIntent, &resp.Diagnostics) {
|
if !validateDNSRecordContentForType(createReq.RecordType, createReq.Content, dynamicIntent, &resp.Diagnostics) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -242,6 +243,7 @@ func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateReque
|
|||||||
Group: stringFromOptional(preferKnownString(plan.Group, state.Group)),
|
Group: stringFromOptional(preferKnownString(plan.Group, state.Group)),
|
||||||
Host: stringFromOptional(preferKnownString(plan.Host, state.Host)),
|
Host: stringFromOptional(preferKnownString(plan.Host, state.Host)),
|
||||||
}
|
}
|
||||||
|
updateReq = normalizeDNSRecordUpdateRequestForType(updateReq)
|
||||||
if !validateDNSRecordContentForType(updateReq.RecordType, updateReq.Content, dynamicIntent, &resp.Diagnostics) {
|
if !validateDNSRecordContentForType(updateReq.RecordType, updateReq.Content, dynamicIntent, &resp.Diagnostics) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -315,7 +317,7 @@ func (r *dnsRecordResource) ImportState(ctx context.Context, req resource.Import
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mapDNSRecordToState(record dynuclient.DNSRecord, dynamicIntent bool) dnsRecordResourceModel {
|
func mapDNSRecordToState(record dynuclient.DNSRecord, dynamicIntent bool) dnsRecordResourceModel {
|
||||||
content := normalizeRecordContentForState(record.RecordType, record.Content, dynamicIntent)
|
content := normalizeRecordContentForState(record.RecordType, record.Content, dynamicIntent, record.Host)
|
||||||
return dnsRecordResourceModel{
|
return dnsRecordResourceModel{
|
||||||
Hostname: mapString(record.Hostname),
|
Hostname: mapString(record.Hostname),
|
||||||
RecordType: mapString(record.RecordType),
|
RecordType: mapString(record.RecordType),
|
||||||
@@ -442,6 +444,35 @@ func stringPointer(value string) *string {
|
|||||||
return &value
|
return &value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeDNSRecordCreateRequestForType(req dynuclient.CreateDNSRecordRequest) dynuclient.CreateDNSRecordRequest {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(req.RecordType), "CNAME") {
|
||||||
|
if normalizedContent := normalizeOptionalContentString(req.Content); normalizedContent != nil {
|
||||||
|
req.Host = *normalizedContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeDNSRecordUpdateRequestForType(req dynuclient.UpdateDNSRecordRequest) dynuclient.UpdateDNSRecordRequest {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(req.RecordType), "CNAME") {
|
||||||
|
if normalizedContent := normalizeOptionalContentString(req.Content); normalizedContent != nil {
|
||||||
|
req.Host = *normalizedContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeOptionalContentString(content *string) *string {
|
||||||
|
if content == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimSpace(*content)
|
||||||
|
if trimmed == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &trimmed
|
||||||
|
}
|
||||||
|
|
||||||
func validateDNSRecordContentForType(recordType string, content *string, dynamicIntent bool, diagnostics *diag.Diagnostics) bool {
|
func validateDNSRecordContentForType(recordType string, content *string, dynamicIntent bool, diagnostics *diag.Diagnostics) bool {
|
||||||
return validateDNSRecordContentForTypeWithKnowledge(recordType, content, true, dynamicIntent, diagnostics)
|
return validateDNSRecordContentForTypeWithKnowledge(recordType, content, true, dynamicIntent, diagnostics)
|
||||||
}
|
}
|
||||||
@@ -548,7 +579,7 @@ func inferDynamicIntentFromState(recordType types.String, content types.String,
|
|||||||
return content.IsNull() || (content.IsUnknown())
|
return content.IsNull() || (content.IsUnknown())
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeRecordContentForState(recordType string, content string, dynamicIntent bool) types.String {
|
func normalizeRecordContentForState(recordType string, content string, dynamicIntent bool, host string) types.String {
|
||||||
if dynamicIntent {
|
if dynamicIntent {
|
||||||
return types.StringNull()
|
return types.StringNull()
|
||||||
}
|
}
|
||||||
@@ -569,6 +600,9 @@ func normalizeRecordContentForState(recordType string, content string, dynamicIn
|
|||||||
return types.StringValue(addr.String())
|
return types.StringValue(addr.String())
|
||||||
}
|
}
|
||||||
case "CNAME":
|
case "CNAME":
|
||||||
|
if trimmedHost := strings.TrimSpace(host); trimmedHost != "" {
|
||||||
|
return types.StringValue(strings.TrimSuffix(trimmedHost, "."))
|
||||||
|
}
|
||||||
return types.StringValue(strings.TrimSuffix(trimmed, "."))
|
return types.StringValue(strings.TrimSuffix(trimmed, "."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,17 +146,33 @@ func TestStringPointerFromOptionalContentForValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeRecordContentForState(t *testing.T) {
|
func TestNormalizeRecordContentForState(t *testing.T) {
|
||||||
if got := normalizeRecordContentForState("AAAA", "2001:0db8:0000:0000:0000:0000:0000:0123", false); got.ValueString() != "2001:db8::123" {
|
if got := normalizeRecordContentForState("AAAA", "2001:0db8:0000:0000:0000:0000:0000:0123", false, ""); got.ValueString() != "2001:db8::123" {
|
||||||
t.Fatalf("expected canonical IPv6, got %q", got.ValueString())
|
t.Fatalf("expected canonical IPv6, got %q", got.ValueString())
|
||||||
}
|
}
|
||||||
if got := normalizeRecordContentForState("CNAME", "Example.COM.", false); got.ValueString() != "Example.COM" {
|
if got := normalizeRecordContentForState("CNAME", "Example.COM.", false, ""); got.ValueString() != "Example.COM" {
|
||||||
t.Fatalf("expected trailing dot removed, got %q", got.ValueString())
|
t.Fatalf("expected trailing dot removed, got %q", got.ValueString())
|
||||||
}
|
}
|
||||||
if got := normalizeRecordContentForState("A", "(167.179.167.166)", true); !got.IsNull() {
|
if got := normalizeRecordContentForState("CNAME", "old.example.com", false, "new.example.co."); got.ValueString() != "new.example.co" {
|
||||||
|
t.Fatalf("expected CNAME content to come from host, got %q", got.ValueString())
|
||||||
|
}
|
||||||
|
if got := normalizeRecordContentForState("A", "(167.179.167.166)", true, ""); !got.IsNull() {
|
||||||
t.Fatalf("expected dynamic content to remain null, got %q", got.ValueString())
|
t.Fatalf("expected dynamic content to remain null, got %q", got.ValueString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeDNSRecordUpdateRequestForType(t *testing.T) {
|
||||||
|
oldHost := "example.com"
|
||||||
|
newContent := "example.co"
|
||||||
|
req := normalizeDNSRecordUpdateRequestForType(dynuclient.UpdateDNSRecordRequest{
|
||||||
|
RecordType: "CNAME",
|
||||||
|
Content: &newContent,
|
||||||
|
Host: oldHost,
|
||||||
|
})
|
||||||
|
if req.Host != "example.co" {
|
||||||
|
t.Fatalf("expected CNAME host to be normalized from content, got %q", req.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInferDynamicIntentFromState(t *testing.T) {
|
func TestInferDynamicIntentFromState(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Reference in New Issue
Block a user