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 }