Handle unknown dns_record content during config validation

This commit is contained in:
beatz174-bit
2026-04-29 10:58:46 +10:00
parent 39f09ed9ae
commit b6fe321060
2 changed files with 71 additions and 2 deletions
+17 -2
View File
@@ -110,12 +110,12 @@ func (r *dnsRecordResource) ValidateConfig(ctx context.Context, req resource.Val
if skip { if skip {
return return
} }
content := stringPointerFromOptionalContent(config.Content) content, contentKnown := stringPointerFromOptionalContentForValidation(config.Content)
dynamicIntent, ok := resolveDynamicIntent(recordType, config.Content, config.Dynamic, &resp.Diagnostics) dynamicIntent, ok := resolveDynamicIntent(recordType, config.Content, config.Dynamic, &resp.Diagnostics)
if !ok { if !ok {
return return
} }
validateDNSRecordContentForType(recordType, content, dynamicIntent, &resp.Diagnostics) validateDNSRecordContentForTypeWithKnowledge(recordType, content, contentKnown, dynamicIntent, &resp.Diagnostics)
} }
func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
@@ -393,11 +393,22 @@ func stringPointerFromOptionalContent(value types.String) *string {
return &trimmed return &trimmed
} }
func stringPointerFromOptionalContentForValidation(value types.String) (*string, bool) {
if value.IsUnknown() {
return nil, false
}
return stringPointerFromOptionalContent(value), true
}
func stringPointer(value string) *string { func stringPointer(value string) *string {
return &value return &value
} }
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)
}
func validateDNSRecordContentForTypeWithKnowledge(recordType string, content *string, contentKnown bool, dynamicIntent bool, diagnostics *diag.Diagnostics) bool {
normalizedType := strings.ToUpper(strings.TrimSpace(recordType)) normalizedType := strings.ToUpper(strings.TrimSpace(recordType))
trimmedContent := "" trimmedContent := ""
if content != nil { if content != nil {
@@ -440,6 +451,10 @@ func validateDNSRecordContentForType(recordType string, content *string, dynamic
return false return false
} }
if !contentKnown {
return true
}
if trimmedContent == "" { if trimmedContent == "" {
diagnostics.AddError( diagnostics.AddError(
"Missing required content for DNS record type", "Missing required content for DNS record type",
@@ -1,6 +1,7 @@
package provider package provider
import ( import (
"strings"
"testing" "testing"
"github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/diag"
@@ -79,6 +80,59 @@ func TestResolveDynamicIntent(t *testing.T) {
} }
} }
func TestValidateDNSRecordContentForTypeWithKnowledge(t *testing.T) {
nonEmpty := "example.com"
blank := ""
tests := []struct {
name string
recordType string
content *string
contentKnown bool
dynamic bool
wantValid bool
wantMessage string
}{
{name: "CNAME unknown content is allowed during validate", recordType: "CNAME", content: nil, contentKnown: false, wantValid: true},
{name: "CNAME null content errors", recordType: "CNAME", content: nil, contentKnown: true, wantValid: false, wantMessage: "Missing required content"},
{name: "CNAME empty content errors", recordType: "CNAME", content: &blank, contentKnown: true, wantValid: false, wantMessage: "Missing required content"},
{name: "CNAME known content passes", recordType: "CNAME", content: &nonEmpty, contentKnown: true, wantValid: true},
{name: "CNAME dynamic true errors", recordType: "CNAME", content: nil, contentKnown: false, dynamic: true, wantValid: false, wantMessage: "Dynamic mode is only supported"},
{name: "A dynamic omitted content passes", recordType: "A", content: nil, contentKnown: true, dynamic: true, wantValid: true},
{name: "AAAA dynamic omitted content passes", recordType: "AAAA", content: nil, contentKnown: true, dynamic: true, wantValid: true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
diags := diag.Diagnostics{}
got := validateDNSRecordContentForTypeWithKnowledge(tc.recordType, tc.content, tc.contentKnown, tc.dynamic, &diags)
if got != tc.wantValid {
t.Fatalf("validateDNSRecordContentForTypeWithKnowledge()=%v, want %v", got, tc.wantValid)
}
if tc.wantValid && diags.HasError() {
t.Fatalf("expected no diagnostics, got %#v", diags)
}
if !tc.wantValid {
if !diags.HasError() {
t.Fatal("expected diagnostics but got none")
}
if tc.wantMessage != "" && diags[0].Summary() != "" && !strings.Contains(diags[0].Summary(), tc.wantMessage) {
t.Fatalf("expected first diagnostic summary to contain %q, got %q", tc.wantMessage, diags[0].Summary())
}
}
})
}
}
func TestStringPointerFromOptionalContentForValidation(t *testing.T) {
if content, known := stringPointerFromOptionalContentForValidation(types.StringUnknown()); known || content != nil {
t.Fatalf("expected unknown content to be unknown with nil pointer, got known=%v content=%v", known, content)
}
if content, known := stringPointerFromOptionalContentForValidation(types.StringNull()); !known || content != nil {
t.Fatalf("expected null content to be known with nil pointer, got known=%v content=%v", known, content)
}
}
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())