diff --git a/.changelog/5191.txt b/.changelog/5191.txt new file mode 100644 index 00000000000..fb33860a986 --- /dev/null +++ b/.changelog/5191.txt @@ -0,0 +1,6 @@ +```release-note:bug +dns: fixed an issue in `google_dns_record_set` where creating the resource would result in an 409 error +``` +```release-note:bug +dns: fixed an issue in `google_dns_record_set` where `rrdatas` could not be updated +``` diff --git a/google/provider.go b/google/provider.go index 9cf9dc9661d..0706db3d252 100644 --- a/google/provider.go +++ b/google/provider.go @@ -809,9 +809,9 @@ func Provider() *schema.Provider { return provider } -// Generated resources: 214 +// Generated resources: 213 // Generated IAM resources: 90 -// Total generated resources: 304 +// Total generated resources: 303 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -989,7 +989,6 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_dialogflow_cx_environment": resourceDialogflowCXEnvironment(), "google_dns_managed_zone": resourceDNSManagedZone(), "google_dns_policy": resourceDNSPolicy(), - "google_dns_record_set": resourceDNSResourceDnsRecordSet(), "google_essential_contacts_contact": resourceEssentialContactsContact(), "google_filestore_instance": resourceFilestoreInstance(), "google_firestore_index": resourceFirestoreIndex(), @@ -1156,6 +1155,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_dataflow_job": resourceDataflowJob(), "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/google/resource_access_context_manager_access_level_condition.go b/google/resource_access_context_manager_access_level_condition.go index 12b6c8540f5..3b36c9588ae 100644 --- a/google/resource_access_context_manager_access_level_condition.go +++ b/google/resource_access_context_manager_access_level_condition.go @@ -22,7 +22,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func resourceAccessContextManagerAccessLevelCondition() *schema.Resource { diff --git a/google/resource_access_context_manager_service_perimeter_resource.go b/google/resource_access_context_manager_service_perimeter_resource.go index ae40c0bdf83..abc1ed184f3 100644 --- a/google/resource_access_context_manager_service_perimeter_resource.go +++ b/google/resource_access_context_manager_service_perimeter_resource.go @@ -21,7 +21,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) func resourceAccessContextManagerServicePerimeterResource() *schema.Resource { diff --git a/google/resource_bigquery_dataset.go b/google/resource_bigquery_dataset.go index 4b467d7ff8e..4fb48010c0f 100644 --- a/google/resource_bigquery_dataset.go +++ b/google/resource_bigquery_dataset.go @@ -23,7 +23,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) const datasetIdRegexp = `[0-9A-Za-z_]+` diff --git a/google/resource_bigquery_dataset_access.go b/google/resource_bigquery_dataset_access.go index 6f2fe3fce13..0725c4a9c46 100644 --- a/google/resource_bigquery_dataset_access.go +++ b/google/resource_bigquery_dataset_access.go @@ -22,7 +22,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) var bigqueryAccessRoleToPrimitiveMap = map[string]string{ diff --git a/google/resource_bigquery_job.go b/google/resource_bigquery_job.go index e23860ed89e..a0759810894 100644 --- a/google/resource_bigquery_job.go +++ b/google/resource_bigquery_job.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) var ( diff --git a/google/resource_bigquery_routine.go b/google/resource_bigquery_routine.go index 740b91a699f..dda6333cae9 100644 --- a/google/resource_bigquery_routine.go +++ b/google/resource_bigquery_routine.go @@ -25,7 +25,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func resourceBigQueryRoutine() *schema.Resource { diff --git a/google/resource_bigtable_app_profile.go b/google/resource_bigtable_app_profile.go index 7289e014de1..cc552eab612 100644 --- a/google/resource_bigtable_app_profile.go +++ b/google/resource_bigtable_app_profile.go @@ -22,7 +22,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/bigtableadmin/v2" ) func resourceBigtableAppProfile() *schema.Resource { diff --git a/google/resource_cloud_run_domain_mapping.go b/google/resource_cloud_run_domain_mapping.go index 37526d8b633..58485a7941b 100644 --- a/google/resource_cloud_run_domain_mapping.go +++ b/google/resource_cloud_run_domain_mapping.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) var domainMappingGoogleProvidedLabels = []string{ diff --git a/google/resource_cloud_run_service.go b/google/resource_cloud_run_service.go index e23750d7006..e9cd03e6463 100644 --- a/google/resource_cloud_run_service.go +++ b/google/resource_cloud_run_service.go @@ -25,7 +25,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) func revisionNameCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { diff --git a/google/resource_composer_environment.go b/google/resource_composer_environment.go index 7a9d37a9e3a..2489bf4d169 100644 --- a/google/resource_composer_environment.go +++ b/google/resource_composer_environment.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - composer "google.golang.org/api/composer/v1beta1" ) const ( diff --git a/google/resource_compute_backend_service.go b/google/resource_compute_backend_service.go index cdcdadbb915..c782bd4bd80 100644 --- a/google/resource_compute_backend_service.go +++ b/google/resource_compute_backend_service.go @@ -23,11 +23,8 @@ import ( "strconv" "time" - "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/compute/v1" - "google.golang.org/api/googleapi" ) // Whether the backend is a global or regional NEG diff --git a/google/resource_compute_disk.go b/google/resource_compute_disk.go index 6b808ab5b93..058c2f7aaa9 100644 --- a/google/resource_compute_disk.go +++ b/google/resource_compute_disk.go @@ -25,7 +25,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) // Is the new disk size smaller than the old one? diff --git a/google/resource_compute_instance_group_named_port.go b/google/resource_compute_instance_group_named_port.go index cb820ccbb97..9f96246d845 100644 --- a/google/resource_compute_instance_group_named_port.go +++ b/google/resource_compute_instance_group_named_port.go @@ -22,7 +22,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) func resourceComputeInstanceGroupNamedPort() *schema.Resource { diff --git a/google/resource_compute_network.go b/google/resource_compute_network.go index 905252d4331..edeb2637325 100644 --- a/google/resource_compute_network.go +++ b/google/resource_compute_network.go @@ -23,7 +23,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func resourceComputeNetwork() *schema.Resource { diff --git a/google/resource_compute_region_backend_service.go b/google/resource_compute_region_backend_service.go index 217f7fd6ef0..89b762f5cfc 100644 --- a/google/resource_compute_region_backend_service.go +++ b/google/resource_compute_region_backend_service.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) // Fields in "backends" that are not allowed for non-managed backend services diff --git a/google/resource_compute_region_disk.go b/google/resource_compute_region_disk.go index 35aba982a79..fe9d5ebb4a0 100644 --- a/google/resource_compute_region_disk.go +++ b/google/resource_compute_region_disk.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) func resourceComputeRegionDisk() *schema.Resource { diff --git a/google/resource_compute_router_nat.go b/google/resource_compute_router_nat.go index dca4d08a91f..f4e0d8071b8 100644 --- a/google/resource_compute_router_nat.go +++ b/google/resource_compute_router_nat.go @@ -25,7 +25,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func resourceNameSetFromSelfLinkSet(v interface{}) *schema.Set { diff --git a/google/resource_compute_router_peer.go b/google/resource_compute_router_peer.go index 1fb9539f990..13c70d1ef23 100644 --- a/google/resource_compute_router_peer.go +++ b/google/resource_compute_router_peer.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func resourceComputeRouterBgpPeer() *schema.Resource { diff --git a/google/resource_compute_subnetwork.go b/google/resource_compute_subnetwork.go index 543938bbe62..4ae5236f37d 100644 --- a/google/resource_compute_subnetwork.go +++ b/google/resource_compute_subnetwork.go @@ -22,7 +22,6 @@ import ( "reflect" "time" - "github.com/apparentlymart/go-cidr/cidr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/google/resource_data_loss_prevention_stored_info_type.go b/google/resource_data_loss_prevention_stored_info_type.go index 8c8df234cd8..40e64e91017 100644 --- a/google/resource_data_loss_prevention_stored_info_type.go +++ b/google/resource_data_loss_prevention_stored_info_type.go @@ -22,7 +22,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) func resourceDataLossPreventionStoredInfoType() *schema.Resource { diff --git a/google/resource_dns_managed_zone.go b/google/resource_dns_managed_zone.go index 8ce04b88a85..138b6cb573c 100644 --- a/google/resource_dns_managed_zone.go +++ b/google/resource_dns_managed_zone.go @@ -25,7 +25,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/dns/v1" ) func resourceDNSManagedZone() *schema.Resource { diff --git a/google/resource_dns_record_set.go b/google/resource_dns_record_set.go index e44fc746ad6..40cfa4c1cd3 100644 --- a/google/resource_dns_record_set.go +++ b/google/resource_dns_record_set.go @@ -1,30 +1,15 @@ -// ---------------------------------------------------------------------------- -// -// *** AUTO GENERATED CODE *** Type: MMv1 *** -// -// ---------------------------------------------------------------------------- -// -// This file is automatically generated by Magic Modules and manual -// changes will be clobbered when the file is regenerated. -// -// Please read more about how to change this file in -// .github/CONTRIBUTING.md. -// -// ---------------------------------------------------------------------------- - package google import ( "fmt" "log" - "net" - "reflect" - "strconv" + "strings" - "time" + + "net" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "google.golang.org/api/dns/v1" ) func rrdatasDnsDiffSuppress(k, old, new string, d *schema.ResourceData) bool { @@ -79,21 +64,14 @@ func rrdatasListDiffSuppress(oldList, newList []string, fun func(x string) strin return true } -func resourceDNSResourceDnsRecordSet() *schema.Resource { +func resourceDnsRecordSet() *schema.Resource { return &schema.Resource{ - Create: resourceDNSResourceDnsRecordSetCreate, - Read: resourceDNSResourceDnsRecordSetRead, - Update: resourceDNSResourceDnsRecordSetUpdate, - Delete: resourceDNSResourceDnsRecordSetDelete, - + Create: resourceDnsRecordSetCreate, + Read: resourceDnsRecordSetRead, + Delete: resourceDnsRecordSetDelete, + Update: resourceDnsRecordSetUpdate, Importer: &schema.ResourceImporter{ - State: resourceDNSResourceDnsRecordSetImport, - }, - - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(4 * time.Minute), - Update: schema.DefaultTimeout(4 * time.Minute), - Delete: schema.DefaultTimeout(4 * time.Minute), + State: resourceDnsRecordSetImportState, }, Schema: map[string]*schema.Schema{ @@ -102,265 +80,309 @@ func resourceDNSResourceDnsRecordSet() *schema.Resource { Required: true, ForceNew: true, DiffSuppressFunc: compareSelfLinkOrResourceName, - Description: `Identifies the managed zone addressed by this request.`, + Description: `The name of the zone in which this record set will reside.`, }, + "name": { Type: schema.TypeString, Required: true, ForceNew: true, - Description: `For example, www.example.com.`, - }, - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"A", "AAAA", "CAA", "CNAME", "DNSKEY", "DS", "IPSECVPNKEY", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "SSHFP", "TLSA", "TXT"}, false), - Description: `One of valid DNS resource types. Possible values: ["A", "AAAA", "CAA", "CNAME", "DNSKEY", "DS", "IPSECVPNKEY", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "SSHFP", "TLSA", "TXT"]`, + Description: `The DNS name this record set will apply to.`, }, + "rrdatas": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - 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").`, + 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: `Number of seconds that this ResourceRecordSet can be cached by -resolvers.`, + 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, + 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 resourceDNSResourceDnsRecordSetCreate(d *schema.ResourceData, meta interface{}) error { +func resourceDnsRecordSetCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) userAgent, err := generateUserAgentString(d, config.userAgent) if err != nil { return err } - obj := make(map[string]interface{}) - nameProp, err := expandDNSResourceDnsRecordSetName(d.Get("name"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { - obj["name"] = nameProp - } - typeProp, err := expandDNSResourceDnsRecordSetType(d.Get("type"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("type"); !isEmptyValue(reflect.ValueOf(typeProp)) && (ok || !reflect.DeepEqual(v, typeProp)) { - obj["type"] = typeProp - } - ttlProp, err := expandDNSResourceDnsRecordSetTtl(d.Get("ttl"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("ttl"); !isEmptyValue(reflect.ValueOf(ttlProp)) && (ok || !reflect.DeepEqual(v, ttlProp)) { - obj["ttl"] = ttlProp - } - rrdatasProp, err := expandDNSResourceDnsRecordSetRrdatas(d.Get("rrdatas"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("rrdatas"); !isEmptyValue(reflect.ValueOf(rrdatasProp)) && (ok || !reflect.DeepEqual(v, rrdatasProp)) { - obj["rrdatas"] = rrdatasProp - } - managed_zoneProp, err := expandDNSResourceDnsRecordSetManagedZone(d.Get("managed_zone"), d, config) + project, err := getProject(d, config) if err != nil { return err - } else if v, ok := d.GetOkExists("managed_zone"); !isEmptyValue(reflect.ValueOf(managed_zoneProp)) && (ok || !reflect.DeepEqual(v, managed_zoneProp)) { - obj["managed_zone"] = managed_zoneProp } - url, err := replaceVars(d, config, "{{DNSBasePath}}projects/{{project}}/managedZones/{{managed_zone}}/rrsets") - if err != nil { - return err - } + name := d.Get("name").(string) + zone := d.Get("managed_zone").(string) + rType := d.Get("type").(string) - log.Printf("[DEBUG] Creating new ResourceDnsRecordSet: %#v", obj) - billingProject := "" + // Build the change + chg := &dns.Change{ + Additions: []*dns.ResourceRecordSet{ + { + Name: name, + Type: rType, + Ttl: int64(d.Get("ttl").(int)), + Rrdatas: rrdata(d), + }, + }, + } - project, err := getProject(d, config) + // 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 fetching project for ResourceDnsRecordSet: %s", err) + return fmt.Errorf("Error retrieving record sets for %q: %s", zone, err) } - billingProject = project + var deletions []*dns.ResourceRecordSet - // err == nil indicates that the billing_project value was found - if bp, err := getBillingProject(d, config); err == nil { - billingProject = bp + for _, record := range res.Rrsets { + if record.Type != rType || record.Name != name { + continue + } + deletions = append(deletions, record) + } + if len(deletions) > 0 { + chg.Deletions = deletions } - res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + 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 ResourceDnsRecordSet: %s", err) + return fmt.Errorf("Error creating DNS RecordSet: %s", err) } - // Store the ID now - id, err := replaceVars(d, config, "projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}") + 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 constructing id: %s", err) + return fmt.Errorf("Error waiting for Google DNS change: %s", err) } - d.SetId(id) - log.Printf("[DEBUG] Finished creating ResourceDnsRecordSet %q: %#v", d.Id(), res) - - return resourceDNSResourceDnsRecordSetRead(d, meta) + return resourceDnsRecordSetRead(d, meta) } -func resourceDNSResourceDnsRecordSetRead(d *schema.ResourceData, meta interface{}) error { +func resourceDnsRecordSetRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) userAgent, err := generateUserAgentString(d, config.userAgent) if err != nil { return err } - url, err := replaceVars(d, config, "{{DNSBasePath}}projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}") + project, err := getProject(d, config) if err != nil { return err } - billingProject := "" + zone := d.Get("managed_zone").(string) - project, err := getProject(d, config) - if err != nil { - return fmt.Errorf("Error fetching project for ResourceDnsRecordSet: %s", err) - } - billingProject = project - - // err == nil indicates that the billing_project value was found - if bp, err := getBillingProject(d, config); err == nil { - billingProject = bp - } + // name and type are effectively the 'key' + name := d.Get("name").(string) + dnsType := d.Get("type").(string) - res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + 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("DNSResourceDnsRecordSet %q", d.Id())) + 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 err := d.Set("project", project); err != nil { - return fmt.Errorf("Error reading ResourceDnsRecordSet: %s", err) + if len(resp.Rrsets) > 1 { + return fmt.Errorf("Only expected 1 record set, got %d", len(resp.Rrsets)) } - if err := d.Set("name", flattenDNSResourceDnsRecordSetName(res["name"], d, config)); err != nil { - return fmt.Errorf("Error reading ResourceDnsRecordSet: %s", err) + if err := d.Set("type", resp.Rrsets[0].Type); err != nil { + return fmt.Errorf("Error setting type: %s", err) } - if err := d.Set("type", flattenDNSResourceDnsRecordSetType(res["type"], d, config)); err != nil { - return fmt.Errorf("Error reading ResourceDnsRecordSet: %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("ttl", flattenDNSResourceDnsRecordSetTtl(res["ttl"], d, config)); err != nil { - return fmt.Errorf("Error reading ResourceDnsRecordSet: %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("rrdatas", flattenDNSResourceDnsRecordSetRrdatas(res["rrdatas"], d, config)); err != nil { - return fmt.Errorf("Error reading ResourceDnsRecordSet: %s", err) + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error setting project: %s", err) } return nil } -func resourceDNSResourceDnsRecordSetUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceDnsRecordSetDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) userAgent, err := generateUserAgentString(d, config.userAgent) if err != nil { return err } - billingProject := "" - project, err := getProject(d, config) if err != nil { - return fmt.Errorf("Error fetching project for ResourceDnsRecordSet: %s", err) + return err } - billingProject = project - obj := make(map[string]interface{}) - typeProp, err := expandDNSResourceDnsRecordSetType(d.Get("type"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("type"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, typeProp)) { - obj["type"] = typeProp + 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 + } } - ttlProp, err := expandDNSResourceDnsRecordSetTtl(d.Get("ttl"), d, config) - if err != nil { - return err - } else if v, ok := d.GetOkExists("ttl"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, ttlProp)) { - obj["ttl"] = ttlProp + + // 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), + }, + }, } - url, err := replaceVars(d, config, "{{DNSBasePath}}projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}") + log.Printf("[DEBUG] DNS Record delete request: %#v", chg) + chg, err = config.NewDnsClient(userAgent).Changes.Create(project, zone, chg).Do() if err != nil { - return err + return handleNotFoundError(err, d, "google_dns_record_set") } - log.Printf("[DEBUG] Updating ResourceDnsRecordSet %q: %#v", d.Id(), obj) - - // err == nil indicates that the billing_project value was found - if bp, err := getBillingProject(d, config); err == nil { - billingProject = bp + w := &DnsChangeWaiter{ + Service: config.NewDnsClient(userAgent), + Change: chg, + Project: project, + ManagedZone: zone, } - - res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) - + _, err = w.Conf().WaitForState() if err != nil { - return fmt.Errorf("Error updating ResourceDnsRecordSet %q: %s", d.Id(), err) - } else { - log.Printf("[DEBUG] Finished updating ResourceDnsRecordSet %q: %#v", d.Id(), res) + return fmt.Errorf("Error waiting for Google DNS change: %s", err) } - return resourceDNSResourceDnsRecordSetRead(d, meta) + d.SetId("") + return nil } -func resourceDNSResourceDnsRecordSetDelete(d *schema.ResourceData, meta interface{}) error { +func resourceDnsRecordSetUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) userAgent, err := generateUserAgentString(d, config.userAgent) if err != nil { return err } - billingProject := "" - project, err := getProject(d, config) - if err != nil { - return fmt.Errorf("Error fetching project for ResourceDnsRecordSet: %s", err) - } - billingProject = project - - url, err := replaceVars(d, config, "{{DNSBasePath}}projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}") if err != nil { return err } - var obj map[string]interface{} - log.Printf("[DEBUG] Deleting ResourceDnsRecordSet %q", d.Id()) + zone := d.Get("managed_zone").(string) + recordName := d.Get("name").(string) - // err == nil indicates that the billing_project value was found - if bp, err := getBillingProject(d, config); err == nil { - billingProject = bp + 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), + }, + }, } - res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + 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 handleNotFoundError(err, d, "ResourceDnsRecordSet") + return fmt.Errorf("Error changing DNS RecordSet: %s", err) } - log.Printf("[DEBUG] Finished deleting ResourceDnsRecordSet %q: %#v", d.Id(), res) - return nil + 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 resourceDNSResourceDnsRecordSetImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { +func resourceDnsRecordSetImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { config := meta.(*Config) if err := parseImportId([]string{ "projects/(?P[^/]+)/managedZones/(?P[^/]+)/rrsets/(?P[^/]+)/(?P[^/]+)", @@ -380,55 +402,20 @@ func resourceDNSResourceDnsRecordSetImport(d *schema.ResourceData, meta interfac return []*schema.ResourceData{d}, nil } -func flattenDNSResourceDnsRecordSetName(v interface{}, d *schema.ResourceData, config *Config) interface{} { - return v -} - -func flattenDNSResourceDnsRecordSetType(v interface{}, d *schema.ResourceData, config *Config) interface{} { - return v -} - -func flattenDNSResourceDnsRecordSetTtl(v interface{}, d *schema.ResourceData, config *Config) interface{} { - // Handles the string fixed64 format - if strVal, ok := v.(string); ok { - if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { - return intVal - } - } - - // number values are represented as float64 - if floatVal, ok := v.(float64); ok { - intVal := int(floatVal) - return intVal +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 v // let terraform core handle it otherwise -} - -func flattenDNSResourceDnsRecordSetRrdatas(v interface{}, d *schema.ResourceData, config *Config) interface{} { - return v -} - -func expandDNSResourceDnsRecordSetName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { - return v, nil + return data } -func expandDNSResourceDnsRecordSetType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandDNSResourceDnsRecordSetTtl(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { - return v, nil -} +func ipv6AddressDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + oldIp := net.ParseIP(old) + newIp := net.ParseIP(new) -func expandDNSResourceDnsRecordSetRrdatas(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { - return v, nil -} - -func expandDNSResourceDnsRecordSetManagedZone(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { - f, err := parseGlobalFieldValue("managedZones", v.(string), "project", d, config, true) - if err != nil { - return nil, fmt.Errorf("Invalid value for managed_zone: %s", err) - } - return f.RelativeLink(), nil + return oldIp.Equal(newIp) } diff --git a/google/resource_dns_record_set_sweeper_test.go b/google/resource_dns_record_set_sweeper_test.go deleted file mode 100644 index 3831e5fc609..00000000000 --- a/google/resource_dns_record_set_sweeper_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// ---------------------------------------------------------------------------- -// -// *** AUTO GENERATED CODE *** Type: MMv1 *** -// -// ---------------------------------------------------------------------------- -// -// This file is automatically generated by Magic Modules and manual -// changes will be clobbered when the file is regenerated. -// -// Please read more about how to change this file in -// .github/CONTRIBUTING.md. -// -// ---------------------------------------------------------------------------- - -package google - -import ( - "context" - "log" - "strings" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func init() { - resource.AddTestSweepers("DNSResourceDnsRecordSet", &resource.Sweeper{ - Name: "DNSResourceDnsRecordSet", - F: testSweepDNSResourceDnsRecordSet, - }) -} - -// At the time of writing, the CI only passes us-central1 as the region -func testSweepDNSResourceDnsRecordSet(region string) error { - resourceName := "DNSResourceDnsRecordSet" - log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) - - config, err := sharedConfigForRegion(region) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) - return err - } - - err = config.LoadAndValidate(context.Background()) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) - return err - } - - t := &testing.T{} - billingId := getTestBillingAccountFromEnv(t) - - // Setup variables to replace in list template - d := &ResourceDataMock{ - FieldsInSchema: map[string]interface{}{ - "project": config.Project, - "region": region, - "location": region, - "zone": "-", - "billing_account": billingId, - }, - } - - listTemplate := strings.Split("https://dns.googleapis.com/dns/v1/projects/{{project}}/managedZones/{{managed_zone}}/rrsets", "?")[0] - listUrl, err := replaceVars(d, config, listTemplate) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) - return nil - } - - res, err := sendRequest(config, "GET", config.Project, listUrl, config.userAgent, nil) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) - return nil - } - - resourceList, ok := res["resourceDnsRecordSets"] - if !ok { - log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") - return nil - } - - rl := resourceList.([]interface{}) - - log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) - // Keep count of items that aren't sweepable for logging. - nonPrefixCount := 0 - for _, ri := range rl { - obj := ri.(map[string]interface{}) - if obj["name"] == nil { - log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) - return nil - } - - name := GetResourceNameFromSelfLink(obj["name"].(string)) - // Skip resources that shouldn't be sweeped - if !isSweepableTestResource(name) { - nonPrefixCount++ - continue - } - - deleteTemplate := "https://dns.googleapis.com/dns/v1/projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}" - deleteUrl, err := replaceVars(d, config, deleteTemplate) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) - return nil - } - deleteUrl = deleteUrl + name - - // Don't wait on operations as we may have a lot to delete - _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, config.userAgent, nil) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) - } else { - log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) - } - } - - if nonPrefixCount > 0 { - log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) - } - - return nil -} diff --git a/google/resource_dns_record_set_test.go b/google/resource_dns_record_set_test.go index 52679cfd503..7f5d96ee750 100644 --- a/google/resource_dns_record_set_test.go +++ b/google/resource_dns_record_set_test.go @@ -53,6 +53,7 @@ func TestIpv6AddressDiffSuppress(t *testing.T) { } } } + func TestAccDNSRecordSet_basic(t *testing.T) { t.Parallel() @@ -157,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", @@ -175,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() @@ -271,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" @@ -281,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/google/resource_monitoring_slo.go b/google/resource_monitoring_slo.go index a3ba736c07a..c22d32d9d86 100644 --- a/google/resource_monitoring_slo.go +++ b/google/resource_monitoring_slo.go @@ -23,7 +23,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func validateMonitoringSloGoal(v interface{}, k string) (warnings []string, errors []error) { diff --git a/google/resource_pubsub_subscription.go b/google/resource_pubsub_subscription.go index cc8ac003d29..0b376ac58b9 100644 --- a/google/resource_pubsub_subscription.go +++ b/google/resource_pubsub_subscription.go @@ -24,7 +24,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) func comparePubsubSubscriptionExpirationPolicy(_, old, new string, _ *schema.ResourceData) bool { diff --git a/google/resource_secret_manager_secret_version.go b/google/resource_secret_manager_secret_version.go index 82d2939b9ad..8ae6181063b 100644 --- a/google/resource_secret_manager_secret_version.go +++ b/google/resource_secret_manager_secret_version.go @@ -24,7 +24,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "google.golang.org/api/googleapi" ) func resourceSecretManagerSecretVersionUpdate(d *schema.ResourceData, meta interface{}) error { diff --git a/google/resource_sql_source_representation_instance.go b/google/resource_sql_source_representation_instance.go index 8847d77c9f4..9c0eb4a96f4 100644 --- a/google/resource_sql_source_representation_instance.go +++ b/google/resource_sql_source_representation_instance.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func resourceSQLSourceRepresentationInstance() *schema.Resource { diff --git a/google/resource_storage_hmac_key.go b/google/resource_storage_hmac_key.go index a53de79a2d9..41ce7b3189b 100644 --- a/google/resource_storage_hmac_key.go +++ b/google/resource_storage_hmac_key.go @@ -22,7 +22,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" ) func resourceStorageHmacKey() *schema.Resource { diff --git a/website/docs/r/dns_record_set.html.markdown b/website/docs/r/dns_record_set.html.markdown index 1e093b7eeb0..4d62df958c6 100644 --- a/website/docs/r/dns_record_set.html.markdown +++ b/website/docs/r/dns_record_set.html.markdown @@ -1,122 +1,176 @@ --- -# ---------------------------------------------------------------------------- -# -# *** AUTO GENERATED CODE *** Type: MMv1 *** -# -# ---------------------------------------------------------------------------- -# -# This file is automatically generated by Magic Modules and manual -# changes will be clobbered when the file is regenerated. -# -# Please read more about how to change this file in -# .github/CONTRIBUTING.md. -# -# ---------------------------------------------------------------------------- subcategory: "Cloud DNS" layout: "google" page_title: "Google: google_dns_record_set" sidebar_current: "docs-google-dns-record-set" description: |- - A single DNS record that exists on a domain name (i. + Manages a set of DNS records within Google Cloud DNS. --- # google\_dns\_record\_set -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. +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). -The record will include the domain/subdomain name, a type (i.e. A, AAA, -CAA, MX, CNAME, NS, etc) +~> **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: -## Example Usage - Dns Record Set Basic +```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 -```hcl -resource "google_dns_managed_zone" "parent-zone" { - provider = "google-beta" - name = "my-zone" - dns_name = "my-zone.hashicorptest.com." - description = "Test Description" + 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 -resource "google_dns_record_set" "resource-recordset" { - provider = "google-beta" - managed_zone = google_dns_managed_zone.parent-zone.name - name = "test-record.my-zone.hashicorptest.com." +```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" - rrdatas = ["10.0.0.1", "10.1.0.1"] - ttl = 86400 + ttl = 300 + + rrdatas = ["8.8.8.8"] +} + +resource "google_dns_managed_zone" "prod" { + name = "prod-zone" + dns_name = "prod.mydomain.com." } ``` -## Argument Reference +### Adding an MX record -The following arguments are supported: +```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." +} +``` -* `name` - - (Required) - For example, www.example.com. +### Adding an SPF record -* `type` - - (Required) - One of valid DNS resource types. - Possible values are `A`, `AAAA`, `CAA`, `CNAME`, `DNSKEY`, `DS`, `IPSECVPNKEY`, `MX`, `NAPTR`, `NS`, `PTR`, `SOA`, `SPF`, `SRV`, `SSHFP`, `TLSA`, and `TXT`. +Quotes (`""`) must be added around your `rrdatas` for a SPF record. Otherwise `rrdatas` string gets split on spaces. -* `managed_zone` - - (Required) - Identifies the managed zone addressed by this request. +```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 -* `ttl` - - (Optional) - Number of seconds that this ResourceRecordSet can be cached by - resolvers. + The list of `rrdatas` should only contain a single string corresponding to the Canonical Name intended. -* `rrdatas` - - (Optional) - 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"). +```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."] +} -* `project` - (Optional) The ID of the project in which the resource belongs. - If it is not provided, the provider project is used. +resource "google_dns_managed_zone" "prod" { + name = "prod-zone" + dns_name = "prod.mydomain.com." +} +``` +## Argument Reference -## Attributes Reference +The following arguments are supported: -In addition to the arguments listed above, the following computed attributes are exported: +* `managed_zone` - (Required) The name of the zone in which this record set will + reside. -* `id` - an identifier for the resource with format `projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}}` +* `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"`). -## Timeouts -This resource provides the following -[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: +* `type` - (Required) The DNS record set type. -- `create` - Default is 4 minutes. -- `update` - Default is 4 minutes. -- `delete` - Default is 4 minutes. +- - - -## Import +* `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: -ResourceDnsRecordSet can be imported using any of these accepted formats: +* `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.default projects/{{project}}/managedZones/{{managed_zone}}/rrsets/{{name}}/{{type}} -$ terraform import google_dns_record_set.default {{project}}/{{managed_zone}}/{{name}}/{{type}} -$ terraform import google_dns_record_set.default {{managed_zone}}/{{name}}/{{type}} +$ 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}} ``` -## User Project Overrides - -This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). +Note: The record name must include the trailing dot at the end. \ No newline at end of file