Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: Geolocation and Latency for Route53 Records (supersedes #2981) #6954

Merged
merged 2 commits into from
May 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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