Skip to content

Commit

Permalink
core: Fix destroy when module vars used in provider config
Browse files Browse the repository at this point in the history
For `terraform destroy`, we currently build up the same graph we do for
`plan` and `apply` and we do a walk with a special Diff that says
"destroy everything".

We have fought the interpolation subsystem time and again through this
code path. Beginning in #2775 we gained a new feature to selectively
prune out problematic graph nodes. The past chain of destroy fixes I
have been involved with (#6557, #6599, #6753) have attempted to massage
the "noop" definitions to properly handle the edge cases reported.

"Variable is depended on by provider config" is another edge case we add
here and try to fix.

This dive only makes me more convinced that the whole `terraform
destroy` code path needs to be reworked.

For now, I went with a "surgical strike" approach to the problem
expressed in #7047. I found a couple of issues with the existing
Noop and DestroyEdgeInclude logic, especially with regards to
flattening, but I'm explicitly ignoring these for now so we can get this
particular bug fixed ahead of the 0.7 release. My hope is that we can
circle around with a fully specced initiative to refactor `terraform
destroy`'s graph to be more state-derived than config-derived.

Until then, this fixes #7407
  • Loading branch information
phinze committed Jun 12, 2016
1 parent 3662bfa commit bf0e770
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
39 changes: 39 additions & 0 deletions terraform/context_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,45 @@ func TestContext2Apply_destroySkipsCBD(t *testing.T) {
}
}

func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) {
m := testModule(t, "apply-destroy-mod-var-provider-config")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: []string{"root", "child"},
Resources: map[string]*ResourceState{
"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
State: state,
Destroy: true,
})

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

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

// https://github.com/hashicorp/terraform/issues/2892
func TestContext2Apply_destroyCrossProviders(t *testing.T) {
m := testModule(t, "apply-destroy-cross-providers")
Expand Down
21 changes: 21 additions & 0 deletions terraform/graph_config_node_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,24 @@ func (n *GraphNodeConfigVariableFlat) Path() []string {

return nil
}

func (n *GraphNodeConfigVariableFlat) Noop(opts *NoopOpts) bool {
// First look for provider nodes that depend on this variable downstream
modDiff := opts.Diff.ModuleByPath(n.ModulePath)
if modDiff != nil && modDiff.Destroy {
ds, err := opts.Graph.Descendents(n)
if err != nil {
log.Printf("[ERROR] Error looking up descendents of %s: %s", n.Name(), err)
} else {
for _, d := range ds.List() {
if _, ok := d.(GraphNodeProvider); ok {
log.Printf("[DEBUG] This variable is depended on by a provider, can't be a noop.")
return false
}
}
}
}

// Then fall back to existing impl
return n.GraphNodeConfigVariable.Noop(opts)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
variable "input" {}

provider "aws" {
region = "us-east-${var.input}"
}

resource "aws_instance" "foo" { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module "child" {
source = "./child"
input = "1"
}

0 comments on commit bf0e770

Please sign in to comment.