github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/builtin/providers/aws/resource_aws_route53_record.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/hashicorp/errwrap"
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  
    16  	"github.com/aws/aws-sdk-go/aws"
    17  	"github.com/aws/aws-sdk-go/aws/awserr"
    18  	"github.com/aws/aws-sdk-go/service/route53"
    19  )
    20  
    21  var r53NoRecordsFound = errors.New("No matching Hosted Zone found")
    22  var r53NoHostedZoneFound = errors.New("No matching records found")
    23  
    24  func resourceAwsRoute53Record() *schema.Resource {
    25  	return &schema.Resource{
    26  		Create: resourceAwsRoute53RecordCreate,
    27  		Read:   resourceAwsRoute53RecordRead,
    28  		Update: resourceAwsRoute53RecordUpdate,
    29  		Delete: resourceAwsRoute53RecordDelete,
    30  		Importer: &schema.ResourceImporter{
    31  			State: schema.ImportStatePassthrough,
    32  		},
    33  		SchemaVersion: 2,
    34  		MigrateState:  resourceAwsRoute53RecordMigrateState,
    35  		Schema: map[string]*schema.Schema{
    36  			"name": {
    37  				Type:     schema.TypeString,
    38  				Required: true,
    39  				ForceNew: true,
    40  				StateFunc: func(v interface{}) string {
    41  					value := strings.TrimSuffix(v.(string), ".")
    42  					return strings.ToLower(value)
    43  				},
    44  			},
    45  
    46  			"fqdn": {
    47  				Type:     schema.TypeString,
    48  				Computed: true,
    49  			},
    50  
    51  			"type": {
    52  				Type:         schema.TypeString,
    53  				Required:     true,
    54  				ValidateFunc: validateRoute53RecordType,
    55  			},
    56  
    57  			"zone_id": {
    58  				Type:     schema.TypeString,
    59  				Required: true,
    60  				ForceNew: true,
    61  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
    62  					value := v.(string)
    63  					if value == "" {
    64  						es = append(es, fmt.Errorf("Cannot have empty zone_id"))
    65  					}
    66  					return
    67  				},
    68  			},
    69  
    70  			"ttl": {
    71  				Type:          schema.TypeInt,
    72  				Optional:      true,
    73  				ConflictsWith: []string{"alias"},
    74  			},
    75  
    76  			"weight": {
    77  				Type:     schema.TypeInt,
    78  				Optional: true,
    79  				Removed:  "Now implemented as weighted_routing_policy; Please see https://www.terraform.io/docs/providers/aws/r/route53_record.html",
    80  			},
    81  
    82  			"set_identifier": {
    83  				Type:     schema.TypeString,
    84  				Optional: true,
    85  			},
    86  
    87  			"alias": {
    88  				Type:          schema.TypeSet,
    89  				Optional:      true,
    90  				ConflictsWith: []string{"records", "ttl"},
    91  				Elem: &schema.Resource{
    92  					Schema: map[string]*schema.Schema{
    93  						"zone_id": {
    94  							Type:     schema.TypeString,
    95  							Required: true,
    96  						},
    97  
    98  						"name": {
    99  							Type:      schema.TypeString,
   100  							Required:  true,
   101  							StateFunc: normalizeAwsAliasName,
   102  						},
   103  
   104  						"evaluate_target_health": {
   105  							Type:     schema.TypeBool,
   106  							Required: true,
   107  						},
   108  					},
   109  				},
   110  				Set: resourceAwsRoute53AliasRecordHash,
   111  			},
   112  
   113  			"failover": { // PRIMARY | SECONDARY
   114  				Type:     schema.TypeString,
   115  				Optional: true,
   116  				Removed:  "Now implemented as failover_routing_policy; see docs",
   117  			},
   118  
   119  			"failover_routing_policy": {
   120  				Type:     schema.TypeList,
   121  				Optional: true,
   122  				ConflictsWith: []string{
   123  					"geolocation_routing_policy",
   124  					"latency_routing_policy",
   125  					"weighted_routing_policy",
   126  				},
   127  				Elem: &schema.Resource{
   128  					Schema: map[string]*schema.Schema{
   129  						"type": {
   130  							Type:     schema.TypeString,
   131  							Required: true,
   132  							ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   133  								value := v.(string)
   134  								if value != "PRIMARY" && value != "SECONDARY" {
   135  									es = append(es, fmt.Errorf("Failover policy type must be PRIMARY or SECONDARY"))
   136  								}
   137  								return
   138  							},
   139  						},
   140  					},
   141  				},
   142  			},
   143  
   144  			"latency_routing_policy": {
   145  				Type:     schema.TypeList,
   146  				Optional: true,
   147  				ConflictsWith: []string{
   148  					"failover_routing_policy",
   149  					"geolocation_routing_policy",
   150  					"weighted_routing_policy",
   151  				},
   152  				Elem: &schema.Resource{
   153  					Schema: map[string]*schema.Schema{
   154  						"region": {
   155  							Type:     schema.TypeString,
   156  							Required: true,
   157  						},
   158  					},
   159  				},
   160  			},
   161  
   162  			"geolocation_routing_policy": { // AWS Geolocation
   163  				Type:     schema.TypeList,
   164  				Optional: true,
   165  				ConflictsWith: []string{
   166  					"failover_routing_policy",
   167  					"latency_routing_policy",
   168  					"weighted_routing_policy",
   169  				},
   170  				Elem: &schema.Resource{
   171  					Schema: map[string]*schema.Schema{
   172  						"continent": {
   173  							Type:     schema.TypeString,
   174  							Optional: true,
   175  						},
   176  						"country": {
   177  							Type:     schema.TypeString,
   178  							Optional: true,
   179  						},
   180  						"subdivision": {
   181  							Type:     schema.TypeString,
   182  							Optional: true,
   183  						},
   184  					},
   185  				},
   186  			},
   187  
   188  			"weighted_routing_policy": {
   189  				Type:     schema.TypeList,
   190  				Optional: true,
   191  				ConflictsWith: []string{
   192  					"failover_routing_policy",
   193  					"geolocation_routing_policy",
   194  					"latency_routing_policy",
   195  				},
   196  				Elem: &schema.Resource{
   197  					Schema: map[string]*schema.Schema{
   198  						"weight": {
   199  							Type:     schema.TypeInt,
   200  							Required: true,
   201  						},
   202  					},
   203  				},
   204  			},
   205  
   206  			"health_check_id": { // ID of health check
   207  				Type:     schema.TypeString,
   208  				Optional: true,
   209  			},
   210  
   211  			"records": {
   212  				Type:          schema.TypeSet,
   213  				ConflictsWith: []string{"alias"},
   214  				Elem:          &schema.Schema{Type: schema.TypeString},
   215  				Optional:      true,
   216  				Set:           schema.HashString,
   217  			},
   218  		},
   219  	}
   220  }
   221  
   222  func resourceAwsRoute53RecordUpdate(d *schema.ResourceData, meta interface{}) error {
   223  	// Route 53 supports CREATE, DELETE, and UPSERT actions. We use UPSERT, and
   224  	// AWS dynamically determines if a record should be created or updated.
   225  	// Amazon Route 53 can update an existing resource record set only when all
   226  	// of the following values match: Name, Type and SetIdentifier
   227  	// See http://docs.aws.amazon.com/Route53/latest/APIReference/API_ChangeResourceRecordSets.html
   228  
   229  	if !d.HasChange("type") && !d.HasChange("set_identifier") {
   230  		// If neither type nor set_identifier changed we use UPSERT,
   231  		// for resouce update here we simply fall through to
   232  		// our resource create function.
   233  		return resourceAwsRoute53RecordCreate(d, meta)
   234  	}
   235  
   236  	// Otherwise we delete the existing record and create a new record within
   237  	// a transactional change
   238  	conn := meta.(*AWSClient).r53conn
   239  	zone := cleanZoneID(d.Get("zone_id").(string))
   240  
   241  	var err error
   242  	zoneRecord, err := conn.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(zone)})
   243  	if err != nil {
   244  		return err
   245  	}
   246  	if zoneRecord.HostedZone == nil {
   247  		return fmt.Errorf("[WARN] No Route53 Zone found for id (%s)", zone)
   248  	}
   249  
   250  	// Build the to be deleted record
   251  	en := expandRecordName(d.Get("name").(string), *zoneRecord.HostedZone.Name)
   252  	typeo, _ := d.GetChange("type")
   253  
   254  	oldRec := &route53.ResourceRecordSet{
   255  		Name: aws.String(en),
   256  		Type: aws.String(typeo.(string)),
   257  	}
   258  
   259  	if v, _ := d.GetChange("ttl"); v.(int) != 0 {
   260  		oldRec.TTL = aws.Int64(int64(v.(int)))
   261  	}
   262  
   263  	// Resource records
   264  	if v, _ := d.GetChange("records"); v != nil {
   265  		recs := v.(*schema.Set).List()
   266  		if len(recs) > 0 {
   267  			oldRec.ResourceRecords = expandResourceRecords(recs, typeo.(string))
   268  		}
   269  	}
   270  
   271  	// Alias record
   272  	if v, _ := d.GetChange("alias"); v != nil {
   273  		aliases := v.(*schema.Set).List()
   274  		if len(aliases) == 1 {
   275  			alias := aliases[0].(map[string]interface{})
   276  			oldRec.AliasTarget = &route53.AliasTarget{
   277  				DNSName:              aws.String(alias["name"].(string)),
   278  				EvaluateTargetHealth: aws.Bool(alias["evaluate_target_health"].(bool)),
   279  				HostedZoneId:         aws.String(alias["zone_id"].(string)),
   280  			}
   281  		}
   282  	}
   283  
   284  	if v, _ := d.GetChange("set_identifier"); v.(string) != "" {
   285  		oldRec.SetIdentifier = aws.String(v.(string))
   286  	}
   287  
   288  	// Build the to be created record
   289  	rec, err := resourceAwsRoute53RecordBuildSet(d, *zoneRecord.HostedZone.Name)
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	// Delete the old and create the new records in a single batch. We abuse
   295  	// StateChangeConf for this to retry for us since Route53 sometimes returns
   296  	// errors about another operation happening at the same time.
   297  	changeBatch := &route53.ChangeBatch{
   298  		Comment: aws.String("Managed by Terraform"),
   299  		Changes: []*route53.Change{
   300  			{
   301  				Action:            aws.String("DELETE"),
   302  				ResourceRecordSet: oldRec,
   303  			},
   304  			{
   305  				Action:            aws.String("CREATE"),
   306  				ResourceRecordSet: rec,
   307  			},
   308  		},
   309  	}
   310  
   311  	req := &route53.ChangeResourceRecordSetsInput{
   312  		HostedZoneId: aws.String(cleanZoneID(*zoneRecord.HostedZone.Id)),
   313  		ChangeBatch:  changeBatch,
   314  	}
   315  
   316  	log.Printf("[DEBUG] Updating resource records for zone: %s, name: %s\n\n%s",
   317  		zone, *rec.Name, req)
   318  
   319  	respRaw, err := changeRoute53RecordSet(conn, req)
   320  	if err != nil {
   321  		return errwrap.Wrapf("[ERR]: Error building changeset: {{err}}", err)
   322  	}
   323  
   324  	changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo
   325  
   326  	// Generate an ID
   327  	vars := []string{
   328  		zone,
   329  		strings.ToLower(d.Get("name").(string)),
   330  		d.Get("type").(string),
   331  	}
   332  	if v, ok := d.GetOk("set_identifier"); ok {
   333  		vars = append(vars, v.(string))
   334  	}
   335  
   336  	d.SetId(strings.Join(vars, "_"))
   337  
   338  	err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id))
   339  	if err != nil {
   340  		return err
   341  	}
   342  
   343  	return resourceAwsRoute53RecordRead(d, meta)
   344  }
   345  
   346  func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) error {
   347  	conn := meta.(*AWSClient).r53conn
   348  	zone := cleanZoneID(d.Get("zone_id").(string))
   349  
   350  	var err error
   351  	zoneRecord, err := conn.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(zone)})
   352  	if err != nil {
   353  		return err
   354  	}
   355  	if zoneRecord.HostedZone == nil {
   356  		return fmt.Errorf("[WARN] No Route53 Zone found for id (%s)", zone)
   357  	}
   358  
   359  	// Build the record
   360  	rec, err := resourceAwsRoute53RecordBuildSet(d, *zoneRecord.HostedZone.Name)
   361  	if err != nil {
   362  		return err
   363  	}
   364  
   365  	// Create the new records. We abuse StateChangeConf for this to
   366  	// retry for us since Route53 sometimes returns errors about another
   367  	// operation happening at the same time.
   368  	changeBatch := &route53.ChangeBatch{
   369  		Comment: aws.String("Managed by Terraform"),
   370  		Changes: []*route53.Change{
   371  			{
   372  				Action:            aws.String("UPSERT"),
   373  				ResourceRecordSet: rec,
   374  			},
   375  		},
   376  	}
   377  
   378  	req := &route53.ChangeResourceRecordSetsInput{
   379  		HostedZoneId: aws.String(cleanZoneID(*zoneRecord.HostedZone.Id)),
   380  		ChangeBatch:  changeBatch,
   381  	}
   382  
   383  	log.Printf("[DEBUG] Creating resource records for zone: %s, name: %s\n\n%s",
   384  		zone, *rec.Name, req)
   385  
   386  	respRaw, err := changeRoute53RecordSet(conn, req)
   387  	if err != nil {
   388  		return errwrap.Wrapf("[ERR]: Error building changeset: {{err}}", err)
   389  	}
   390  
   391  	changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo
   392  
   393  	// Generate an ID
   394  	vars := []string{
   395  		zone,
   396  		strings.ToLower(d.Get("name").(string)),
   397  		d.Get("type").(string),
   398  	}
   399  	if v, ok := d.GetOk("set_identifier"); ok {
   400  		vars = append(vars, v.(string))
   401  	}
   402  
   403  	d.SetId(strings.Join(vars, "_"))
   404  
   405  	err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id))
   406  	if err != nil {
   407  		return err
   408  	}
   409  
   410  	return resourceAwsRoute53RecordRead(d, meta)
   411  }
   412  
   413  func changeRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResourceRecordSetsInput) (interface{}, error) {
   414  	wait := resource.StateChangeConf{
   415  		Pending:    []string{"rejected"},
   416  		Target:     []string{"accepted"},
   417  		Timeout:    5 * time.Minute,
   418  		MinTimeout: 1 * time.Second,
   419  		Refresh: func() (interface{}, string, error) {
   420  			resp, err := conn.ChangeResourceRecordSets(input)
   421  			if err != nil {
   422  				if r53err, ok := err.(awserr.Error); ok {
   423  					if r53err.Code() == "PriorRequestNotComplete" {
   424  						// There is some pending operation, so just retry
   425  						// in a bit.
   426  						return nil, "rejected", nil
   427  					}
   428  				}
   429  
   430  				return nil, "failure", err
   431  			}
   432  
   433  			return resp, "accepted", nil
   434  		},
   435  	}
   436  
   437  	return wait.WaitForState()
   438  }
   439  
   440  func waitForRoute53RecordSetToSync(conn *route53.Route53, requestId string) error {
   441  	wait := resource.StateChangeConf{
   442  		Delay:      30 * time.Second,
   443  		Pending:    []string{"PENDING"},
   444  		Target:     []string{"INSYNC"},
   445  		Timeout:    30 * time.Minute,
   446  		MinTimeout: 5 * time.Second,
   447  		Refresh: func() (result interface{}, state string, err error) {
   448  			changeRequest := &route53.GetChangeInput{
   449  				Id: aws.String(requestId),
   450  			}
   451  			return resourceAwsGoRoute53Wait(conn, changeRequest)
   452  		},
   453  	}
   454  	_, err := wait.WaitForState()
   455  	return err
   456  }
   457  
   458  func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) error {
   459  	// If we don't have a zone ID we're doing an import. Parse it from the ID.
   460  	if _, ok := d.GetOk("zone_id"); !ok {
   461  		parts := strings.Split(d.Id(), "_")
   462  
   463  		//we check that we have parsed the id into the correct number of segments
   464  		//we need at least 3 segments!
   465  		if len(parts) == 1 || len(parts) < 3 {
   466  			return fmt.Errorf("Error Importing aws_route_53 record. Please make sure the record ID is in the form ZONEID_RECORDNAME_TYPE (i.e. Z4KAPRWWNC7JR_dev_A")
   467  		}
   468  
   469  		d.Set("zone_id", parts[0])
   470  		d.Set("name", parts[1])
   471  		d.Set("type", parts[2])
   472  		if len(parts) > 3 {
   473  			d.Set("set_identifier", parts[3])
   474  		}
   475  	}
   476  
   477  	record, err := findRecord(d, meta)
   478  	if err != nil {
   479  		switch err {
   480  		case r53NoHostedZoneFound, r53NoRecordsFound:
   481  			log.Printf("[DEBUG] %s for: %s, removing from state file", err, d.Id())
   482  			d.SetId("")
   483  			return nil
   484  		default:
   485  			return err
   486  		}
   487  	}
   488  
   489  	err = d.Set("records", flattenResourceRecords(record.ResourceRecords, *record.Type))
   490  	if err != nil {
   491  		return fmt.Errorf("[DEBUG] Error setting records for: %s, error: %#v", d.Id(), err)
   492  	}
   493  
   494  	if alias := record.AliasTarget; alias != nil {
   495  		name := normalizeAwsAliasName(*alias.DNSName)
   496  		d.Set("alias", []interface{}{
   497  			map[string]interface{}{
   498  				"zone_id": *alias.HostedZoneId,
   499  				"name":    name,
   500  				"evaluate_target_health": *alias.EvaluateTargetHealth,
   501  			},
   502  		})
   503  	}
   504  
   505  	d.Set("ttl", record.TTL)
   506  
   507  	if record.Failover != nil {
   508  		v := []map[string]interface{}{{
   509  			"type": aws.StringValue(record.Failover),
   510  		}}
   511  		if err := d.Set("failover_routing_policy", v); err != nil {
   512  			return fmt.Errorf("[DEBUG] Error setting failover records for: %s, error: %#v", d.Id(), err)
   513  		}
   514  	}
   515  
   516  	if record.GeoLocation != nil {
   517  		v := []map[string]interface{}{{
   518  			"continent":   aws.StringValue(record.GeoLocation.ContinentCode),
   519  			"country":     aws.StringValue(record.GeoLocation.CountryCode),
   520  			"subdivision": aws.StringValue(record.GeoLocation.SubdivisionCode),
   521  		}}
   522  		if err := d.Set("geolocation_routing_policy", v); err != nil {
   523  			return fmt.Errorf("[DEBUG] Error setting gelocation records for: %s, error: %#v", d.Id(), err)
   524  		}
   525  	}
   526  
   527  	if record.Region != nil {
   528  		v := []map[string]interface{}{{
   529  			"region": aws.StringValue(record.Region),
   530  		}}
   531  		if err := d.Set("latency_routing_policy", v); err != nil {
   532  			return fmt.Errorf("[DEBUG] Error setting latency records for: %s, error: %#v", d.Id(), err)
   533  		}
   534  	}
   535  
   536  	if record.Weight != nil {
   537  		v := []map[string]interface{}{{
   538  			"weight": aws.Int64Value((record.Weight)),
   539  		}}
   540  		if err := d.Set("weighted_routing_policy", v); err != nil {
   541  			return fmt.Errorf("[DEBUG] Error setting weighted records for: %s, error: %#v", d.Id(), err)
   542  		}
   543  	}
   544  
   545  	d.Set("set_identifier", record.SetIdentifier)
   546  	d.Set("health_check_id", record.HealthCheckId)
   547  
   548  	return nil
   549  }
   550  
   551  // findRecord takes a ResourceData struct for aws_resource_route53_record. It
   552  // uses the referenced zone_id to query Route53 and find information on it's
   553  // records.
   554  //
   555  // If records are found, it returns the matching
   556  // route53.ResourceRecordSet and nil for the error.
   557  //
   558  // If no hosted zone is found, it returns a nil recordset and r53NoHostedZoneFound
   559  // error.
   560  //
   561  // If no matching recordset is found, it returns nil and a r53NoRecordsFound
   562  // error
   563  //
   564  // If there are other errors, it returns nil a nil recordset and passes on the
   565  // error.
   566  func findRecord(d *schema.ResourceData, meta interface{}) (*route53.ResourceRecordSet, error) {
   567  	conn := meta.(*AWSClient).r53conn
   568  	// Scan for a
   569  	zone := cleanZoneID(d.Get("zone_id").(string))
   570  
   571  	// get expanded name
   572  	zoneRecord, err := conn.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(zone)})
   573  	if err != nil {
   574  		if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" {
   575  			return nil, r53NoHostedZoneFound
   576  		}
   577  		return nil, err
   578  	}
   579  
   580  	en := expandRecordName(d.Get("name").(string), *zoneRecord.HostedZone.Name)
   581  	log.Printf("[DEBUG] Expanded record name: %s", en)
   582  	d.Set("fqdn", en)
   583  
   584  	lopts := &route53.ListResourceRecordSetsInput{
   585  		HostedZoneId:    aws.String(cleanZoneID(zone)),
   586  		StartRecordName: aws.String(en),
   587  		StartRecordType: aws.String(d.Get("type").(string)),
   588  	}
   589  
   590  	log.Printf("[DEBUG] List resource records sets for zone: %s, opts: %s",
   591  		zone, lopts)
   592  	resp, err := conn.ListResourceRecordSets(lopts)
   593  	if err != nil {
   594  		return nil, err
   595  	}
   596  
   597  	for _, record := range resp.ResourceRecordSets {
   598  		name := cleanRecordName(*record.Name)
   599  		if FQDN(strings.ToLower(name)) != FQDN(strings.ToLower(*lopts.StartRecordName)) {
   600  			continue
   601  		}
   602  		if strings.ToUpper(*record.Type) != strings.ToUpper(*lopts.StartRecordType) {
   603  			continue
   604  		}
   605  
   606  		if record.SetIdentifier != nil && *record.SetIdentifier != d.Get("set_identifier") {
   607  			continue
   608  		}
   609  		// The only safe return where a record is found
   610  		return record, nil
   611  	}
   612  	return nil, r53NoRecordsFound
   613  }
   614  
   615  func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) error {
   616  	conn := meta.(*AWSClient).r53conn
   617  	// Get the records
   618  	rec, err := findRecord(d, meta)
   619  	if err != nil {
   620  		switch err {
   621  		case r53NoHostedZoneFound, r53NoRecordsFound:
   622  			log.Printf("[DEBUG] %s for: %s, removing from state file", err, d.Id())
   623  			d.SetId("")
   624  			return nil
   625  		default:
   626  			return err
   627  		}
   628  	}
   629  
   630  	// Change batch for deleting
   631  	changeBatch := &route53.ChangeBatch{
   632  		Comment: aws.String("Deleted by Terraform"),
   633  		Changes: []*route53.Change{
   634  			{
   635  				Action:            aws.String("DELETE"),
   636  				ResourceRecordSet: rec,
   637  			},
   638  		},
   639  	}
   640  
   641  	zone := cleanZoneID(d.Get("zone_id").(string))
   642  
   643  	req := &route53.ChangeResourceRecordSetsInput{
   644  		HostedZoneId: aws.String(cleanZoneID(zone)),
   645  		ChangeBatch:  changeBatch,
   646  	}
   647  
   648  	respRaw, err := deleteRoute53RecordSet(conn, req)
   649  	if err != nil {
   650  		return errwrap.Wrapf("[ERR]: Error building changeset: {{err}}", err)
   651  	}
   652  
   653  	changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo
   654  	if changeInfo == nil {
   655  		log.Printf("[INFO] No ChangeInfo Found. Waiting for Sync not required")
   656  		return nil
   657  	}
   658  
   659  	err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id))
   660  	if err != nil {
   661  		return err
   662  	}
   663  
   664  	return err
   665  }
   666  
   667  func deleteRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResourceRecordSetsInput) (interface{}, error) {
   668  	wait := resource.StateChangeConf{
   669  		Pending:    []string{"rejected"},
   670  		Target:     []string{"accepted"},
   671  		Timeout:    5 * time.Minute,
   672  		MinTimeout: 1 * time.Second,
   673  		Refresh: func() (interface{}, string, error) {
   674  			resp, err := conn.ChangeResourceRecordSets(input)
   675  			if err != nil {
   676  				if r53err, ok := err.(awserr.Error); ok {
   677  					if r53err.Code() == "PriorRequestNotComplete" {
   678  						// There is some pending operation, so just retry
   679  						// in a bit.
   680  						return 42, "rejected", nil
   681  					}
   682  
   683  					if r53err.Code() == "InvalidChangeBatch" {
   684  						// This means that the record is already gone.
   685  						return resp, "accepted", nil
   686  					}
   687  				}
   688  
   689  				return 42, "failure", err
   690  			}
   691  
   692  			return resp, "accepted", nil
   693  		},
   694  	}
   695  
   696  	return wait.WaitForState()
   697  }
   698  
   699  func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData, zoneName string) (*route53.ResourceRecordSet, error) {
   700  	// get expanded name
   701  	en := expandRecordName(d.Get("name").(string), zoneName)
   702  
   703  	// Create the RecordSet request with the fully expanded name, e.g.
   704  	// sub.domain.com. Route 53 requires a fully qualified domain name, but does
   705  	// not require the trailing ".", which it will itself, so we don't call FQDN
   706  	// here.
   707  	rec := &route53.ResourceRecordSet{
   708  		Name: aws.String(en),
   709  		Type: aws.String(d.Get("type").(string)),
   710  	}
   711  
   712  	if v, ok := d.GetOk("ttl"); ok {
   713  		rec.TTL = aws.Int64(int64(v.(int)))
   714  	}
   715  
   716  	// Resource records
   717  	if v, ok := d.GetOk("records"); ok {
   718  		recs := v.(*schema.Set).List()
   719  		rec.ResourceRecords = expandResourceRecords(recs, d.Get("type").(string))
   720  	}
   721  
   722  	// Alias record
   723  	if v, ok := d.GetOk("alias"); ok {
   724  		aliases := v.(*schema.Set).List()
   725  		if len(aliases) > 1 {
   726  			return nil, fmt.Errorf("You can only define a single alias target per record")
   727  		}
   728  		alias := aliases[0].(map[string]interface{})
   729  		rec.AliasTarget = &route53.AliasTarget{
   730  			DNSName:              aws.String(alias["name"].(string)),
   731  			EvaluateTargetHealth: aws.Bool(alias["evaluate_target_health"].(bool)),
   732  			HostedZoneId:         aws.String(alias["zone_id"].(string)),
   733  		}
   734  		log.Printf("[DEBUG] Creating alias: %#v", alias)
   735  	} else {
   736  		if _, ok := d.GetOk("ttl"); !ok {
   737  			return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "ttl": required field is not set`, d.Get("name").(string))
   738  		}
   739  
   740  		if _, ok := d.GetOk("records"); !ok {
   741  			return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "records": required field is not set`, d.Get("name").(string))
   742  		}
   743  	}
   744  
   745  	if v, ok := d.GetOk("failover_routing_policy"); ok {
   746  		if _, ok := d.GetOk("set_identifier"); !ok {
   747  			return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "set_identifier": required field is not set when "failover_routing_policy" is set`, d.Get("name").(string))
   748  		}
   749  		records := v.([]interface{})
   750  		if len(records) > 1 {
   751  			return nil, fmt.Errorf("You can only define a single failover_routing_policy per record")
   752  		}
   753  		failover := records[0].(map[string]interface{})
   754  
   755  		rec.Failover = aws.String(failover["type"].(string))
   756  	}
   757  
   758  	if v, ok := d.GetOk("health_check_id"); ok {
   759  		rec.HealthCheckId = aws.String(v.(string))
   760  	}
   761  
   762  	if v, ok := d.GetOk("weighted_routing_policy"); ok {
   763  		if _, ok := d.GetOk("set_identifier"); !ok {
   764  			return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "set_identifier": required field is not set when "weight_routing_policy" is set`, d.Get("name").(string))
   765  		}
   766  		records := v.([]interface{})
   767  		if len(records) > 1 {
   768  			return nil, fmt.Errorf("You can only define a single weighed_routing_policy per record")
   769  		}
   770  		weight := records[0].(map[string]interface{})
   771  
   772  		rec.Weight = aws.Int64(int64(weight["weight"].(int)))
   773  	}
   774  
   775  	if v, ok := d.GetOk("set_identifier"); ok {
   776  		rec.SetIdentifier = aws.String(v.(string))
   777  	}
   778  
   779  	if v, ok := d.GetOk("latency_routing_policy"); ok {
   780  		if _, ok := d.GetOk("set_identifier"); !ok {
   781  			return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "set_identifier": required field is not set when "latency_routing_policy" is set`, d.Get("name").(string))
   782  		}
   783  		records := v.([]interface{})
   784  		if len(records) > 1 {
   785  			return nil, fmt.Errorf("You can only define a single latency_routing_policy per record")
   786  		}
   787  		latency := records[0].(map[string]interface{})
   788  
   789  		rec.Region = aws.String(latency["region"].(string))
   790  	}
   791  
   792  	if v, ok := d.GetOk("geolocation_routing_policy"); ok {
   793  		if _, ok := d.GetOk("set_identifier"); !ok {
   794  			return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "set_identifier": required field is not set when "geolocation_routing_policy" is set`, d.Get("name").(string))
   795  		}
   796  		geolocations := v.([]interface{})
   797  		if len(geolocations) > 1 {
   798  			return nil, fmt.Errorf("You can only define a single geolocation_routing_policy per record")
   799  		}
   800  		geolocation := geolocations[0].(map[string]interface{})
   801  
   802  		rec.GeoLocation = &route53.GeoLocation{
   803  			ContinentCode:   nilString(geolocation["continent"].(string)),
   804  			CountryCode:     nilString(geolocation["country"].(string)),
   805  			SubdivisionCode: nilString(geolocation["subdivision"].(string)),
   806  		}
   807  		log.Printf("[DEBUG] Creating geolocation: %#v", geolocation)
   808  	}
   809  
   810  	return rec, nil
   811  }
   812  
   813  func FQDN(name string) string {
   814  	n := len(name)
   815  	if n == 0 || name[n-1] == '.' {
   816  		return name
   817  	} else {
   818  		return name + "."
   819  	}
   820  }
   821  
   822  // Route 53 stores the "*" wildcard indicator as ASCII 42 and returns the
   823  // octal equivalent, "\\052". Here we look for that, and convert back to "*"
   824  // as needed.
   825  func cleanRecordName(name string) string {
   826  	str := name
   827  	if strings.HasPrefix(name, "\\052") {
   828  		str = strings.Replace(name, "\\052", "*", 1)
   829  		log.Printf("[DEBUG] Replacing octal \\052 for * in: %s", name)
   830  	}
   831  	return str
   832  }
   833  
   834  // Check if the current record name contains the zone suffix.
   835  // If it does not, add the zone name to form a fully qualified name
   836  // and keep AWS happy.
   837  func expandRecordName(name, zone string) string {
   838  	rn := strings.ToLower(strings.TrimSuffix(name, "."))
   839  	zone = strings.TrimSuffix(zone, ".")
   840  	if !strings.HasSuffix(rn, zone) {
   841  		if len(name) == 0 {
   842  			rn = zone
   843  		} else {
   844  			rn = strings.Join([]string{name, zone}, ".")
   845  		}
   846  	}
   847  	return rn
   848  }
   849  
   850  func resourceAwsRoute53AliasRecordHash(v interface{}) int {
   851  	var buf bytes.Buffer
   852  	m := v.(map[string]interface{})
   853  	buf.WriteString(fmt.Sprintf("%s-", normalizeAwsAliasName(m["name"].(string))))
   854  	buf.WriteString(fmt.Sprintf("%s-", m["zone_id"].(string)))
   855  	buf.WriteString(fmt.Sprintf("%t-", m["evaluate_target_health"].(bool)))
   856  
   857  	return hashcode.String(buf.String())
   858  }
   859  
   860  // nilString takes a string as an argument and returns a string
   861  // pointer. The returned pointer is nil if the string argument is
   862  // empty, otherwise it is a pointer to a copy of the string.
   863  func nilString(s string) *string {
   864  	if s == "" {
   865  		return nil
   866  	}
   867  	return aws.String(s)
   868  }
   869  
   870  func normalizeAwsAliasName(alias interface{}) string {
   871  	input := alias.(string)
   872  	if strings.HasPrefix(input, "dualstack.") {
   873  		return strings.Replace(input, "dualstack.", "", -1)
   874  	}
   875  
   876  	return strings.TrimRight(input, ".")
   877  }