diff --git a/internal/provider/provider.go b/internal/provider/provider.go index f1faf094..89588ded 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -153,7 +153,7 @@ func New() *schema.Provider { "dns_cname_record": resourceDnsCnameRecord(), "dns_mx_record_set": resourceDnsMXRecordSet(), "dns_ns_record_set": resourceDnsNSRecordSet(), - "dns_ptr_record": resourceDnsPtrRecord(), + "dns_ptr_record_set": resourceDnsPtrRecordSet(), "dns_srv_record_set": resourceDnsSRVRecordSet(), "dns_txt_record_set": resourceDnsTXTRecordSet(), }, diff --git a/internal/provider/resource_dns_ptr_record.go b/internal/provider/resource_dns_ptr_record_set.go similarity index 50% rename from internal/provider/resource_dns_ptr_record.go rename to internal/provider/resource_dns_ptr_record_set.go index c4cdbe55..cfe929dc 100644 --- a/internal/provider/resource_dns_ptr_record.go +++ b/internal/provider/resource_dns_ptr_record_set.go @@ -2,17 +2,18 @@ package provider import ( "fmt" + "sort" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/miekg/dns" ) -func resourceDnsPtrRecord() *schema.Resource { +func resourceDnsPtrRecordSet() *schema.Resource { return &schema.Resource{ - Create: resourceDnsPtrRecordCreate, - Read: resourceDnsPtrRecordRead, - Update: resourceDnsPtrRecordUpdate, - Delete: resourceDnsPtrRecordDelete, + Create: resourceDnsPtrRecordSetCreate, + Read: resourceDnsPtrRecordSetRead, + Update: resourceDnsPtrRecordSetUpdate, + Delete: resourceDnsPtrRecordSetDelete, Importer: &schema.ResourceImporter{ State: resourceDnsImport, }, @@ -30,10 +31,14 @@ func resourceDnsPtrRecord() *schema.Resource { ForceNew: true, ValidateFunc: validateName, }, - "ptr": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validateZone, + "ptrs": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateZone, + }, + Set: schema.HashString, }, "ttl": { Type: schema.TypeInt, @@ -45,14 +50,14 @@ func resourceDnsPtrRecord() *schema.Resource { } } -func resourceDnsPtrRecordCreate(d *schema.ResourceData, meta interface{}) error { +func resourceDnsPtrRecordSetCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(resourceFQDN(d)) - return resourceDnsPtrRecordUpdate(d, meta) + return resourceDnsPtrRecordSetUpdate(d, meta) } -func resourceDnsPtrRecordRead(d *schema.ResourceData, meta interface{}) error { +func resourceDnsPtrRecordSetRead(d *schema.ResourceData, meta interface{}) error { answers, err := resourceDnsRead(d, meta, dns.TypePTR) if err != nil { @@ -61,24 +66,30 @@ func resourceDnsPtrRecordRead(d *schema.ResourceData, meta interface{}) error { if len(answers) > 0 { - if len(answers) > 1 { - return fmt.Errorf("Error querying DNS record: multiple responses received") - } - record := answers[0] - ptr, ttl, err := getPtrVal(record) - if err != nil { - return fmt.Errorf("Error querying DNS record: %s", err) + var ttl sort.IntSlice + + ptrs := schema.NewSet(schema.HashString, nil) + for _, record := range answers { + ptr, t, err := getPtrVal(record) + if err != nil { + return fmt.Errorf("Error querying DNS record: %s", err) + } + ptrs.Add(ptr) + ttl = append(ttl, t) } - d.Set("ptr", ptr) - d.Set("ttl", ttl) + sort.Sort(ttl) + + d.Set("ptrs", ptrs) + d.Set("ttl", ttl[0]) } else { d.SetId("") } return nil + } -func resourceDnsPtrRecordUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceDnsPtrRecordSetUpdate(d *schema.ResourceData, meta interface{}) error { if meta != nil { @@ -90,15 +101,21 @@ func resourceDnsPtrRecordUpdate(d *schema.ResourceData, meta interface{}) error msg.SetUpdate(d.Get("zone").(string)) - if d.HasChange("ptr") { - o, n := d.GetChange("ptr") + if d.HasChange("ptrs") { + o, n := d.GetChange("ptrs") + os := o.(*schema.Set) + ns := n.(*schema.Set) + remove := os.Difference(ns).List() + add := ns.Difference(os).List() - if o != "" { - rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, o)) + // Loop through all the old ptrs and remove them + for _, ptr := range remove { + rr_remove, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, ptr.(string))) msg.Remove([]dns.RR{rr_remove}) } - if n != "" { - rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, n)) + // Loop through all the new ptrs and insert them + for _, ptr := range add { + rr_insert, _ := dns.NewRR(fmt.Sprintf("%s %d PTR %s", rec_fqdn, ttl, ptr.(string))) msg.Insert([]dns.RR{rr_insert}) } @@ -113,13 +130,13 @@ func resourceDnsPtrRecordUpdate(d *schema.ResourceData, meta interface{}) error } } - return resourceDnsPtrRecordRead(d, meta) + return resourceDnsPtrRecordSetRead(d, meta) } else { return fmt.Errorf("update server is not set") } } -func resourceDnsPtrRecordDelete(d *schema.ResourceData, meta interface{}) error { +func resourceDnsPtrRecordSetDelete(d *schema.ResourceData, meta interface{}) error { return resourceDnsDelete(d, meta, dns.TypePTR) } diff --git a/internal/provider/resource_dns_ptr_record_set_test.go b/internal/provider/resource_dns_ptr_record_set_test.go new file mode 100644 index 00000000..3fb5b47d --- /dev/null +++ b/internal/provider/resource_dns_ptr_record_set_test.go @@ -0,0 +1,166 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/miekg/dns" +) + +func TestAccDnsPtrRecordSet_basic(t *testing.T) { + + var rec_name, rec_zone string + resourceName := "dns_ptr_record_set.foo" + resourceRoot := "dns_ptr_record_set.root" + + deletePtrRecordSet := func() { + meta := testAccProvider.Meta() + + msg := new(dns.Msg) + + msg.SetUpdate(rec_zone) + + rec_fqdn := testResourceFQDN(rec_name, rec_zone) + + rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 PTR", rec_fqdn)) + msg.RemoveRRset([]dns.RR{rr_remove}) + + r, err := exchange(msg, true, meta) + if err != nil { + t.Fatalf("Error deleting DNS record: %s", err) + } + if r.Rcode != dns.RcodeSuccess { + t.Fatalf("Error deleting DNS record: %v", r.Rcode) + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDnsPtrRecordSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDnsPtrRecordSet_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "ptrs.#", "1"), + testAccCheckDnsPtrRecordSetExists(t, resourceName, []interface{}{"bar.example.com."}, &rec_name, &rec_zone), + ), + }, + { + Config: testAccDnsPtrRecordSet_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "ptrs.#", "2"), + testAccCheckDnsPtrRecordSetExists(t, resourceName, []interface{}{"bar.example.com.", "baz.example.com."}, &rec_name, &rec_zone), + ), + }, + { + PreConfig: deletePtrRecordSet, + Config: testAccDnsPtrRecordSet_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "ptrs.#", "2"), + testAccCheckDnsPtrRecordSetExists(t, resourceName, []interface{}{"bar.example.com.", "baz.example.com."}, &rec_name, &rec_zone), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDnsPtrRecordSet_root, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceRoot, "ptrs.#", "3"), + testAccCheckDnsPtrRecordSetExists(t, resourceRoot, []interface{}{"machine1.example.com.", "machine2.example.com.", "machine3.example.com."}, &rec_name, &rec_zone), + ), + }, + { + ResourceName: resourceRoot, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckDnsPtrRecordSetDestroy(s *terraform.State) error { + return testAccCheckDnsDestroy(s, "dns_ptr_record_set", dns.TypePTR) +} + +func testAccCheckDnsPtrRecordSetExists(t *testing.T, n string, pointers []interface{}, rec_name, rec_zone *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + *rec_name = rs.Primary.Attributes["name"] + *rec_zone = rs.Primary.Attributes["zone"] + + rec_fqdn := testResourceFQDN(*rec_name, *rec_zone) + + meta := testAccProvider.Meta() + + msg := new(dns.Msg) + msg.SetQuestion(rec_fqdn, dns.TypePTR) + + r, err := exchange(msg, false, meta) + if err != nil { + return fmt.Errorf("Error querying DNS record: %s", err) + } + if r.Rcode != dns.RcodeSuccess { + return fmt.Errorf("Error querying DNS record") + } + + ptrs := schema.NewSet(schema.HashString, nil) + expected := schema.NewSet(schema.HashString, pointers) + for _, record := range r.Answer { + ptr, _, err := getPtrVal(record) + if err != nil { + return fmt.Errorf("Error querying DNS record: %s", err) + } + ptrs.Add(ptr) + } + if !ptrs.Equal(expected) { + return fmt.Errorf("DNS record differs: expected %v, found %v", expected, ptrs) + } + return nil + } +} + +var testAccDnsPtrRecordSet_basic = fmt.Sprintf(` + resource "dns_ptr_record_set" "foo" { + zone = "example.com." + name = "r._dns-sd._udp" + ptrs = [ + "bar.example.com.", + ] + ttl = 300 + }`) + +var testAccDnsPtrRecordSet_update = fmt.Sprintf(` + resource "dns_ptr_record_set" "foo" { + zone = "example.com." + name = "r._dns-sd._udp" + ptrs = [ + "bar.example.com.", + "baz.example.com.", + ] + ttl = 300 + }`) + +var testAccDnsPtrRecordSet_root = fmt.Sprintf(` + resource "dns_ptr_record_set" "root" { + zone = "example.com." + ptrs = [ + "machine1.example.com.", + "machine2.example.com.", + "machine3.example.com.", + ] + ttl = 300 + }`) diff --git a/internal/provider/resource_dns_ptr_record_test.go b/internal/provider/resource_dns_ptr_record_test.go deleted file mode 100644 index 580cadd9..00000000 --- a/internal/provider/resource_dns_ptr_record_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package provider - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "github.com/miekg/dns" -) - -func TestAccDnsPtrRecord_basic(t *testing.T) { - - var rec_name, rec_zone string - resourceName := "dns_ptr_record.foo" - resourceRoot := "dns_ptr_record.root" - - deletePtrRecord := func() { - meta := testAccProvider.Meta() - - msg := new(dns.Msg) - - msg.SetUpdate(rec_zone) - - rec_fqdn := testResourceFQDN(rec_name, rec_zone) - - rr_remove, _ := dns.NewRR(fmt.Sprintf("%s 0 PTR", rec_fqdn)) - msg.RemoveRRset([]dns.RR{rr_remove}) - - r, err := exchange(msg, true, meta) - if err != nil { - t.Fatalf("Error deleting DNS record: %s", err) - } - if r.Rcode != dns.RcodeSuccess { - t.Fatalf("Error deleting DNS record: %v", r.Rcode) - } - } - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckDnsPtrRecordDestroy, - Steps: []resource.TestStep{ - { - Config: testAccDnsPtrRecord_basic, - Check: resource.ComposeTestCheckFunc( - testAccCheckDnsPtrRecordExists(t, resourceName, "bar.example.com.", &rec_name, &rec_zone), - ), - }, - { - Config: testAccDnsPtrRecord_update, - Check: resource.ComposeTestCheckFunc( - testAccCheckDnsPtrRecordExists(t, resourceName, "baz.example.com.", &rec_name, &rec_zone), - ), - }, - { - PreConfig: deletePtrRecord, - Config: testAccDnsPtrRecord_update, - Check: resource.ComposeTestCheckFunc( - testAccCheckDnsPtrRecordExists(t, resourceName, "baz.example.com.", &rec_name, &rec_zone), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccDnsPtrRecord_root, - Check: resource.ComposeTestCheckFunc( - testAccCheckDnsPtrRecordExists(t, resourceRoot, "baz.example.com.", &rec_name, &rec_zone), - ), - }, - { - ResourceName: resourceRoot, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccCheckDnsPtrRecordDestroy(s *terraform.State) error { - return testAccCheckDnsDestroy(s, "dns_ptr_record", dns.TypePTR) -} - -func testAccCheckDnsPtrRecordExists(t *testing.T, n string, expected string, rec_name, rec_zone *string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - *rec_name = rs.Primary.Attributes["name"] - *rec_zone = rs.Primary.Attributes["zone"] - - rec_fqdn := testResourceFQDN(*rec_name, *rec_zone) - - meta := testAccProvider.Meta() - - msg := new(dns.Msg) - msg.SetQuestion(rec_fqdn, dns.TypePTR) - r, err := exchange(msg, false, meta) - if err != nil { - return fmt.Errorf("Error querying DNS record: %s", err) - } - if r.Rcode != dns.RcodeSuccess { - return fmt.Errorf("Error querying DNS record") - } - - if len(r.Answer) > 1 { - return fmt.Errorf("Error querying DNS record: multiple responses received") - } - record := r.Answer[0] - ptr, _, err := getPtrVal(record) - if err != nil { - return fmt.Errorf("Error querying DNS record: %s", err) - } - if expected != ptr { - return fmt.Errorf("DNS record differs: expected %v, found %v", expected, ptr) - } - return nil - } -} - -var testAccDnsPtrRecord_basic = fmt.Sprintf(` - resource "dns_ptr_record" "foo" { - zone = "example.com." - name = "r._dns-sd._udp" - ptr = "bar.example.com." - ttl = 300 - }`) - -var testAccDnsPtrRecord_update = fmt.Sprintf(` - resource "dns_ptr_record" "foo" { - zone = "example.com." - name = "r._dns-sd._udp" - ptr = "baz.example.com." - ttl = 300 - }`) - -var testAccDnsPtrRecord_root = fmt.Sprintf(` - resource "dns_ptr_record" "root" { - zone = "example.com." - ptr = "baz.example.com." - ttl = 300 - }`) diff --git a/website/dns.erb b/website/dns.erb index 7598714f..50b009f4 100644 --- a/website/dns.erb +++ b/website/dns.erb @@ -59,8 +59,8 @@ > dns_ns_record_set - > - dns_ptr_record + > + dns_ptr_record_set > dns_srv_record_set diff --git a/website/docs/r/dns_ptr_record.html.markdown b/website/docs/r/dns_ptr_record_set.html.markdown similarity index 69% rename from website/docs/r/dns_ptr_record.html.markdown rename to website/docs/r/dns_ptr_record_set.html.markdown index 700a65a2..80f5ddad 100644 --- a/website/docs/r/dns_ptr_record.html.markdown +++ b/website/docs/r/dns_ptr_record_set.html.markdown @@ -1,22 +1,22 @@ --- layout: "dns" -page_title: "DNS: dns_ptr_record" -sidebar_current: "docs-dns-ptr-record" +page_title: "DNS: dns_ptr_record_set" +sidebar_current: "docs-dns-ptr-record-set" description: |- - Creates a PTR type DNS record. + Creates a PTR type DNS record set. --- -# dns_ptr_record +# dns_ptr_record_set -Creates a PTR type DNS record. +Creates a PTR type DNS record set. ## Example Usage ```hcl -resource "dns_ptr_record" "dns-sd" { +resource "dns_ptr_record_set" "dns-sd" { zone = "example.com." name = "r._dns-sd" - ptr = "example.com." + ptrs = ["example.com."] ttl = 300 } ``` @@ -27,7 +27,7 @@ The following arguments are supported: * `zone` - (Required) DNS zone the record belongs to. It must be an FQDN, that is, include the trailing dot. * `name` - (Optional) The name of the record. The `zone` argument will be appended to this value to create the full record path. -* `ptr` - (Required) The canonical name this record will point to. +* `ptrs` - (Required) A list of names that this record will point to. * `ttl` - (Optional) The TTL of the record set. Defaults to `3600`. ## Attributes Reference @@ -36,7 +36,7 @@ The following attributes are exported: * `zone` - See Argument Reference above. * `name` - See Argument Reference above. -* `ptr` - See Argument Reference above. +* `ptrs` - See Argument Reference above. * `ttl` - See Argument Reference above. ## Import