Skip to content

Commit

Permalink
core: fix diff mismatch when RequiresNew field and list both change
Browse files Browse the repository at this point in the history
fixes hashicorp#1752

Includes AccTest reproducing example from the issue as well as a bunch
of explanatory comments in the tests and impls.
  • Loading branch information
phinze authored and bigkraig committed Mar 1, 2016
1 parent a5468a8 commit cc890c1
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 0 deletions.
85 changes: 85 additions & 0 deletions builtin/providers/aws/resource_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,41 @@ func TestAccAWSInstance_rootBlockDeviceMismatch(t *testing.T) {
})
}

// This test reproduces the bug here:
// https://github.com/hashicorp/terraform/issues/1752
//
// I wish there were a way to exercise resources built with helper.Schema in a
// unit context, in which case this test could be moved there, but for now this
// will cover the bugfix.
//
// The following triggers "diffs didn't match during apply" without the fix in to
// set NewRemoved on the .# field when it changes to 0.
func TestAccAWSInstance_forceNewAndTagsDrift(t *testing.T) {
var v ec2.Instance

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccInstanceConfigForceNewAndTagsDrift,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
driftTags(&v),
),
ExpectNonEmptyPlan: true,
},
resource.TestStep{
Config: testAccInstanceConfigForceNewAndTagsDrift_Update,
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("aws_instance.foo", &v),
),
},
},
})
}

func testAccCheckInstanceDestroy(s *terraform.State) error {
return testAccCheckInstanceDestroyWithProvider(s, testAccProvider)
}
Expand Down Expand Up @@ -622,6 +657,22 @@ func TestInstanceTenancySchema(t *testing.T) {
}
}

func driftTags(instance *ec2.Instance) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
_, err := conn.CreateTags(&ec2.CreateTagsInput{
Resources: []*string{instance.InstanceId},
Tags: []*ec2.Tag{
&ec2.Tag{
Key: aws.String("Drift"),
Value: aws.String("Happens"),
},
},
})
return err
}
}

const testAccInstanceConfig_pre = `
resource "aws_security_group" "tf_test_foo" {
name = "tf_test_foo"
Expand Down Expand Up @@ -988,3 +1039,37 @@ resource "aws_instance" "foo" {
}
}
`

const testAccInstanceConfigForceNewAndTagsDrift = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "foo" {
cidr_block = "10.1.1.0/24"
vpc_id = "${aws_vpc.foo.id}"
}
resource "aws_instance" "foo" {
ami = "ami-22b9a343"
instance_type = "t2.nano"
subnet_id = "${aws_subnet.foo.id}"
}
`

const testAccInstanceConfigForceNewAndTagsDrift_Update = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "foo" {
cidr_block = "10.1.1.0/24"
vpc_id = "${aws_vpc.foo.id}"
}
resource "aws_instance" "foo" {
ami = "ami-22b9a343"
instance_type = "t2.micro"
subnet_id = "${aws_subnet.foo.id}"
}
`
7 changes: 7 additions & 0 deletions terraform/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,13 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
ok = true
}

// Similarly, in a RequiresNew scenario, a list that shows up in the plan
// diff can disappear from the apply diff, which is calculated from an
// empty state.
if d.RequiresNew() && strings.HasSuffix(k, ".#") {
ok = true
}

if !ok {
return false, fmt.Sprintf("attribute mismatch: %s", k)
}
Expand Down
37 changes: 37 additions & 0 deletions terraform/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,43 @@ func TestInstanceDiffSame(t *testing.T) {
true,
"",
},

// Another thing that can occur in DESTROY/CREATE scenarios is that list
// values that are going to zero have diffs that show up at plan time but
// are gone at apply time. The NewRemoved handling catches the fields and
// treats them as OK, but it also needs to treat the .# field itself as
// okay to be present in the old diff but not in the new one.
{
&InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"reqnew": &ResourceAttrDiff{
Old: "old",
New: "new",
RequiresNew: true,
},
"somemap.#": &ResourceAttrDiff{
Old: "1",
New: "0",
},
"somemap.oldkey": &ResourceAttrDiff{
Old: "long ago",
New: "",
NewRemoved: true,
},
},
},
&InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"reqnew": &ResourceAttrDiff{
Old: "",
New: "new",
RequiresNew: true,
},
},
},
true,
"",
},
}

for i, tc := range cases {
Expand Down

0 comments on commit cc890c1

Please sign in to comment.