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