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 4641387d..0a398685 100644 --- a/powerdns/resource_powerdns_record_test.go +++ b/powerdns/resource_powerdns_record_test.go @@ -2,7 +2,10 @@ package powerdns import ( "fmt" + "hash/crc32" "regexp" + "strconv" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -15,7 +18,7 @@ func TestAccPDNSRecord_Empty(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testPDNSRecordConfigRecordEmpty, + Config: testPDNSRecordConfigRecordEmpty().ResourceDeclaration(), ExpectError: regexp.MustCompile("'records' must not be empty"), }, }, @@ -23,33 +26,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 +38,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 +66,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,322 +89,74 @@ 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 Helper Functions +// + +// 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() + + updateRecordConfig := recordConfigGenerator() + updateRecordConfig.Arguments.TTL += 100 + updateRecordConfig.Arguments.Records = updateRecordConfig.Arguments.UpdateRecords resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -429,14 +164,16 @@ func TestAccPDNSRecord_SOA(t *testing.T) { CheckDestroy: testAccCheckPDNSRecordDestroy, Steps: []resource.TestStep{ { - Config: testPDNSRecordConfigSOA, - Check: resource.ComposeTestCheckFunc( - testAccCheckPDNSRecordExists(resourceName), - ), + Config: recordConfig.ResourceDeclaration(), + Check: recordConfig.ResourceChecks(), + }, + { + Config: updateRecordConfig.ResourceDeclaration(), + Check: updateRecordConfig.ResourceChecks(), }, { - ResourceName: resourceName, - ImportStateId: resourceID, + ResourceName: recordConfig.ResourceName(), + ImportStateId: recordConfig.ResourceID(), ImportState: true, ImportStateVerify: true, }, @@ -491,166 +228,371 @@ 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 { + errorPrefix := ("[#" + strconv.Itoa(idx) + "/" + desiredRecordContents + "] ") + + if idx >= len(matchingRecords) { + return fmt.Errorf(errorPrefix + "Record not found") + } + + rec := matchingRecords[idx] + + if 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(errorPrefix+"Record type field does not match: %#v : %#v", rec.Type, recordConfig.Arguments.Type) + } + + if 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(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. + + } + + return nil + } +} + +// +// 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{} + + // 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 +} + +// +// 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" + 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") + + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "2.2.2.2") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "3.3.3.3") + 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.UpdateRecords = append(record.Arguments.UpdateRecords, "2.2.2.2") + 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") + 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 +} + +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.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "redis.example.net.") + 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"`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `"PC-Intel-3.2ghz" "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") + 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 +} + +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.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "10 mail2.example.net.") + 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.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "10 mail3.example.com.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "10 mail4.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'.`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `100 70 "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.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "ns3.sysa.xyz.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "ns4.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"`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `"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") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "1 1 fedcba9876543210fedcba9876543210fedcba98") + 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.") + + 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 +} + +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"`) + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, `"updated 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.") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "www.some-other-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") + record.Arguments.UpdateRecords = append(record.Arguments.UpdateRecords, "something.something. hostmaster.sysa.xyz. 2021021801 10800 3600 604800 3600") + return record +}