diff --git a/Makefile b/Makefile index 8dbb67c01..4ab79833c 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index 4d5e32024..dfd6f604a 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -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 } diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index 12669c0c2..fde9f31ad 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -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"), ), }, @@ -205,7 +205,7 @@ resource "aws_autoscaling_group" "bar" { desired_capacity = 4 force_delete = true termination_policies = ["OldestInstance"] - + launch_configuration = "${aws_launch_configuration.foobar.name}" } ` diff --git a/builtin/providers/aws/resource_aws_db_parameter_group_test.go b/builtin/providers/aws/resource_aws_db_parameter_group_test.go index bf730e156..f8d57539e 100644 --- a/builtin/providers/aws/resource_aws_db_parameter_group_test.go +++ b/builtin/providers/aws/resource_aws_db_parameter_group_test.go @@ -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"), ), }, }, diff --git a/builtin/providers/aws/resource_aws_elb_test.go b/builtin/providers/aws/resource_aws_elb_test.go index ec78b22e8..ae65aa6cf 100644 --- a/builtin/providers/aws/resource_aws_elb_test.go +++ b/builtin/providers/aws/resource_aws_elb_test.go @@ -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 } ` diff --git a/builtin/providers/aws/resource_aws_network_acl_test.go b/builtin/providers/aws/resource_aws_network_acl_test.go index 56ca35fb7..a44eeeff8 100644 --- a/builtin/providers/aws/resource_aws_network_acl_test.go +++ b/builtin/providers/aws/resource_aws_network_acl_test.go @@ -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}" } diff --git a/builtin/providers/aws/resource_aws_security_group_test.go b/builtin/providers/aws/resource_aws_security_group_test.go index 8ee38f89e..1b8773b51 100644 --- a/builtin/providers/aws/resource_aws_security_group_test.go +++ b/builtin/providers/aws/resource_aws_security_group_test.go @@ -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, ), }, @@ -355,124 +355,144 @@ func testAccCheckAWSSecurityGroupAttributesChanged(group *ec2.SecurityGroupInfo) const testAccAWSSecurityGroupConfig = ` resource "aws_security_group" "web" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" + 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"] - } + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } } ` const testAccAWSSecurityGroupConfigChange = ` resource "aws_security_group" "web" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" - ingress { - protocol = "tcp" - from_port = 80 - to_port = 9000 - cidr_blocks = ["10.0.0.0/8"] - } + ingress { + protocol = "tcp" + from_port = 80 + to_port = 9000 + cidr_blocks = ["10.0.0.0/8"] + } - ingress { - protocol = "tcp" - from_port = 80 - to_port = 8000 - cidr_blocks = ["10.0.0.0/8", "0.0.0.0/0"] - } + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["0.0.0.0/0", "10.0.0.0/8"] + } } ` const testAccAWSSecurityGroupConfigSelf = ` resource "aws_security_group" "web" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" - ingress { - protocol = "tcp" - from_port = 80 - to_port = 8000 - self = true - } + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + self = true + } } ` const testAccAWSSecurityGroupConfigVpc = ` resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" + cidr_block = "10.1.0.0/16" } resource "aws_security_group" "web" { - name = "terraform_acceptance_test_example" - description = "Used in the terraform acceptance tests" - vpc_id = "${aws_vpc.foo.id}" + name = "terraform_acceptance_test_example" + description = "Used in the terraform acceptance tests" + vpc_id = "${aws_vpc.foo.id}" - ingress { - protocol = "tcp" - from_port = 80 - to_port = 8000 - cidr_blocks = ["10.0.0.0/8"] - } + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } } ` const testAccAWSSecurityGroupConfigMultiIngress = ` resource "aws_security_group" "worker" { - name = "terraform_acceptance_test_example_1" - description = "Used in the terraform acceptance tests" + name = "terraform_acceptance_test_example_1" + description = "Used in the terraform acceptance tests" - ingress { - protocol = "tcp" - from_port = 80 - to_port = 8000 - cidr_blocks = ["10.0.0.0/8"] - } + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } } resource "aws_security_group" "web" { - name = "terraform_acceptance_test_example_2" - description = "Used in the terraform acceptance tests" + name = "terraform_acceptance_test_example_2" + description = "Used in the terraform acceptance tests" - ingress { - protocol = "tcp" - from_port = 22 - to_port = 22 - cidr_blocks = ["10.0.0.0/8"] - } + ingress { + protocol = "tcp" + from_port = 22 + to_port = 22 + cidr_blocks = ["10.0.0.0/8"] + } - ingress { - protocol = "tcp" - from_port = 800 - to_port = 800 - cidr_blocks = ["10.0.0.0/8"] - } + ingress { + protocol = "tcp" + from_port = 800 + to_port = 800 + cidr_blocks = ["10.0.0.0/8"] + } - ingress { - protocol = "tcp" - from_port = 80 - to_port = 8000 - security_groups = ["${aws_security_group.worker.id}"] - } + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + security_groups = ["${aws_security_group.worker.id}"] + } } ` const testAccAWSSecurityGroupConfigTags = ` resource "aws_security_group" "foo" { - tags { - foo = "bar" - } + 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" + } } ` const testAccAWSSecurityGroupConfigTagsUpdate = ` resource "aws_security_group" "foo" { - tags { - bar = "baz" - } + 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" + } } ` diff --git a/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go b/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go index 865f49507..f2fa8a78b 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go @@ -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 { diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go index 620065363..5b71ec5de 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go @@ -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 { diff --git a/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go b/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go index 4d65df303..624baab09 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go @@ -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 } diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 23be6f290..0521bd2a8 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -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 { + 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{}) + 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) + + // 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 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 { + panic(fmt.Sprintf("unable to convert %s to int: %v", idx, err)) + } + codes[idx] = code + } + } + + if d.setMap != nil && setSource == getSourceSet { + for k, _ := range d.setMap { + 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 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 !raw.Exists { - if len(parts) > 0 { - return d.getList(k, parts, schema, source) + + 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 + } } - - 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{}) - 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) - } + // We still have parts left over meaning we're accessing an + // element of this set. + idx := parts[0] + parts = parts[1:] - // Build the set from all the items using the given hash code - for i, v := range list { - code := s.add(v) - if indexMap != nil { - indexMap[code] = i + // 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) } } - // 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 err != nil { - return getResultEmpty - } - - codes := s.listCode() - if int(indexInt) >= len(codes) { - return getResultEmpty - } - code := codes[indexInt] - realIndex := indexMap[code] - - parts[0] = strconv.FormatInt(int64(realIndex), 10) - return d.getList(k, parts, schema, source) - } - - result.Exists = true 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 { diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index 751555964..e66a681e8 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -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{ @@ -516,8 +528,8 @@ func TestResourceDataGet(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "ports.#": "1", - "ports.0": "80", + "ports.#": "1", + "ports.80": "80", }, }, @@ -528,6 +540,7 @@ func TestResourceDataGet(t *testing.T) { Value: []interface{}{80}, }, + // #18 { Schema: map[string]*Schema{ "data": &Schema{ @@ -555,15 +568,15 @@ func TestResourceDataGet(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "data.#": "1", - "data.0.index": "10", - "data.0.value": "50", + "data.#": "1", + "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", }, @@ -1397,10 +1410,10 @@ func TestResourceDataSet(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "ports.#": "3", - "ports.0": "100", - "ports.1": "80", - "ports.2": "80", + "ports.#": "3", + "ports.100": "100", + "ports.80": "80", + "ports.81": "81", }, }, @@ -1432,13 +1445,13 @@ func TestResourceDataSet(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "ports.#": "2", - "ports.0": "100", - "ports.1": "80", + "ports.#": "2", + "ports.100": "100", + "ports.80": "80", }, }, - Key: "ports.0", + Key: "ports.100", Value: 256, Err: true, @@ -1821,10 +1834,10 @@ func TestResourceDataState(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "ports.#": "3", - "ports.0": "100", - "ports.1": "80", - "ports.2": "80", + "ports.#": "3", + "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", }, }, }, @@ -1862,9 +1876,9 @@ func TestResourceDataState(t *testing.T) { Result: &terraform.InstanceState{ Attributes: map[string]string{ - "ports.#": "2", - "ports.0": "80", - "ports.1": "100", + "ports.#": "2", + "ports.80": "80", + "ports.100": "100", }, }, }, @@ -1901,13 +1915,13 @@ 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.#": "2", + "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", }, }, @@ -1926,13 +1940,13 @@ 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.#": "2", + "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", }, }, }, @@ -2160,16 +2174,16 @@ func TestResourceDataState(t *testing.T) { State: &terraform.InstanceState{ Attributes: map[string]string{ - "ports.#": "3", - "ports.0": "100", - "ports.1": "80", - "ports.2": "80", + "ports.#": "3", + "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", }, }, }, diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 336292227..4c20484fd 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -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 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 + } + + // 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 } - // If we had no changes, then we're done - if tempD.Empty() { - 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) } } - // 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) + return nil } func (m schemaMap) diffString( diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 175b16127..9aba38316 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -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{ @@ -979,16 +996,16 @@ 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.#": "2", + "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{ - Old: "0", + 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) } } diff --git a/terraform/diff.go b/terraform/diff.go index c5e821cdf..7a2734c32 100644 --- a/terraform/diff.go +++ b/terraform/diff.go @@ -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 - return false + // 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) diff --git a/terraform/diff_test.go b/terraform/diff_test.go index 47e78c5e8..bd29aab55 100644 --- a/terraform/diff_test.go +++ b/terraform/diff_test.go @@ -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 { diff --git a/terraform/resource_provider_mock.go b/terraform/resource_provider_mock.go index 6a1c29b6d..fa2bd24c6 100644 --- a/terraform/resource_provider_mock.go +++ b/terraform/resource_provider_mock.go @@ -1,8 +1,6 @@ package terraform -import ( - "sync" -) +import "sync" // MockResourceProvider implements ResourceProvider but mocks out all the // calls for testing purposes.