From e1c8d760d31bc0f442983035860fe32df7308930 Mon Sep 17 00:00:00 2001 From: jrperritt Date: Wed, 24 May 2017 23:58:44 -0500 Subject: [PATCH] provider/openstack: Add DNS V2 RecordSet resource (#14813) * vendor gophercloud dns v2 recordset deps * openstack dns v2 recordset resource * fix type assertion panic condition * address pr review feedback --- .../import_openstack_dns_recordset_v2_test.go | 28 ++ builtin/providers/openstack/provider.go | 1 + .../resource_openstack_dns_recordset_v2.go | 276 ++++++++++++++++++ ...esource_openstack_dns_recordset_v2_test.go | 249 ++++++++++++++++ .../resource_openstack_dns_zone_v2.go | 7 +- builtin/providers/openstack/types.go | 22 ++ .../openstack/dns/v2/recordsets/doc.go | 6 + .../openstack/dns/v2/recordsets/requests.go | 157 ++++++++++ .../openstack/dns/v2/recordsets/results.go | 141 +++++++++ .../openstack/dns/v2/recordsets/urls.go | 11 + vendor/vendor.json | 6 + .../r/dns_recordset_v2.html.markdown | 82 ++++++ 12 files changed, 985 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/openstack/import_openstack_dns_recordset_v2_test.go create mode 100644 builtin/providers/openstack/resource_openstack_dns_recordset_v2.go create mode 100644 builtin/providers/openstack/resource_openstack_dns_recordset_v2_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/urls.go create mode 100644 website/source/docs/providers/openstack/r/dns_recordset_v2.html.markdown diff --git a/builtin/providers/openstack/import_openstack_dns_recordset_v2_test.go b/builtin/providers/openstack/import_openstack_dns_recordset_v2_test.go new file mode 100644 index 000000000..dc07afaf0 --- /dev/null +++ b/builtin/providers/openstack/import_openstack_dns_recordset_v2_test.go @@ -0,0 +1,28 @@ +package openstack + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDNSV2RecordSet_importBasic(t *testing.T) { + zoneName := randomZoneName() + resourceName := "openstack_dns_recordset_v2.recordset_1" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckDNSRecordSetV2(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDNSV2RecordSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDNSV2RecordSet_basic(zoneName), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 1290ee266..49a2d45ec 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -150,6 +150,7 @@ func Provider() terraform.ResourceProvider { "openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(), "openstack_compute_floatingip_associate_v2": resourceComputeFloatingIPAssociateV2(), "openstack_compute_volume_attach_v2": resourceComputeVolumeAttachV2(), + "openstack_dns_recordset_v2": resourceDNSRecordSetV2(), "openstack_dns_zone_v2": resourceDNSZoneV2(), "openstack_fw_firewall_v1": resourceFWFirewallV1(), "openstack_fw_policy_v1": resourceFWPolicyV1(), diff --git a/builtin/providers/openstack/resource_openstack_dns_recordset_v2.go b/builtin/providers/openstack/resource_openstack_dns_recordset_v2.go new file mode 100644 index 000000000..cf911cd4a --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_dns_recordset_v2.go @@ -0,0 +1,276 @@ +package openstack + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDNSRecordSetV2() *schema.Resource { + return &schema.Resource{ + Create: resourceDNSRecordSetV2Create, + Read: resourceDNSRecordSetV2Read, + Update: resourceDNSRecordSetV2Update, + Delete: resourceDNSRecordSetV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + "zone_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "records": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ttl": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: false, + }, + "type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "value_specs": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceDNSRecordSetV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + dnsClient, err := config.dnsV2Client(GetRegion(d)) + if err != nil { + return fmt.Errorf("Error creating OpenStack DNS client: %s", err) + } + + recordsraw := d.Get("records").([]interface{}) + records := make([]string, len(recordsraw)) + for i, recordraw := range recordsraw { + records[i] = recordraw.(string) + } + + createOpts := RecordSetCreateOpts{ + recordsets.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Records: records, + TTL: d.Get("ttl").(int), + Type: d.Get("type").(string), + }, + MapValueSpecs(d), + } + + zoneID := d.Get("zone_id").(string) + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + n, err := recordsets.Create(dnsClient, zoneID, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack DNS record set: %s", err) + } + + log.Printf("[DEBUG] Waiting for DNS record set (%s) to become available", n.ID) + stateConf := &resource.StateChangeConf{ + Target: []string{"ACTIVE"}, + Pending: []string{"PENDING"}, + Refresh: waitForDNSRecordSet(dnsClient, zoneID, n.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + + id := fmt.Sprintf("%s/%s", zoneID, n.ID) + d.SetId(id) + + log.Printf("[DEBUG] Created OpenStack DNS record set %s: %#v", n.ID, n) + return resourceDNSRecordSetV2Read(d, meta) +} + +func resourceDNSRecordSetV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + dnsClient, err := config.dnsV2Client(GetRegion(d)) + if err != nil { + return fmt.Errorf("Error creating OpenStack DNS client: %s", err) + } + + // Obtain relevant info from parsing the ID + zoneID, recordsetID, err := parseDNSV2RecordSetID(d.Id()) + if err != nil { + return err + } + + n, err := recordsets.Get(dnsClient, zoneID, recordsetID).Extract() + if err != nil { + return CheckDeleted(d, err, "record_set") + } + + log.Printf("[DEBUG] Retrieved record set %s: %#v", recordsetID, n) + + d.Set("name", n.Name) + d.Set("description", n.Description) + d.Set("ttl", n.TTL) + d.Set("type", n.Type) + d.Set("records", n.Records) + d.Set("region", GetRegion(d)) + d.Set("zone_id", zoneID) + + return nil +} + +func resourceDNSRecordSetV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + dnsClient, err := config.dnsV2Client(GetRegion(d)) + if err != nil { + return fmt.Errorf("Error creating OpenStack DNS client: %s", err) + } + + var updateOpts recordsets.UpdateOpts + if d.HasChange("ttl") { + updateOpts.TTL = d.Get("ttl").(int) + } + + if d.HasChange("records") { + recordsraw := d.Get("records").([]interface{}) + records := make([]string, len(recordsraw)) + for i, recordraw := range recordsraw { + records[i] = recordraw.(string) + } + updateOpts.Records = records + } + + if d.HasChange("description") { + updateOpts.Description = d.Get("description").(string) + } + + // Obtain relevant info from parsing the ID + zoneID, recordsetID, err := parseDNSV2RecordSetID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating record set %s with options: %#v", recordsetID, updateOpts) + + _, err = recordsets.Update(dnsClient, zoneID, recordsetID, updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack DNS record set: %s", err) + } + + log.Printf("[DEBUG] Waiting for DNS record set (%s) to update", recordsetID) + stateConf := &resource.StateChangeConf{ + Target: []string{"ACTIVE"}, + Pending: []string{"PENDING"}, + Refresh: waitForDNSRecordSet(dnsClient, zoneID, recordsetID), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + + return resourceDNSRecordSetV2Read(d, meta) +} + +func resourceDNSRecordSetV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + dnsClient, err := config.dnsV2Client(GetRegion(d)) + if err != nil { + return fmt.Errorf("Error creating OpenStack DNS client: %s", err) + } + + // Obtain relevant info from parsing the ID + zoneID, recordsetID, err := parseDNSV2RecordSetID(d.Id()) + if err != nil { + return err + } + + err = recordsets.Delete(dnsClient, zoneID, recordsetID).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack DNS record set: %s", err) + } + + log.Printf("[DEBUG] Waiting for DNS record set (%s) to be deleted", recordsetID) + stateConf := &resource.StateChangeConf{ + Target: []string{"DELETED"}, + Pending: []string{"ACTIVE", "PENDING"}, + Refresh: waitForDNSRecordSet(dnsClient, zoneID, recordsetID), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + + d.SetId("") + return nil +} + +func waitForDNSRecordSet(dnsClient *gophercloud.ServiceClient, zoneID, recordsetId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + recordset, err := recordsets.Get(dnsClient, zoneID, recordsetId).Extract() + if err != nil { + if _, ok := err.(gophercloud.ErrDefault404); ok { + return recordset, "DELETED", nil + } + + return nil, "", err + } + + log.Printf("[DEBUG] OpenStack DNS record set (%s) current status: %s", recordset.ID, recordset.Status) + return recordset, recordset.Status, nil + } +} + +func parseDNSV2RecordSetID(id string) (string, string, error) { + idParts := strings.Split(id, "/") + if len(idParts) != 2 { + return "", "", fmt.Errorf("Unable to determine DNS record set ID from raw ID: %s", id) + } + + zoneID := idParts[0] + recordsetID := idParts[1] + + return zoneID, recordsetID, nil +} diff --git a/builtin/providers/openstack/resource_openstack_dns_recordset_v2_test.go b/builtin/providers/openstack/resource_openstack_dns_recordset_v2_test.go new file mode 100644 index 000000000..051e88411 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_dns_recordset_v2_test.go @@ -0,0 +1,249 @@ +package openstack + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" +) + +func randomZoneName() string { + return fmt.Sprintf("ACPTTEST-zone-%s.com.", acctest.RandString(5)) +} + +func TestAccDNSV2RecordSet_basic(t *testing.T) { + var recordset recordsets.RecordSet + zoneName := randomZoneName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckDNSRecordSetV2(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDNSV2RecordSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDNSV2RecordSet_basic(zoneName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDNSV2RecordSetExists("openstack_dns_recordset_v2.recordset_1", &recordset), + resource.TestCheckResourceAttr( + "openstack_dns_recordset_v2.recordset_1", "description", "a record set"), + resource.TestCheckResourceAttr( + "openstack_dns_recordset_v2.recordset_1", "records.0", "10.1.0.0"), + ), + }, + resource.TestStep{ + Config: testAccDNSV2RecordSet_update(zoneName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_dns_recordset_v2.recordset_1", "name", zoneName), + resource.TestCheckResourceAttr("openstack_dns_recordset_v2.recordset_1", "ttl", "6000"), + resource.TestCheckResourceAttr("openstack_dns_recordset_v2.recordset_1", "type", "A"), + resource.TestCheckResourceAttr( + "openstack_dns_recordset_v2.recordset_1", "description", "an updated record set"), + resource.TestCheckResourceAttr( + "openstack_dns_recordset_v2.recordset_1", "records.0", "10.1.0.1"), + ), + }, + }, + }) +} + +func TestAccDNSV2RecordSet_readTTL(t *testing.T) { + var recordset recordsets.RecordSet + zoneName := randomZoneName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckDNSRecordSetV2(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDNSV2RecordSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDNSV2RecordSet_readTTL(zoneName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDNSV2RecordSetExists("openstack_dns_recordset_v2.recordset_1", &recordset), + resource.TestMatchResourceAttr( + "openstack_dns_recordset_v2.recordset_1", "ttl", regexp.MustCompile("^[0-9]+$")), + ), + }, + }, + }) +} + +func TestAccDNSV2RecordSet_timeout(t *testing.T) { + var recordset recordsets.RecordSet + zoneName := randomZoneName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckDNSRecordSetV2(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDNSV2RecordSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDNSV2RecordSet_timeout(zoneName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDNSV2RecordSetExists("openstack_dns_recordset_v2.recordset_1", &recordset), + ), + }, + }, + }) +} + +func testAccCheckDNSV2RecordSetDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + dnsClient, err := config.dnsV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating OpenStack DNS client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_dns_recordset_v2" { + continue + } + + zoneID, recordsetID, err := parseDNSV2RecordSetID(rs.Primary.ID) + if err != nil { + return err + } + + _, err = recordsets.Get(dnsClient, zoneID, recordsetID).Extract() + if err == nil { + return fmt.Errorf("Record set still exists") + } + } + + return nil +} + +func testAccCheckDNSV2RecordSetExists(n string, recordset *recordsets.RecordSet) 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") + } + + config := testAccProvider.Meta().(*Config) + dnsClient, err := config.dnsV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating OpenStack DNS client: %s", err) + } + + zoneID, recordsetID, err := parseDNSV2RecordSetID(rs.Primary.ID) + if err != nil { + return err + } + + found, err := recordsets.Get(dnsClient, zoneID, recordsetID).Extract() + if err != nil { + return err + } + + if found.ID != recordsetID { + return fmt.Errorf("Record set not found") + } + + *recordset = *found + + return nil + } +} + +func testAccPreCheckDNSRecordSetV2(t *testing.T) { + if os.Getenv("OS_AUTH_URL") == "" { + t.Fatal("OS_AUTH_URL must be set for acceptance tests") + } +} + +func testAccDNSV2RecordSet_basic(zoneName string) string { + return fmt.Sprintf(` + resource "openstack_dns_zone_v2" "zone_1" { + name = "%s" + email = "email2@example.com" + description = "a zone" + ttl = 6000 + type = "PRIMARY" + } + + resource "openstack_dns_recordset_v2" "recordset_1" { + zone_id = "${openstack_dns_zone_v2.zone_1.id}" + name = "%s" + type = "A" + description = "a record set" + ttl = 3000 + records = ["10.1.0.0"] + } + `, zoneName, zoneName) +} + +func testAccDNSV2RecordSet_update(zoneName string) string { + return fmt.Sprintf(` + resource "openstack_dns_zone_v2" "zone_1" { + name = "%s" + email = "email2@example.com" + description = "an updated zone" + ttl = 6000 + type = "PRIMARY" + } + + resource "openstack_dns_recordset_v2" "recordset_1" { + zone_id = "${openstack_dns_zone_v2.zone_1.id}" + name = "%s" + type = "A" + description = "an updated record set" + ttl = 6000 + records = ["10.1.0.1"] + } + `, zoneName, zoneName) +} + +func testAccDNSV2RecordSet_readTTL(zoneName string) string { + return fmt.Sprintf(` + resource "openstack_dns_zone_v2" "zone_1" { + name = "%s" + email = "email2@example.com" + description = "an updated zone" + ttl = 6000 + type = "PRIMARY" + } + + resource "openstack_dns_recordset_v2" "recordset_1" { + zone_id = "${openstack_dns_zone_v2.zone_1.id}" + name = "%s" + type = "A" + records = ["10.1.0.2"] + } + `, zoneName, zoneName) +} + +func testAccDNSV2RecordSet_timeout(zoneName string) string { + return fmt.Sprintf(` + resource "openstack_dns_zone_v2" "zone_1" { + name = "%s" + email = "email2@example.com" + description = "an updated zone" + ttl = 6000 + type = "PRIMARY" + } + + resource "openstack_dns_recordset_v2" "recordset_1" { + zone_id = "${openstack_dns_zone_v2.zone_1.id}" + name = "%s" + type = "A" + ttl = 3000 + records = ["10.1.0.3"] + + timeouts { + create = "5m" + update = "5m" + delete = "5m" + } + } + `, zoneName, zoneName) +} diff --git a/builtin/providers/openstack/resource_openstack_dns_zone_v2.go b/builtin/providers/openstack/resource_openstack_dns_zone_v2.go index 542502fc2..2b4b7995b 100644 --- a/builtin/providers/openstack/resource_openstack_dns_zone_v2.go +++ b/builtin/providers/openstack/resource_openstack_dns_zone_v2.go @@ -180,7 +180,12 @@ func resourceDNSZoneV2Update(d *schema.ResourceData, meta interface{}) error { updateOpts.TTL = d.Get("ttl").(int) } if d.HasChange("masters") { - updateOpts.Masters = d.Get("masters").([]string) + mastersraw := d.Get("masters").(*schema.Set).List() + masters := make([]string, len(mastersraw)) + for i, masterraw := range mastersraw { + masters[i] = masterraw.(string) + } + updateOpts.Masters = masters } if d.HasChange("description") { updateOpts.Description = d.Get("description").(string) diff --git a/builtin/providers/openstack/types.go b/builtin/providers/openstack/types.go index 6acd04ec0..491d4c79f 100644 --- a/builtin/providers/openstack/types.go +++ b/builtin/providers/openstack/types.go @@ -12,6 +12,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups" + "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies" @@ -225,6 +226,27 @@ func (opts PortCreateOpts) ToPortCreateMap() (map[string]interface{}, error) { return BuildRequest(opts, "port") } +// RecordSetCreateOpts represents the attributes used when creating a new DNS record set. +type RecordSetCreateOpts struct { + recordsets.CreateOpts + ValueSpecs map[string]string `json:"value_specs,omitempty"` +} + +// ToRecordSetCreateMap casts a CreateOpts struct to a map. +// It overrides recordsets.ToRecordSetCreateMap to add the ValueSpecs field. +func (opts RecordSetCreateOpts) ToRecordSetCreateMap() (map[string]interface{}, error) { + b, err := BuildRequest(opts, "") + if err != nil { + return nil, err + } + + if m, ok := b[""].(map[string]interface{}); ok { + return m, nil + } + + return nil, fmt.Errorf("Expected map but got %T", b[""]) +} + // RouterCreateOpts represents the attributes used when creating a new router. type RouterCreateOpts struct { routers.CreateOpts diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go new file mode 100644 index 000000000..82a0aed5d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go @@ -0,0 +1,6 @@ +// Package recordsets provides information and interaction with the zone API +// resource for the OpenStack DNS service. +// +// For more information, see: +// http://developer.openstack.org/api-ref/dns/#recordsets +package recordsets diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go new file mode 100644 index 000000000..eab6bc926 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go @@ -0,0 +1,157 @@ +package recordsets + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToRecordSetListQuery() (string, error) +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the server attributes you want to see returned. Marker and Limit are used +// for pagination. +// https://developer.openstack.org/api-ref/dns/ +type ListOpts struct { + // Integer value for the limit of values to return. + Limit int `q:"limit"` + + // UUID of the recordset at which you want to set a marker. + Marker string `q:"marker"` + + Data string `q:"data"` + Description string `q:"description"` + Name string `q:"name"` + SortDir string `q:"sort_dir"` + SortKey string `q:"sort_key"` + Status string `q:"status"` + TTL int `q:"ttl"` + Type string `q:"type"` + ZoneID string `q:"zone_id"` +} + +// ToRecordSetListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToRecordSetListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// ListByZone implements the recordset list request. +func ListByZone(client *gophercloud.ServiceClient, zoneID string, opts ListOptsBuilder) pagination.Pager { + url := baseURL(client, zoneID) + if opts != nil { + query, err := opts.ToRecordSetListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return RecordSetPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Get implements the recordset get request. +func Get(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r GetResult) { + _, r.Err = client.Get(rrsetURL(client, zoneID, rrsetID), &r.Body, nil) + return +} + +// CreateOptsBuilder allows extensions to add additional attributes to the Create request. +type CreateOptsBuilder interface { + ToRecordSetCreateMap() (map[string]interface{}, error) +} + +// CreateOpts specifies the base attributes that may be used to create a RecordSet. +type CreateOpts struct { + // Name is the name of the RecordSet. + Name string `json:"name" required:"true"` + + // Description is a description of the RecordSet. + Description string `json:"description,omitempty"` + + // Records are the DNS records of the RecordSet. + Records []string `json:"records,omitempty"` + + // TTL is the time to live of the RecordSet. + TTL int `json:"ttl,omitempty"` + + // Type is the RRTYPE of the RecordSet. + Type string `json:"type,omitempty"` +} + +// ToRecordSetCreateMap formats an CreateOpts structure into a request body. +func (opts CreateOpts) ToRecordSetCreateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + return b, nil +} + +// Create creates a recordset in a given zone. +func Create(client *gophercloud.ServiceClient, zoneID string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToRecordSetCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(baseURL(client, zoneID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201, 202}, + }) + return +} + +// UpdateOptsBuilder allows extensions to add additional attributes to the Update request. +type UpdateOptsBuilder interface { + ToRecordSetUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts specifies the base attributes that may be updated on an existing RecordSet. +type UpdateOpts struct { + Description string `json:"description,omitempty"` + TTL int `json:"ttl,omitempty"` + Records []string `json:"records,omitempty"` +} + +// ToRecordSetUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToRecordSetUpdateMap() (map[string]interface{}, error) { + b, err := gophercloud.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.TTL > 0 { + b["ttl"] = opts.TTL + } else { + b["ttl"] = nil + } + + return b, nil +} + +// Update updates a recordset in a given zone +func Update(client *gophercloud.ServiceClient, zoneID string, rrsetID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToRecordSetUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(rrsetURL(client, zoneID, rrsetID), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 202}, + }) + return +} + +// Delete removes an existing RecordSet. +func Delete(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r DeleteResult) { + _, r.Err = client.Delete(rrsetURL(client, zoneID, rrsetID), &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go new file mode 100644 index 000000000..f7ce9f59e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go @@ -0,0 +1,141 @@ +package recordsets + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete RecordSet. +// An error is returned if the original call or the extraction failed. +func (r commonResult) Extract() (*RecordSet, error) { + var s *RecordSet + err := r.ExtractInto(&s) + return s, err +} + +// CreateResult is the deferred result of a Create call. +type CreateResult struct { + commonResult +} + +// GetResult is the deferred result of a Get call. +type GetResult struct { + commonResult +} + +// RecordSetPage is a single page of RecordSet results. +type RecordSetPage struct { + pagination.LinkedPageBase +} + +// UpdateResult is the deferred result of an Update call. +type UpdateResult struct { + commonResult +} + +// DeleteResult is the deferred result of an Delete call. +type DeleteResult struct { + gophercloud.ErrResult +} + +// IsEmpty returns true if the page contains no results. +func (r RecordSetPage) IsEmpty() (bool, error) { + s, err := ExtractRecordSets(r) + return len(s) == 0, err +} + +// ExtractRecordSets extracts a slice of RecordSets from a Collection acquired from List. +func ExtractRecordSets(r pagination.Page) ([]RecordSet, error) { + var s struct { + RecordSets []RecordSet `json:"recordsets"` + } + err := (r.(RecordSetPage)).ExtractInto(&s) + return s.RecordSets, err +} + +type RecordSet struct { + // ID is the unique ID of the recordset + ID string `json:"id"` + + // ZoneID is the ID of the zone the recordset belongs to. + ZoneID string `json:"zone_id"` + + // ProjectID is the ID of the project that owns the recordset. + ProjectID string `json:"project_id"` + + // Name is the name of the recordset. + Name string `json:"name"` + + // ZoneName is the name of the zone the recordset belongs to. + ZoneName string `json:"zone_name"` + + // Type is the RRTYPE of the recordset. + Type string `json:"type"` + + // Records are the DNS records of the recordset. + Records []string `json:"records"` + + // TTL is the time to live of the recordset. + TTL int `json:"ttl"` + + // Status is the status of the recordset. + Status string `json:"status"` + + // Action is the current action in progress of the recordset. + Action string `json:"action"` + + // Description is the description of the recordset. + Description string `json:"description"` + + // Version is the revision of the recordset. + Version int `json:version"` + + // CreatedAt is the date when the recordset was created. + CreatedAt time.Time `json:"-"` + + // UpdatedAt is the date when the recordset was updated. + UpdatedAt time.Time `json:"-"` + + // Links includes HTTP references to the itself, + // useful for passing along to other APIs that might want a recordset reference. + Links []gophercloud.Link `json:"-"` +} + +func (r *RecordSet) UnmarshalJSON(b []byte) error { + type tmp RecordSet + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + Links map[string]interface{} `json:"links"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = RecordSet(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + if s.Links != nil { + for rel, href := range s.Links { + if v, ok := href.(string); ok { + link := gophercloud.Link{ + Rel: rel, + Href: v, + } + r.Links = append(r.Links, link) + } + } + } + + return err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/urls.go new file mode 100644 index 000000000..5ec18d1bb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/urls.go @@ -0,0 +1,11 @@ +package recordsets + +import "github.com/gophercloud/gophercloud" + +func baseURL(c *gophercloud.ServiceClient, zoneID string) string { + return c.ServiceURL("zones", zoneID, "recordsets") +} + +func rrsetURL(c *gophercloud.ServiceClient, zoneID string, rrsetID string) string { + return c.ServiceURL("zones", zoneID, "recordsets", rrsetID) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index cddea4204..6f0791b98 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1714,6 +1714,12 @@ "revision": "0f64da0e36de86a0ca1a8f2fc1b0570a0d3f7504", "revisionTime": "2017-03-10T01:59:53Z" }, + { + "checksumSHA1": "Mugw0ROrabHNN4nG9YlimPbyENk=", + "path": "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets", + "revision": "5a6e13341e63bd202a0da558a2a7187720042676", + "revisionTime": "2017-05-22T01:35:44Z" + }, { "checksumSHA1": "OGb3suDcMij0bnu0r/eQgWAX5Ho=", "path": "github.com/gophercloud/gophercloud/openstack/dns/v2/zones", diff --git a/website/source/docs/providers/openstack/r/dns_recordset_v2.html.markdown b/website/source/docs/providers/openstack/r/dns_recordset_v2.html.markdown new file mode 100644 index 000000000..c8312e7f9 --- /dev/null +++ b/website/source/docs/providers/openstack/r/dns_recordset_v2.html.markdown @@ -0,0 +1,82 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_dns_recordset_v2" +sidebar_current: "docs-openstack-resource-dns-recordset-v2" +description: |- + Manages a DNS record set in the OpenStack DNS Service +--- + +# openstack\_dns\_recordset_v2 + +Manages a DNS record set in the OpenStack DNS Service. + +## Example Usage + +### Automatically detect the correct network + +```hcl +resource "openstack_dns_zone_v2" "example_zone" { + name = "example.com." + email = "email2@example.com" + description = "a zone" + ttl = 6000 + type = "PRIMARY" +} + +resource "openstack_dns_recordset_v2" "rs_example_com" { + zone_id = "${openstack_dns_zone_v2.example_zone.id}" + name = "rs.example.com." + description = "An example record set" + ttl = 3000 + type = "A" + records = ["10.0.0.1"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Required) The region in which to obtain the V2 DNS client. + If omitted, the `OS_REGION_NAME` environment variable is used. + Changing this creates a new DNS record set. + +* `zone_id` - (Required) The ID of the zone in which to create the record set. + Changing this creates a new DNS record set. + +* `name` - (Required) The name of the record set. Note the `.` at the end of the name. + Changing this creates a new DNS record set. + +* `type` - (Optional) The type of record set. Examples: "A", "MX". + Changing this creates a new DNS record set. + +* `ttl` - (Optional) The time to live (TTL) of the record set. + +* `description` - (Optional) A description of the record set. + +* `records` - (Optional) An array of DNS records. + +* `value_specs` - (Optional) Map of additional options. Changing this creates a + new record set. + +## Attributes Reference + +The following attributes are exported: + +* `region` - See Argument Reference above. +* `name` - See Argument Reference above. +* `type` - See Argument Reference above. +* `ttl` - See Argument Reference above. +* `description` - See Argument Reference above. +* `records` - See Argument Reference above. +* `zone_id` - See Argument Reference above. +* `value_specs` - See Argument Reference above. + +## Import + +This resource can be imported by specifying the zone ID and recordset ID, +separated by a forward slash. + +``` +$ terraform import openstack_dns_recordset_v2.recordset_1 / +```