core: fix targeting in destroy w/ provisioners

The `TargetTransform` was dropping provisioner nodes, which caused graph
validation to fail with messages about uninitialized provisioners when a
`terraform destroy` was attempted.

This was because `destroy` flops the dependency calculation to try and
address any nodes in the graph that "depend on" the target node. But we
still need to keep the provisioner node in the graph.

Here we switch the strategy for filtering nodes to only drop
addressable, non-targeted nodes. This should prevent us from having to
whitelist nodes to keep in the future.

closes #1541
This commit is contained in:
Paul Hinze 2015-04-15 13:53:32 -05:00
parent 4ef208d81b
commit 5e67657325
3 changed files with 76 additions and 22 deletions

View File

@ -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) { func TestContext2Validate_varRef(t *testing.T) {
m := testModule(t, "validate-variable-ref") m := testModule(t, "validate-variable-ref")
p := testProvider("aws") p := testProvider("aws")

View File

@ -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"
}
}

View File

@ -28,9 +28,10 @@ func (t *TargetsTransformer) Transform(g *Graph) error {
} }
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
if targetedNodes.Include(v) { if _, ok := v.(GraphNodeAddressable); ok {
} else { if !targetedNodes.Include(v) {
g.Remove(v) g.Remove(v)
}
} }
} }
} }
@ -49,35 +50,29 @@ func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) {
return addrs, nil 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( func (t *TargetsTransformer) selectTargetedNodes(
g *Graph, addrs []ResourceAddress) (*dag.Set, error) { g *Graph, addrs []ResourceAddress) (*dag.Set, error) {
targetedNodes := new(dag.Set) targetedNodes := new(dag.Set)
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
// Keep all providers; they'll be pruned later if necessary if t.nodeIsTarget(v, addrs) {
if r, ok := v.(GraphNodeProvider); ok { targetedNodes.Add(v)
targetedNodes.Add(r)
continue
}
// For the remaining filter, we only care about addressable nodes // We inform nodes that ask about the list of targets - helps for nodes
r, ok := v.(GraphNodeAddressable) // that need to dynamically expand. Note that this only occurs for nodes
if !ok { // that are already directly targeted.
continue if tn, ok := v.(GraphNodeTargetable); ok {
} tn.SetTargets(addrs)
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)
} }
var deps *dag.Set var deps *dag.Set
var err error var err error
if t.Destroy { if t.Destroy {
deps, err = g.Descendents(r) deps, err = g.Descendents(v)
} else { } else {
deps, err = g.Ancestors(r) deps, err = g.Ancestors(v)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -92,7 +87,11 @@ func (t *TargetsTransformer) selectTargetedNodes(
} }
func (t *TargetsTransformer) nodeIsTarget( 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() addr := r.ResourceAddress()
for _, targetAddr := range addrs { for _, targetAddr := range addrs {
if targetAddr.Equals(addr) { if targetAddr.Equals(addr) {