Skip to content

Commit

Permalink
Merge pull request #1086 from hashicorp/b-pph
Browse files Browse the repository at this point in the history
terraform: catch scenario where both "foo" and "foo.0" are in state
  • Loading branch information
mitchellh committed Mar 2, 2015
2 parents 58cc129 + 11d073f commit 4894080
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 7 deletions.
123 changes: 123 additions & 0 deletions terraform/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,64 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) {
}
}

// https://github.com/PeoplePerHour/terraform/pull/11
//
// This tests a case where both a "resource" and "resource.0" are in
// the state file, which apparently is a reasonable backwards compatibility
// concern found in the above 3rd party repo.
func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) {
m := testModule(t, "plan-count-inc")
p := testProvider("aws")
p.DiffFn = testDiffFn
s := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "foo",
"type": "aws_instance",
},
},
},
"aws_instance.foo.0": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "foo",
"type": "aws_instance",
},
},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
State: s,
})

plan, err := ctx.Plan(nil)
if err != nil {
t.Fatalf("err: %s", err)
}

actual := strings.TrimSpace(plan.String())
expected := strings.TrimSpace(testTerraformPlanCountIncreaseFromOneCorruptedStr)
if actual != expected {
t.Fatalf("bad:\n%s", actual)
}
}

func TestContext2Plan_destroy(t *testing.T) {
m := testModule(t, "plan-destroy")
p := testProvider("aws")
Expand Down Expand Up @@ -3097,6 +3155,7 @@ func TestContext2Apply_countDecrease(t *testing.T) {
func TestContext2Apply_countDecreaseToOne(t *testing.T) {
m := testModule(t, "apply-count-dec-one")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
s := &State{
Modules: []*ModuleState{
Expand Down Expand Up @@ -3153,6 +3212,70 @@ func TestContext2Apply_countDecreaseToOne(t *testing.T) {
}
}

// https://github.com/PeoplePerHour/terraform/pull/11
//
// This tests a case where both a "resource" and "resource.0" are in
// the state file, which apparently is a reasonable backwards compatibility
// concern found in the above 3rd party repo.
func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) {
m := testModule(t, "apply-count-dec-one")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
s := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "bar",
Attributes: map[string]string{
"foo": "foo",
"type": "aws_instance",
},
},
},
"aws_instance.foo.0": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "baz",
Attributes: map[string]string{
"type": "aws_instance",
},
},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
State: s,
})

if p, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
} else {
testStringMatch(t, p, testTerraformApplyCountDecToOneCorruptedPlanStr)
}

state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}

actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}

func TestContext2Apply_countTainted(t *testing.T) {
m := testModule(t, "apply-count-tainted")
p := testProvider("aws")
Expand Down
14 changes: 11 additions & 3 deletions terraform/eval_count.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,18 @@ func (n *EvalCountFixZeroOneBoundary) Eval(ctx EvalContext) (interface{}, error)
}

// Look for the resource state. If we don't have one, then it is okay.
if rs, ok := mod.Resources[hunt]; ok {
mod.Resources[replace] = rs
delete(mod.Resources, hunt)
rs, ok := mod.Resources[hunt]
if !ok {
return nil, nil
}

// If the replacement key exists, we just keep both
if _, ok := mod.Resources[replace]; ok {
return nil, nil
}

mod.Resources[replace] = rs
delete(mod.Resources, hunt)

return nil, nil
}
8 changes: 8 additions & 0 deletions terraform/graph_config_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,14 @@ func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState)
return true
}
}

// If we're in the state as _both_ "foo" and "foo.0", then
// keep it, since we treat the latter as an orphan.
_, okOne := s.Resources[prefix]
_, okTwo := s.Resources[prefix+".0"]
if okOne && okTwo {
return true
}
}

return false
Expand Down
59 changes: 59 additions & 0 deletions terraform/terraform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"crypto/sha1"
"encoding/gob"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"testing"

Expand Down Expand Up @@ -68,6 +70,14 @@ func testModule(t *testing.T, name string) *module.Tree {
return mod
}

func testStringMatch(t *testing.T, s fmt.Stringer, expected string) {
actual := strings.TrimSpace(s.String())
expected = strings.TrimSpace(expected)
if actual != expected {
t.Fatalf("Actual\n\n%s\n\nExpected:\n\n%s", actual, expected)
}
}

func testProviderFuncFixed(rp ResourceProvider) ResourceProviderFactory {
return func() (ResourceProvider, error) {
return rp, nil
Expand Down Expand Up @@ -246,6 +256,29 @@ aws_instance.foo:
type = aws_instance
`

const testTerraformApplyCountDecToOneCorruptedStr = `
aws_instance.foo:
ID = bar
foo = foo
type = aws_instance
`

const testTerraformApplyCountDecToOneCorruptedPlanStr = `
DIFF:
DESTROY: aws_instance.foo.0
STATE:
aws_instance.foo:
ID = bar
foo = foo
type = aws_instance
aws_instance.foo.0:
ID = baz
type = aws_instance
`

const testTerraformApplyCountTaintedStr = `
<no state>
`
Expand Down Expand Up @@ -796,6 +829,32 @@ aws_instance.foo.0:
type = aws_instance
`

const testTerraformPlanCountIncreaseFromOneCorruptedStr = `
DIFF:
CREATE: aws_instance.bar
foo: "" => "bar"
type: "" => "aws_instance"
DESTROY: aws_instance.foo
CREATE: aws_instance.foo.1
foo: "" => "foo"
type: "" => "aws_instance"
CREATE: aws_instance.foo.2
foo: "" => "foo"
type: "" => "aws_instance"
STATE:
aws_instance.foo:
ID = bar
foo = foo
type = aws_instance
aws_instance.foo.0:
ID = bar
foo = foo
type = aws_instance
`

const testTerraformPlanDestroyStr = `
DIFF:
Expand Down
4 changes: 0 additions & 4 deletions terraform/test-fixtures/apply-count-dec-one/main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
resource "aws_instance" "foo" {
foo = "foo"
}

resource "aws_instance" "bar" {
foo = "bar"
}

0 comments on commit 4894080

Please sign in to comment.