Merge pull request #388 from hashicorp/f-elb-schema

aws_elb to helper/schema, fix issues in helper/schema around computed sets
This commit is contained in:
Mitchell Hashimoto 2014-10-10 10:03:35 -07:00
commit 1acb546d74
6 changed files with 349 additions and 302 deletions

View File

@ -43,6 +43,7 @@ func Provider() *schema.Provider {
ResourcesMap: map[string]*schema.Resource{ ResourcesMap: map[string]*schema.Resource{
"aws_eip": resourceAwsEip(), "aws_eip": resourceAwsEip(),
"aws_elb": resourceAwsElb(),
"aws_instance": resourceAwsInstance(), "aws_instance": resourceAwsInstance(),
"aws_security_group": resourceAwsSecurityGroup(), "aws_security_group": resourceAwsSecurityGroup(),
"aws_db_subnet_group": resourceAwsDbSubnetGroup(), "aws_db_subnet_group": resourceAwsDbSubnetGroup(),

View File

@ -1,369 +1,344 @@
package aws package aws
import ( import (
"bytes"
"fmt" "fmt"
"log" "log"
"strconv"
"github.com/hashicorp/terraform/flatmap" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/config" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/diff"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/elb" "github.com/mitchellh/goamz/elb"
) )
func resource_aws_elb_create( func resourceAwsElb() *schema.Resource {
s *terraform.InstanceState, return &schema.Resource{
d *terraform.InstanceDiff, Create: resourceAwsElbCreate,
meta interface{}) (*terraform.InstanceState, error) { Read: resourceAwsElbRead,
Update: resourceAwsElbUpdate,
Delete: resourceAwsElbDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"internal": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Computed: true,
},
"availability_zones": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
},
"instances": &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
// TODO: could be not ForceNew
"security_groups": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
Computed: true,
},
// TODO: could be not ForceNew
"subnets": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
Computed: true,
},
// TODO: could be not ForceNew
"listener": &schema.Schema{
Type: schema.TypeSet,
Required: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"instance_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"instance_protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"lb_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"lb_protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ssl_certificate_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
Set: resourceAwsElbListenerHash,
},
// TODO: could be not ForceNew
"health_check": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"healthy_threshold": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"unhealthy_threshold": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"target": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"interval": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"timeout": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
},
},
Set: resourceAwsElbHealthCheckHash,
},
"dns_name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceAwsElbHealthCheckHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%d-", m["healthy_threshold"].(int)))
buf.WriteString(fmt.Sprintf("%d-", m["unhealthy_threshold"].(int)))
buf.WriteString(fmt.Sprintf("%s-", m["target"].(string)))
buf.WriteString(fmt.Sprintf("%d-", m["interval"].(int)))
buf.WriteString(fmt.Sprintf("%d-", m["timeout"].(int)))
return hashcode.String(buf.String())
}
func resourceAwsElbListenerHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int)))
buf.WriteString(fmt.Sprintf("%s-", m["instance_protocol"].(string)))
buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int)))
buf.WriteString(fmt.Sprintf("%s-", m["lb_protocol"].(string)))
if v, ok := m["ssl_certificate_id"]; ok {
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
}
return hashcode.String(buf.String())
}
func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error {
p := meta.(*ResourceProvider) p := meta.(*ResourceProvider)
elbconn := p.elbconn elbconn := p.elbconn
// Merge the diff into the state so that we have all the attributes // Expand the "listener" set to goamz compat []elb.Listener
// properly. listeners, err := expandListeners(d.Get("listener").(*schema.Set).List())
rs := s.MergeDiff(d)
// The name specified for the ELB. This is also our unique ID
// we save to state if the creation is successful (amazon verifies
// it is unique)
elbName := rs.Attributes["name"]
// Expand the "listener" array to goamz compat []elb.Listener
v := flatmap.Expand(rs.Attributes, "listener").([]interface{})
listeners, err := expandListeners(v)
if err != nil { if err != nil {
return nil, err return err
} }
// Provision the elb // Provision the elb
elbOpts := &elb.CreateLoadBalancer{ elbOpts := &elb.CreateLoadBalancer{
LoadBalancerName: elbName, LoadBalancerName: d.Get("name").(string),
Listeners: listeners, Listeners: listeners,
Internal: d.Get("internal").(bool),
} }
if rs.Attributes["internal"] == "true" { if v, ok := d.GetOk("availability_zones"); ok {
elbOpts.Internal = true elbOpts.AvailZone = expandStringList(v.([]interface{}))
} }
if _, ok := rs.Attributes["availability_zones.#"]; ok { if v, ok := d.GetOk("security_groups"); ok {
v = flatmap.Expand(rs.Attributes, "availability_zones").([]interface{}) elbOpts.SecurityGroups = expandStringList(v.([]interface{}))
elbOpts.AvailZone = expandStringList(v)
} }
if _, ok := rs.Attributes["security_groups.#"]; ok { if v, ok := d.GetOk("subnets"); ok {
v = flatmap.Expand(rs.Attributes, "security_groups").([]interface{}) elbOpts.Subnets = expandStringList(v.([]interface{}))
elbOpts.SecurityGroups = expandStringList(v)
}
if _, ok := rs.Attributes["subnets.#"]; ok {
v = flatmap.Expand(rs.Attributes, "subnets").([]interface{})
elbOpts.Subnets = expandStringList(v)
} }
log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts) log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts)
if _, err := elbconn.CreateLoadBalancer(elbOpts); err != nil {
_, err = elbconn.CreateLoadBalancer(elbOpts) return fmt.Errorf("Error creating ELB: %s", err)
if err != nil {
return nil, fmt.Errorf("Error creating ELB: %s", err)
} }
// Assign the elb's unique identifier for use later // Assign the elb's unique identifier for use later
rs.ID = elbName d.SetId(d.Get("name").(string))
log.Printf("[INFO] ELB ID: %s", elbName) log.Printf("[INFO] ELB ID: %s", d.Id())
if _, ok := rs.Attributes["instances.#"]; ok { // Enable partial mode and record what we set
// If we have any instances, we need to register them d.Partial(true)
v = flatmap.Expand(rs.Attributes, "instances").([]interface{}) d.SetPartial("name")
instances := expandStringList(v) d.SetPartial("internal")
d.SetPartial("availability_zones")
d.SetPartial("security_groups")
d.SetPartial("subnets")
if len(instances) > 0 { if d.HasChange("health_check") {
registerInstancesOpts := elb.RegisterInstancesWithLoadBalancer{ vs := d.Get("health_check").(*schema.Set).List()
LoadBalancerName: elbName, if len(vs) > 0 {
Instances: instances, check := vs[0].(map[string]interface{})
}
_, err := elbconn.RegisterInstancesWithLoadBalancer(&registerInstancesOpts)
if err != nil {
return rs, fmt.Errorf("Failure registering instances: %s", err)
}
}
}
if _, ok := rs.Attributes["health_check.#"]; ok {
v := flatmap.Expand(rs.Attributes, "health_check").([]interface{})
health_check := v[0].(map[string]interface{})
healthyThreshold, err := strconv.ParseInt(health_check["healthy_threshold"].(string), 0, 0)
unhealthyThreshold, err := strconv.ParseInt(health_check["unhealthy_threshold"].(string), 0, 0)
interval, err := strconv.ParseInt(health_check["interval"].(string), 0, 0)
timeout, err := strconv.ParseInt(health_check["timeout"].(string), 0, 0)
if err != nil {
return nil, err
}
configureHealthCheckOpts := elb.ConfigureHealthCheck{ configureHealthCheckOpts := elb.ConfigureHealthCheck{
LoadBalancerName: elbName, LoadBalancerName: d.Id(),
Check: elb.HealthCheck{ Check: elb.HealthCheck{
HealthyThreshold: healthyThreshold, HealthyThreshold: int64(check["healthy_threshold"].(int)),
UnhealthyThreshold: unhealthyThreshold, UnhealthyThreshold: int64(check["unhealthy_threshold"].(int)),
Interval: interval, Interval: int64(check["interval"].(int)),
Target: health_check["target"].(string), Target: check["target"].(string),
Timeout: timeout, Timeout: int64(check["timeout"].(int)),
}, },
} }
_, err = elbconn.ConfigureHealthCheck(&configureHealthCheckOpts) _, err = elbconn.ConfigureHealthCheck(&configureHealthCheckOpts)
if err != nil { if err != nil {
return rs, fmt.Errorf("Failure configuring health check: %s", err) return fmt.Errorf("Failure configuring health check: %s", err)
}
} }
} }
loadBalancer, err := resource_aws_elb_retrieve_balancer(rs.ID, elbconn) return resourceAwsElbUpdate(d, meta)
if err != nil {
return rs, err
} }
return resource_aws_elb_update_state(rs, loadBalancer) func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error {
}
func resource_aws_elb_update(
s *terraform.InstanceState,
d *terraform.InstanceDiff,
meta interface{}) (*terraform.InstanceState, error) {
p := meta.(*ResourceProvider) p := meta.(*ResourceProvider)
elbconn := p.elbconn elbconn := p.elbconn
rs := s.MergeDiff(d) d.Partial(true)
// If we currently have instances, or did have instances, // If we currently have instances, or did have instances,
// we want to figure out what to add and remove from the load // we want to figure out what to add and remove from the load
// balancer // balancer
if attr, ok := d.Attributes["instances.#"]; ok && attr.Old != "" { if d.HasChange("instances") {
// The new state of instances merged with the diff o, n := d.GetChange("instances")
mergedInstances := expandStringList(flatmap.Expand( os := o.(*schema.Set)
rs.Attributes, "instances").([]interface{})) ns := n.(*schema.Set)
remove := expandStringList(os.Difference(ns).List())
add := expandStringList(ns.Difference(os).List())
// The state before the diff merge if len(add) > 0 {
previousInstances := expandStringList(flatmap.Expand(
s.Attributes, "instances").([]interface{}))
// keep track of what instances we are removing, and which
// we are adding
var toRemove []string
var toAdd []string
for _, instanceId := range mergedInstances {
for _, prevId := range previousInstances {
// If the merged instance ID existed
// previously, we don't have to do anything
if instanceId == prevId {
continue
// Otherwise, we need to add it to the load balancer
} else {
toAdd = append(toAdd, instanceId)
}
}
}
for i, instanceId := range toAdd {
for _, prevId := range previousInstances {
// If the instance ID we are adding existed
// previously, we want to not add it, but rather remove
// it
if instanceId == prevId {
toRemove = append(toRemove, instanceId)
toAdd = append(toAdd[:i], toAdd[i+1:]...)
// Otherwise, we continue adding it to the ELB
} else {
continue
}
}
}
if len(toAdd) > 0 {
registerInstancesOpts := elb.RegisterInstancesWithLoadBalancer{ registerInstancesOpts := elb.RegisterInstancesWithLoadBalancer{
LoadBalancerName: rs.ID, LoadBalancerName: d.Id(),
Instances: toAdd, Instances: add,
} }
_, err := elbconn.RegisterInstancesWithLoadBalancer(&registerInstancesOpts) _, err := elbconn.RegisterInstancesWithLoadBalancer(&registerInstancesOpts)
if err != nil { if err != nil {
return s, fmt.Errorf("Failure registering instances: %s", err) return fmt.Errorf("Failure registering instances: %s", err)
} }
} }
if len(remove) > 0 {
if len(toRemove) > 0 {
deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancer{ deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancer{
LoadBalancerName: rs.ID, LoadBalancerName: d.Id(),
Instances: toRemove, Instances: remove,
} }
_, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts) _, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts)
if err != nil { if err != nil {
return s, fmt.Errorf("Failure deregistering instances: %s", err) return fmt.Errorf("Failure deregistering instances: %s", err)
}
} }
} }
loadBalancer, err := resource_aws_elb_retrieve_balancer(rs.ID, elbconn) d.SetPartial("instances")
if err != nil {
return s, err
} }
return resource_aws_elb_update_state(rs, loadBalancer) d.Partial(false)
return nil
} }
func resource_aws_elb_destroy( func resourceAwsElbDelete(d *schema.ResourceData, meta interface{}) error {
s *terraform.InstanceState,
meta interface{}) error {
p := meta.(*ResourceProvider) p := meta.(*ResourceProvider)
elbconn := p.elbconn elbconn := p.elbconn
log.Printf("[INFO] Deleting ELB: %s", s.ID) log.Printf("[INFO] Deleting ELB: %s", d.Id())
// Destroy the load balancer // Destroy the load balancer
deleteElbOpts := elb.DeleteLoadBalancer{ deleteElbOpts := elb.DeleteLoadBalancer{
LoadBalancerName: s.ID, LoadBalancerName: d.Id(),
} }
_, err := elbconn.DeleteLoadBalancer(&deleteElbOpts) if _, err := elbconn.DeleteLoadBalancer(&deleteElbOpts); err != nil {
if err != nil {
return fmt.Errorf("Error deleting ELB: %s", err) return fmt.Errorf("Error deleting ELB: %s", err)
} }
return nil return nil
} }
func resource_aws_elb_refresh( func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error {
s *terraform.InstanceState,
meta interface{}) (*terraform.InstanceState, error) {
p := meta.(*ResourceProvider) p := meta.(*ResourceProvider)
elbconn := p.elbconn elbconn := p.elbconn
loadBalancer, err := resource_aws_elb_retrieve_balancer(s.ID, elbconn) // Retrieve the ELB properties for updating the state
describeElbOpts := &elb.DescribeLoadBalancer{
Names: []string{d.Id()},
}
describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts)
if err != nil { if err != nil {
return nil, err return fmt.Errorf("Error retrieving ELB: %s", err)
}
if len(describeResp.LoadBalancers) != 1 {
return fmt.Errorf("Unable to find ELB: %#v", describeResp.LoadBalancers)
} }
return resource_aws_elb_update_state(s, loadBalancer) lb := describeResp.LoadBalancers[0]
}
func resource_aws_elb_diff( d.Set("name", lb.LoadBalancerName)
s *terraform.InstanceState, d.Set("dns_name", lb.DNSName)
c *terraform.ResourceConfig, d.Set("internal", lb.Scheme == "internal")
meta interface{}) (*terraform.InstanceDiff, error) { d.Set("instances", flattenInstances(lb.Instances))
d.Set("security_groups", lb.SecurityGroups)
b := &diff.ResourceBuilder{ d.Set("subnets", lb.Subnets)
Attrs: map[string]diff.AttrType{
"name": diff.AttrTypeCreate,
"availability_zone": diff.AttrTypeCreate,
"security_groups": diff.AttrTypeCreate, // TODO could be AttrTypeUpdate
"subnets": diff.AttrTypeCreate, // TODO could be AttrTypeUpdate
"listener": diff.AttrTypeCreate,
"instances": diff.AttrTypeUpdate,
"health_check": diff.AttrTypeCreate,
"internal": diff.AttrTypeCreate,
},
ComputedAttrs: []string{
"dns_name",
},
}
return b.Diff(s, c)
}
func resource_aws_elb_update_state(
s *terraform.InstanceState,
balancer *elb.LoadBalancer) (*terraform.InstanceState, error) {
s.Attributes["name"] = balancer.LoadBalancerName
s.Attributes["dns_name"] = balancer.DNSName
if balancer.Scheme == "internal" {
s.Attributes["internal"] = "true"
}
// Flatten our group values
toFlatten := make(map[string]interface{})
if len(balancer.Instances) > 0 && balancer.Instances[0].InstanceId != "" {
toFlatten["instances"] = flattenInstances(balancer.Instances)
}
if len(balancer.SecurityGroups) > 0 && balancer.SecurityGroups[0] != "" {
toFlatten["security_groups"] = balancer.SecurityGroups
}
if len(balancer.Subnets) > 0 && balancer.Subnets[0] != "" {
toFlatten["subnets"] = balancer.Subnets
}
// There's only one health check, so save that to state as we // There's only one health check, so save that to state as we
// currently can // currently can
if balancer.HealthCheck.Target != "" { if lb.HealthCheck.Target != "" {
toFlatten["health_check"] = flattenHealthCheck(balancer.HealthCheck) d.Set("health_check", flattenHealthCheck(lb.HealthCheck))
} }
for k, v := range flatmap.Flatten(toFlatten) { return nil
s.Attributes[k] = v
}
return s, nil
}
// retrieves an ELB by its ID
func resource_aws_elb_retrieve_balancer(id string, elbconn *elb.ELB) (*elb.LoadBalancer, error) {
describeElbOpts := &elb.DescribeLoadBalancer{
Names: []string{id},
}
// Retrieve the ELB properties for updating the state
describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts)
if err != nil {
return nil, fmt.Errorf("Error retrieving ELB: %s", err)
}
loadBalancer := describeResp.LoadBalancers[0]
// Verify AWS returned our ELB
if len(describeResp.LoadBalancers) != 1 ||
describeResp.LoadBalancers[0].LoadBalancerName != id {
if err != nil {
return nil, fmt.Errorf("Unable to find ELB: %#v", describeResp.LoadBalancers)
}
}
return &loadBalancer, nil
}
func resource_aws_elb_validation() *config.Validator {
return &config.Validator{
Required: []string{
"name",
"listener.*",
"listener.*.instance_port",
"listener.*.instance_protocol",
"listener.*.lb_port",
"listener.*.lb_protocol",
},
Optional: []string{
"instances.*",
"listener.*.ssl_certificate_id",
"internal",
"availability_zones.*",
"security_groups.*",
"subnets.*",
"health_check.#",
"health_check.0.healthy_threshold",
"health_check.0.unhealthy_threshold",
"health_check.0.interval",
"health_check.0.target",
"health_check.0.timeout",
},
}
} }

View File

@ -38,15 +38,6 @@ func init() {
Refresh: resource_aws_db_security_group_refresh, Refresh: resource_aws_db_security_group_refresh,
}, },
"aws_elb": resource.Resource{
ConfigValidator: resource_aws_elb_validation(),
Create: resource_aws_elb_create,
Update: resource_aws_elb_update,
Destroy: resource_aws_elb_destroy,
Diff: resource_aws_elb_diff,
Refresh: resource_aws_elb_refresh,
},
"aws_internet_gateway": resource.Resource{ "aws_internet_gateway": resource.Resource{
Create: resource_aws_internet_gateway_create, Create: resource_aws_internet_gateway_create,
Destroy: resource_aws_internet_gateway_destroy, Destroy: resource_aws_internet_gateway_destroy,

View File

@ -1,7 +1,6 @@
package aws package aws
import ( import (
"strconv"
"strings" "strings"
"github.com/mitchellh/goamz/autoscaling" "github.com/mitchellh/goamz/autoscaling"
@ -16,28 +15,20 @@ func expandListeners(configured []interface{}) ([]elb.Listener, error) {
// Loop over our configured listeners and create // Loop over our configured listeners and create
// an array of goamz compatabile objects // an array of goamz compatabile objects
for _, listener := range configured { for _, lRaw := range configured {
newL := listener.(map[string]interface{}) data := lRaw.(map[string]interface{})
instancePort, err := strconv.ParseInt(newL["instance_port"].(string), 0, 0)
lbPort, err := strconv.ParseInt(newL["lb_port"].(string), 0, 0)
if err != nil {
return nil, err
}
l := elb.Listener{ l := elb.Listener{
InstancePort: instancePort, InstancePort: int64(data["instance_port"].(int)),
InstanceProtocol: newL["instance_protocol"].(string), InstanceProtocol: data["instance_protocol"].(string),
LoadBalancerPort: lbPort, LoadBalancerPort: int64(data["lb_port"].(int)),
Protocol: newL["lb_protocol"].(string), Protocol: data["lb_protocol"].(string),
} }
if attr, ok := newL["ssl_certificate_id"].(string); ok { if v, ok := data["ssl_certificate_id"]; ok {
l.SSLCertificateId = attr l.SSLCertificateId = v.(string)
} }
listeners = append(listeners, l) listeners = append(listeners, l)
} }

View File

@ -291,6 +291,13 @@ func (d *ResourceData) getSet(
return result return result
} }
// If the entire list is computed, then the entire set is
// necessarilly computed.
if raw.Computed {
result.Computed = true
return result
}
list := raw.Value.([]interface{}) list := raw.Value.([]interface{})
if len(list) == 0 { if len(list) == 0 {
if len(parts) > 0 { if len(parts) > 0 {

View File

@ -641,6 +641,88 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false, Err: false,
}, },
{
Schema: map[string]*Schema{
"ports": &Schema{
Type: TypeSet,
Required: true,
Elem: &Schema{Type: TypeInt},
Set: func(a interface{}) int {
return a.(int)
},
},
},
State: nil,
Config: map[string]interface{}{
"ports": []interface{}{"${var.foo}", 1},
},
ConfigVariables: map[string]string{
"var.foo": "2" + config.InterpSplitDelim + "5",
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"ports.#": &terraform.ResourceAttrDiff{
Old: "0",
New: "3",
},
"ports.0": &terraform.ResourceAttrDiff{
Old: "",
New: "1",
},
"ports.1": &terraform.ResourceAttrDiff{
Old: "",
New: "2",
},
"ports.2": &terraform.ResourceAttrDiff{
Old: "",
New: "5",
},
},
},
Err: false,
},
{
Schema: map[string]*Schema{
"ports": &Schema{
Type: TypeSet,
Required: true,
Elem: &Schema{Type: TypeInt},
Set: func(a interface{}) int {
return a.(int)
},
},
},
State: nil,
Config: map[string]interface{}{
"ports": []interface{}{1, "${var.foo}"},
},
ConfigVariables: map[string]string{
"var.foo": config.UnknownVariableValue +
config.InterpSplitDelim + "5",
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"ports.#": &terraform.ResourceAttrDiff{
Old: "0",
New: "",
NewComputed: true,
},
},
},
Err: false,
},
{ {
Schema: map[string]*Schema{ Schema: map[string]*Schema{
"ports": &Schema{ "ports": &Schema{