core: refactoring the way sets work internally v2

This is a refactored solution for PR #616. Functionally this is still
the same change, but it’s implemented a lot cleaner with less code and
less changes to existing parts of TF.
This commit is contained in:
Sander van Harmelen 2014-12-12 23:21:20 +01:00
parent 69b2c245dd
commit 83c760fcb3
17 changed files with 891 additions and 440 deletions

View File

@ -16,7 +16,7 @@ testacc: config/y.go
echo "ERROR: Set TEST to a specific package"; \
exit 1; \
fi
TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 30m
TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 45m
testrace: config/y.go
TF_ACC= go test -race $(TEST) $(TESTARGS)

View File

@ -3,6 +3,7 @@ package aws
import (
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/terraform/helper/hashcode"
@ -195,7 +196,7 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e
d.Set("min_size", g.MinSize)
d.Set("max_size", g.MaxSize)
d.Set("name", g.Name)
d.Set("vpc_zone_identifier", g.VPCZoneIdentifier)
d.Set("vpc_zone_identifier", strings.Split(g.VPCZoneIdentifier, ","))
return nil
}

View File

@ -23,7 +23,7 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
testAccCheckAWSAutoScalingGroupAttributes(&group),
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "availability_zones.0", "us-west-2a"),
"aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"),
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "name", "foobar3-terraform-test"),
resource.TestCheckResourceAttr(
@ -39,7 +39,7 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "force_delete", "true"),
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "termination_policies.0", "OldestInstance"),
"aws_autoscaling_group.bar", "termination_policies.912102603", "OldestInstance"),
),
},

View File

@ -29,17 +29,17 @@ func TestAccAWSDBParameterGroup(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "description", "Test parameter group for terraform"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.0.name", "character_set_results"),
"aws_db_parameter_group.bar", "parameter.1708034931.name", "character_set_results"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.0.value", "utf8"),
"aws_db_parameter_group.bar", "parameter.1708034931.value", "utf8"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.1.name", "character_set_server"),
"aws_db_parameter_group.bar", "parameter.2421266705.name", "character_set_server"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.1.value", "utf8"),
"aws_db_parameter_group.bar", "parameter.2421266705.value", "utf8"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.2.name", "character_set_client"),
"aws_db_parameter_group.bar", "parameter.2478663599.name", "character_set_client"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.2.value", "utf8"),
"aws_db_parameter_group.bar", "parameter.2478663599.value", "utf8"),
),
},
resource.TestStep{
@ -54,25 +54,25 @@ func TestAccAWSDBParameterGroup(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "description", "Test parameter group for terraform"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.0.name", "collation_connection"),
"aws_db_parameter_group.bar", "parameter.1706463059.name", "collation_connection"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.0.value", "utf8_unicode_ci"),
"aws_db_parameter_group.bar", "parameter.1706463059.value", "utf8_unicode_ci"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.1.name", "character_set_results"),
"aws_db_parameter_group.bar", "parameter.1708034931.name", "character_set_results"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.1.value", "utf8"),
"aws_db_parameter_group.bar", "parameter.1708034931.value", "utf8"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.2.name", "character_set_server"),
"aws_db_parameter_group.bar", "parameter.2421266705.name", "character_set_server"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.2.value", "utf8"),
"aws_db_parameter_group.bar", "parameter.2421266705.value", "utf8"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.3.name", "collation_server"),
"aws_db_parameter_group.bar", "parameter.2475805061.name", "collation_server"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.3.value", "utf8_unicode_ci"),
"aws_db_parameter_group.bar", "parameter.2475805061.value", "utf8_unicode_ci"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.4.name", "character_set_client"),
"aws_db_parameter_group.bar", "parameter.2478663599.name", "character_set_client"),
resource.TestCheckResourceAttr(
"aws_db_parameter_group.bar", "parameter.4.value", "utf8"),
"aws_db_parameter_group.bar", "parameter.2478663599.value", "utf8"),
),
},
},

View File

@ -34,15 +34,15 @@ func TestAccAWSELB_basic(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_elb.bar", "availability_zones.2", "us-west-2c"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "listener.0.instance_port", "8000"),
"aws_elb.bar", "listener.206423021.instance_port", "8000"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "listener.0.instance_protocol", "http"),
"aws_elb.bar", "listener.206423021.instance_protocol", "http"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "listener.0.ssl_certificate_id", ssl_certificate_id),
"aws_elb.bar", "listener.206423021.ssl_certificate_id", ssl_certificate_id),
resource.TestCheckResourceAttr(
"aws_elb.bar", "listener.0.lb_port", "80"),
"aws_elb.bar", "listener.206423021.lb_port", "80"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "listener.0.lb_protocol", "http"),
"aws_elb.bar", "listener.206423021.lb_protocol", "http"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "cross_zone_load_balancing", "true"),
),
@ -101,15 +101,15 @@ func TestAccAWSELB_HealthCheck(t *testing.T) {
testAccCheckAWSELBExists("aws_elb.bar", &conf),
testAccCheckAWSELBAttributesHealthCheck(&conf),
resource.TestCheckResourceAttr(
"aws_elb.bar", "health_check.0.healthy_threshold", "5"),
"aws_elb.bar", "health_check.3484319807.healthy_threshold", "5"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "health_check.0.unhealthy_threshold", "5"),
"aws_elb.bar", "health_check.3484319807.unhealthy_threshold", "5"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "health_check.0.target", "HTTP:8000/"),
"aws_elb.bar", "health_check.3484319807.target", "HTTP:8000/"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "health_check.0.timeout", "30"),
"aws_elb.bar", "health_check.3484319807.timeout", "30"),
resource.TestCheckResourceAttr(
"aws_elb.bar", "health_check.0.interval", "60"),
"aws_elb.bar", "health_check.3484319807.interval", "60"),
),
},
},
@ -257,7 +257,6 @@ resource "aws_elb" "bar" {
lb_protocol = "http"
}
instances = []
cross_zone_load_balancing = true
}
`

View File

@ -24,29 +24,29 @@ func TestAccAWSNetworkAclsWithEgressAndIngressRules(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSNetworkAclExists("aws_network_acl.bar", &networkAcl),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.0.protocol", "tcp"),
"aws_network_acl.bar", "ingress.580214135.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.0.rule_no", "1"),
"aws_network_acl.bar", "ingress.580214135.rule_no", "1"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.0.from_port", "80"),
"aws_network_acl.bar", "ingress.580214135.from_port", "80"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.0.to_port", "80"),
"aws_network_acl.bar", "ingress.580214135.to_port", "80"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.0.action", "allow"),
"aws_network_acl.bar", "ingress.580214135.action", "allow"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "ingress.0.cidr_block", "10.3.10.3/18"),
"aws_network_acl.bar", "ingress.580214135.cidr_block", "10.3.10.3/18"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.0.protocol", "tcp"),
"aws_network_acl.bar", "egress.1730430240.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.0.rule_no", "2"),
"aws_network_acl.bar", "egress.1730430240.rule_no", "2"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.0.from_port", "443"),
"aws_network_acl.bar", "egress.1730430240.from_port", "443"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.0.to_port", "443"),
"aws_network_acl.bar", "egress.1730430240.to_port", "443"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.0.cidr_block", "10.3.2.3/18"),
"aws_network_acl.bar", "egress.1730430240.cidr_block", "10.3.2.3/18"),
resource.TestCheckResourceAttr(
"aws_network_acl.bar", "egress.0.action", "allow"),
"aws_network_acl.bar", "egress.1730430240.action", "allow"),
),
},
},
@ -67,17 +67,17 @@ func TestAccAWSNetworkAclsOnlyIngressRules(t *testing.T) {
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
// testAccCheckSubnetAssociation("aws_network_acl.foos", "aws_subnet.blob"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.protocol", "tcp"),
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.rule_no", "2"),
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.from_port", "443"),
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.to_port", "443"),
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.action", "deny"),
"aws_network_acl.foos", "ingress.3697634361.action", "deny"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.cidr_block", "10.2.2.3/18"),
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"),
),
},
},
@ -98,23 +98,21 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
testIngressRuleLength(&networkAcl, 2),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.protocol", "tcp"),
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.rule_no", "2"),
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.from_port", "443"),
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.to_port", "443"),
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.action", "deny"),
"aws_network_acl.foos", "ingress.3697634361.action", "deny"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.cidr_block", "10.2.2.3/18"),
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.1.rule_no", "1"),
"aws_network_acl.foos", "ingress.2438803013.from_port", "443"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.1.from_port", "0"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.1.to_port", "22"),
"aws_network_acl.foos", "ingress.2438803013.rule_no", "2"),
),
},
resource.TestStep{
@ -123,17 +121,17 @@ func TestAccAWSNetworkAclsOnlyIngressRulesChange(t *testing.T) {
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
testIngressRuleLength(&networkAcl, 1),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.protocol", "tcp"),
"aws_network_acl.foos", "ingress.3697634361.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.rule_no", "1"),
"aws_network_acl.foos", "ingress.3697634361.rule_no", "1"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.from_port", "443"),
"aws_network_acl.foos", "ingress.3697634361.from_port", "0"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.to_port", "443"),
"aws_network_acl.foos", "ingress.3697634361.to_port", "22"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.action", "deny"),
"aws_network_acl.foos", "ingress.3697634361.action", "deny"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.0.cidr_block", "10.2.2.3/18"),
"aws_network_acl.foos", "ingress.3697634361.cidr_block", "10.2.2.3/18"),
),
},
},
@ -349,8 +347,8 @@ resource "aws_network_acl" "foos" {
rule_no = 1
action = "deny"
cidr_block = "10.2.2.3/18"
from_port = 443
to_port = 443
from_port = 0
to_port = 22
}
subnet_id = "${aws_subnet.blob.id}"
}

View File

@ -28,15 +28,15 @@ func TestAccAWSSecurityGroup_normal(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_security_group.web", "description", "Used in the terraform acceptance tests"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.protocol", "tcp"),
"aws_security_group.web", "ingress.332851786.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.from_port", "80"),
"aws_security_group.web", "ingress.332851786.from_port", "80"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.to_port", "8000"),
"aws_security_group.web", "ingress.332851786.to_port", "8000"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.cidr_blocks.#", "1"),
"aws_security_group.web", "ingress.332851786.cidr_blocks.#", "1"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.cidr_blocks.0", "10.0.0.0/8"),
"aws_security_group.web", "ingress.332851786.cidr_blocks.0", "10.0.0.0/8"),
),
},
},
@ -74,13 +74,13 @@ func TestAccAWSSecurityGroup_self(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_security_group.web", "description", "Used in the terraform acceptance tests"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.protocol", "tcp"),
"aws_security_group.web", "ingress.3128515109.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.from_port", "80"),
"aws_security_group.web", "ingress.3128515109.from_port", "80"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.to_port", "8000"),
"aws_security_group.web", "ingress.3128515109.to_port", "8000"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.self", "true"),
"aws_security_group.web", "ingress.3128515109.self", "true"),
checkSelf,
),
},
@ -114,15 +114,15 @@ func TestAccAWSSecurityGroup_vpc(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_security_group.web", "description", "Used in the terraform acceptance tests"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.protocol", "tcp"),
"aws_security_group.web", "ingress.332851786.protocol", "tcp"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.from_port", "80"),
"aws_security_group.web", "ingress.332851786.from_port", "80"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.to_port", "8000"),
"aws_security_group.web", "ingress.332851786.to_port", "8000"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.cidr_blocks.#", "1"),
"aws_security_group.web", "ingress.332851786.cidr_blocks.#", "1"),
resource.TestCheckResourceAttr(
"aws_security_group.web", "ingress.0.cidr_blocks.0", "10.0.0.0/8"),
"aws_security_group.web", "ingress.332851786.cidr_blocks.0", "10.0.0.0/8"),
testCheck,
),
},
@ -383,7 +383,7 @@ resource "aws_security_group" "web" {
protocol = "tcp"
from_port = 80
to_port = 8000
cidr_blocks = ["10.0.0.0/8", "0.0.0.0/0"]
cidr_blocks = ["0.0.0.0/0", "10.0.0.0/8"]
}
}
`
@ -463,6 +463,16 @@ resource "aws_security_group" "web" {
const testAccAWSSecurityGroupConfigTags = `
resource "aws_security_group" "foo" {
name = "terraform_acceptance_test_example"
description = "Used in the terraform acceptance tests"
ingress {
protocol = "tcp"
from_port = 80
to_port = 8000
cidr_blocks = ["10.0.0.0/8"]
}
tags {
foo = "bar"
}
@ -471,6 +481,16 @@ resource "aws_security_group" "foo" {
const testAccAWSSecurityGroupConfigTagsUpdate = `
resource "aws_security_group" "foo" {
name = "terraform_acceptance_test_example"
description = "Used in the terraform acceptance tests"
ingress {
protocol = "tcp"
from_port = 80
to_port = 8000
cidr_blocks = ["10.0.0.0/8"]
}
tags {
bar = "baz"
}

View File

@ -23,22 +23,21 @@ func TestAccCloudStackFirewall_basic(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.source_cidr", "10.0.0.0/24"),
"cloudstack_firewall.foo", "rule.1702320581.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.protocol", "tcp"),
"cloudstack_firewall.foo", "rule.1702320581.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.#", "2"),
"cloudstack_firewall.foo", "rule.1702320581.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"),
"cloudstack_firewall.foo", "rule.1702320581.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.1", "80"),
"cloudstack_firewall.foo", "rule.1702320581.ports.1889509032", "80"),
),
},
},
})
}
/*
func TestAccCloudStackFirewall_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -54,15 +53,15 @@ func TestAccCloudStackFirewall_update(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.source_cidr", "10.0.0.0/24"),
"cloudstack_firewall.foo", "rule.1702320581.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.protocol", "tcp"),
"cloudstack_firewall.foo", "rule.1702320581.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.#", "2"),
"cloudstack_firewall.foo", "rule.1702320581.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"),
"cloudstack_firewall.foo", "rule.1702320581.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.1", "80"),
"cloudstack_firewall.foo", "rule.1702320581.ports.1889509032", "80"),
),
},
@ -75,31 +74,30 @@ func TestAccCloudStackFirewall_update(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.source_cidr", "10.0.0.0/24"),
"cloudstack_firewall.foo", "rule.1702320581.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.protocol", "tcp"),
"cloudstack_firewall.foo", "rule.1702320581.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.#", "2"),
"cloudstack_firewall.foo", "rule.1702320581.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"),
"cloudstack_firewall.foo", "rule.1702320581.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.1", "80"),
"cloudstack_firewall.foo", "rule.1702320581.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.source_cidr", "172.16.100.0/24"),
"cloudstack_firewall.foo", "rule.3779782959.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.protocol", "tcp"),
"cloudstack_firewall.foo", "rule.3779782959.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.ports.#", "2"),
"cloudstack_firewall.foo", "rule.3779782959.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.ports.0", "80"),
"cloudstack_firewall.foo", "rule.3779782959.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.ports.1", "443"),
"cloudstack_firewall.foo", "rule.3779782959.ports.3638101695", "443"),
),
},
},
})
}
*/
func testAccCheckCloudStackFirewallRulesExist(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {

View File

@ -23,26 +23,25 @@ func TestAccCloudStackNetworkACLRule_basic(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.action", "allow"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.action", "allow"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.source_cidr", "172.16.100.0/24"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.protocol", "tcp"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.#", "2"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.0", "80"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.1", "443"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.3638101695", "443"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.traffic_type", "ingress"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.traffic_type", "ingress"),
),
},
},
})
}
/*
func TestAccCloudStackNetworkACLRule_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -56,19 +55,19 @@ func TestAccCloudStackNetworkACLRule_update(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.action", "allow"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.action", "allow"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.source_cidr", "172.16.100.0/24"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.protocol", "tcp"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.#", "2"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.0", "80"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.1", "443"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.3638101695", "443"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.traffic_type", "ingress"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.traffic_type", "ingress"),
),
},
@ -79,39 +78,38 @@ func TestAccCloudStackNetworkACLRule_update(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.action", "allow"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.action", "allow"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.source_cidr", "172.16.100.0/24"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.protocol", "tcp"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.#", "2"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.0", "80"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.1", "443"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.ports.3638101695", "443"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.traffic_type", "ingress"),
"cloudstack_network_acl_rule.foo", "rule.3247834462.traffic_type", "ingress"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.action", "deny"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.action", "deny"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.source_cidr", "10.0.0.0/24"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.protocol", "tcp"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.ports.#", "2"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.ports.0", "1000-2000"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.ports.1", "80"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.traffic_type", "engress"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.traffic_type", "engress"),
),
},
},
})
}
*/
func testAccCheckCloudStackNetworkACLRulesExist(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {

View File

@ -23,20 +23,19 @@ func TestAccCloudStackPortForward_basic(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
"cloudstack_port_forward.foo", "forward.1537694805.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.private_port", "443"),
"cloudstack_port_forward.foo", "forward.1537694805.private_port", "443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.public_port", "8443"),
"cloudstack_port_forward.foo", "forward.1537694805.public_port", "8443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.virtual_machine", "terraform-test"),
"cloudstack_port_forward.foo", "forward.1537694805.virtual_machine", "terraform-test"),
),
},
},
})
}
/*
func TestAccCloudStackPortForward_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -52,13 +51,13 @@ func TestAccCloudStackPortForward_update(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
"cloudstack_port_forward.foo", "forward.1537694805.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.private_port", "443"),
"cloudstack_port_forward.foo", "forward.1537694805.private_port", "443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.public_port", "8443"),
"cloudstack_port_forward.foo", "forward.1537694805.public_port", "8443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.virtual_machine", "terraform-test"),
"cloudstack_port_forward.foo", "forward.1537694805.virtual_machine", "terraform-test"),
),
},
@ -71,27 +70,26 @@ func TestAccCloudStackPortForward_update(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
"cloudstack_port_forward.foo", "forward.8416686.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.private_port", "80"),
"cloudstack_port_forward.foo", "forward.8416686.private_port", "80"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.public_port", "8080"),
"cloudstack_port_forward.foo", "forward.8416686.public_port", "8080"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.virtual_machine", "terraform-test"),
"cloudstack_port_forward.foo", "forward.8416686.virtual_machine", "terraform-test"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.protocol", "tcp"),
"cloudstack_port_forward.foo", "forward.1537694805.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.private_port", "443"),
"cloudstack_port_forward.foo", "forward.1537694805.private_port", "443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.public_port", "8443"),
"cloudstack_port_forward.foo", "forward.1537694805.public_port", "8443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.virtual_machine", "terraform-test"),
"cloudstack_port_forward.foo", "forward.1537694805.virtual_machine", "terraform-test"),
),
},
},
})
}
*/
func testAccCheckCloudStackPortForwardsExist(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
@ -157,12 +155,10 @@ func testAccCheckCloudStackPortForwardDestroy(s *terraform.State) error {
var testAccCloudStackPortForward_basic = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}
@ -185,12 +181,10 @@ resource "cloudstack_port_forward" "foo" {
var testAccCloudStackPortForward_update = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}

View File

@ -113,6 +113,25 @@ func (d *ResourceData) HasChange(key string) bool {
return !reflect.DeepEqual(o, n)
}
// hasComputedSubKeys walks true a schema and returns whether or not the
// given key contains any subkeys that are computed.
func (d *ResourceData) hasComputedSubKeys(key string, schema *Schema) bool {
prefix := key + "."
switch t := schema.Elem.(type) {
case *Resource:
for k, schema := range t.Schema {
if d.config.IsComputed(prefix + k) {
return true
}
if d.hasComputedSubKeys(prefix+k, schema) {
return true
}
}
}
return false
}
// Partial turns partial state mode on/off.
//
// When partial state mode is enabled, then only key prefixes specified
@ -285,101 +304,178 @@ func (d *ResourceData) getSet(
source getSource) getResult {
s := &Set{F: schema.Set}
result := getResult{Schema: schema, Value: s}
prefix := k + "."
// Get the list. For sets, the entire source must be exact: the
// Get the set. For sets, the entire source must be exact: the
// entire set must come from set, diff, state, etc. So we go backwards
// and once we get a result, we take it. Or, we never get a result.
var raw getResult
var indexMap map[int]int
codes := make(map[string]int)
sourceLevel := source & getSourceLevelMask
sourceFlags := source & ^getSourceLevelMask
for listSource := sourceLevel; listSource > 0; listSource >>= 1 {
sourceDiff := sourceFlags&getSourceDiff != 0
for setSource := sourceLevel; setSource > 0; setSource >>= 1 {
// If we're already asking for an exact source and it doesn't
// match, then leave since the original source was the match.
if sourceFlags&getSourceExact != 0 && listSource != sourceLevel {
if sourceFlags&getSourceExact != 0 && setSource != sourceLevel {
break
}
// The source we get from is the level we're on, plus the flags
// we had, plus the exact flag.
getSource := listSource
getSource |= sourceFlags
getSource |= getSourceExact
raw = d.getList(k, nil, schema, getSource)
if raw.Exists {
break
}
}
if !raw.Exists {
if len(parts) > 0 {
return d.getList(k, parts, schema, source)
}
return result
}
if d.config != nil && setSource == getSourceConfig {
raw := d.getList(k, nil, schema, setSource)
// If the entire list is computed, then the entire set is
// necessarilly computed.
if raw.Computed {
result.Computed = true
if len(parts) > 0 {
break
}
return result
}
if raw.Exists {
result.Exists = true
list := raw.Value.([]interface{})
if len(list) == 0 {
if len(parts) > 0 {
return d.getList(k, parts, schema, source)
}
result.Exists = raw.Exists
return result
}
// This is a reverse map of hash code => index in config used to
// resolve direct set item lookup for turning into state. Confused?
// Read on...
//
// To create the state (the state* functions), a Get call is done
// with a full key such as "ports.0". The index of a set ("0") doesn't
// make a lot of sense, but we need to deterministically list out
// elements of the set like this. Luckily, same sets have a deterministic
// List() output, so we can use that to look things up.
//
// This mapping makes it so that we can look up the hash code of an
// object back to its index in the REAL config.
var indexMap map[int]int
if len(parts) > 0 {
indexMap = make(map[int]int)
}
indexMap = make(map[int]int, len(list))
// Build the set from all the items using the given hash code
for i, v := range list {
code := s.add(v)
if indexMap != nil {
// Check if any of the keys in this item are computed
computed := false
if len(d.config.ComputedKeys) > 0 {
prefix := fmt.Sprintf("%s.%d", k, i)
computed = d.hasComputedSubKeys(prefix, schema)
}
// Check if we are computed and if so negatate the hash to
// this is a approximate hash
if computed {
s.m[-code] = s.m[code]
delete(s.m, code)
code = -code
}
indexMap[code] = i
}
break
}
}
// If we're trying to get a specific element, then rewrite the
// index to be just that, then jump direct to getList.
if len(parts) > 0 {
index := parts[0]
indexInt, err := strconv.ParseInt(index, 0, 0)
if d.state != nil && setSource == getSourceState {
for k, _ := range d.state.Attributes {
if !strings.HasPrefix(k, prefix) || strings.HasPrefix(k, prefix+"#") {
continue
}
parts := strings.Split(k[len(prefix):], ".")
idx := parts[0]
if _, ok := codes[idx]; ok {
continue
}
code, err := strconv.Atoi(strings.Replace(parts[0], "~", "-", -1))
if err != nil {
return getResultEmpty
panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err))
}
codes[idx] = code
}
}
codes := s.listCode()
if int(indexInt) >= len(codes) {
return getResultEmpty
if d.setMap != nil && setSource == getSourceSet {
for k, _ := range d.setMap {
if !strings.HasPrefix(k, prefix) || strings.HasPrefix(k, prefix+"#") {
continue
}
code := codes[indexInt]
realIndex := indexMap[code]
parts[0] = strconv.FormatInt(int64(realIndex), 10)
return d.getList(k, parts, schema, source)
parts := strings.Split(k[len(prefix):], ".")
idx := parts[0]
if _, ok := codes[idx]; ok {
continue
}
code, err := strconv.Atoi(strings.Replace(parts[0], "~", "-", -1))
if err != nil {
panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err))
}
codes[idx] = code
}
}
if d.diff != nil && sourceDiff {
for k, _ := range d.diff.Attributes {
if !strings.HasPrefix(k, prefix) || strings.HasPrefix(k, prefix+"#") {
continue
}
parts := strings.Split(k[len(prefix):], ".")
idx := parts[0]
if _, ok := codes[idx]; ok {
continue
}
code, err := strconv.Atoi(strings.Replace(parts[0], "~", "-", -1))
if err != nil {
panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err))
}
codes[idx] = code
}
}
if len(codes) > 0 {
break
}
}
if indexMap == nil {
s.m = make(map[int]interface{})
for idx, code := range codes {
switch t := schema.Elem.(type) {
case *Schema:
// Get a single value
s.m[code] = d.get(prefix+idx, nil, t, source).Value
result.Exists = true
case *Resource:
// Get the entire object
m := make(map[string]interface{})
for field, _ := range t.Schema {
m[field] = d.getObject(prefix+idx, []string{field}, t.Schema, source).Value
}
s.m[code] = m
result.Exists = true
}
}
}
if len(parts) > 0 {
// We still have parts left over meaning we're accessing an
// element of this set.
idx := parts[0]
parts = parts[1:]
// Special case if we're accessing the count of the set
if idx == "#" {
schema := &Schema{Type: TypeInt}
return d.get(prefix+"#", parts, schema, source)
}
if source&getSourceLevelMask == getSourceConfig {
i, err := strconv.Atoi(strings.Replace(idx, "~", "-", -1))
if err != nil {
panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err))
}
if i, ok := indexMap[i]; ok {
idx = strconv.Itoa(i)
}
}
switch t := schema.Elem.(type) {
case *Schema:
return d.get(prefix+idx, parts, t, source)
case *Resource:
return d.getObject(prefix+idx, parts, t.Schema, source)
}
}
return result
}
@ -916,8 +1012,13 @@ func (d *ResourceData) setSet(
// If it is a slice, then we have to turn it into a *Set so that
// we get the proper order back based on the hash code.
if v := reflect.ValueOf(value); v.Kind() == reflect.Slice {
// Build a temp *ResourceData to use for the conversion
tempD := &ResourceData{
setMap: make(map[string]string),
}
// Set the entire list, this lets us get sane values out of it
if err := d.setList(k, nil, schema, value); err != nil {
if err := tempD.setList(k, nil, schema, value); err != nil {
return err
}
@ -930,7 +1031,7 @@ func (d *ResourceData) setSet(
source := getSourceSet | getSourceExact
for i := 0; i < v.Len(); i++ {
is := strconv.FormatInt(int64(i), 10)
result := d.getList(k, []string{is}, schema, source)
result := tempD.getList(k, []string{is}, schema, source)
if !result.Exists {
panic("just set item doesn't exist")
}
@ -941,11 +1042,32 @@ func (d *ResourceData) setSet(
value = s
}
if s, ok := value.(*Set); ok {
value = s.List()
switch t := schema.Elem.(type) {
case *Schema:
for code, elem := range value.(*Set).m {
subK := fmt.Sprintf("%s.%d", k, code)
err := d.set(subK, nil, t, elem)
if err != nil {
return err
}
}
case *Resource:
for code, elem := range value.(*Set).m {
for field, _ := range t.Schema {
subK := fmt.Sprintf("%s.%d", k, code)
err := d.setObject(
subK, []string{field}, t.Schema, elem.(map[string]interface{})[field])
if err != nil {
return err
}
}
}
default:
return fmt.Errorf("%s: unknown element type (internal)", k)
}
return d.setList(k, nil, schema, value)
d.setMap[k+".#"] = strconv.Itoa(value.(*Set).Len())
return nil
}
func (d *ResourceData) stateList(
@ -1071,18 +1193,18 @@ func (d *ResourceData) stateSet(
}
set := raw.Value.(*Set)
list := set.List()
result := make(map[string]string)
result[prefix+".#"] = strconv.FormatInt(int64(len(list)), 10)
for i := 0; i < len(list); i++ {
key := fmt.Sprintf("%s.%d", prefix, i)
result[prefix+".#"] = strconv.Itoa(set.Len())
for _, idx := range set.listCode() {
key := fmt.Sprintf("%s.%d", prefix, idx)
var m map[string]string
switch t := schema.Elem.(type) {
case *Resource:
m = d.stateObject(key, t.Schema)
case *Schema:
m = d.stateSingle(key, t)
case *Resource:
m = d.stateObject(key, t.Schema)
}
for k, v := range m {

View File

@ -15,6 +15,7 @@ func TestResourceDataGet(t *testing.T) {
Key string
Value interface{}
}{
// #0
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -41,6 +42,7 @@ func TestResourceDataGet(t *testing.T) {
Value: "",
},
// #1
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -68,6 +70,7 @@ func TestResourceDataGet(t *testing.T) {
Value: "foo",
},
// #2
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -94,6 +97,7 @@ func TestResourceDataGet(t *testing.T) {
Value: "foo",
},
// #3
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -117,6 +121,7 @@ func TestResourceDataGet(t *testing.T) {
Value: "bar",
},
// #4
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -147,6 +152,7 @@ func TestResourceDataGet(t *testing.T) {
Value: "",
},
// #5
{
Schema: map[string]*Schema{
"port": &Schema{
@ -170,6 +176,7 @@ func TestResourceDataGet(t *testing.T) {
Value: 80,
},
// #6
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -193,6 +200,7 @@ func TestResourceDataGet(t *testing.T) {
Value: 2,
},
// #7
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -216,6 +224,7 @@ func TestResourceDataGet(t *testing.T) {
Value: 3,
},
// #8
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -232,6 +241,7 @@ func TestResourceDataGet(t *testing.T) {
Value: 0,
},
// #9
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -255,6 +265,7 @@ func TestResourceDataGet(t *testing.T) {
Value: []interface{}{1, 2, 5},
},
// #10
{
Schema: map[string]*Schema{
"ingress": &Schema{
@ -293,6 +304,7 @@ func TestResourceDataGet(t *testing.T) {
},
},
// #11
{
Schema: map[string]*Schema{
"ingress": &Schema{
@ -333,7 +345,7 @@ func TestResourceDataGet(t *testing.T) {
},
},
// Computed get
// #12 Computed get
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -353,7 +365,7 @@ func TestResourceDataGet(t *testing.T) {
Value: "foo",
},
// Full object
// #13 Full object
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -383,7 +395,7 @@ func TestResourceDataGet(t *testing.T) {
},
},
// List of maps
// #14 List of maps
{
Schema: map[string]*Schema{
"config_vars": &Schema{
@ -427,7 +439,7 @@ func TestResourceDataGet(t *testing.T) {
},
},
// List of maps in state
// #15 List of maps in state
{
Schema: map[string]*Schema{
"config_vars": &Schema{
@ -462,7 +474,7 @@ func TestResourceDataGet(t *testing.T) {
},
},
// List of maps with removal in diff
// #16 List of maps with removal in diff
{
Schema: map[string]*Schema{
"config_vars": &Schema{
@ -500,7 +512,7 @@ func TestResourceDataGet(t *testing.T) {
Value: []interface{}{},
},
// Sets
// #17 Sets
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -517,7 +529,7 @@ func TestResourceDataGet(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "1",
"ports.0": "80",
"ports.80": "80",
},
},
@ -528,6 +540,7 @@ func TestResourceDataGet(t *testing.T) {
Value: []interface{}{80},
},
// #18
{
Schema: map[string]*Schema{
"data": &Schema{
@ -556,14 +569,14 @@ func TestResourceDataGet(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"data.#": "1",
"data.0.index": "10",
"data.0.value": "50",
"data.10.index": "10",
"data.10.value": "50",
},
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"data.0.value": &terraform.ResourceAttrDiff{
"data.10.value": &terraform.ResourceAttrDiff{
Old: "50",
New: "80",
},
@ -1398,9 +1411,9 @@ func TestResourceDataSet(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "3",
"ports.0": "100",
"ports.1": "80",
"ports.2": "80",
"ports.100": "100",
"ports.80": "80",
"ports.81": "81",
},
},
@ -1433,12 +1446,12 @@ func TestResourceDataSet(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0": "100",
"ports.1": "80",
"ports.100": "100",
"ports.80": "80",
},
},
Key: "ports.0",
Key: "ports.100",
Value: 256,
Err: true,
@ -1822,9 +1835,9 @@ func TestResourceDataState(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "3",
"ports.0": "100",
"ports.1": "80",
"ports.2": "80",
"ports.100": "100",
"ports.80": "80",
"ports.81": "81",
},
},
@ -1832,9 +1845,10 @@ func TestResourceDataState(t *testing.T) {
Result: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0": "80",
"ports.1": "100",
"ports.#": "3",
"ports.80": "80",
"ports.81": "81",
"ports.100": "100",
},
},
},
@ -1863,8 +1877,8 @@ func TestResourceDataState(t *testing.T) {
Result: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0": "80",
"ports.1": "100",
"ports.80": "80",
"ports.100": "100",
},
},
},
@ -1902,12 +1916,12 @@ func TestResourceDataState(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0.order": "10",
"ports.0.a.#": "1",
"ports.0.a.0": "80",
"ports.1.order": "20",
"ports.1.b.#": "1",
"ports.1.b.0": "100",
"ports.10.order": "10",
"ports.10.a.#": "1",
"ports.10.a.0": "80",
"ports.20.order": "20",
"ports.20.b.#": "1",
"ports.20.b.0": "100",
},
},
@ -1927,12 +1941,12 @@ func TestResourceDataState(t *testing.T) {
Result: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0.order": "10",
"ports.0.a.#": "1",
"ports.0.a.0": "80",
"ports.1.order": "20",
"ports.1.b.#": "1",
"ports.1.b.0": "100",
"ports.10.order": "10",
"ports.10.a.#": "1",
"ports.10.a.0": "80",
"ports.20.order": "20",
"ports.20.b.#": "1",
"ports.20.b.0": "100",
},
},
},
@ -2161,15 +2175,15 @@ func TestResourceDataState(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "3",
"ports.0": "100",
"ports.1": "80",
"ports.2": "80",
"ports.100": "100",
"ports.80": "80",
"ports.81": "81",
},
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"ports.1": &terraform.ResourceAttrDiff{
"ports.120": &terraform.ResourceAttrDiff{
New: "120",
},
},
@ -2179,9 +2193,10 @@ func TestResourceDataState(t *testing.T) {
Result: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0": "80",
"ports.1": "100",
"ports.#": "3",
"ports.80": "80",
"ports.81": "81",
"ports.100": "100",
},
},
},

View File

@ -618,25 +618,109 @@ func (m schemaMap) diffSet(
diff *terraform.InstanceDiff,
d *ResourceData,
all bool) error {
if !all {
// This is a bit strange, but we expect the entire set to be in the diff,
// so we first diff the set normally but with a new diff. Then, if
// there IS any change, we just set the change to the entire list.
tempD := new(terraform.InstanceDiff)
tempD.Attributes = make(map[string]*terraform.ResourceAttrDiff)
if err := m.diffList(k, schema, tempD, d, false); err != nil {
return err
}
o, n, _, computedSet := d.diffChange(k)
nSet := n != nil
// If we had no changes, then we're done
if tempD.Empty() {
// If we have an old value and no new value is set or will be
// computed once all variables can be interpolated and we're
// computed, then nothing has changed.
if o != nil && n == nil && !computedSet && schema.Computed {
return nil
}
if o == nil {
o = &Set{F: schema.Set}
}
if n == nil {
n = &Set{F: schema.Set}
}
os := o.(*Set)
ns := n.(*Set)
// If the new value was set, compare the listCode's to determine if
// the two are equal. Comparing listCode's instead of the actuall values
// is needed because there could be computed values in the set which
// would result in false positives while comparing.
if !all && nSet && reflect.DeepEqual(os.listCode(), ns.listCode()) {
return nil
}
// We have changes, so re-run the diff, but set a flag to force
// getting all diffs, even if there is no change.
return m.diffList(k, schema, diff, d, true)
// Get the counts
oldLen := os.Len()
newLen := ns.Len()
oldStr := strconv.Itoa(oldLen)
newStr := strconv.Itoa(newLen)
// If the set computed then say that the # is computed
if computedSet || (schema.Computed && !nSet) {
// If # already exists, equals 0 and no new set is supplied, there
// is nothing to record in the diff
count, ok := d.GetOk(k + ".#")
if ok && count.(int) == 0 && !nSet && !computedSet {
return nil
}
// Set the count but make sure that if # does not exist, we don't
// use the zeroed value
countStr := strconv.Itoa(count.(int))
if !ok {
countStr = ""
}
diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{
Old: countStr,
NewComputed: true,
}
return nil
}
// If the counts are not the same, then record that diff
changed := oldLen != newLen
if changed || all {
countSchema := &Schema{
Type: TypeInt,
Computed: schema.Computed,
ForceNew: schema.ForceNew,
}
diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{
Old: oldStr,
New: newStr,
})
}
for _, code := range ns.listCode() {
switch t := schema.Elem.(type) {
case *Schema:
// Copy the schema so that we can set Computed/ForceNew from
// the parent schema (the TypeSet).
t2 := *t
t2.ForceNew = schema.ForceNew
// This is just a primitive element, so go through each and
// just diff each.
subK := fmt.Sprintf("%s.%d", k, code)
subK = strings.Replace(subK, "-", "~", -1)
err := m.diff(subK, &t2, diff, d, true)
if err != nil {
return err
}
case *Resource:
// This is a complex resource
for k2, schema := range t.Schema {
subK := fmt.Sprintf("%s.%d.%s", k, code, k2)
subK = strings.Replace(subK, "-", "~", -1)
err := m.diff(subK, schema, diff, d, true)
if err != nil {
return err
}
}
default:
return fmt.Errorf("%s: unknown element type (internal)", k)
}
}
return nil
}
func (m schemaMap) diffString(

View File

@ -21,6 +21,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* String decode
*/
// #0
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -50,6 +51,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #1
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -77,6 +79,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #2
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -98,7 +101,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// Computed, but set in config
// #3 Computed, but set in config
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -130,7 +133,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// Default
// #4 Default
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -156,7 +159,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// DefaultFunc, value
// #5 DefaultFunc, value
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -184,7 +187,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// DefaultFunc, configuration set
// #6 DefaultFunc, configuration set
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -214,7 +217,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// String with StateFunc
// #7 String with StateFunc
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -246,7 +249,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// Variable (just checking)
// #8 Variable (just checking)
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -277,7 +280,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// Variable computed
// #9 Variable computed
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -312,6 +315,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* Int decode
*/
// #10
{
Schema: map[string]*Schema{
"port": &Schema{
@ -345,6 +349,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* Bool decode
*/
// #11
{
Schema: map[string]*Schema{
"port": &Schema{
@ -377,6 +382,8 @@ func TestSchemaMap_Diff(t *testing.T) {
/*
* Bool
*/
// #12
{
Schema: map[string]*Schema{
"delete": &Schema{
@ -403,6 +410,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* List decode
*/
// #13
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -442,6 +450,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #14
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -485,6 +494,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #15
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -518,6 +528,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #16
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -545,6 +556,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #17
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -582,6 +594,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #18
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -626,6 +639,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #19
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -656,6 +670,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* Set
*/
// #20
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -680,15 +695,15 @@ func TestSchemaMap_Diff(t *testing.T) {
Old: "0",
New: "3",
},
"ports.0": &terraform.ResourceAttrDiff{
"ports.1": &terraform.ResourceAttrDiff{
Old: "",
New: "1",
},
"ports.1": &terraform.ResourceAttrDiff{
"ports.2": &terraform.ResourceAttrDiff{
Old: "",
New: "2",
},
"ports.2": &terraform.ResourceAttrDiff{
"ports.5": &terraform.ResourceAttrDiff{
Old: "",
New: "5",
},
@ -698,6 +713,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #21
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -724,6 +740,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #22
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -753,6 +770,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #23
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -781,15 +799,15 @@ func TestSchemaMap_Diff(t *testing.T) {
Old: "0",
New: "3",
},
"ports.0": &terraform.ResourceAttrDiff{
"ports.1": &terraform.ResourceAttrDiff{
Old: "",
New: "1",
},
"ports.1": &terraform.ResourceAttrDiff{
"ports.2": &terraform.ResourceAttrDiff{
Old: "",
New: "2",
},
"ports.2": &terraform.ResourceAttrDiff{
"ports.5": &terraform.ResourceAttrDiff{
Old: "",
New: "5",
},
@ -799,6 +817,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #24
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -825,7 +844,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"ports.#": &terraform.ResourceAttrDiff{
Old: "0",
Old: "",
New: "",
NewComputed: true,
},
@ -835,6 +854,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #25
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -850,8 +870,8 @@ func TestSchemaMap_Diff(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0": "2",
"ports.1": "1",
"ports.2": "2",
},
},
@ -865,15 +885,15 @@ func TestSchemaMap_Diff(t *testing.T) {
Old: "2",
New: "3",
},
"ports.0": &terraform.ResourceAttrDiff{
"ports.1": &terraform.ResourceAttrDiff{
Old: "1",
New: "1",
},
"ports.1": &terraform.ResourceAttrDiff{
"ports.2": &terraform.ResourceAttrDiff{
Old: "2",
New: "2",
},
"ports.2": &terraform.ResourceAttrDiff{
"ports.5": &terraform.ResourceAttrDiff{
Old: "",
New: "5",
},
@ -883,6 +903,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #26
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -898,8 +919,8 @@ func TestSchemaMap_Diff(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "2",
"ports.0": "2",
"ports.1": "1",
"ports.2": "2",
},
},
@ -911,20 +932,13 @@ func TestSchemaMap_Diff(t *testing.T) {
Old: "2",
New: "0",
},
"ports.0": &terraform.ResourceAttrDiff{
Old: "1",
NewRemoved: true,
},
"ports.1": &terraform.ResourceAttrDiff{
Old: "2",
NewRemoved: true,
},
},
},
Err: false,
},
// #27
{
Schema: map[string]*Schema{
"ports": &Schema{
@ -932,7 +946,9 @@ func TestSchemaMap_Diff(t *testing.T) {
Optional: true,
Computed: true,
Elem: &Schema{Type: TypeInt},
Set: func(v interface{}) int { return v.(int) },
Set: func(a interface{}) int {
return a.(int)
},
},
},
@ -940,7 +956,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Attributes: map[string]string{
"availability_zone": "bar",
"ports.#": "1",
"ports.0": "80",
"ports.80": "80",
},
},
@ -951,6 +967,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #28
{
Schema: map[string]*Schema{
"ingress": &Schema{
@ -980,15 +997,15 @@ func TestSchemaMap_Diff(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"ingress.#": "2",
"ingress.0.ports.#": "1",
"ingress.0.ports.0": "80",
"ingress.1.ports.#": "1",
"ingress.1.ports.0": "443",
"ingress.80.ports.#": "1",
"ingress.80.ports.0": "80",
"ingress.443.ports.#": "1",
"ingress.443.ports.0": "443",
},
},
Config: map[string]interface{}{
"ingress": []interface{}{
"ingress": []map[string]interface{}{
map[string]interface{}{
"ports": []interface{}{443},
},
@ -1007,6 +1024,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* List of structure decode
*/
// #29
{
Schema: map[string]*Schema{
"ingress": &Schema{
@ -1053,6 +1071,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* ComputedWhen
*/
// #30
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -1083,6 +1102,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #31
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -1165,6 +1185,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* Maps
*/
// #32
{
Schema: map[string]*Schema{
"config_vars": &Schema{
@ -1194,6 +1215,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #33
{
Schema: map[string]*Schema{
"config_vars": &Schema{
@ -1231,6 +1253,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #34
{
Schema: map[string]*Schema{
"vars": &Schema{
@ -1271,6 +1294,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #35
{
Schema: map[string]*Schema{
"vars": &Schema{
@ -1292,6 +1316,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #36
{
Schema: map[string]*Schema{
"config_vars": &Schema{
@ -1331,6 +1356,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #37
{
Schema: map[string]*Schema{
"config_vars": &Schema{
@ -1373,6 +1399,7 @@ func TestSchemaMap_Diff(t *testing.T) {
* ForceNews
*/
// #38
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -1418,7 +1445,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// Set
// #39 Set
{
Schema: map[string]*Schema{
"availability_zone": &Schema{
@ -1432,7 +1459,9 @@ func TestSchemaMap_Diff(t *testing.T) {
Optional: true,
Computed: true,
Elem: &Schema{Type: TypeInt},
Set: func(v interface{}) int { return v.(int) },
Set: func(a interface{}) int {
return a.(int)
},
},
},
@ -1440,7 +1469,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Attributes: map[string]string{
"availability_zone": "bar",
"ports.#": "1",
"ports.0": "80",
"ports.80": "80",
},
},
@ -1467,13 +1496,9 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #40 Set
{
Schema: map[string]*Schema{
"internal": &Schema{
Type: TypeBool,
Required: true,
},
"instances": &Schema{
Type: TypeSet,
Elem: &Schema{Type: TypeString},
@ -1487,13 +1512,11 @@ func TestSchemaMap_Diff(t *testing.T) {
State: &terraform.InstanceState{
Attributes: map[string]string{
"internal": "false",
"instances.#": "0",
},
},
Config: map[string]interface{}{
"internal": true,
"instances": []interface{}{"${var.foo}"},
},
@ -1503,13 +1526,136 @@ func TestSchemaMap_Diff(t *testing.T) {
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"internal": &terraform.ResourceAttrDiff{
"instances.#": &terraform.ResourceAttrDiff{
Old: "0",
NewComputed: true,
},
},
},
Err: false,
},
// #41 Set
{
Schema: map[string]*Schema{
"route": &Schema{
Type: TypeSet,
Optional: true,
Elem: &Resource{
Schema: map[string]*Schema{
"index": &Schema{
Type: TypeInt,
Required: true,
},
"gateway": &Schema{
Type: TypeString,
Optional: true,
},
},
},
Set: func(v interface{}) int {
m := v.(map[string]interface{})
return m["index"].(int)
},
},
},
State: nil,
Config: map[string]interface{}{
"route": []map[string]interface{}{
map[string]interface{}{
"index": "1",
"gateway": "${var.foo}",
},
},
},
ConfigVariables: map[string]string{
"var.foo": config.UnknownVariableValue,
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"route.#": &terraform.ResourceAttrDiff{
Old: "0",
New: "1",
},
"route.~1.index": &terraform.ResourceAttrDiff{
Old: "",
New: "1",
},
"route.~1.gateway": &terraform.ResourceAttrDiff{
Old: "",
New: "${var.foo}",
},
},
},
"instances.#": &terraform.ResourceAttrDiff{
Err: false,
},
// #42 Set
{
Schema: map[string]*Schema{
"route": &Schema{
Type: TypeSet,
Optional: true,
Elem: &Resource{
Schema: map[string]*Schema{
"index": &Schema{
Type: TypeInt,
Required: true,
},
"gateway": &Schema{
Type: TypeSet,
Optional: true,
Elem: &Schema{Type: TypeInt},
Set: func(a interface{}) int {
return a.(int)
},
},
},
},
Set: func(v interface{}) int {
m := v.(map[string]interface{})
return m["index"].(int)
},
},
},
State: nil,
Config: map[string]interface{}{
"route": []map[string]interface{}{
map[string]interface{}{
"index": "1",
"gateway": []interface{}{
"${var.foo}",
},
},
},
},
ConfigVariables: map[string]string{
"var.foo": config.UnknownVariableValue,
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"route.#": &terraform.ResourceAttrDiff{
Old: "0",
New: "1",
},
"route.~1.index": &terraform.ResourceAttrDiff{
Old: "",
New: "1",
},
"route.~1.gateway.#": &terraform.ResourceAttrDiff{
Old: "",
NewComputed: true,
},
},
@ -1522,12 +1668,12 @@ func TestSchemaMap_Diff(t *testing.T) {
for i, tc := range cases {
c, err := config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("err: %s", err)
t.Fatalf("#%d err: %s", i, err)
}
if len(tc.ConfigVariables) > 0 {
if err := c.Interpolate(tc.ConfigVariables); err != nil {
t.Fatalf("err: %s", err)
t.Fatalf("#%d err: %s", i, err)
}
}

View File

@ -5,6 +5,7 @@ import (
"bytes"
"fmt"
"reflect"
"regexp"
"sort"
"strings"
)
@ -348,7 +349,7 @@ func (d *InstanceDiff) RequiresNew() bool {
return false
}
// Same checks whether or not to InstanceDiff are the "same." When
// Same checks whether or not two InstanceDiff's are the "same". When
// we say "same", it is not necessarily exactly equal. Instead, it is
// just checking that the same attributes are changing, a destroy
// isn't suddenly happening, etc.
@ -376,7 +377,18 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) bool {
for k, _ := range d2.Attributes {
checkNew[k] = struct{}{}
}
for k, diffOld := range d.Attributes {
// Make an ordered list so we are sure the approximated hashes are left
// to process at the end of the loop
keys := make([]string, 0, len(d.Attributes))
for k, _ := range d.Attributes {
keys = append(keys, k)
}
sort.StringSlice(keys).Sort()
for _, k := range keys {
diffOld := d.Attributes[k]
if _, ok := checkOld[k]; !ok {
// We're not checking this key for whatever reason (see where
// check is modified).
@ -389,14 +401,52 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) bool {
_, ok := d2.Attributes[k]
if !ok {
// The matching attribute was not found, we're different
// No exact match, but maybe this is a set containing computed
// values. So check if there is an approximate hash in the key
// and if so, try to match the key.
if strings.Contains(k, "~") {
// TODO (SvH): There should be a better way to do this...
parts := strings.Split(k, ".")
parts2 := strings.Split(k, ".")
re := regexp.MustCompile(`^~\d+$`)
for i, part := range parts {
if re.MatchString(part) {
parts2[i] = `\d+`
}
}
re, err := regexp.Compile("^" + strings.Join(parts2, `\.`) + "$")
if err != nil {
return false
}
for k2, _ := range checkNew {
if re.MatchString(k2) {
delete(checkNew, k2)
if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
// This is a computed list or set, so remove any keys with this
// prefix from the check list.
prefix := k2[:len(k2)-1]
for k2, _ := range checkNew {
if strings.HasPrefix(k2, prefix) {
delete(checkNew, k2)
}
}
}
ok = true
break
}
}
}
if !ok {
return false
}
}
if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
// This is a computed list, so remove any keys with this
// This is a computed list or set, so remove any keys with this
// prefix from the check list.
kprefix := k[0:len(k)-2] + "."
kprefix := k[:len(k)-1]
for k2, _ := range checkOld {
if strings.HasPrefix(k2, kprefix) {
delete(checkOld, k2)

View File

@ -464,6 +464,34 @@ func TestInstanceDiffSame(t *testing.T) {
},
true,
},
{
&InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"foo.#": &ResourceAttrDiff{
Old: "0",
New: "1",
},
"foo.~35964334.bar": &ResourceAttrDiff{
Old: "",
New: "${var.foo}",
},
},
},
&InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"foo.#": &ResourceAttrDiff{
Old: "0",
New: "1",
},
"foo.87654323.bar": &ResourceAttrDiff{
Old: "",
New: "12",
},
},
},
true,
},
}
for i, tc := range cases {

View File

@ -1,8 +1,6 @@
package terraform
import (
"sync"
)
import "sync"
// MockResourceProvider implements ResourceProvider but mocks out all the
// calls for testing purposes.