Skip to content

Commit

Permalink
provider/aws: Add support for geolocation and latency records to aws …
Browse files Browse the repository at this point in the history
…route53

provider/aws: Geolocation and Latency for Route53 Records (supersedes #2981)
  • Loading branch information
catsby committed May 31, 2016
2 parents e97720f + d89a240 commit c315331
Show file tree
Hide file tree
Showing 5 changed files with 447 additions and 88 deletions.
204 changes: 186 additions & 18 deletions builtin/providers/aws/resource_aws_route53_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ func resourceAwsRoute53Record() *schema.Resource {
Update: resourceAwsRoute53RecordUpdate,
Delete: resourceAwsRoute53RecordDelete,

SchemaVersion: 1,
SchemaVersion: 2,
MigrateState: resourceAwsRoute53RecordMigrateState,

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Expand Down Expand Up @@ -71,13 +70,10 @@ func resourceAwsRoute53Record() *schema.Resource {
ConflictsWith: []string{"alias"},
},

// Weight uses a special sentinel value to indicate its presence.
// Because 0 is a valid value for Weight, we default to -1 so that any
// inclusion of a weight (zero or not) will be a usable value
"weight": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: -1,
Removed: "Now implemented as weighted_routing_policy; Please see https://www.terraform.io/docs/providers/aws/r/route53_record.html",
},

"set_identifier": &schema.Schema{
Expand Down Expand Up @@ -114,6 +110,94 @@ func resourceAwsRoute53Record() *schema.Resource {
"failover": &schema.Schema{ // PRIMARY | SECONDARY
Type: schema.TypeString,
Optional: true,
Removed: "Now implemented as failover_routing_policy; see docs",
},

"failover_routing_policy": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ConflictsWith: []string{
"geolocation_routing_policy",
"latency_routing_policy",
"weighted_routing_policy",
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if value != "PRIMARY" && value != "SECONDARY" {
es = append(es, fmt.Errorf("Failover policy type must be PRIMARY or SECONDARY"))
}
return
},
},
},
},
},

"latency_routing_policy": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ConflictsWith: []string{
"failover_routing_policy",
"geolocation_routing_policy",
"weighted_routing_policy",
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"region": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
},

"geolocation_routing_policy": &schema.Schema{ // AWS Geolocation
Type: schema.TypeList,
Optional: true,
ConflictsWith: []string{
"failover_routing_policy",
"latency_routing_policy",
"weighted_routing_policy",
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"continent": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"country": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"subdivision": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
},

"weighted_routing_policy": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ConflictsWith: []string{
"failover_routing_policy",
"geolocation_routing_policy",
"latency_routing_policy",
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"weight": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
},
},
},

"health_check_id": &schema.Schema{ // ID of health check
Expand Down Expand Up @@ -292,14 +376,46 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro
}

d.Set("ttl", record.TTL)
// Only set the weight if it's non-nil, otherwise we end up with a 0 weight
// which has actual contextual meaning with Route 53 records
// See http://docs.aws.amazon.com/fr_fr/Route53/latest/APIReference/API_ChangeResourceRecordSets_Examples.html

if record.Failover != nil {
v := []map[string]interface{}{{
"type": aws.StringValue(record.Failover),
}}
if err := d.Set("failover_routing_policy", v); err != nil {
return fmt.Errorf("[DEBUG] Error setting failover records for: %s, error: %#v", d.Id(), err)
}
}

if record.GeoLocation != nil {
v := []map[string]interface{}{{
"continent": aws.StringValue(record.GeoLocation.ContinentCode),
"country": aws.StringValue(record.GeoLocation.CountryCode),
"subdivision": aws.StringValue(record.GeoLocation.SubdivisionCode),
}}
if err := d.Set("geolocation_routing_policy", v); err != nil {
return fmt.Errorf("[DEBUG] Error setting gelocation records for: %s, error: %#v", d.Id(), err)
}
}

if record.Region != nil {
v := []map[string]interface{}{{
"region": aws.StringValue(record.Region),
}}
if err := d.Set("latency_routing_policy", v); err != nil {
return fmt.Errorf("[DEBUG] Error setting latency records for: %s, error: %#v", d.Id(), err)
}
}

if record.Weight != nil {
d.Set("weight", record.Weight)
v := []map[string]interface{}{{
"weight": aws.Int64Value((record.Weight)),
}}
if err := d.Set("weighted_routing_policy", v); err != nil {
return fmt.Errorf("[DEBUG] Error setting weighted records for: %s, error: %#v", d.Id(), err)
}
}

d.Set("set_identifier", record.SetIdentifier)
d.Set("failover", record.Failover)
d.Set("health_check_id", record.HealthCheckId)

return nil
Expand Down Expand Up @@ -483,27 +599,69 @@ func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData, zoneName string) (
}
}

if v, ok := d.GetOk("failover"); ok {
if v, ok := d.GetOk("failover_routing_policy"); ok {
if _, ok := d.GetOk("set_identifier"); !ok {
return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "set_identifier": required field is not set when "failover" is set`, d.Get("name").(string))
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))
}
records := v.([]interface{})
if len(records) > 1 {
return nil, fmt.Errorf("You can only define a single failover_routing_policy per record")
}
rec.Failover = aws.String(v.(string))
failover := records[0].(map[string]interface{})

rec.Failover = aws.String(failover["type"].(string))
}

if v, ok := d.GetOk("health_check_id"); ok {
rec.HealthCheckId = aws.String(v.(string))
}

if v, ok := d.GetOk("weighted_routing_policy"); ok {
if _, ok := d.GetOk("set_identifier"); !ok {
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))
}
records := v.([]interface{})
if len(records) > 1 {
return nil, fmt.Errorf("You can only define a single weighed_routing_policy per record")
}
weight := records[0].(map[string]interface{})

rec.Weight = aws.Int64(int64(weight["weight"].(int)))
}

if v, ok := d.GetOk("set_identifier"); ok {
rec.SetIdentifier = aws.String(v.(string))
}

w := d.Get("weight").(int)
if w > -1 {
if v, ok := d.GetOk("latency_routing_policy"); ok {
if _, ok := d.GetOk("set_identifier"); !ok {
return nil, fmt.Errorf(`provider.aws: aws_route53_record: %s: "set_identifier": required field is not set when "weight" is set`, d.Get("name").(string))
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))
}
records := v.([]interface{})
if len(records) > 1 {
return nil, fmt.Errorf("You can only define a single latency_routing_policy per record")
}
rec.Weight = aws.Int64(int64(w))
latency := records[0].(map[string]interface{})

rec.Region = aws.String(latency["region"].(string))
}

if v, ok := d.GetOk("geolocation_routing_policy"); ok {
if _, ok := d.GetOk("set_identifier"); !ok {
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))
}
geolocations := v.([]interface{})
if len(geolocations) > 1 {
return nil, fmt.Errorf("You can only define a single geolocation_routing_policy per record")
}
geolocation := geolocations[0].(map[string]interface{})

rec.GeoLocation = &route53.GeoLocation{
ContinentCode: nilString(geolocation["continent"].(string)),
CountryCode: nilString(geolocation["country"].(string)),
SubdivisionCode: nilString(geolocation["subdivision"].(string)),
}
log.Printf("[DEBUG] Creating geolocation: %#v", geolocation)
}

return rec, nil
Expand Down Expand Up @@ -551,3 +709,13 @@ func resourceAwsRoute53AliasRecordHash(v interface{}) int {

return hashcode.String(buf.String())
}

// nilString takes a string as an argument and returns a string
// pointer. The returned pointer is nil if the string argument is
// empty, otherwise it is a pointer to a copy of the string.
func nilString(s string) *string {
if s == "" {
return nil
}
return aws.String(s)
}
25 changes: 25 additions & 0 deletions builtin/providers/aws/resource_aws_route53_record_migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ func resourceAwsRoute53RecordMigrateState(
case 0:
log.Println("[INFO] Found AWS Route53 Record State v0; migrating to v1")
return migrateRoute53RecordStateV0toV1(is)
case 1:
log.Println("[INFO] Found AWS Route53 Record State v1; migrating to v2")
return migrateRoute53RecordStateV1toV2(is)
default:
return is, fmt.Errorf("Unexpected schema version: %d", v)
}
Expand All @@ -31,3 +34,25 @@ func migrateRoute53RecordStateV0toV1(is *terraform.InstanceState) (*terraform.In
log.Printf("[DEBUG] Attributes after migration: %#v, new name: %s", is.Attributes, newName)
return is, nil
}

func migrateRoute53RecordStateV1toV2(is *terraform.InstanceState) (*terraform.InstanceState, error) {
if is.Empty() {
log.Println("[DEBUG] Empty InstanceState; nothing to migrate.")
return is, nil
}
log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes)
if is.Attributes["weight"] != "" && is.Attributes["weight"] != "-1" {
is.Attributes["weighted_routing_policy.#"] = "1"
key := fmt.Sprintf("weighted_routing_policy.0.weight")
is.Attributes[key] = is.Attributes["weight"]
}
if is.Attributes["failover"] != "" {
is.Attributes["failover_routing_policy.#"] = "1"
key := fmt.Sprintf("failover_routing_policy.0.type")
is.Attributes[key] = is.Attributes["failover"]
}
delete(is.Attributes, "weight")
delete(is.Attributes, "failover")
log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes)
return is, nil
}
51 changes: 51 additions & 0 deletions builtin/providers/aws/resource_aws_route53_record_migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,54 @@ func TestAWSRoute53RecordMigrateState(t *testing.T) {
}
}
}

func TestAWSRoute53RecordMigrateStateV1toV2(t *testing.T) {
cases := map[string]struct {
StateVersion int
Attributes map[string]string
Expected map[string]string
Meta interface{}
}{
"v0_1": {
StateVersion: 1,
Attributes: map[string]string{
"weight": "0",
"failover": "PRIMARY",
},
Expected: map[string]string{
"weighted_routing_policy.#": "1",
"weighted_routing_policy.0.weight": "0",
"failover_routing_policy.#": "1",
"failover_routing_policy.0.type": "PRIMARY",
},
},
"v0_2": {
StateVersion: 1,
Attributes: map[string]string{
"weight": "-1",
},
Expected: map[string]string{},
},
}

for tn, tc := range cases {
is := &terraform.InstanceState{
ID: "route53_record",
Attributes: tc.Attributes,
}
is, err := resourceAwsRoute53Record().MigrateState(
tc.StateVersion, is, tc.Meta)

if err != nil {
t.Fatalf("bad: %s, err: %#v", tn, err)
}

for k, v := range tc.Expected {
if is.Attributes[k] != v {
t.Fatalf(
"bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v",
tn, k, v, k, is.Attributes[k], is.Attributes)
}
}
}
}
Loading

0 comments on commit c315331

Please sign in to comment.