diff --git a/terraform/context.go b/terraform/context.go index d91a851765..a645f29f78 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -316,9 +316,10 @@ func (c *Context) Plan() (*Plan, error) { defer c.releaseRun(v) p := &Plan{ - Module: c.module, - Vars: c.variables, - State: c.state, + Module: c.module, + Vars: c.variables, + State: c.state, + Targets: c.targets, } var operation walkOperation diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index ffb225107a..a815f3928a 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -4010,7 +4010,76 @@ template_file.parent: ID = foo template = Hi type = template_file - `) + `) + if actual != expected { + t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual) + } +} + +func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { + p := testProvider("aws") + p.DiffFn = testDiffFn + p.ApplyFn = testApplyFn + ctx := testContext2(t, &ContextOpts{ + Module: testModule(t, "apply-tainted-targets"), + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Targets: []string{"aws_instance.iambeingadded"}, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.ifailedprovisioners": &ResourceState{ + Tainted: []*InstanceState{ + &InstanceState{ + ID: "ifailedprovisioners", + }, + }, + }, + }, + }, + }, + }, + }) + + plan, err := ctx.Plan() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Write / Read plan to simulate running it through a Plan file + var buf bytes.Buffer + if err := WritePlan(plan, &buf); err != nil { + t.Fatalf("err: %s", err) + } + + planFromFile, err := ReadPlan(&buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + ctx = planFromFile.Context(&ContextOpts{ + Module: testModule(t, "apply-tainted-targets"), + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(` +aws_instance.iambeingadded: + ID = foo +aws_instance.ifailedprovisioners: (1 tainted) + ID = + Tainted ID 1 = ifailedprovisioners + `) if actual != expected { t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual) } diff --git a/terraform/plan.go b/terraform/plan.go index 715136edcf..b15ea5c594 100644 --- a/terraform/plan.go +++ b/terraform/plan.go @@ -21,10 +21,11 @@ func init() { // Plan represents a single Terraform execution plan, which contains // all the information necessary to make an infrastructure change. type Plan struct { - Diff *Diff - Module *module.Tree - State *State - Vars map[string]string + Diff *Diff + Module *module.Tree + State *State + Vars map[string]string + Targets []string once sync.Once } @@ -38,6 +39,7 @@ func (p *Plan) Context(opts *ContextOpts) *Context { opts.Module = p.Module opts.State = p.State opts.Variables = p.Vars + opts.Targets = p.Targets return NewContext(opts) } diff --git a/terraform/test-fixtures/apply-tainted-targets/main.tf b/terraform/test-fixtures/apply-tainted-targets/main.tf new file mode 100644 index 0000000000..8f6b317d5b --- /dev/null +++ b/terraform/test-fixtures/apply-tainted-targets/main.tf @@ -0,0 +1,3 @@ +resource "aws_instance" "ifailedprovisioners" { } + +resource "aws_instance" "iambeingadded" { } diff --git a/terraform/test-fixtures/plan-targeted-with-tainted/main.tf b/terraform/test-fixtures/plan-targeted-with-tainted/main.tf new file mode 100644 index 0000000000..f17e08094b --- /dev/null +++ b/terraform/test-fixtures/plan-targeted-with-tainted/main.tf @@ -0,0 +1,5 @@ +resource "aws_instance" "ifailedprovisioners" { +} + +resource "aws_instance" "iambeingadded" { +}