diff --git a/terraform/context_test.go b/terraform/context_test.go index 6ff63d813..e9781918b 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2688,6 +2688,48 @@ func TestContext2Validate_tainted(t *testing.T) { } } +func TestContext2Validate_targetedDestroy(t *testing.T) { + m := testModule(t, "validate-targeted") + p := testProvider("aws") + pr := testProvisioner() + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Provisioners: map[string]ResourceProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": resourceState("aws_instance", "i-bcd345"), + "aws_instance.bar": resourceState("aws_instance", "i-abc123"), + }, + }, + }, + }, + Targets: []string{"aws_instance.foo"}, + Destroy: true, + }) + + w, e := ctx.Validate() + if len(w) > 0 { + warnStr := "" + for _, v := range w { + warnStr = warnStr + " " + v + } + t.Fatalf("bad: %s", warnStr) + } + if len(e) > 0 { + t.Fatalf("bad: %s", e) + } +} + func TestContext2Validate_varRef(t *testing.T) { m := testModule(t, "validate-variable-ref") p := testProvider("aws") diff --git a/terraform/test-fixtures/validate-targeted/main.tf b/terraform/test-fixtures/validate-targeted/main.tf new file mode 100644 index 000000000..b40b126fa --- /dev/null +++ b/terraform/test-fixtures/validate-targeted/main.tf @@ -0,0 +1,13 @@ +resource "aws_instance" "foo" { + num = "2" + provisioner "shell" { + command = "echo hi" + } +} + +resource "aws_instance" "bar" { + foo = "bar" + provisioner "shell" { + command = "echo hi" + } +} diff --git a/terraform/transform_targets.go b/terraform/transform_targets.go index 29a6d53c6..1ddb7f9c4 100644 --- a/terraform/transform_targets.go +++ b/terraform/transform_targets.go @@ -28,9 +28,10 @@ func (t *TargetsTransformer) Transform(g *Graph) error { } for _, v := range g.Vertices() { - if targetedNodes.Include(v) { - } else { - g.Remove(v) + if _, ok := v.(GraphNodeAddressable); ok { + if !targetedNodes.Include(v) { + g.Remove(v) + } } } } @@ -49,35 +50,29 @@ func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) { return addrs, nil } +// Returns the list of targeted nodes. A targeted node is either addressed +// directly, or is an Ancestor of a targeted node. Destroy mode keeps +// Descendents instead of Ancestors. func (t *TargetsTransformer) selectTargetedNodes( g *Graph, addrs []ResourceAddress) (*dag.Set, error) { targetedNodes := new(dag.Set) for _, v := range g.Vertices() { - // Keep all providers; they'll be pruned later if necessary - if r, ok := v.(GraphNodeProvider); ok { - targetedNodes.Add(r) - continue - } + if t.nodeIsTarget(v, addrs) { + targetedNodes.Add(v) - // For the remaining filter, we only care about addressable nodes - r, ok := v.(GraphNodeAddressable) - if !ok { - continue - } - - if t.nodeIsTarget(r, addrs) { - targetedNodes.Add(r) - // If the node would like to know about targets, tell it. - if n, ok := r.(GraphNodeTargetable); ok { - n.SetTargets(addrs) + // We inform nodes that ask about the list of targets - helps for nodes + // that need to dynamically expand. Note that this only occurs for nodes + // that are already directly targeted. + if tn, ok := v.(GraphNodeTargetable); ok { + tn.SetTargets(addrs) } var deps *dag.Set var err error if t.Destroy { - deps, err = g.Descendents(r) + deps, err = g.Descendents(v) } else { - deps, err = g.Ancestors(r) + deps, err = g.Ancestors(v) } if err != nil { return nil, err @@ -92,7 +87,11 @@ func (t *TargetsTransformer) selectTargetedNodes( } func (t *TargetsTransformer) nodeIsTarget( - r GraphNodeAddressable, addrs []ResourceAddress) bool { + v dag.Vertex, addrs []ResourceAddress) bool { + r, ok := v.(GraphNodeAddressable) + if !ok { + return false + } addr := r.ResourceAddress() for _, targetAddr := range addrs { if targetAddr.Equals(addr) {