diff --git a/.changelog/3888.txt b/.changelog/3888.txt new file mode 100644 index 0000000000..36fc356d8f --- /dev/null +++ b/.changelog/3888.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/cloudflare_record: Suppress matching ipv6 dns record +``` diff --git a/internal/sdkv2provider/resource_cloudflare_record.go b/internal/sdkv2provider/resource_cloudflare_record.go index d738ef95f7..33beea687e 100644 --- a/internal/sdkv2provider/resource_cloudflare_record.go +++ b/internal/sdkv2provider/resource_cloudflare_record.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "strings" "time" @@ -501,3 +502,22 @@ func suppressTrailingDots(k, old, new string, d *schema.ResourceData) bool { return strings.TrimSuffix(old, ".") == newTrimmed } + +func suppressMatchingIpv6(old, new string) bool { + oldIpv6 := net.ParseIP(old) + if oldIpv6 == nil || oldIpv6.To16() == nil { + return false + } + newIpv6 := net.ParseIP(new) + if newIpv6 == nil || newIpv6.To16() == nil { + return false + } + return oldIpv6.Equal(newIpv6) +} + +func suppressContent(k, old, new string, d *schema.ResourceData) bool { + if suppressMatchingIpv6(old, new) { + return true + } + return suppressTrailingDots(k, old, new, d) +} diff --git a/internal/sdkv2provider/resource_cloudflare_record_test.go b/internal/sdkv2provider/resource_cloudflare_record_test.go index 7297150291..0456329958 100644 --- a/internal/sdkv2provider/resource_cloudflare_record_test.go +++ b/internal/sdkv2provider/resource_cloudflare_record_test.go @@ -13,6 +13,7 @@ import ( "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -672,6 +673,35 @@ func TestAccCloudflareRecord_ClearTags(t *testing.T) { }) } +func TestAccCloudflareRecord_CompareIPv6(t *testing.T) { + t.Parallel() + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + rnd := generateRandomResourceName() + name := fmt.Sprintf("cloudflare_record.%s", rnd) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckCloudflareRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareRecordConfigIPv6(zoneID, rnd, rnd, "2001:4860:4860:0:0:0:0:8888"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "name", rnd), + resource.TestCheckResourceAttr(name, "type", "AAAA"), + resource.TestCheckResourceAttr(name, "content", "2001:4860:4860::8888"), + ), + }, + { + Config: testAccCheckCloudflareRecordConfigIPv6(zoneID, rnd, rnd, "2001:4860:4860::8888"), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, + }, + }, + }, + }) +} + func TestSuppressTrailingDots(t *testing.T) { t.Parallel() @@ -697,6 +727,28 @@ func TestSuppressTrailingDots(t *testing.T) { } } +func TestIPv6Comparison(t *testing.T) { + t.Parallel() + + cases := []struct { + old string + new string + }{ + {"2001:db8:3333:4444:5555:6666:7777:8888", "2001:db8:3333:4444:5555:6666:7777:8888"}, + {"1050:0000:0000:0000:0005:0600:300c:326b", "1050:0:0:0:5:0600:300c:326b"}, + {"ff06:0:0:0:0:0:0:c3", "ff06::c3"}, + {"0:0:0:0:0:0:0:0", "::"}, + {"2001:db8::", "2001:db8:0:0:0:0:0:0"}, + {"::1234:5678", "0:0:0:0:0:0:1234:5678"}, + {"2001:db8:0:0:0:0:1234:5678", "2001:db8::1234:5678"}, + } + + for _, c := range cases { + got := suppressContent("", c.old, c.new, nil) + assert.Equal(t, true, got) + } +} + func testAccCheckCloudflareRecordRecreated(before, after *cloudflare.DNSRecord) resource.TestCheckFunc { return func(s *terraform.State) error { if before.ID == after.ID { @@ -1076,3 +1128,14 @@ func testAccCheckCloudflareRecordDNSKEY(zoneID, name string) string { } `, zoneID, name) } + +func testAccCheckCloudflareRecordConfigIPv6(zoneID, name, rnd, content string) string { + return fmt.Sprintf(` +resource "cloudflare_record" "%[3]s" { + zone_id = "%[1]s" + name = "%[2]s" + content = "%[4]s" + type = "AAAA" + ttl = 3600 +}`, zoneID, name, rnd, content) +} diff --git a/internal/sdkv2provider/schema_cloudflare_record.go b/internal/sdkv2provider/schema_cloudflare_record.go index 40fbc8eb8a..f370829af1 100644 --- a/internal/sdkv2provider/schema_cloudflare_record.go +++ b/internal/sdkv2provider/schema_cloudflare_record.go @@ -58,7 +58,7 @@ func resourceCloudflareRecordSchema() map[string]*schema.Schema { Optional: true, Computed: true, ExactlyOneOf: []string{"data", "content", "value"}, - DiffSuppressFunc: suppressTrailingDots, + DiffSuppressFunc: suppressContent, Description: "The content of the record.", },