From be05e0d78b5f9d289d6272844da30d7dacdd0477 Mon Sep 17 00:00:00 2001 From: Tom Noonan II Date: Wed, 10 Feb 2021 15:02:02 -0500 Subject: [PATCH 1/4] Refactor powerdns/resource_powerdns_record_test.go to support checking attribute values --- powerdns/resource_powerdns_record_test.go | 826 +++++++++------------- 1 file changed, 349 insertions(+), 477 deletions(-) diff --git a/powerdns/resource_powerdns_record_test.go b/powerdns/resource_powerdns_record_test.go index 4641387d..dd610b70 100644 --- a/powerdns/resource_powerdns_record_test.go +++ b/powerdns/resource_powerdns_record_test.go @@ -3,19 +3,25 @@ package powerdns import ( "fmt" "regexp" + "strconv" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" ) +// +// Tests +// + func TestAccPDNSRecord_Empty(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testPDNSRecordConfigRecordEmpty, + Config: testPDNSRecordConfigRecordEmpty().ResourceDeclaration(), ExpectError: regexp.MustCompile("'records' must not be empty"), }, }, @@ -23,33 +29,11 @@ func TestAccPDNSRecord_Empty(t *testing.T) { } func TestAccPDNSRecord_A(t *testing.T) { - resourceName := "powerdns_record.test-a" - resourceID := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfiga.sysa.xyz.:::A"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigA, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigA()) } func TestAccPDNSRecord_WithPtr(t *testing.T) { - resourceName := "powerdns_record.test-a-ptr" - resourceID := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfigawithptr.sysa.xyz.:::A"}` + recordConfig := testPDNSRecordConfigAWithPtr() resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -57,23 +41,25 @@ func TestAccPDNSRecord_WithPtr(t *testing.T) { CheckDestroy: testAccCheckPDNSRecordDestroy, Steps: []resource.TestStep{ { - Config: testPDNSRecordConfigAWithPtr, + Config: recordConfig.ResourceDeclaration(), Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), + testAccCheckPDNSRecordContents(recordConfig), ), }, { - ResourceName: resourceName, - ImportStateId: resourceID, + ResourceName: recordConfig.ResourceName(), + ImportStateId: recordConfig.ResourceID(), ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"set_ptr"}, + ImportStateVerifyIgnore: []string{"set_ptr"}, // Variance from common function }, }, }) } +// Use a basic existance check on the count resources, to avoid having to resolve interpolations in the names. func TestAccPDNSRecord_WithCount(t *testing.T) { + recordConfig := testPDNSRecordConfigHyphenedWithCount() resourceID0 := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfighyphenedwithcount-0.sysa.xyz.:::A"}` resourceID1 := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfighyphenedwithcount-1.sysa.xyz.:::A"}` @@ -83,7 +69,7 @@ func TestAccPDNSRecord_WithCount(t *testing.T) { CheckDestroy: testAccCheckPDNSRecordDestroy, Steps: []resource.TestStep{ { - Config: testPDNSRecordConfigHyphenedWithCount, + Config: recordConfig.ResourceDeclaration(), Check: resource.ComposeTestCheckFunc( testAccCheckPDNSRecordExists("powerdns_record.test-counted.0"), testAccCheckPDNSRecordExists("powerdns_record.test-counted.1"), @@ -106,337 +92,136 @@ func TestAccPDNSRecord_WithCount(t *testing.T) { } func TestAccPDNSRecord_AAAA(t *testing.T) { - resourceName := "powerdns_record.test-aaaa" - resourceID := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfigaaaa.sysa.xyz.:::AAAA"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigAAAA, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigAAAA()) } func TestAccPDNSRecord_CNAME(t *testing.T) { - resourceName := "powerdns_record.test-cname" - resourceID := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfigcname.sysa.xyz.:::CNAME"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigCNAME, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigCNAME()) } func TestAccPDNSRecord_HINFO(t *testing.T) { - resourceName := "powerdns_record.test-hinfo" - resourceID := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfighinfo.sysa.xyz.:::HINFO"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigHINFO, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigHINFO()) } func TestAccPDNSRecord_LOC(t *testing.T) { - resourceName := "powerdns_record.test-loc" - resourceID := `{"zone":"sysa.xyz.","id":"testpdnsrecordconfigloc.sysa.xyz.:::LOC"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigLOC, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigLOC()) } func TestAccPDNSRecord_MX(t *testing.T) { - resourceName := "powerdns_record.test-mx" - resourceNameMulti := "powerdns_record.test-mx-multi" - resourceID := `{"zone":"sysa.xyz.","id":"sysa.xyz.:::MX"}` - resourceIDMulti := `{"zone":"sysa.xyz.","id":"multi.sysa.xyz.:::MX"}` + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigMX()) +} - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigMX, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testPDNSRecordConfigMXMulti, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceNameMulti), - ), - }, - { - ResourceName: resourceNameMulti, - ImportStateId: resourceIDMulti, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) +func TestAccPDNSRecord_MXMulti(t *testing.T) { + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigMXMulti()) } func TestAccPDNSRecord_NAPTR(t *testing.T) { - resourceName := "powerdns_record.test-naptr" - resourceID := `{"zone":"sysa.xyz.","id":"sysa.xyz.:::NAPTR"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigNAPTR, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigNAPTR()) } func TestAccPDNSRecord_NS(t *testing.T) { - resourceName := "powerdns_record.test-ns" - resourceID := `{"zone":"sysa.xyz.","id":"lab.sysa.xyz.:::NS"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigNS, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigNS()) } func TestAccPDNSRecord_SPF(t *testing.T) { - resourceName := "powerdns_record.test-spf" - resourceID := `{"zone":"sysa.xyz.","id":"sysa.xyz.:::SPF"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigSPF, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSPF()) } func TestAccPDNSRecord_SSHFP(t *testing.T) { - resourceName := "powerdns_record.test-sshfp" - resourceID := `{"zone":"sysa.xyz.","id":"ssh.sysa.xyz.:::SSHFP"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigSSHFP, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSSHFP()) } func TestAccPDNSRecord_SRV(t *testing.T) { - resourceName := "powerdns_record.test-srv" - resourceID := `{"zone":"sysa.xyz.","id":"_redis._tcp.sysa.xyz.:::SRV"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigSRV, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSRV()) } func TestAccPDNSRecord_TXT(t *testing.T) { - resourceName := "powerdns_record.test-txt" - resourceID := `{"zone":"sysa.xyz.","id":"text.sysa.xyz.:::TXT"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigTXT, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigTXT()) } func TestAccPDNSRecord_ALIAS(t *testing.T) { - resourceName := "powerdns_record.test-alias" - resourceID := `{"zone":"sysa.xyz.","id":"alias.sysa.xyz.:::ALIAS"}` - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPDNSRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testPDNSRecordConfigALIAS, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportStateId: resourceID, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigALIAS()) } func TestAccPDNSRecord_SOA(t *testing.T) { - resourceName := "powerdns_record.test-soa" - resourceID := `{"zone":"test-soa-sysa.xyz.","id":"test-soa-sysa.xyz.:::SOA"}` + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSOA()) +} +// +// Test resource declaration functions +// +// Pattern: testPDNSRecordConfigXXX() returns a PowerDNSRecordResource struct +// The PowerDNSRecordResource struct can be used to query test config, update attributes for update tests, +// and can have ResourceDeclaration() called against it to generate the Terraform DSL resource block string. +// +type PowerDNSRecordResourceArguments struct { + Count int + Zone string + Name string + Type string + TTL int + Records []string + SetPtr bool +} + +type PowerDNSRecordResource struct { + Name string + Arguments *PowerDNSRecordResourceArguments +} + +func (resourceConfig *PowerDNSRecordResource) ResourceDeclaration() string { + var encapsulatedRecords []string + for _, record := range resourceConfig.Arguments.Records { + encapsulatedRecords = append(encapsulatedRecords, (`"` + strings.Replace(record, `"`, `\"`, -1) + `"`)) + } + + resourceDeclaration := `resource "powerdns_record" "` + resourceConfig.Name + "\" {\n" + if resourceConfig.Arguments.Count > 0 { + resourceDeclaration += " count = " + strconv.Itoa(resourceConfig.Arguments.Count) + "\n" + } + + // zone, name, type, ttl, and records are mandatory + resourceDeclaration += ` zone = "` + resourceConfig.Arguments.Zone + "\"\n" + resourceDeclaration += ` name = "` + resourceConfig.Arguments.Name + "\"\n" + resourceDeclaration += ` type = "` + resourceConfig.Arguments.Type + "\"\n" + resourceDeclaration += " ttl = " + strconv.Itoa(resourceConfig.Arguments.TTL) + "\n" + resourceDeclaration += " records = [ " + strings.Join(encapsulatedRecords, ", ") + " ]\n" + + if resourceConfig.Arguments.SetPtr { + resourceDeclaration += " set_ptr = true\n" + } + + resourceDeclaration += "}" + + return resourceDeclaration +} + +func (resourceConfig *PowerDNSRecordResource) ResourceName() string { + return "powerdns_record." + resourceConfig.Name +} + +func (resourceConfig *PowerDNSRecordResource) ResourceID() string { + return `{"zone":"` + resourceConfig.Arguments.Zone + `","id":"` + resourceConfig.Arguments.Name + ":::" + resourceConfig.Arguments.Type + `"}` +} + +// +// Test Helper Functions +// +func testPDNSRecordCommonTestCore(t *testing.T, recordConfig *PowerDNSRecordResource) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPDNSRecordDestroy, Steps: []resource.TestStep{ { - Config: testPDNSRecordConfigSOA, + Config: recordConfig.ResourceDeclaration(), Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), + testAccCheckPDNSRecordContents(recordConfig), ), }, { - ResourceName: resourceName, - ImportStateId: resourceID, + ResourceName: recordConfig.ResourceName(), + ImportStateId: recordConfig.ResourceID(), ImportState: true, ImportStateVerify: true, }, @@ -491,166 +276,253 @@ func testAccCheckPDNSRecordExists(n string) resource.TestCheckFunc { } } -const testPDNSRecordConfigRecordEmpty = ` -resource "powerdns_record" "test-a" { - zone = "sysa.xyz." - name = "testpdnsrecordconfigrecordempty.sysa.xyz." - type = "A" - ttl = 60 - records = [ ] -}` - -const testPDNSRecordConfigA = ` -resource "powerdns_record" "test-a" { - zone = "sysa.xyz." - name = "testpdnsrecordconfiga.sysa.xyz." - type = "A" - ttl = 60 - records = [ "1.1.1.1", "2.2.2.2" ] -}` - -const testPDNSRecordConfigAWithPtr = ` -resource "powerdns_record" "test-a-ptr" { - zone = "sysa.xyz." - name = "testpdnsrecordconfigawithptr.sysa.xyz." - type = "A" - ttl = 60 - set_ptr = true - records = [ "1.1.1.1" ] -}` - -const testPDNSRecordConfigHyphenedWithCount = ` -resource "powerdns_record" "test-counted" { - count = "2" - zone = "sysa.xyz." - name = "testpdnsrecordconfighyphenedwithcount-${count.index}.sysa.xyz." - type = "A" - ttl = 60 - records = [ "1.1.1.${count.index}" ] -}` - -const testPDNSRecordConfigAAAA = ` -resource "powerdns_record" "test-aaaa" { - zone = "sysa.xyz." - name = "testpdnsrecordconfigaaaa.sysa.xyz." - type = "AAAA" - ttl = 60 - records = [ "2001:db8:2000:bf0::1", "2001:db8:2000:bf1::1" ] -}` - -const testPDNSRecordConfigCNAME = ` -resource "powerdns_record" "test-cname" { - zone = "sysa.xyz." - name = "testpdnsrecordconfigcname.sysa.xyz." - type = "CNAME" - ttl = 60 - records = [ "redis.example.com." ] -}` - -const testPDNSRecordConfigHINFO = ` -resource "powerdns_record" "test-hinfo" { - zone = "sysa.xyz." - name = "testpdnsrecordconfighinfo.sysa.xyz." - type = "HINFO" - ttl = 60 - records = [ "\"PC-Intel-2.4ghz\" \"Linux\"" ] -}` - -const testPDNSRecordConfigLOC = ` -resource "powerdns_record" "test-loc" { - zone = "sysa.xyz." - name = "testpdnsrecordconfigloc.sysa.xyz." - type = "LOC" - ttl = 60 - records = [ "51 56 0.123 N 5 54 0.000 E 4.00m 1.00m 10000.00m 10.00m" ] -}` - -const testPDNSRecordConfigMX = ` -resource "powerdns_record" "test-mx" { - zone = "sysa.xyz." - name = "sysa.xyz." - type = "MX" - ttl = 60 - records = [ "10 mail.example.com." ] -}` - -const testPDNSRecordConfigMXMulti = ` -resource "powerdns_record" "test-mx-multi" { - zone = "sysa.xyz." - name = "multi.sysa.xyz." - type = "MX" - ttl = 60 - records = [ "10 mail1.example.com.", "20 mail2.example.com." ] -}` - -const testPDNSRecordConfigNAPTR = ` -resource "powerdns_record" "test-naptr" { - zone = "sysa.xyz." - name = "sysa.xyz." - type = "NAPTR" - ttl = 60 - records = [ "100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu'." ] -}` - -const testPDNSRecordConfigNS = ` -resource "powerdns_record" "test-ns" { - zone = "sysa.xyz." - name = "lab.sysa.xyz." - type = "NS" - ttl = 60 - records = [ "ns1.sysa.xyz.", "ns2.sysa.xyz." ] -}` - -const testPDNSRecordConfigSPF = ` -resource "powerdns_record" "test-spf" { - zone = "sysa.xyz." - name = "sysa.xyz." - type = "SPF" - ttl = 60 - records = [ "\"v=spf1 +all\"" ] -}` - -const testPDNSRecordConfigSSHFP = ` -resource "powerdns_record" "test-sshfp" { - zone = "sysa.xyz." - name = "ssh.sysa.xyz." - type = "SSHFP" - ttl = 60 - records = [ "1 1 123456789abcdef67890123456789abcdef67890" ] -}` - -const testPDNSRecordConfigSRV = ` -resource "powerdns_record" "test-srv" { - zone = "sysa.xyz." - name = "_redis._tcp.sysa.xyz." - type = "SRV" - ttl = 60 - records = [ "0 10 6379 redis1.sysa.xyz.", "0 10 6379 redis2.sysa.xyz.", "10 10 6379 redis-replica.sysa.xyz." ] -}` - -const testPDNSRecordConfigTXT = ` -resource "powerdns_record" "test-txt" { - zone = "sysa.xyz." - name = "text.sysa.xyz." - type = "TXT" - ttl = 60 - records = [ "\"text record payload\"" ] -}` - -const testPDNSRecordConfigALIAS = ` -resource "powerdns_record" "test-alias" { - zone = "sysa.xyz." - name = "alias.sysa.xyz." - type = "ALIAS" - ttl = 3600 - records = [ "www.some-alias.com." ] -}` - -const testPDNSRecordConfigSOA = ` -resource "powerdns_record" "test-soa" { - zone = "test-soa-sysa.xyz." - name = "test-soa-sysa.xyz." - type = "SOA" - ttl = 3600 - records = [ "something.something. hostmaster.sysa.xyz. 2019090301 10800 3600 604800 3600" ] -}` +func testAccCheckPDNSRecordContents(recordConfig *PowerDNSRecordResource) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[recordConfig.ResourceName()] + if !ok { + return fmt.Errorf("Not found: %s", recordConfig.ResourceName()) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + client := testAccProvider.Meta().(*Client) + foundRecords, err := client.ListRecordsByID(rs.Primary.Attributes["zone"], rs.Primary.ID) + if err != nil { + return err + } + if len(foundRecords) == 0 { + return fmt.Errorf("Record does not exist") + } + + var matchingRecords []Record + for _, rec := range foundRecords { + // ListRecordsByID returns a list of records in v0 record structure format, which is a flat array of one entry per record content. + if rec.ID() == rs.Primary.ID { + matchingRecords = append(matchingRecords, rec) + } + } + + if len(matchingRecords) == 0 { + return fmt.Errorf("Record does not exist: %#v", rs.Primary.ID) + } + + // Assumption: Order will match between foundRecords and recordConfig.Arguments.Records + for idx, desiredRecordContents := range recordConfig.Arguments.Records { + error_prefix := ("[#" + strconv.Itoa(idx) + "/" + desiredRecordContents + "] ") + + if idx >= len(matchingRecords) { + return fmt.Errorf(error_prefix + "Record not found") + } + + rec := matchingRecords[idx] + + if rec.Name != recordConfig.Arguments.Name { + return fmt.Errorf(error_prefix+"Record name field does not match: %#v : %#v", rec.Name, recordConfig.Arguments.Name) + } + + if rec.Type != recordConfig.Arguments.Type { + return fmt.Errorf(error_prefix+"Record type field does not match: %#v : %#v", rec.Type, recordConfig.Arguments.Type) + } + + if rec.Content != desiredRecordContents { + return fmt.Errorf(error_prefix+"Record content field does not match: %#v : %#v", rec.Content, desiredRecordContents) + } + + if rec.TTL != recordConfig.Arguments.TTL { + return fmt.Errorf(error_prefix+"Record TTL field does not match: %#v : %#v", rec.TTL, recordConfig.Arguments.TTL) + } + + // Skipping check of SetPtr: this setting has been deprecated since PowerDNS 4.3.0 so the check will fail. + + } + + return nil + } +} + +// Test Configs +func NewPowerDNSRecordResource() *PowerDNSRecordResource { + record := &PowerDNSRecordResource{} + record.Arguments = &PowerDNSRecordResourceArguments{} + + // The zone argument is common across all the tests + record.Arguments.Zone = "sysa.xyz." + // TTL is set to 60 in the majority of the tests, default to 60 do deduplicate code. + record.Arguments.TTL = 60 + record.Arguments.Records = make([]string, 0) + return record +} + +func testPDNSRecordConfigRecordEmpty() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-a" + record.Arguments.Name = "testpdnsrecordconfigrecordempty.sysa.xyz." + record.Arguments.Type = "A" + return record +} + +func testPDNSRecordConfigA() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-a" + record.Arguments.Name = "testpdnsrecordconfigrecorda.sysa.xyz." + record.Arguments.Type = "A" + record.Arguments.Records = append(record.Arguments.Records, "1.1.1.1") + record.Arguments.Records = append(record.Arguments.Records, "2.2.2.2") + return record +} + +func testPDNSRecordConfigAWithPtr() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-a" + record.Arguments.Name = "testpdnsrecordconfigrecordawithptr.sysa.xyz." + record.Arguments.Type = "A" + record.Arguments.Records = append(record.Arguments.Records, "1.1.1.1") + record.Arguments.SetPtr = true + return record +} + +func testPDNSRecordConfigHyphenedWithCount() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-counted" + record.Arguments.Count = 2 + record.Arguments.Name = "testpdnsrecordconfighyphenedwithcount-${count.index}.sysa.xyz." + record.Arguments.Type = "A" + record.Arguments.Records = append(record.Arguments.Records, "1.1.1.${count.index}") + return record +} + +func testPDNSRecordConfigAAAA() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-aaaa" + record.Arguments.Name = "testpdnsrecordconfigaaaa.sysa.xyz." + record.Arguments.Type = "AAAA" + record.Arguments.Records = append(record.Arguments.Records, "2001:db8:2000:bf0::1") + record.Arguments.Records = append(record.Arguments.Records, "2001:db8:2000:bf1::1") + return record +} + +func testPDNSRecordConfigCNAME() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-cname" + record.Arguments.Name = "testpdnsrecordconfigcname.sysa.xyz." + record.Arguments.Type = "CNAME" + record.Arguments.Records = append(record.Arguments.Records, "redis.example.com.") + return record +} + +func testPDNSRecordConfigHINFO() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-hinfo" + record.Arguments.Name = "testpdnsrecordconfighinfo.sysa.xyz." + record.Arguments.Type = "HINFO" + record.Arguments.Records = append(record.Arguments.Records, `"PC-Intel-2.4ghz" "Linux"`) + return record +} + +func testPDNSRecordConfigLOC() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-loc" + record.Arguments.Name = "testpdnsrecordconfigloc.sysa.xyz." + record.Arguments.Type = "LOC" + record.Arguments.Records = append(record.Arguments.Records, "51 56 0.123 N 5 54 0.000 E 4.00m 1.00m 10000.00m 10.00m") + return record +} + +func testPDNSRecordConfigMX() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-mx" + record.Arguments.Name = "sysa.xyz." + record.Arguments.Type = "MX" + record.Arguments.Records = append(record.Arguments.Records, "10 mail.example.com.") + return record +} + +func testPDNSRecordConfigMXMulti() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-mx-multi" + record.Arguments.Name = "multi.sysa.xyz." + record.Arguments.Type = "MX" + record.Arguments.Records = append(record.Arguments.Records, "10 mail.example.com.") + record.Arguments.Records = append(record.Arguments.Records, "20 mail2.example.com.") + return record +} + +func testPDNSRecordConfigNAPTR() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-naptr" + record.Arguments.Name = "sysa.xyz." + record.Arguments.Type = "NAPTR" + record.Arguments.Records = append(record.Arguments.Records, `100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu'.`) + return record +} + +func testPDNSRecordConfigNS() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-ns" + record.Arguments.Name = "lab.sysa.xyz." + record.Arguments.Type = "NS" + record.Arguments.Records = append(record.Arguments.Records, "ns1.sysa.xyz.") + record.Arguments.Records = append(record.Arguments.Records, "ns2.sysa.xyz.") + return record +} + +func testPDNSRecordConfigSPF() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-spf" + record.Arguments.Name = "sysa.xyz." + record.Arguments.Type = "SPF" + record.Arguments.Records = append(record.Arguments.Records, `"v=spf1 +all"`) + return record +} + +func testPDNSRecordConfigSSHFP() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-sshfp" + record.Arguments.Name = "ssh.sysa.xyz." + record.Arguments.Type = "SSHFP" + record.Arguments.Records = append(record.Arguments.Records, "1 1 123456789abcdef67890123456789abcdef67890") + return record +} + +func testPDNSRecordConfigSRV() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-srv" + record.Arguments.Name = "_redis._tcp.sysa.xyz." + record.Arguments.Type = "SRV" + record.Arguments.Records = append(record.Arguments.Records, "0 10 6379 redis1.sysa.xyz.") + record.Arguments.Records = append(record.Arguments.Records, "0 10 6379 redis2.sysa.xyz.") + record.Arguments.Records = append(record.Arguments.Records, "10 10 6379 redis-replica.sysa.xyz.") + return record +} + +func testPDNSRecordConfigTXT() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-txt" + record.Arguments.Name = "text.sysa.xyz." + record.Arguments.Type = "TXT" + record.Arguments.Records = append(record.Arguments.Records, `"text record payload"`) + return record +} + +func testPDNSRecordConfigALIAS() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-alias" + record.Arguments.Name = "alias.sysa.xyz." + record.Arguments.Type = "ALIAS" + record.Arguments.TTL = 3600 + record.Arguments.Records = append(record.Arguments.Records, "www.some-alias.com.") + return record +} + +func testPDNSRecordConfigSOA() *PowerDNSRecordResource { + record := NewPowerDNSRecordResource() + record.Name = "test-soa" + record.Arguments.Zone = "test-soa-sysa.xyz." + record.Arguments.Name = "test-soa-sysa.xyz." + record.Arguments.Type = "SOA" + record.Arguments.TTL = 3600 + record.Arguments.Records = append(record.Arguments.Records, "something.something. hostmaster.sysa.xyz. 2019090301 10800 3600 604800 3600") + return record +} From 632c0819a68a82eab65e2ad648c26c683e3c982e Mon Sep 17 00:00:00 2001 From: Tom Noonan II Date: Tue, 9 Feb 2021 17:17:57 -0500 Subject: [PATCH 2/4] Allow existing powerdns_records to be updated instead of destroyed & recreated --- powerdns/resource_powerdns_record.go | 5 +- powerdns/resource_powerdns_record_test.go | 94 ++++++++++++++++++----- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/powerdns/resource_powerdns_record.go b/powerdns/resource_powerdns_record.go index d2be002c..a0eb60a8 100644 --- a/powerdns/resource_powerdns_record.go +++ b/powerdns/resource_powerdns_record.go @@ -12,6 +12,7 @@ import ( func resourcePDNSRecord() *schema.Resource { return &schema.Resource{ Create: resourcePDNSRecordCreate, + Update: resourcePDNSRecordCreate, Read: resourcePDNSRecordRead, Delete: resourcePDNSRecordDelete, Exists: resourcePDNSRecordExists, @@ -41,13 +42,13 @@ func resourcePDNSRecord() *schema.Resource { "ttl": { Type: schema.TypeInt, Required: true, - ForceNew: true, + ForceNew: false, }, "records": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Required: true, - ForceNew: true, + ForceNew: false, Set: schema.HashString, }, diff --git a/powerdns/resource_powerdns_record_test.go b/powerdns/resource_powerdns_record_test.go index dd610b70..70a3d18d 100644 --- a/powerdns/resource_powerdns_record_test.go +++ b/powerdns/resource_powerdns_record_test.go @@ -11,10 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/terraform" ) -// -// Tests -// - func TestAccPDNSRecord_Empty(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -29,7 +25,7 @@ func TestAccPDNSRecord_Empty(t *testing.T) { } func TestAccPDNSRecord_A(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigA()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigA) } func TestAccPDNSRecord_WithPtr(t *testing.T) { @@ -92,59 +88,59 @@ func TestAccPDNSRecord_WithCount(t *testing.T) { } func TestAccPDNSRecord_AAAA(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigAAAA()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigAAAA) } func TestAccPDNSRecord_CNAME(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigCNAME()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigCNAME) } func TestAccPDNSRecord_HINFO(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigHINFO()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigHINFO) } func TestAccPDNSRecord_LOC(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigLOC()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigLOC) } func TestAccPDNSRecord_MX(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigMX()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigMX) } func TestAccPDNSRecord_MXMulti(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigMXMulti()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigMXMulti) } func TestAccPDNSRecord_NAPTR(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigNAPTR()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigNAPTR) } func TestAccPDNSRecord_NS(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigNS()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigNS) } func TestAccPDNSRecord_SPF(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSPF()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSPF) } func TestAccPDNSRecord_SSHFP(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSSHFP()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSSHFP) } func TestAccPDNSRecord_SRV(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSRV()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSRV) } func TestAccPDNSRecord_TXT(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigTXT()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigTXT) } func TestAccPDNSRecord_ALIAS(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigALIAS()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigALIAS) } func TestAccPDNSRecord_SOA(t *testing.T) { - testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSOA()) + testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSOA) } // @@ -161,7 +157,9 @@ type PowerDNSRecordResourceArguments struct { Type string TTL int Records []string - SetPtr bool + // UpdateRecords are recordsets used for testing update behavior. + UpdateRecords []string + SetPtr bool } type PowerDNSRecordResource struct { @@ -207,16 +205,45 @@ func (resourceConfig *PowerDNSRecordResource) ResourceID() string { // // Test Helper Functions // -func testPDNSRecordCommonTestCore(t *testing.T, recordConfig *PowerDNSRecordResource) { + +// Common Test Core: This function builds a create / update test for the majority of test cases +// Takes a function variable to avoid deep copy issues on updates. +func testPDNSRecordCommonTestCore(t *testing.T, recordConfigGenerator func() *PowerDNSRecordResource) { + // Update test resources. + recordConfig := recordConfigGenerator() + + ttlUpdateRecordConfig := recordConfigGenerator() + ttlUpdateRecordConfig.Arguments.TTL += 100 + + recordUpdateRecordConfig := recordConfigGenerator() + recordUpdateRecordConfig.Arguments.Records = recordUpdateRecordConfig.Arguments.UpdateRecords + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPDNSRecordDestroy, Steps: []resource.TestStep{ + // Initial record creation { Config: recordConfig.ResourceDeclaration(), Check: resource.ComposeTestCheckFunc( testAccCheckPDNSRecordContents(recordConfig), + // TestCheckResourceAttr() checks are skipped because records is not directly accessible + // https://github.com/hashicorp/terraform/issues/21618 + ), + }, + // TTL update + { + Config: ttlUpdateRecordConfig.ResourceDeclaration(), + Check: resource.ComposeTestCheckFunc( + testAccCheckPDNSRecordContents(ttlUpdateRecordConfig), + ), + }, + // Records update + { + Config: recordUpdateRecordConfig.ResourceDeclaration(), + Check: resource.ComposeTestCheckFunc( + testAccCheckPDNSRecordContents(recordUpdateRecordConfig), ), }, { @@ -370,6 +397,9 @@ func testPDNSRecordConfigA() *PowerDNSRecordResource { record.Arguments.Type = "A" record.Arguments.Records = append(record.Arguments.Records, "1.1.1.1") record.Arguments.Records = append(record.Arguments.Records, "2.2.2.2") + + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "2.2.2.2") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "3.3.3.3") return record } @@ -379,6 +409,7 @@ func testPDNSRecordConfigAWithPtr() *PowerDNSRecordResource { record.Arguments.Name = "testpdnsrecordconfigrecordawithptr.sysa.xyz." record.Arguments.Type = "A" record.Arguments.Records = append(record.Arguments.Records, "1.1.1.1") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "2.2.2.2") record.Arguments.SetPtr = true return record } @@ -400,6 +431,8 @@ func testPDNSRecordConfigAAAA() *PowerDNSRecordResource { record.Arguments.Type = "AAAA" record.Arguments.Records = append(record.Arguments.Records, "2001:db8:2000:bf0::1") record.Arguments.Records = append(record.Arguments.Records, "2001:db8:2000:bf1::1") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "2001:db8:2000:bf3::1") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "2001:db8:2000:bf4::1") return record } @@ -409,6 +442,7 @@ func testPDNSRecordConfigCNAME() *PowerDNSRecordResource { record.Arguments.Name = "testpdnsrecordconfigcname.sysa.xyz." record.Arguments.Type = "CNAME" record.Arguments.Records = append(record.Arguments.Records, "redis.example.com.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "redis.example.net.") return record } @@ -418,6 +452,7 @@ func testPDNSRecordConfigHINFO() *PowerDNSRecordResource { record.Arguments.Name = "testpdnsrecordconfighinfo.sysa.xyz." record.Arguments.Type = "HINFO" record.Arguments.Records = append(record.Arguments.Records, `"PC-Intel-2.4ghz" "Linux"`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `"PC-Intel-3.2ghz" "Linux"`) return record } @@ -427,6 +462,7 @@ func testPDNSRecordConfigLOC() *PowerDNSRecordResource { record.Arguments.Name = "testpdnsrecordconfigloc.sysa.xyz." record.Arguments.Type = "LOC" record.Arguments.Records = append(record.Arguments.Records, "51 56 0.123 N 5 54 0.000 E 4.00m 1.00m 10000.00m 10.00m") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "51 10 43.900 N 1 49 34.300 E 4.00m 1.00m 10000.00m 10.00m") return record } @@ -436,6 +472,7 @@ func testPDNSRecordConfigMX() *PowerDNSRecordResource { record.Arguments.Name = "sysa.xyz." record.Arguments.Type = "MX" record.Arguments.Records = append(record.Arguments.Records, "10 mail.example.com.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "10 mail2.example.net.") return record } @@ -446,6 +483,8 @@ func testPDNSRecordConfigMXMulti() *PowerDNSRecordResource { record.Arguments.Type = "MX" record.Arguments.Records = append(record.Arguments.Records, "10 mail.example.com.") record.Arguments.Records = append(record.Arguments.Records, "20 mail2.example.com.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "10 mail3.example.com.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "10 mail4.example.com.") return record } @@ -455,6 +494,7 @@ func testPDNSRecordConfigNAPTR() *PowerDNSRecordResource { record.Arguments.Name = "sysa.xyz." record.Arguments.Type = "NAPTR" record.Arguments.Records = append(record.Arguments.Records, `100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu'.`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `100 70 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu'.`) return record } @@ -465,6 +505,8 @@ func testPDNSRecordConfigNS() *PowerDNSRecordResource { record.Arguments.Type = "NS" record.Arguments.Records = append(record.Arguments.Records, "ns1.sysa.xyz.") record.Arguments.Records = append(record.Arguments.Records, "ns2.sysa.xyz.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "ns3.sysa.xyz.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "ns4.sysa.xyz.") return record } @@ -474,6 +516,7 @@ func testPDNSRecordConfigSPF() *PowerDNSRecordResource { record.Arguments.Name = "sysa.xyz." record.Arguments.Type = "SPF" record.Arguments.Records = append(record.Arguments.Records, `"v=spf1 +all"`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `"v=spf1 -all"`) return record } @@ -483,6 +526,7 @@ func testPDNSRecordConfigSSHFP() *PowerDNSRecordResource { record.Arguments.Name = "ssh.sysa.xyz." record.Arguments.Type = "SSHFP" record.Arguments.Records = append(record.Arguments.Records, "1 1 123456789abcdef67890123456789abcdef67890") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "1 1 fedcba9876543210fedcba9876543210fedcba98") return record } @@ -494,6 +538,11 @@ func testPDNSRecordConfigSRV() *PowerDNSRecordResource { record.Arguments.Records = append(record.Arguments.Records, "0 10 6379 redis1.sysa.xyz.") record.Arguments.Records = append(record.Arguments.Records, "0 10 6379 redis2.sysa.xyz.") record.Arguments.Records = append(record.Arguments.Records, "10 10 6379 redis-replica.sysa.xyz.") + + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "0 10 6379 redis1.sysa.xyz.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "0 10 6379 redis2.sysa.xyz.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "0 10 6379 redis3.sysa.xyz.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "10 10 6379 redis-replica.sysa.xyz.") return record } @@ -503,6 +552,7 @@ func testPDNSRecordConfigTXT() *PowerDNSRecordResource { record.Arguments.Name = "text.sysa.xyz." record.Arguments.Type = "TXT" record.Arguments.Records = append(record.Arguments.Records, `"text record payload"`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `"updated text record payload"`) return record } @@ -513,6 +563,7 @@ func testPDNSRecordConfigALIAS() *PowerDNSRecordResource { record.Arguments.Type = "ALIAS" record.Arguments.TTL = 3600 record.Arguments.Records = append(record.Arguments.Records, "www.some-alias.com.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "www.some-other-alias.com.") return record } @@ -524,5 +575,6 @@ func testPDNSRecordConfigSOA() *PowerDNSRecordResource { record.Arguments.Type = "SOA" record.Arguments.TTL = 3600 record.Arguments.Records = append(record.Arguments.Records, "something.something. hostmaster.sysa.xyz. 2019090301 10800 3600 604800 3600") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "something.something. hostmaster.sysa.xyz. 2021021801 10800 3600 604800 3600") return record } From d744fb3a14f6a4a6ed92ddc89ef4289e134edea2 Mon Sep 17 00:00:00 2001 From: Tom Noonan II Date: Wed, 10 Mar 2021 13:41:26 -0500 Subject: [PATCH 3/4] Add Terraform attribute state check support, reorder code blocks and improce inline documentation --- powerdns/resource_powerdns_record_test.go | 184 ++++++++++++---------- 1 file changed, 101 insertions(+), 83 deletions(-) diff --git a/powerdns/resource_powerdns_record_test.go b/powerdns/resource_powerdns_record_test.go index 70a3d18d..1b5a68bf 100644 --- a/powerdns/resource_powerdns_record_test.go +++ b/powerdns/resource_powerdns_record_test.go @@ -2,6 +2,7 @@ package powerdns import ( "fmt" + "hash/crc32" "regexp" "strconv" "strings" @@ -143,65 +144,6 @@ func TestAccPDNSRecord_SOA(t *testing.T) { testPDNSRecordCommonTestCore(t, testPDNSRecordConfigSOA) } -// -// Test resource declaration functions -// -// Pattern: testPDNSRecordConfigXXX() returns a PowerDNSRecordResource struct -// The PowerDNSRecordResource struct can be used to query test config, update attributes for update tests, -// and can have ResourceDeclaration() called against it to generate the Terraform DSL resource block string. -// -type PowerDNSRecordResourceArguments struct { - Count int - Zone string - Name string - Type string - TTL int - Records []string - // UpdateRecords are recordsets used for testing update behavior. - UpdateRecords []string - SetPtr bool -} - -type PowerDNSRecordResource struct { - Name string - Arguments *PowerDNSRecordResourceArguments -} - -func (resourceConfig *PowerDNSRecordResource) ResourceDeclaration() string { - var encapsulatedRecords []string - for _, record := range resourceConfig.Arguments.Records { - encapsulatedRecords = append(encapsulatedRecords, (`"` + strings.Replace(record, `"`, `\"`, -1) + `"`)) - } - - resourceDeclaration := `resource "powerdns_record" "` + resourceConfig.Name + "\" {\n" - if resourceConfig.Arguments.Count > 0 { - resourceDeclaration += " count = " + strconv.Itoa(resourceConfig.Arguments.Count) + "\n" - } - - // zone, name, type, ttl, and records are mandatory - resourceDeclaration += ` zone = "` + resourceConfig.Arguments.Zone + "\"\n" - resourceDeclaration += ` name = "` + resourceConfig.Arguments.Name + "\"\n" - resourceDeclaration += ` type = "` + resourceConfig.Arguments.Type + "\"\n" - resourceDeclaration += " ttl = " + strconv.Itoa(resourceConfig.Arguments.TTL) + "\n" - resourceDeclaration += " records = [ " + strings.Join(encapsulatedRecords, ", ") + " ]\n" - - if resourceConfig.Arguments.SetPtr { - resourceDeclaration += " set_ptr = true\n" - } - - resourceDeclaration += "}" - - return resourceDeclaration -} - -func (resourceConfig *PowerDNSRecordResource) ResourceName() string { - return "powerdns_record." + resourceConfig.Name -} - -func (resourceConfig *PowerDNSRecordResource) ResourceID() string { - return `{"zone":"` + resourceConfig.Arguments.Zone + `","id":"` + resourceConfig.Arguments.Name + ":::" + resourceConfig.Arguments.Type + `"}` -} - // // Test Helper Functions // @@ -212,39 +154,22 @@ func testPDNSRecordCommonTestCore(t *testing.T, recordConfigGenerator func() *Po // Update test resources. recordConfig := recordConfigGenerator() - ttlUpdateRecordConfig := recordConfigGenerator() - ttlUpdateRecordConfig.Arguments.TTL += 100 - - recordUpdateRecordConfig := recordConfigGenerator() - recordUpdateRecordConfig.Arguments.Records = recordUpdateRecordConfig.Arguments.UpdateRecords + updateRecordConfig := recordConfigGenerator() + updateRecordConfig.Arguments.TTL += 100 + updateRecordConfig.Arguments.Records = updateRecordConfig.Arguments.UpdateRecords resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckPDNSRecordDestroy, Steps: []resource.TestStep{ - // Initial record creation { Config: recordConfig.ResourceDeclaration(), - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordContents(recordConfig), - // TestCheckResourceAttr() checks are skipped because records is not directly accessible - // https://github.com/hashicorp/terraform/issues/21618 - ), + Check: recordConfig.ResourceChecks(), }, - // TTL update { - Config: ttlUpdateRecordConfig.ResourceDeclaration(), - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordContents(ttlUpdateRecordConfig), - ), - }, - // Records update - { - Config: recordUpdateRecordConfig.ResourceDeclaration(), - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordContents(recordUpdateRecordConfig), - ), + Config: updateRecordConfig.ResourceDeclaration(), + Check: updateRecordConfig.ResourceChecks(), }, { ResourceName: recordConfig.ResourceName(), @@ -369,7 +294,93 @@ func testAccCheckPDNSRecordContents(recordConfig *PowerDNSRecordResource) resour } } -// Test Configs +// +// Resource Declaration types and methods +// These types & methods define a object layout declare resources during test, to allow for easy update tests and code deduplication +// +type PowerDNSRecordResourceArguments struct { + Count int + Zone string + Name string + Type string + TTL int + Records []string + // UpdateRecords are recordsets used for testing update behavior. + UpdateRecords []string + SetPtr bool +} + +type PowerDNSRecordResource struct { + Name string + Arguments *PowerDNSRecordResourceArguments +} + +// This function returns the record attribute ID in "records.[hash]" format for the given record +// Record hash lookup per https://github.com/pan-net/terraform-provider-powerdns/pull/78#issuecomment-793288653 +func RecordAttributeIDForRecord(record string) string { + crc := int(crc32.ChecksumIEEE([]byte(record))) + if -crc >= 0 { + crc = -crc + } + + return "records." + strconv.Itoa(crc) +} + +// This function builds out a suite of checks for the resource, suitable for passing to a TestStep as Check +func (resourceConfig *PowerDNSRecordResource) ResourceChecks() resource.TestCheckFunc { + var checks []resource.TestCheckFunc + + checks = append(checks, testAccCheckPDNSRecordContents(resourceConfig)) + checks = append(checks, resource.TestCheckResourceAttr(resourceConfig.ResourceName(), "zone", resourceConfig.Arguments.Zone)) + checks = append(checks, resource.TestCheckResourceAttr(resourceConfig.ResourceName(), "name", resourceConfig.Arguments.Name)) + checks = append(checks, resource.TestCheckResourceAttr(resourceConfig.ResourceName(), "type", resourceConfig.Arguments.Type)) + checks = append(checks, resource.TestCheckResourceAttr(resourceConfig.ResourceName(), "ttl", strconv.Itoa(resourceConfig.Arguments.TTL))) + + for _, record := range resourceConfig.Arguments.Records { + checks = append(checks, resource.TestCheckResourceAttr(resourceConfig.ResourceName(), RecordAttributeIDForRecord(record), record)) + } + + return resource.ComposeTestCheckFunc(checks...) +} + +// This function builds out the Terraform DSL for the resource, suitable for passing to a TestStep as Config +func (resourceConfig *PowerDNSRecordResource) ResourceDeclaration() string { + var encapsulatedRecords []string + for _, record := range resourceConfig.Arguments.Records { + encapsulatedRecords = append(encapsulatedRecords, (`"` + strings.Replace(record, `"`, `\"`, -1) + `"`)) + } + + resourceDeclaration := `resource "powerdns_record" "` + resourceConfig.Name + "\" {\n" + if resourceConfig.Arguments.Count > 0 { + resourceDeclaration += " count = " + strconv.Itoa(resourceConfig.Arguments.Count) + "\n" + } + + // zone, name, type, ttl, and records are mandatory + resourceDeclaration += ` zone = "` + resourceConfig.Arguments.Zone + "\"\n" + resourceDeclaration += ` name = "` + resourceConfig.Arguments.Name + "\"\n" + resourceDeclaration += ` type = "` + resourceConfig.Arguments.Type + "\"\n" + resourceDeclaration += " ttl = " + strconv.Itoa(resourceConfig.Arguments.TTL) + "\n" + resourceDeclaration += " records = [ " + strings.Join(encapsulatedRecords, ", ") + " ]\n" + + if resourceConfig.Arguments.SetPtr { + resourceDeclaration += " set_ptr = true\n" + } + + resourceDeclaration += "}" + + return resourceDeclaration +} + +// This function builds out the Terraform resource ID for the resource +func (resourceConfig *PowerDNSRecordResource) ResourceID() string { + return `{"zone":"` + resourceConfig.Arguments.Zone + `","id":"` + resourceConfig.Arguments.Name + ":::" + resourceConfig.Arguments.Type + `"}` +} + +// This function is a trivial helper to return the Terraform resource name +func (resourceConfig *PowerDNSRecordResource) ResourceName() string { + return "powerdns_record." + resourceConfig.Name +} + func NewPowerDNSRecordResource() *PowerDNSRecordResource { record := &PowerDNSRecordResource{} record.Arguments = &PowerDNSRecordResourceArguments{} @@ -382,6 +393,13 @@ func NewPowerDNSRecordResource() *PowerDNSRecordResource { return record } +// +// Test resource declaration functions +// +// Pattern: testPDNSRecordConfigXXX() returns a PowerDNSRecordResource struct +// The PowerDNSRecordResource struct can be used to query test config, update attributes for update tests, +// and can have ResourceDeclaration() called against it to generate the Terraform DSL resource block string. +// func testPDNSRecordConfigRecordEmpty() *PowerDNSRecordResource { record := NewPowerDNSRecordResource() record.Name = "test-a" From 86f2cdca5fb5d0fd026128c60d9ffcbaa892d799 Mon Sep 17 00:00:00 2001 From: Tom Noonan II Date: Mon, 7 Jun 2021 12:28:12 -0400 Subject: [PATCH 4/4] Fix variable name which did not meet go lint specifications --- powerdns/resource_powerdns_record_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/powerdns/resource_powerdns_record_test.go b/powerdns/resource_powerdns_record_test.go index 1b5a68bf..0a398685 100644 --- a/powerdns/resource_powerdns_record_test.go +++ b/powerdns/resource_powerdns_record_test.go @@ -262,28 +262,28 @@ func testAccCheckPDNSRecordContents(recordConfig *PowerDNSRecordResource) resour // Assumption: Order will match between foundRecords and recordConfig.Arguments.Records for idx, desiredRecordContents := range recordConfig.Arguments.Records { - error_prefix := ("[#" + strconv.Itoa(idx) + "/" + desiredRecordContents + "] ") + errorPrefix := ("[#" + strconv.Itoa(idx) + "/" + desiredRecordContents + "] ") if idx >= len(matchingRecords) { - return fmt.Errorf(error_prefix + "Record not found") + return fmt.Errorf(errorPrefix + "Record not found") } rec := matchingRecords[idx] if rec.Name != recordConfig.Arguments.Name { - return fmt.Errorf(error_prefix+"Record name field does not match: %#v : %#v", rec.Name, recordConfig.Arguments.Name) + return fmt.Errorf(errorPrefix+"Record name field does not match: %#v : %#v", rec.Name, recordConfig.Arguments.Name) } if rec.Type != recordConfig.Arguments.Type { - return fmt.Errorf(error_prefix+"Record type field does not match: %#v : %#v", rec.Type, recordConfig.Arguments.Type) + return fmt.Errorf(errorPrefix+"Record type field does not match: %#v : %#v", rec.Type, recordConfig.Arguments.Type) } if rec.Content != desiredRecordContents { - return fmt.Errorf(error_prefix+"Record content field does not match: %#v : %#v", rec.Content, desiredRecordContents) + return fmt.Errorf(errorPrefix+"Record content field does not match: %#v : %#v", rec.Content, desiredRecordContents) } if rec.TTL != recordConfig.Arguments.TTL { - return fmt.Errorf(error_prefix+"Record TTL field does not match: %#v : %#v", rec.TTL, recordConfig.Arguments.TTL) + return fmt.Errorf(errorPrefix+"Record TTL field does not match: %#v : %#v", rec.TTL, recordConfig.Arguments.TTL) } // Skipping check of SetPtr: this setting has been deprecated since PowerDNS 4.3.0 so the check will fail.