Skip to content

Commit

Permalink
add support for geolocation and latency records to aws route53 provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Mielke committed Mar 28, 2016
1 parent 1b1e462 commit b542278
Show file tree
Hide file tree
Showing 6 changed files with 17,248 additions and 40 deletions.
188 changes: 172 additions & 16 deletions builtin/providers/aws/resource_aws_route53_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func resourceAwsRoute53Record() *schema.Resource {
Update: resourceAwsRoute53RecordUpdate,
Delete: resourceAwsRoute53RecordDelete,

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

// Weight uses a special sentinel value to indicate it's presense.
// 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; see docs",
},

"set_identifier": &schema.Schema{
Expand Down Expand Up @@ -111,6 +110,95 @@ 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,
Optional: false,
},
},
},
},

"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 @@ -264,14 +352,30 @@ 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 {
d.Set("failover_routing_policy.type", record.Failover)
}

if record.GeoLocation != nil {
geo := []map[string]interface{}{{
"continent": aws.StringValue(record.GeoLocation.ContinentCode),
"country": aws.StringValue(record.GeoLocation.CountryCode),
"subdivision": aws.StringValue(record.GeoLocation.SubdivisionCode),
}}
d.Set("geolocation_routing_policy", geo)
}
if record.Region != nil {
d.Set("latency_routing_policy.region", record.Region)
}
if record.Weight != nil {
d.Set("weight", record.Weight)
w := []map[string]interface{}{
{"weight": aws.Int64Value((record.Weight))},
}
d.Set("weighted_routing_policy", w)
}

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

return nil
Expand Down Expand Up @@ -454,27 +558,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))
}
rec.Failover = aws.String(v.(string))
records := v.([]interface{})
if len(records) > 1 {
return nil, fmt.Errorf("You can only define a single failover_routing_policy per record")
}
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 "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")
}
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 "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 "geolocation_routing_policy" is set`, d.Get("name").(string))
}
rec.Weight = aws.Int64(int64(w))
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 @@ -522,3 +668,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)
}
41 changes: 41 additions & 0 deletions builtin/providers/aws/resource_aws_route53_record_migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package aws

import (
"fmt"
"log"

"github.com/hashicorp/terraform/terraform"
)

func resourceAwsRoute53RecordMigrateState(
v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
switch v {
case 0:
log.Println("[INFO] Found AWS Route 53 Record State v0; migrating to v1")
return migrateR53RecordStateV0toV1(is)
default:
return is, fmt.Errorf("Unexpected schema version: %d", v)
}
}

func migrateR53RecordStateV0toV1(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"] != "-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
}
58 changes: 58 additions & 0 deletions builtin/providers/aws/resource_aws_route53_record_migrate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package aws

import (
"testing"

"github.com/hashicorp/terraform/terraform"
)

func TestAWSRoute53RecordMigrateState(t *testing.T) {
cases := map[string]struct {
StateVersion int
Attributes map[string]string
Expected map[string]string
Meta interface{}
}{
"v0_1": {
StateVersion: 0,
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: 0,
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 b542278

Please sign in to comment.