From 13be49e14700121c0b3b294c223816fcc89acd8c Mon Sep 17 00:00:00 2001 From: Cameron Thornton Date: Thu, 16 Sep 2021 15:29:56 -0500 Subject: [PATCH] Revert `google_dns_record_set` to previous implementation (#5191) --- mmv1/products/dns/ansible.yaml | 4 +- mmv1/products/dns/api.yaml | 65 --- mmv1/products/dns/inspec.yaml | 2 - mmv1/products/dns/terraform.yaml | 17 - .../resources/resource_dns_record_set.go | 421 ++++++++++++++++++ .../tests/resource_dns_record_set_test.go.erb | 40 +- .../terraform/utils/provider.go.erb | 1 + .../docs/r/dns_record_set.html.markdown | 176 ++++++++ 8 files changed, 632 insertions(+), 94 deletions(-) create mode 100644 mmv1/third_party/terraform/resources/resource_dns_record_set.go create mode 100644 mmv1/third_party/terraform/website/docs/r/dns_record_set.html.markdown diff --git a/mmv1/products/dns/ansible.yaml b/mmv1/products/dns/ansible.yaml index 1e69b4b0b2ce..8d0f8da54260 100644 --- a/mmv1/products/dns/ansible.yaml +++ b/mmv1/products/dns/ansible.yaml @@ -86,9 +86,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides contain_extra_docs: false Project: !ruby/object:Overrides::Ansible::ResourceOverride # TODO(alexstephen): Re-evaluate merging Project into Ansible - exclude: true - ResourceDnsRecordSet: !ruby/object:Overrides::Ansible::ResourceOverride - exclude: true + exclude: true files: !ruby/object:Provider::Config::Files resource: <%= lines(indent(compile('provider/ansible/resource~compile.yaml'), 4)) -%> diff --git a/mmv1/products/dns/api.yaml b/mmv1/products/dns/api.yaml index 6e8faf173fc0..1ab8cfa1e8cd 100644 --- a/mmv1/products/dns/api.yaml +++ b/mmv1/products/dns/api.yaml @@ -497,68 +497,3 @@ objects: description: | As defined in RFC 1035 (section 5) and RFC 1034 (section 3.6.1) api_name: rrdatas - - !ruby/object:Api::Resource - name: 'ResourceDnsRecordSet' - kind: 'dns#resourceRecordSet' - description: | - A single DNS record that exists on a domain name (i.e. in a managed zone). - This record defines the information about the domain and where the - domain / subdomains direct to. - - The record will include the domain/subdomain name, a type (i.e. A, AAA, - CAA, MX, CNAME, NS, etc) - base_url: 'projects/{{project}}/managedZones/{{managed_zone}}/rrsets' - self_link: 'projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}' - update_verb: :PATCH - parameters: - - !ruby/object:Api::Type::ResourceRef - name: 'managed_zone' - input: true - description: | - Identifies the managed zone addressed by this request. - required: true - resource: 'ManagedZone' - imports: 'name' - properties: - - !ruby/object:Api::Type::String - name: 'name' - description: For example, www.example.com. - required: true - input: true - - !ruby/object:Api::Type::Enum - name: 'type' - values: - - :A - - :AAAA - - :CAA - - :CNAME - - :DNSKEY - - :DS - - :IPSECVPNKEY - - :MX - - :NAPTR - - :NS - - :PTR - - :SOA - - :SPF - - :SRV - - :SSHFP - - :TLSA - - :TXT - description: One of valid DNS resource types. - required: true - - !ruby/object:Api::Type::Integer - name: 'ttl' - description: | - Number of seconds that this ResourceRecordSet can be cached by - resolvers. - - !ruby/object:Api::Type::Array - name: rrdatas - input: true - description: | - The string data for the records in this record set whose meaning depends on the DNS type. - For TXT record, if the string data contains spaces, add surrounding \" if you don't want your string to get - split on spaces. To specify a single record value longer than 255 characters such as a TXT record for - DKIM, add \"\" inside the Terraform configuration string (e.g. "first255characters\"\"morecharacters"). - item_type: Api::Type::String - diff --git a/mmv1/products/dns/inspec.yaml b/mmv1/products/dns/inspec.yaml index 7af33fb297b2..bfd87b9dfffd 100644 --- a/mmv1/products/dns/inspec.yaml +++ b/mmv1/products/dns/inspec.yaml @@ -34,6 +34,4 @@ overrides: !ruby/object:Overrides::ResourceOverrides exclude: true Policy: !ruby/object:Overrides::Inspec::ResourceOverride exclude: true - ResourceDnsRecordSet: !ruby/object:Overrides::Inspec::ResourceOverride - exclude: true diff --git a/mmv1/products/dns/terraform.yaml b/mmv1/products/dns/terraform.yaml index 6d3db4ab593d..73f6568391be 100644 --- a/mmv1/products/dns/terraform.yaml +++ b/mmv1/products/dns/terraform.yaml @@ -191,23 +191,6 @@ overrides: !ruby/object:Overrides::ResourceOverrides pre_delete: templates/terraform/pre_delete/detach_network.erb ResourceRecordSet: !ruby/object:Overrides::Terraform::ResourceOverride exclude: true - ResourceDnsRecordSet: !ruby/object:Overrides::Terraform::ResourceOverride - legacy_name: "google_dns_record_set" - import_format: ["projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}"] - custom_code: !ruby/object:Provider::Terraform::CustomCode - constants: 'templates/terraform/constants/resource_dns_resource_record_set.go.erb' - examples: - - !ruby/object:Provider::Terraform::Examples - skip_test: true - name: "dns_record_set_basic" - primary_resource_id: "resource-recordset" - vars: - zone_name: "my-zone" - properties: - rrdatas: !ruby/object:Overrides::Terraform::PropertyOverride - diff_suppress_func: 'rrdatasDnsDiffSuppress' - managed_zone: !ruby/object:Overrides::Terraform::PropertyOverride - ignore_read: true Project: !ruby/object:Overrides::Terraform::ResourceOverride exclude: true # This is for copying files over diff --git a/mmv1/third_party/terraform/resources/resource_dns_record_set.go b/mmv1/third_party/terraform/resources/resource_dns_record_set.go new file mode 100644 index 000000000000..40cfa4c1cd37 --- /dev/null +++ b/mmv1/third_party/terraform/resources/resource_dns_record_set.go @@ -0,0 +1,421 @@ +package google + +import ( + "fmt" + "log" + + "strings" + + "net" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "google.golang.org/api/dns/v1" +) + +func rrdatasDnsDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + o, n := d.GetChange("rrdatas") + if o == nil || n == nil { + return false + } + + oList := convertStringArr(o.([]interface{})) + nList := convertStringArr(n.([]interface{})) + + parseFunc := func(record string) string { + switch d.Get("type") { + case "AAAA": + // parse ipv6 to a key from one list + return net.ParseIP(record).String() + case "MX", "DS": + return strings.ToLower(record) + case "TXT": + return strings.ToLower(strings.Trim(record, `"`)) + default: + return record + } + } + return rrdatasListDiffSuppress(oList, nList, parseFunc, d) +} + +// suppress on a list when 1) its items have dups that need to be ignored +// and 2) string comparison on the items may need a special parse function +// example of usage can be found ../../../third_party/terraform/tests/resource_dns_record_set_test.go.erb +func rrdatasListDiffSuppress(oldList, newList []string, fun func(x string) string, _ *schema.ResourceData) bool { + // compare two lists of unordered records + diff := make(map[string]bool, len(oldList)) + for _, oldRecord := range oldList { + // set all new IPs to true + diff[fun(oldRecord)] = true + } + for _, newRecord := range newList { + // set matched IPs to false otherwise can't suppress + if diff[fun(newRecord)] { + diff[fun(newRecord)] = false + } else { + return false + } + } + // can't suppress if unmatched records are found + for _, element := range diff { + if element { + return false + } + } + return true +} + +func resourceDnsRecordSet() *schema.Resource { + return &schema.Resource{ + Create: resourceDnsRecordSetCreate, + Read: resourceDnsRecordSetRead, + Delete: resourceDnsRecordSetDelete, + Update: resourceDnsRecordSetUpdate, + Importer: &schema.ResourceImporter{ + State: resourceDnsRecordSetImportState, + }, + + Schema: map[string]*schema.Schema{ + "managed_zone": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: `The name of the zone in which this record set will reside.`, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The DNS name this record set will apply to.`, + }, + + "rrdatas": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + DiffSuppressFunc: rrdatasDnsDiffSuppress, + Description: `The string data for the records in this record set whose meaning depends on the DNS type. For TXT record, if the string data contains spaces, add surrounding \" if you don't want your string to get split on spaces. To specify a single record value longer than 255 characters such as a TXT record for DKIM, add \"\" inside the Terraform configuration string (e.g. "first255characters\"\"morecharacters").`, + }, + + "ttl": { + Type: schema.TypeInt, + Optional: true, + Description: `The time-to-live of this record set (seconds).`, + }, + + "type": { + Type: schema.TypeString, + Required: true, + Description: `The DNS record set type.`, + }, + + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: `The ID of the project in which the resource belongs. If it is not provided, the provider project is used.`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceDnsRecordSetCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + + name := d.Get("name").(string) + zone := d.Get("managed_zone").(string) + rType := d.Get("type").(string) + + // Build the change + chg := &dns.Change{ + Additions: []*dns.ResourceRecordSet{ + { + Name: name, + Type: rType, + Ttl: int64(d.Get("ttl").(int)), + Rrdatas: rrdata(d), + }, + }, + } + + // The terraform provider is authoritative, so what we do here is check if + // any records that we are trying to create already exist and make sure we + // delete them, before adding in the changes requested. Normally this would + // result in an AlreadyExistsError. + log.Printf("[DEBUG] DNS record list request for %q", zone) + res, err := config.NewDnsClient(userAgent).ResourceRecordSets.List(project, zone).Do() + if err != nil { + return fmt.Errorf("Error retrieving record sets for %q: %s", zone, err) + } + var deletions []*dns.ResourceRecordSet + + for _, record := range res.Rrsets { + if record.Type != rType || record.Name != name { + continue + } + deletions = append(deletions, record) + } + if len(deletions) > 0 { + chg.Deletions = deletions + } + + log.Printf("[DEBUG] DNS Record create request: %#v", chg) + chg, err = config.NewDnsClient(userAgent).Changes.Create(project, zone, chg).Do() + if err != nil { + return fmt.Errorf("Error creating DNS RecordSet: %s", err) + } + + d.SetId(fmt.Sprintf("projects/%s/managedZones/%s/rrsets/%s/%s", project, zone, name, rType)) + + w := &DnsChangeWaiter{ + Service: config.NewDnsClient(userAgent), + Change: chg, + Project: project, + ManagedZone: zone, + } + _, err = w.Conf().WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Google DNS change: %s", err) + } + + return resourceDnsRecordSetRead(d, meta) +} + +func resourceDnsRecordSetRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + + zone := d.Get("managed_zone").(string) + + // name and type are effectively the 'key' + name := d.Get("name").(string) + dnsType := d.Get("type").(string) + + var resp *dns.ResourceRecordSetsListResponse + err = retry(func() error { + var reqErr error + resp, reqErr = config.NewDnsClient(userAgent).ResourceRecordSets.List( + project, zone).Name(name).Type(dnsType).Do() + return reqErr + }) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("DNS Record Set %q", d.Get("name").(string))) + } + if len(resp.Rrsets) == 0 { + // The resource doesn't exist anymore + d.SetId("") + return nil + } + + if len(resp.Rrsets) > 1 { + return fmt.Errorf("Only expected 1 record set, got %d", len(resp.Rrsets)) + } + + if err := d.Set("type", resp.Rrsets[0].Type); err != nil { + return fmt.Errorf("Error setting type: %s", err) + } + if err := d.Set("ttl", resp.Rrsets[0].Ttl); err != nil { + return fmt.Errorf("Error setting ttl: %s", err) + } + if err := d.Set("rrdatas", resp.Rrsets[0].Rrdatas); err != nil { + return fmt.Errorf("Error setting rrdatas: %s", err) + } + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error setting project: %s", err) + } + + return nil +} + +func resourceDnsRecordSetDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + + zone := d.Get("managed_zone").(string) + + // NS records must always have a value, so we short-circuit delete + // this allows terraform delete to work, but may have unexpected + // side-effects when deleting just that record set. + // Unfortunately, you can set NS records on subdomains, and those + // CAN and MUST be deleted, so we need to retrieve the managed zone, + // check if what we're looking at is a subdomain, and only not delete + // if it's not actually a subdomain + if d.Get("type").(string) == "NS" { + mz, err := config.NewDnsClient(userAgent).ManagedZones.Get(project, zone).Do() + if err != nil { + return fmt.Errorf("Error retrieving managed zone %q from %q: %s", zone, project, err) + } + domain := mz.DnsName + + if domain == d.Get("name").(string) { + log.Println("[DEBUG] NS records can't be deleted due to API restrictions, so they're being left in place. See https://www.terraform.io/docs/providers/google/r/dns_record_set.html for more information.") + return nil + } + } + + // Build the change + chg := &dns.Change{ + Deletions: []*dns.ResourceRecordSet{ + { + Name: d.Get("name").(string), + Type: d.Get("type").(string), + Ttl: int64(d.Get("ttl").(int)), + Rrdatas: rrdata(d), + }, + }, + } + + log.Printf("[DEBUG] DNS Record delete request: %#v", chg) + chg, err = config.NewDnsClient(userAgent).Changes.Create(project, zone, chg).Do() + if err != nil { + return handleNotFoundError(err, d, "google_dns_record_set") + } + + w := &DnsChangeWaiter{ + Service: config.NewDnsClient(userAgent), + Change: chg, + Project: project, + ManagedZone: zone, + } + _, err = w.Conf().WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Google DNS change: %s", err) + } + + d.SetId("") + return nil +} + +func resourceDnsRecordSetUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + + zone := d.Get("managed_zone").(string) + recordName := d.Get("name").(string) + + oldTtl, newTtl := d.GetChange("ttl") + oldType, newType := d.GetChange("type") + + oldCountRaw, _ := d.GetChange("rrdatas.#") + oldCount := oldCountRaw.(int) + + chg := &dns.Change{ + Deletions: []*dns.ResourceRecordSet{ + { + Name: recordName, + Type: oldType.(string), + Ttl: int64(oldTtl.(int)), + Rrdatas: make([]string, oldCount), + }, + }, + Additions: []*dns.ResourceRecordSet{ + { + Name: recordName, + Type: newType.(string), + Ttl: int64(newTtl.(int)), + Rrdatas: rrdata(d), + }, + }, + } + + for i := 0; i < oldCount; i++ { + rrKey := fmt.Sprintf("rrdatas.%d", i) + oldRR, _ := d.GetChange(rrKey) + chg.Deletions[0].Rrdatas[i] = oldRR.(string) + } + log.Printf("[DEBUG] DNS Record change request: %#v old: %#v new: %#v", chg, chg.Deletions[0], chg.Additions[0]) + chg, err = config.NewDnsClient(userAgent).Changes.Create(project, zone, chg).Do() + if err != nil { + return fmt.Errorf("Error changing DNS RecordSet: %s", err) + } + + w := &DnsChangeWaiter{ + Service: config.NewDnsClient(userAgent), + Change: chg, + Project: project, + ManagedZone: zone, + } + if _, err = w.Conf().WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Google DNS change: %s", err) + } + + d.SetId(fmt.Sprintf("projects/%s/managedZones/%s/rrsets/%s/%s", project, zone, recordName, newType)) + + return resourceDnsRecordSetRead(d, meta) +} + +func resourceDnsRecordSetImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/managedZones/(?P[^/]+)/rrsets/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func rrdata( + d *schema.ResourceData, +) []string { + rrdatasCount := d.Get("rrdatas.#").(int) + data := make([]string, rrdatasCount) + for i := 0; i < rrdatasCount; i++ { + data[i] = d.Get(fmt.Sprintf("rrdatas.%d", i)).(string) + } + return data +} + +func ipv6AddressDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + oldIp := net.ParseIP(old) + newIp := net.ParseIP(new) + + return oldIp.Equal(newIp) +} diff --git a/mmv1/third_party/terraform/tests/resource_dns_record_set_test.go.erb b/mmv1/third_party/terraform/tests/resource_dns_record_set_test.go.erb index e9e545899b21..55b4fb622574 100644 --- a/mmv1/third_party/terraform/tests/resource_dns_record_set_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_dns_record_set_test.go.erb @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) + func TestIpv6AddressDiffSuppress(t *testing.T) { cases := map[string]struct { Old, New []string @@ -52,6 +53,7 @@ func TestIpv6AddressDiffSuppress(t *testing.T) { } } } + func TestAccDNSRecordSet_basic(t *testing.T) { t.Parallel() @@ -143,9 +145,9 @@ func TestAccDNSRecordSet_changeType(t *testing.T) { Config: testAccDnsRecordSet_bigChange(zoneName, 600), }, { - ResourceName: "google_dns_record_set.foobar", - ImportStateId: fmt.Sprintf("%s/%s/test-record.%s.hashicorptest.com./CNAME", getTestProjectFromEnv(), zoneName, zoneName), - ImportState: true, + ResourceName: "google_dns_record_set.foobar", + ImportStateId: fmt.Sprintf("%s/%s/test-record.%s.hashicorptest.com./CNAME", getTestProjectFromEnv(), zoneName, zoneName), + ImportState: true, ImportStateVerify: true, }, }, @@ -156,13 +158,14 @@ func TestAccDNSRecordSet_nestedNS(t *testing.T) { t.Parallel() zoneName := fmt.Sprintf("dnszone-test-ns-%s", randString(t, 10)) + recordSetName := fmt.Sprintf("\"nested.%s.hashicorptest.com.\"", zoneName) vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckDnsRecordSetDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccDnsRecordSet_nestedNS(zoneName, 300), + Config: testAccDnsRecordSet_NS(zoneName, recordSetName, 300), }, { ResourceName: "google_dns_record_set.foobar", @@ -174,6 +177,29 @@ func TestAccDNSRecordSet_nestedNS(t *testing.T) { }) } +func TestAccDNSRecordSet_secondaryNS(t *testing.T) { + t.Parallel() + + zoneName := fmt.Sprintf("dnszone-test-ns-%s", randString(t, 10)) + recordSetName := "google_dns_managed_zone.parent-zone.dns_name" + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDnsRecordSetDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDnsRecordSet_NS(zoneName, recordSetName, 300), + }, + { + ResourceName: "google_dns_record_set.foobar", + ImportStateId: fmt.Sprintf("projects/%s/managedZones/%s/rrsets/%s.hashicorptest.com./NS", getTestProjectFromEnv(), zoneName, zoneName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccDNSRecordSet_quotedTXT(t *testing.T) { t.Parallel() @@ -270,7 +296,7 @@ resource "google_dns_record_set" "foobar" { `, zoneName, zoneName, zoneName, addr2, ttl) } -func testAccDnsRecordSet_nestedNS(name string, ttl int) string { +func testAccDnsRecordSet_NS(name string, recordSetName string, ttl int) string { return fmt.Sprintf(` resource "google_dns_managed_zone" "parent-zone" { name = "%s" @@ -280,12 +306,12 @@ resource "google_dns_managed_zone" "parent-zone" { resource "google_dns_record_set" "foobar" { managed_zone = google_dns_managed_zone.parent-zone.name - name = "nested.%s.hashicorptest.com." + name = %s type = "NS" rrdatas = ["ns.hashicorp.services.", "ns2.hashicorp.services."] ttl = %d } -`, name, name, name, ttl) +`, name, name, recordSetName, ttl) } func testAccDnsRecordSet_bigChange(zoneName string, ttl int) string { diff --git a/mmv1/third_party/terraform/utils/provider.go.erb b/mmv1/third_party/terraform/utils/provider.go.erb index cf276eacc9f9..f9a23dd3fed9 100644 --- a/mmv1/third_party/terraform/utils/provider.go.erb +++ b/mmv1/third_party/terraform/utils/provider.go.erb @@ -395,6 +395,7 @@ end # products.each do <% end -%> "google_dataproc_cluster": resourceDataprocCluster(), "google_dataproc_job": resourceDataprocJob(), + "google_dns_record_set": resourceDnsRecordSet(), "google_endpoints_service": resourceEndpointsService(), "google_folder": resourceGoogleFolder(), "google_folder_organization_policy": resourceGoogleFolderOrganizationPolicy(), diff --git a/mmv1/third_party/terraform/website/docs/r/dns_record_set.html.markdown b/mmv1/third_party/terraform/website/docs/r/dns_record_set.html.markdown new file mode 100644 index 000000000000..4d62df958c6c --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/r/dns_record_set.html.markdown @@ -0,0 +1,176 @@ +--- +subcategory: "Cloud DNS" +layout: "google" +page_title: "Google: google_dns_record_set" +sidebar_current: "docs-google-dns-record-set" +description: |- + Manages a set of DNS records within Google Cloud DNS. +--- + +# google\_dns\_record\_set + +Manages a set of DNS records within Google Cloud DNS. For more information see [the official documentation](https://cloud.google.com/dns/records/) and +[API](https://cloud.google.com/dns/api/v1/resourceRecordSets). + +~> **Note:** The provider treats this resource as an authoritative record set. This means existing records (including the default records) for the given type will be overwritten when you create this resource in Terraform. In addition, the Google Cloud DNS API requires NS records to be present at all times, so Terraform will not actually remove NS records during destroy but will report that it did. + +## Example Usage + +### Binding a DNS name to the ephemeral IP of a new instance: + +```hcl +resource "google_dns_record_set" "frontend" { + name = "frontend.${google_dns_managed_zone.prod.dns_name}" + type = "A" + ttl = 300 + + managed_zone = google_dns_managed_zone.prod.name + + rrdatas = [google_compute_instance.frontend.network_interface[0].access_config[0].nat_ip] +} + +resource "google_compute_instance" "frontend" { + name = "frontend" + machine_type = "g1-small" + zone = "us-central1-b" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + network = "default" + access_config { + } + } +} + +resource "google_dns_managed_zone" "prod" { + name = "prod-zone" + dns_name = "prod.mydomain.com." +} +``` + +### Adding an A record + +```hcl +resource "google_dns_record_set" "a" { + name = "backend.${google_dns_managed_zone.prod.dns_name}" + managed_zone = google_dns_managed_zone.prod.name + type = "A" + ttl = 300 + + rrdatas = ["8.8.8.8"] +} + +resource "google_dns_managed_zone" "prod" { + name = "prod-zone" + dns_name = "prod.mydomain.com." +} +``` + +### Adding an MX record + +```hcl +resource "google_dns_record_set" "mx" { + name = google_dns_managed_zone.prod.dns_name + managed_zone = google_dns_managed_zone.prod.name + type = "MX" + ttl = 3600 + + rrdatas = [ + "1 aspmx.l.google.com.", + "5 alt1.aspmx.l.google.com.", + "5 alt2.aspmx.l.google.com.", + "10 alt3.aspmx.l.google.com.", + "10 alt4.aspmx.l.google.com.", + ] +} + +resource "google_dns_managed_zone" "prod" { + name = "prod-zone" + dns_name = "prod.mydomain.com." +} +``` + +### Adding an SPF record + +Quotes (`""`) must be added around your `rrdatas` for a SPF record. Otherwise `rrdatas` string gets split on spaces. + +```hcl +resource "google_dns_record_set" "spf" { + name = "frontend.${google_dns_managed_zone.prod.dns_name}" + managed_zone = google_dns_managed_zone.prod.name + type = "TXT" + ttl = 300 + + rrdatas = ["\"v=spf1 ip4:111.111.111.111 include:backoff.email-example.com -all\""] +} + +resource "google_dns_managed_zone" "prod" { + name = "prod-zone" + dns_name = "prod.mydomain.com." +} +``` + +### Adding a CNAME record + + The list of `rrdatas` should only contain a single string corresponding to the Canonical Name intended. + +```hcl +resource "google_dns_record_set" "cname" { + name = "frontend.${google_dns_managed_zone.prod.dns_name}" + managed_zone = google_dns_managed_zone.prod.name + type = "CNAME" + ttl = 300 + rrdatas = ["frontend.mydomain.com."] +} + +resource "google_dns_managed_zone" "prod" { + name = "prod-zone" + dns_name = "prod.mydomain.com." +} +``` + +## Argument Reference + +The following arguments are supported: + +* `managed_zone` - (Required) The name of the zone in which this record set will + reside. + +* `name` - (Required) The DNS name this record set will apply to. + +* `rrdatas` - (Required) The string data for the records in this record set + whose meaning depends on the DNS type. For TXT record, if the string data contains spaces, add surrounding `\"` if you don't want your string to get split on spaces. To specify a single record value longer than 255 characters such as a TXT record for DKIM, add `\" \"` inside the Terraform configuration string (e.g. `"first255characters\" \"morecharacters"`). + + +* `type` - (Required) The DNS record set type. + +- - - + +* `ttl` - (Optional) The time-to-live of this record set (seconds). + +* `project` - (Optional) The ID of the project in which the resource belongs. If it + is not provided, the provider project is used. + +## Attributes Reference + +-In addition to the arguments listed above, the following computed attributes are +-exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/managedZones/{{zone}}/rrsets/{{name}}/{{type}}` + +## Import + +DNS record sets can be imported using either of these accepted formats: + +``` +$ terraform import google_dns_record_set.frontend projects/{{project}}/managedZones/{{zone}}/rrsets/{{name}}/{{type}} +$ terraform import google_dns_record_set.frontend {{project}}/{{zone}}/{{name}}/{{type}} +$ terraform import google_dns_record_set.frontend {{zone}}/{{name}}/{{type}} +``` + +Note: The record name must include the trailing dot at the end. \ No newline at end of file