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