Add tests and fix last issues

This commit is contained in:
Sander van Harmelen 2016-04-21 21:59:10 +02:00 committed by Paul Hinze
parent 8560f50cbc
commit d97b24e3c1
No known key found for this signature in database
GPG Key ID: B69DEDF2D55501C0
22 changed files with 229 additions and 511 deletions

View File

@ -1361,16 +1361,20 @@ const applyVarFileJSON = `
const testApplyDisableBackupStr = ` const testApplyDisableBackupStr = `
ID = bar ID = bar
Tainted = false
` `
const testApplyDisableBackupStateStr = ` const testApplyDisableBackupStateStr = `
ID = bar ID = bar
Tainted = false
` `
const testApplyStateStr = ` const testApplyStateStr = `
ID = bar ID = bar
Tainted = false
` `
const testApplyStateDiffStr = ` const testApplyStateDiffStr = `
ID = bar ID = bar
Tainted = false
` `

View File

@ -603,8 +603,10 @@ const testPlanNoStateStr = `
const testPlanStateStr = ` const testPlanStateStr = `
ID = bar ID = bar
Tainted = false
` `
const testPlanStateDefaultStr = ` const testPlanStateDefaultStr = `
ID = bar ID = bar
Tainted = false
` `

View File

@ -347,9 +347,8 @@ func TestTaint_module(t *testing.T) {
} }
const testTaintStr = ` const testTaintStr = `
test_instance.foo: (1 tainted) test_instance.foo: (tainted)
ID = <not created> ID = bar
Tainted ID 1 = bar
` `
const testTaintDefaultStr = ` const testTaintDefaultStr = `
@ -362,7 +361,6 @@ test_instance.foo:
ID = bar ID = bar
module.child: module.child:
test_instance.blah: (1 tainted) test_instance.blah: (tainted)
ID = <not created> ID = blah
Tainted ID 1 = blah
` `

View File

@ -17,8 +17,9 @@ func TestUntaint(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -49,101 +50,6 @@ test_instance.foo:
testStateOutput(t, statePath, expected) testStateOutput(t, statePath, expected)
} }
func TestUntaint_indexRequired(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
Type: "test_instance",
Tainted: []*terraform.InstanceState{
&terraform.InstanceState{ID: "bar"},
&terraform.InstanceState{ID: "bar2"},
},
},
},
},
},
}
statePath := testStateFile(t, state)
ui := new(cli.MockUi)
c := &UntaintCommand{
Meta: Meta{
Ui: ui,
},
}
args := []string{
"-state", statePath,
"test_instance.foo",
}
if code := c.Run(args); code == 0 {
t.Fatalf("Expected non-zero exit. Output:\n\n%s", ui.OutputWriter.String())
}
// Nothing should have gotten untainted
expected := strings.TrimSpace(`
test_instance.foo: (2 tainted)
ID = <not created>
Tainted ID 1 = bar
Tainted ID 2 = bar2
`)
testStateOutput(t, statePath, expected)
// Should have gotten an error message mentioning index
errOut := ui.ErrorWriter.String()
errContains := "please specify an index"
if !strings.Contains(errOut, errContains) {
t.Fatalf("Expected err output: %s, to contain: %s", errOut, errContains)
}
}
func TestUntaint_indexSpecified(t *testing.T) {
state := &terraform.State{
Modules: []*terraform.ModuleState{
&terraform.ModuleState{
Path: []string{"root"},
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
Type: "test_instance",
Tainted: []*terraform.InstanceState{
&terraform.InstanceState{ID: "bar"},
&terraform.InstanceState{ID: "bar2"},
},
},
},
},
},
}
statePath := testStateFile(t, state)
ui := new(cli.MockUi)
c := &UntaintCommand{
Meta: Meta{
Ui: ui,
},
}
args := []string{
"-state", statePath,
"-index", "1",
"test_instance.foo",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Nothing should have gotten untainted
expected := strings.TrimSpace(`
test_instance.foo: (1 tainted)
ID = bar2
Tainted ID 1 = bar
`)
testStateOutput(t, statePath, expected)
}
func TestUntaint_backup(t *testing.T) { func TestUntaint_backup(t *testing.T) {
// Get a temp cwd // Get a temp cwd
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
@ -157,8 +63,9 @@ func TestUntaint_backup(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -183,9 +90,8 @@ func TestUntaint_backup(t *testing.T) {
// Backup is still tainted // Backup is still tainted
testStateOutput(t, path+".backup", strings.TrimSpace(` testStateOutput(t, path+".backup", strings.TrimSpace(`
test_instance.foo: (1 tainted) test_instance.foo: (tainted)
ID = <not created> ID = bar
Tainted ID 1 = bar
`)) `))
// State is untainted // State is untainted
@ -208,8 +114,9 @@ func TestUntaint_backupDisable(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -273,8 +180,9 @@ func TestUntaint_defaultState(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -311,8 +219,9 @@ func TestUntaint_missing(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -345,8 +254,9 @@ func TestUntaint_missingAllow(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -385,8 +295,9 @@ func TestUntaint_stateOut(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -411,9 +322,8 @@ func TestUntaint_stateOut(t *testing.T) {
} }
testStateOutput(t, path, strings.TrimSpace(` testStateOutput(t, path, strings.TrimSpace(`
test_instance.foo: (1 tainted) test_instance.foo: (tainted)
ID = <not created> ID = bar
Tainted ID 1 = bar
`)) `))
testStateOutput(t, "foo", strings.TrimSpace(` testStateOutput(t, "foo", strings.TrimSpace(`
test_instance.foo: test_instance.foo:
@ -429,8 +339,9 @@ func TestUntaint_module(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -440,8 +351,9 @@ func TestUntaint_module(t *testing.T) {
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"test_instance.blah": &terraform.ResourceState{ "test_instance.blah": &terraform.ResourceState{
Type: "test_instance", Type: "test_instance",
Tainted: []*terraform.InstanceState{ Primary: &terraform.InstanceState{
&terraform.InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
}, },
@ -467,9 +379,8 @@ func TestUntaint_module(t *testing.T) {
} }
testStateOutput(t, statePath, strings.TrimSpace(` testStateOutput(t, statePath, strings.TrimSpace(`
test_instance.foo: (1 tainted) test_instance.foo: (tainted)
ID = <not created> ID = bar
Tainted ID 1 = bar
module.child: module.child:
test_instance.blah: test_instance.blah:

View File

@ -884,14 +884,13 @@ func TestContext2Apply_countTainted(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.foo.0": &ResourceState{ "aws_instance.foo.0": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "bar",
ID: "bar", Attributes: map[string]string{
Attributes: map[string]string{ "foo": "foo",
"foo": "foo", "type": "aws_instance",
"type": "aws_instance",
},
}, },
Tainted: true,
}, },
}, },
}, },
@ -2989,13 +2988,12 @@ func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{ "aws_instance.foo": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "bar",
ID: "bar", Attributes: map[string]string{
Attributes: map[string]string{ "id": "bar",
"id": "bar",
},
}, },
Tainted: true,
}, },
}, },
}, },
@ -3505,14 +3503,13 @@ func TestContext2Apply_taint(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "baz",
ID: "baz", Attributes: map[string]string{
Attributes: map[string]string{ "num": "2",
"num": "2", "type": "aws_instance",
"type": "aws_instance",
},
}, },
Tainted: true,
}, },
}, },
}, },
@ -3559,14 +3556,13 @@ func TestContext2Apply_taintDep(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{ "aws_instance.foo": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "baz",
ID: "baz", Attributes: map[string]string{
Attributes: map[string]string{ "num": "2",
"num": "2", "type": "aws_instance",
"type": "aws_instance",
},
}, },
Tainted: true,
}, },
}, },
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
@ -3622,14 +3618,13 @@ func TestContext2Apply_taintDepRequiresNew(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{ "aws_instance.foo": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "baz",
ID: "baz", Attributes: map[string]string{
Attributes: map[string]string{ "num": "2",
"num": "2", "type": "aws_instance",
"type": "aws_instance",
},
}, },
Tainted: true,
}, },
}, },
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
@ -4438,10 +4433,9 @@ func TestContext2Apply_targetedWithTaintedInState(t *testing.T) {
Path: rootModulePath, Path: rootModulePath,
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.ifailedprovisioners": &ResourceState{ "aws_instance.ifailedprovisioners": &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "ifailedprovisioners",
ID: "ifailedprovisioners", Tainted: true,
},
}, },
}, },
}, },
@ -4485,9 +4479,8 @@ func TestContext2Apply_targetedWithTaintedInState(t *testing.T) {
expected := strings.TrimSpace(` expected := strings.TrimSpace(`
aws_instance.iambeingadded: aws_instance.iambeingadded:
ID = foo ID = foo
aws_instance.ifailedprovisioners: (1 tainted) aws_instance.ifailedprovisioners: (tainted)
ID = <not created> ID = ifailedprovisioners
Tainted ID 1 = ifailedprovisioners
`) `)
if actual != expected { if actual != expected {
t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual) t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual)

View File

@ -1718,10 +1718,9 @@ func TestContext2Plan_taint(t *testing.T) {
}, },
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "baz",
ID: "baz", Tainted: true,
},
}, },
}, },
}, },
@ -1748,57 +1747,6 @@ func TestContext2Plan_taint(t *testing.T) {
} }
} }
func TestContext2Plan_multiple_taint(t *testing.T) {
m := testModule(t, "plan-taint")
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{"num": "2"},
},
},
"aws_instance.bar": &ResourceState{
Type: "aws_instance",
Tainted: []*InstanceState{
&InstanceState{
ID: "baz",
},
&InstanceState{
ID: "zip",
},
},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
State: s,
})
plan, err := ctx.Plan()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(plan.String())
expected := strings.TrimSpace(testTerraformPlanMultipleTaintStr)
if actual != expected {
t.Fatalf("bad:\n%s", actual)
}
}
// Fails about 50% of the time before the fix for GH-4982, covers the fix. // Fails about 50% of the time before the fix for GH-4982, covers the fix.
func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) {
m := testModule(t, "plan-taint-interpolated-count") m := testModule(t, "plan-taint-interpolated-count")
@ -1811,8 +1759,9 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.foo.0": &ResourceState{ "aws_instance.foo.0": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ID: "bar"}, ID: "bar",
Tainted: true,
}, },
}, },
"aws_instance.foo.1": &ResourceState{ "aws_instance.foo.1": &ResourceState{
@ -1849,9 +1798,8 @@ DESTROY/CREATE: aws_instance.foo.0
STATE: STATE:
aws_instance.foo.0: (1 tainted) aws_instance.foo.0: (tainted)
ID = <not created> ID = bar
Tainted ID 1 = bar
aws_instance.foo.1: aws_instance.foo.1:
ID = bar ID = bar
aws_instance.foo.2: aws_instance.foo.2:

View File

@ -324,10 +324,9 @@ func TestContext2Refresh_modules(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{ "aws_instance.web": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "bar",
ID: "bar", Tainted: true,
},
}, },
}, },
}, },
@ -650,10 +649,9 @@ func TestContext2Refresh_tainted(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{ "aws_instance.web": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "bar",
ID: "bar", Tainted: true,
},
}, },
}, },
}, },
@ -670,7 +668,8 @@ func TestContext2Refresh_tainted(t *testing.T) {
p.RefreshFn = nil p.RefreshFn = nil
p.RefreshReturn = &InstanceState{ p.RefreshReturn = &InstanceState{
ID: "foo", ID: "foo",
Tainted: true,
} }
s, err := ctx.Refresh() s, err := ctx.Refresh()

View File

@ -107,9 +107,13 @@ func testDiffFn(
info *InstanceInfo, info *InstanceInfo,
s *InstanceState, s *InstanceState,
c *ResourceConfig) (*InstanceDiff, error) { c *ResourceConfig) (*InstanceDiff, error) {
var diff InstanceDiff diff := new(InstanceDiff)
diff.Attributes = make(map[string]*ResourceAttrDiff) diff.Attributes = make(map[string]*ResourceAttrDiff)
if s != nil {
diff.DestroyTainted = s.Tainted
}
for k, v := range c.Raw { for k, v := range c.Raw {
if _, ok := v.(string); !ok { if _, ok := v.(string); !ok {
continue continue
@ -181,17 +185,21 @@ func testDiffFn(
} }
} }
for k, v := range diff.Attributes { // If we recreate this resource because it's tainted, we keep all attrs
if v.NewComputed { if !diff.RequiresNew() {
continue for k, v := range diff.Attributes {
} if v.NewComputed {
continue
}
old, ok := s.Attributes[k] old, ok := s.Attributes[k]
if !ok { if !ok {
continue continue
} }
if old == v.New {
delete(diff.Attributes, k) if old == v.New {
delete(diff.Attributes, k)
}
} }
} }
@ -202,7 +210,7 @@ func testDiffFn(
} }
} }
return &diff, nil return diff, nil
} }
func testProvider(prefix string) *MockResourceProvider { func testProvider(prefix string) *MockResourceProvider {
@ -276,9 +284,8 @@ root
` `
const testContextRefreshModuleStr = ` const testContextRefreshModuleStr = `
aws_instance.web: (1 tainted) aws_instance.web: (tainted)
ID = <not created> ID = bar
Tainted ID 1 = bar
module.child: module.child:
aws_instance.web: aws_instance.web:
@ -300,7 +307,6 @@ const testContextRefreshOutputPartialStr = `
` `
const testContextRefreshTaintedStr = ` const testContextRefreshTaintedStr = `
aws_instance.web: (1 tainted) aws_instance.web: (tainted)
ID = <not created> ID = foo
Tainted ID 1 = foo
` `

View File

@ -651,10 +651,9 @@ func TestContext2Validate_tainted(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{ "aws_instance.foo": &ResourceState{
Type: "aws_instance", Type: "aws_instance",
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ ID: "bar",
ID: "bar", Tainted: true,
},
}, },
}, },
}, },

View File

@ -139,7 +139,6 @@ type EvalApplyProvisioners struct {
Resource *config.Resource Resource *config.Resource
InterpResource *Resource InterpResource *Resource
CreateNew *bool CreateNew *bool
Tainted *bool
Error *error Error *error
} }
@ -159,9 +158,7 @@ func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
if n.Error != nil && *n.Error != nil { if n.Error != nil && *n.Error != nil {
// We're already errored creating, so mark as tainted and continue // We're already errored creating, so mark as tainted and continue
if n.Tainted != nil { state.Tainted = true
*n.Tainted = true
}
// We're already tainted, so just return out // We're already tainted, so just return out
return nil, nil return nil, nil
@ -180,10 +177,10 @@ func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
// If there are no errors, then we append it to our output error // If there are no errors, then we append it to our output error
// if we have one, otherwise we just output it. // if we have one, otherwise we just output it.
err := n.apply(ctx) err := n.apply(ctx)
if n.Tainted != nil {
*n.Tainted = err != nil
}
if err != nil { if err != nil {
// Provisioning failed, so mark the resource as tainted
state.Tainted = true
if n.Error != nil { if n.Error != nil {
*n.Error = multierror.Append(*n.Error, err) *n.Error = multierror.Append(*n.Error, err)
} else { } else {

View File

@ -69,8 +69,9 @@ type EvalDiff struct {
Info *InstanceInfo Info *InstanceInfo
Config **ResourceConfig Config **ResourceConfig
Provider *ResourceProvider Provider *ResourceProvider
Diff **InstanceDiff
State **InstanceState State **InstanceState
Output **InstanceDiff OutputDiff **InstanceDiff
OutputState **InstanceState OutputState **InstanceState
} }
@ -104,6 +105,11 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
diff = new(InstanceDiff) diff = new(InstanceDiff)
} }
// Preserve the DestroyTainted flag
if n.Diff != nil {
diff.DestroyTainted = (*n.Diff).DestroyTainted
}
// Require a destroy if there is an ID and it requires new. // Require a destroy if there is an ID and it requires new.
if diff.RequiresNew() && state != nil && state.ID != "" { if diff.RequiresNew() && state != nil && state.ID != "" {
diff.Destroy = true diff.Destroy = true
@ -135,7 +141,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
} }
// Update our output // Update our output
*n.Output = diff *n.OutputDiff = diff
// Update the state if we care // Update the state if we care
if n.OutputState != nil { if n.OutputState != nil {

View File

@ -285,7 +285,8 @@ func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
// EvalUndeposeState is an EvalNode implementation that reads the // EvalUndeposeState is an EvalNode implementation that reads the
// InstanceState for a specific resource out of the state. // InstanceState for a specific resource out of the state.
type EvalUndeposeState struct { type EvalUndeposeState struct {
Name string Name string
State **InstanceState
} }
// TODO: test // TODO: test
@ -316,7 +317,7 @@ func (n *EvalUndeposeState) Eval(ctx EvalContext) (interface{}, error) {
// Undepose // Undepose
idx := len(rs.Deposed) - 1 idx := len(rs.Deposed) - 1
rs.Primary = rs.Deposed[idx] rs.Primary = rs.Deposed[idx]
rs.Deposed[idx] = nil rs.Deposed[idx] = *n.State
return nil, nil return nil, nil
} }

View File

@ -88,21 +88,6 @@ func TestEvalReadState(t *testing.T) {
}, },
ExpectedInstanceId: "i-abc123", ExpectedInstanceId: "i-abc123",
}, },
"ReadStateTainted gets tainted instance": {
Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{
Tainted: []*InstanceState{
&InstanceState{ID: "i-abc123"},
},
},
},
Node: &EvalReadStateTainted{
Name: "aws_instance.bar",
Output: &output,
Index: 0,
},
ExpectedInstanceId: "i-abc123",
},
"ReadStateDeposed gets deposed instance": { "ReadStateDeposed gets deposed instance": {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
@ -175,32 +160,6 @@ restype.resname:
`) `)
} }
func TestEvalWriteStateTainted(t *testing.T) {
state := &State{}
ctx := new(MockEvalContext)
ctx.StateState = state
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
is := &InstanceState{ID: "i-abc123"}
node := &EvalWriteStateTainted{
Name: "restype.resname",
ResourceType: "restype",
State: &is,
Index: -1,
}
_, err := node.Eval(ctx)
if err != nil {
t.Fatalf("Got err: %#v", err)
}
checkStateString(t, state, `
restype.resname: (1 tainted)
ID = <not created>
Tainted ID 1 = i-abc123
`)
}
func TestEvalWriteStateDeposed(t *testing.T) { func TestEvalWriteStateDeposed(t *testing.T) {
state := &State{} state := &State{}
ctx := new(MockEvalContext) ctx := new(MockEvalContext)

View File

@ -295,16 +295,11 @@ provider.aws (close)
const testBuiltinGraphBuilderVerboseStr = ` const testBuiltinGraphBuilderVerboseStr = `
aws_instance.db aws_instance.db
aws_instance.db (destroy tainted)
aws_instance.db (destroy) aws_instance.db (destroy)
aws_instance.db (destroy tainted)
aws_instance.web (destroy tainted)
aws_instance.db (destroy) aws_instance.db (destroy)
aws_instance.web (destroy) aws_instance.web (destroy)
aws_instance.web aws_instance.web
aws_instance.db aws_instance.db
aws_instance.web (destroy tainted)
provider.aws
aws_instance.web (destroy) aws_instance.web (destroy)
provider.aws provider.aws
provider.aws provider.aws

View File

@ -120,7 +120,11 @@ func (n *GraphNodeConfigResource) VarWalk(fn func(config.InterpolatedVariable))
} }
func (n *GraphNodeConfigResource) Name() string { func (n *GraphNodeConfigResource) Name() string {
return n.Resource.Id() + " (destroy)" result := n.Resource.Id()
if n.Destroy {
result += " (destroy)"
}
return result
} }
// GraphNodeDotter impl. // GraphNodeDotter impl.

View File

@ -42,7 +42,6 @@ type Resource struct {
State *InstanceState State *InstanceState
Provisioners []*ResourceProvisionerConfig Provisioners []*ResourceProvisionerConfig
Flags ResourceFlag Flags ResourceFlag
TaintedIndex int
} }
// ResourceKind specifies what kind of instance we're working with, whether // ResourceKind specifies what kind of instance we're working with, whether
@ -53,7 +52,6 @@ const (
FlagPrimary ResourceFlag = 1 << iota FlagPrimary ResourceFlag = 1 << iota
FlagTainted FlagTainted
FlagOrphan FlagOrphan
FlagHasTainted
FlagReplacePrimary FlagReplacePrimary
FlagDeposed FlagDeposed
) )

View File

@ -327,7 +327,7 @@ func (s *State) removeInstance(path []string, r *ResourceState, v *InstanceState
} }
// Check lists // Check lists
lists := [][]*InstanceState{r.Tainted, r.Deposed} lists := [][]*InstanceState{r.Deposed}
for _, is := range lists { for _, is := range lists {
for i, instance := range is { for i, instance := range is {
if instance == v { if instance == v {
@ -861,7 +861,11 @@ func (m *ModuleState) String() string {
} }
for idx, t := range rs.Deposed { for idx, t := range rs.Deposed {
buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s\n", idx+1, t.ID)) taintStr := ""
if t.Tainted {
taintStr = " (tainted)"
}
buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s%s\n", idx+1, t.ID, taintStr))
} }
if len(rs.Dependencies) > 0 { if len(rs.Dependencies) > 0 {
@ -1030,11 +1034,14 @@ type ResourceState struct {
// Deposed is used in the mechanics of CreateBeforeDestroy: the existing // Deposed is used in the mechanics of CreateBeforeDestroy: the existing
// Primary is Deposed to get it out of the way for the replacement Primary to // Primary is Deposed to get it out of the way for the replacement Primary to
// be created by Apply. If the replacement Primary creates successfully, the // be created by Apply. If the replacement Primary creates successfully, the
// Deposed instance is cleaned up. If there were problems creating the // Deposed instance is cleaned up.
// replacement, the instance remains in the Deposed list so it can be //
// destroyed in a future run. Functionally, Deposed instances are very // If there were problems creating the replacement Primary, the Deposed
// similar to Tainted instances in that Terraform is only tracking them in // instance and the (now tainted) replacement Primary will be swapped so the
// order to remember to destroy them. // tainted replacement will be cleaned up instead.
//
// An instance will remain in the Deposed list until it is successfully
// destroyed and purged.
Deposed []*InstanceState `json:"deposed,omitempty"` Deposed []*InstanceState `json:"deposed,omitempty"`
// Provider is used when a resource is connected to a provider with an alias. // Provider is used when a resource is connected to a provider with an alias.
@ -1071,22 +1078,21 @@ func (s *ResourceState) Equal(other *ResourceState) bool {
return false return false
} }
// Tainted
if s.Primary.Tainted != other.Primary.Tainted {
return false
}
return true return true
} }
// Taint marks a resource as tainted. // Taint marks a resource as tainted.
func (r *ResourceState) Taint() { func (r *ResourceState) Taint() {
r.Primary.Tainted = true if r.Primary != nil {
r.Primary.Tainted = true
}
} }
// Untaint unmarks a resource as tainted. // Untaint unmarks a resource as tainted.
func (r *ResourceState) Untaint() { func (r *ResourceState) Untaint() {
r.Primary.Tainted = false if r.Primary != nil {
r.Primary.Tainted = false
}
} }
func (r *ResourceState) init() { func (r *ResourceState) init() {

View File

@ -133,19 +133,6 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
}) })
} }
for _, instance := range r.Tainted {
if f.relevant(a, instance) {
addr.InstanceType = TypeTainted
addr.InstanceTypeSet = true
results = append(results, &StateFilterResult{
Path: addr.Path,
Address: addr.String(),
Parent: resourceResult,
Value: instance,
})
}
}
for _, instance := range r.Deposed { for _, instance := range r.Deposed {
if f.relevant(a, instance) { if f.relevant(a, instance) {
addr.InstanceType = TypeDeposed addr.InstanceType = TypeDeposed

View File

@ -742,11 +742,14 @@ func TestResourceStateEqual(t *testing.T) {
{ {
false, false,
&ResourceState{ &ResourceState{
Tainted: nil, Primary: &InstanceState{
ID: "foo",
},
}, },
&ResourceState{ &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ID: "foo"}, ID: "foo",
Tainted: true,
}, },
}, },
}, },
@ -754,28 +757,15 @@ func TestResourceStateEqual(t *testing.T) {
{ {
true, true,
&ResourceState{ &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ID: "foo"}, ID: "foo",
Tainted: true,
}, },
}, },
&ResourceState{ &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ID: "foo"}, ID: "foo",
}, Tainted: true,
},
},
{
true,
&ResourceState{
Tainted: []*InstanceState{
&InstanceState{ID: "foo"},
nil,
},
},
&ResourceState{
Tainted: []*InstanceState{
&InstanceState{ID: "foo"},
}, },
}, },
}, },
@ -801,28 +791,29 @@ func TestResourceStateTaint(t *testing.T) {
&ResourceState{}, &ResourceState{},
}, },
"primary, no tainted": { "primary, not tainted": {
&ResourceState{ &ResourceState{
Primary: &InstanceState{ID: "foo"}, Primary: &InstanceState{ID: "foo"},
}, },
&ResourceState{ &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ID: "foo"}, ID: "foo",
Tainted: true,
}, },
}, },
}, },
"primary, with tainted": { "primary, tainted": {
&ResourceState{ &ResourceState{
Primary: &InstanceState{ID: "foo"}, Primary: &InstanceState{
Tainted: []*InstanceState{ ID: "foo",
&InstanceState{ID: "bar"}, Tainted: true,
}, },
}, },
&ResourceState{ &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ID: "bar"}, ID: "foo",
&InstanceState{ID: "foo"}, Tainted: true,
}, },
}, },
}, },
@ -841,92 +832,36 @@ func TestResourceStateTaint(t *testing.T) {
func TestResourceStateUntaint(t *testing.T) { func TestResourceStateUntaint(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Input *ResourceState Input *ResourceState
Index func() int
ExpectedOutput *ResourceState ExpectedOutput *ResourceState
ExpectedErrMsg string
}{ }{
"no primary, no tainted, err": { "no primary, err": {
Input: &ResourceState{}, Input: &ResourceState{},
ExpectedOutput: &ResourceState{}, ExpectedOutput: &ResourceState{},
ExpectedErrMsg: "Nothing to untaint",
}, },
"one tainted, no primary": { "primary, not tainted": {
Input: &ResourceState{ Input: &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{ID: "foo"},
&InstanceState{ID: "foo"}, },
ExpectedOutput: &ResourceState{
Primary: &InstanceState{ID: "foo"},
},
},
"primary, tainted": {
Input: &ResourceState{
Primary: &InstanceState{
ID: "foo",
Tainted: true,
}, },
}, },
ExpectedOutput: &ResourceState{ ExpectedOutput: &ResourceState{
Primary: &InstanceState{ID: "foo"}, Primary: &InstanceState{ID: "foo"},
Tainted: []*InstanceState{},
}, },
}, },
"one tainted, existing primary error": {
Input: &ResourceState{
Primary: &InstanceState{ID: "foo"},
Tainted: []*InstanceState{
&InstanceState{ID: "foo"},
},
},
ExpectedErrMsg: "Resource has a primary",
},
"multiple tainted, no index": {
Input: &ResourceState{
Tainted: []*InstanceState{
&InstanceState{ID: "bar"},
&InstanceState{ID: "foo"},
},
},
ExpectedErrMsg: "please specify an index",
},
"multiple tainted, with index": {
Input: &ResourceState{
Tainted: []*InstanceState{
&InstanceState{ID: "bar"},
&InstanceState{ID: "foo"},
},
},
Index: func() int { return 1 },
ExpectedOutput: &ResourceState{
Primary: &InstanceState{ID: "foo"},
Tainted: []*InstanceState{
&InstanceState{ID: "bar"},
},
},
},
"index out of bounds error": {
Input: &ResourceState{
Tainted: []*InstanceState{
&InstanceState{ID: "bar"},
&InstanceState{ID: "foo"},
},
},
Index: func() int { return 2 },
ExpectedErrMsg: "out of range",
},
} }
for k, tc := range cases { for k, tc := range cases {
index := -1 tc.Input.Untaint()
if tc.Index != nil {
index = tc.Index()
}
err := tc.Input.Untaint(index)
if tc.ExpectedErrMsg == "" && err != nil {
t.Fatalf("[%s] unexpected err: %s", k, err)
}
if tc.ExpectedErrMsg != "" {
if strings.Contains(err.Error(), tc.ExpectedErrMsg) {
continue
}
t.Fatalf("[%s] expected err: %s to contain: %s",
k, err, tc.ExpectedErrMsg)
}
if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) { if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) {
t.Fatalf( t.Fatalf(
"Failure: %s\n\nExpected: %#v\n\nGot: %#v", "Failure: %s\n\nExpected: %#v\n\nGot: %#v",
@ -1152,7 +1087,7 @@ func TestInstanceState_MergeDiff(t *testing.T) {
} }
func TestInstanceState_MergeDiff_nil(t *testing.T) { func TestInstanceState_MergeDiff_nil(t *testing.T) {
var is *InstanceState = nil var is *InstanceState
diff := &InstanceDiff{ diff := &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{ Attributes: map[string]*ResourceAttrDiff{
@ -1504,7 +1439,7 @@ func TestUpgradeV0State(t *testing.T) {
foo.Primary.Attributes["key"] != "val" { foo.Primary.Attributes["key"] != "val" {
t.Fatalf("bad: %#v", foo) t.Fatalf("bad: %#v", foo)
} }
if len(foo.Tainted) > 0 { if foo.Primary.Tainted {
t.Fatalf("bad: %#v", foo) t.Fatalf("bad: %#v", foo)
} }
@ -1512,16 +1447,9 @@ func TestUpgradeV0State(t *testing.T) {
if bar.Type != "test_resource" { if bar.Type != "test_resource" {
t.Fatalf("bad: %#v", bar) t.Fatalf("bad: %#v", bar)
} }
if bar.Primary != nil { if !bar.Primary.Tainted {
t.Fatalf("bad: %#v", bar) t.Fatalf("bad: %#v", bar)
} }
if len(bar.Tainted) != 1 {
t.Fatalf("bad: %#v", bar)
}
bt := bar.Tainted[0]
if bt.ID != "1234" || bt.Attributes["a"] != "b" {
t.Fatalf("bad: %#v", bt)
}
} }
func TestParseResourceStateKey(t *testing.T) { func TestParseResourceStateKey(t *testing.T) {

View File

@ -399,9 +399,8 @@ aws_instance.foo:
` `
const testTerraformApplyProvisionerFailStr = ` const testTerraformApplyProvisionerFailStr = `
aws_instance.bar: (1 tainted) aws_instance.bar: (tainted)
ID = <not created> ID = foo
Tainted ID 1 = foo
aws_instance.foo: aws_instance.foo:
ID = foo ID = foo
num = 2 num = 2
@ -409,9 +408,8 @@ aws_instance.foo:
` `
const testTerraformApplyProvisionerFailCreateStr = ` const testTerraformApplyProvisionerFailCreateStr = `
aws_instance.bar: (1 tainted) aws_instance.bar: (tainted)
ID = <not created> ID = foo
Tainted ID 1 = foo
` `
const testTerraformApplyProvisionerFailCreateNoIdStr = ` const testTerraformApplyProvisionerFailCreateNoIdStr = `
@ -419,10 +417,10 @@ const testTerraformApplyProvisionerFailCreateNoIdStr = `
` `
const testTerraformApplyProvisionerFailCreateBeforeDestroyStr = ` const testTerraformApplyProvisionerFailCreateBeforeDestroyStr = `
aws_instance.bar: (1 tainted) aws_instance.bar: (1 deposed)
ID = bar ID = bar
require_new = abc require_new = abc
Tainted ID 1 = foo Deposed ID 1 = foo (tainted)
` `
const testTerraformApplyProvisionerResourceRefStr = ` const testTerraformApplyProvisionerResourceRefStr = `
@ -1242,9 +1240,8 @@ DESTROY/CREATE: aws_instance.bar
STATE: STATE:
aws_instance.bar: (1 tainted) aws_instance.bar: (tainted)
ID = <not created> ID = baz
Tainted ID 1 = baz
aws_instance.foo: aws_instance.foo:
ID = bar ID = bar
num = 2 num = 2

View File

@ -359,8 +359,9 @@ func TestPruneDestroyTransformer_tainted(t *testing.T) {
Path: RootModulePath, Path: RootModulePath,
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
Tainted: []*InstanceState{ Primary: &InstanceState{
&InstanceState{ID: "foo"}, ID: "foo",
Tainted: true,
}, },
}, },
}, },
@ -399,16 +400,11 @@ func TestPruneDestroyTransformer_tainted(t *testing.T) {
const testTransformDestroyBasicStr = ` const testTransformDestroyBasicStr = `
aws_instance.bar aws_instance.bar
aws_instance.bar (destroy tainted)
aws_instance.bar (destroy) aws_instance.bar (destroy)
aws_instance.foo aws_instance.foo
aws_instance.bar (destroy tainted)
aws_instance.bar (destroy) aws_instance.bar (destroy)
aws_instance.foo aws_instance.foo
aws_instance.foo (destroy tainted)
aws_instance.foo (destroy) aws_instance.foo (destroy)
aws_instance.foo (destroy tainted)
aws_instance.bar (destroy tainted)
aws_instance.foo (destroy) aws_instance.foo (destroy)
aws_instance.bar (destroy) aws_instance.bar (destroy)
` `
@ -456,40 +452,28 @@ aws_instance.foo-bar (destroy)
const testTransformPruneDestroyTaintedStr = ` const testTransformPruneDestroyTaintedStr = `
aws_instance.bar aws_instance.bar
aws_instance.bar (destroy tainted)
aws_instance.foo aws_instance.foo
aws_instance.bar (destroy tainted)
aws_instance.foo aws_instance.foo
` `
const testTransformCreateBeforeDestroyBasicStr = ` const testTransformCreateBeforeDestroyBasicStr = `
aws_instance.web aws_instance.web
aws_instance.web (destroy tainted)
aws_instance.web (destroy tainted)
aws_load_balancer.lb (destroy tainted)
aws_instance.web (destroy) aws_instance.web (destroy)
aws_instance.web aws_instance.web
aws_load_balancer.lb aws_load_balancer.lb
aws_load_balancer.lb (destroy) aws_load_balancer.lb (destroy)
aws_load_balancer.lb aws_load_balancer.lb
aws_instance.web aws_instance.web
aws_load_balancer.lb (destroy tainted)
aws_load_balancer.lb (destroy) aws_load_balancer.lb (destroy)
aws_load_balancer.lb (destroy tainted)
aws_load_balancer.lb (destroy) aws_load_balancer.lb (destroy)
` `
const testTransformCreateBeforeDestroyTwiceStr = ` const testTransformCreateBeforeDestroyTwiceStr = `
aws_autoscale.bar aws_autoscale.bar
aws_autoscale.bar (destroy tainted)
aws_lc.foo aws_lc.foo
aws_autoscale.bar (destroy tainted)
aws_autoscale.bar (destroy) aws_autoscale.bar (destroy)
aws_autoscale.bar aws_autoscale.bar
aws_lc.foo aws_lc.foo
aws_lc.foo (destroy tainted)
aws_lc.foo (destroy tainted)
aws_autoscale.bar (destroy tainted)
aws_lc.foo (destroy) aws_lc.foo (destroy)
aws_autoscale.bar aws_autoscale.bar
aws_autoscale.bar (destroy) aws_autoscale.bar (destroy)

View File

@ -337,7 +337,7 @@ func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource,
Config: &resourceConfig, Config: &resourceConfig,
Provider: &provider, Provider: &provider,
State: &state, State: &state,
Output: &diff, OutputDiff: &diff,
OutputState: &state, OutputState: &state,
}, },
&EvalCheckPreventDestroy{ &EvalCheckPreventDestroy{
@ -392,7 +392,7 @@ func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource,
// Apply // Apply
var diffApply *InstanceDiff var diffApply *InstanceDiff
var err error var err error
var createNew, tainted bool var createNew bool
var createBeforeDestroyEnabled bool var createBeforeDestroyEnabled bool
var wasChangeType DiffChangeType var wasChangeType DiffChangeType
nodes = append(nodes, &EvalOpFilter{ nodes = append(nodes, &EvalOpFilter{
@ -456,11 +456,12 @@ func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource,
}, },
&EvalDiff{ &EvalDiff{
Info: info, Info: info,
Config: &resourceConfig, Config: &resourceConfig,
Provider: &provider, Provider: &provider,
State: &state, Diff: &diffApply,
Output: &diffApply, State: &state,
OutputDiff: &diffApply,
}, },
&EvalIgnoreChanges{ &EvalIgnoreChanges{
Resource: n.Resource, Resource: n.Resource,
@ -511,20 +512,22 @@ func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource,
Resource: n.Resource, Resource: n.Resource,
InterpResource: resource, InterpResource: resource,
CreateNew: &createNew, CreateNew: &createNew,
Tainted: &tainted,
Error: &err, Error: &err,
}, },
&EvalIf{ &EvalIf{
If: func(ctx EvalContext) (bool, error) { If: func(ctx EvalContext) (bool, error) {
if createBeforeDestroyEnabled { return createBeforeDestroyEnabled && err != nil, nil
tainted = err != nil
}
failure := tainted || err != nil
return createBeforeDestroyEnabled && failure, nil
}, },
Then: &EvalUndeposeState{ Then: &EvalUndeposeState{
Name: n.stateId(), Name: n.stateId(),
State: &state,
},
Else: &EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
}, },
}, },
@ -536,13 +539,6 @@ func (n *graphNodeExpandedResource) managedResourceEvalNodes(resource *Resource,
Diff: nil, Diff: nil,
}, },
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Provider: n.Resource.Provider,
Dependencies: n.StateDependencies(),
State: &state,
},
&EvalApplyPost{ &EvalApplyPost{
Info: info, Info: info,
State: &state, State: &state,