From 0fff7d1673eaf7ab98ee56b08fded2d29f6f0e12 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Tue, 5 May 2015 17:24:44 -0500 Subject: [PATCH] core: validate graph w/ diff during plan phase This reimplements my prior attempt at nipping issues where a plan did not yield the same cycle an apply did. My prior attempt was to have ctx.Validate generate a "Verbose" worst-case graph. It turns out that skipping PruneDestroyTransformer to generate this graph misses important heuristics that prevent cycles by dropping destroy nodes that are determined to be unused. This resulted in Validate improperly failing in scenarios where these heuristics would have broken the cycle. We detected the problem during the work on #1781 and worked around the issue by reverting to the non-Verbose graph in Validate. This commit accomplishes the original goal in a better way - by generating the full graph and checking it once Plan has calculated the diff. This guarantees that any graph issue that would be caught by Apply will be caught by Plan. --- terraform/context.go | 17 +++++++++++------ terraform/graph_config_node_resource.go | 9 ++++++--- terraform/transform_destroy_test.go | 4 +--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index d061b47a60e7..66e95424597c 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -331,6 +331,12 @@ func (c *Context) Plan() (*Plan, error) { } p.Diff = c.diff + // Now that we have a diff, we can build the exact graph that Apply will use + // and catch any possible cycles during the Plan phase. + if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { + return nil, err + } + return p, nil } @@ -406,12 +412,11 @@ func (c *Context) Validate() ([]string, []error) { } } - // Build a Verbose version of the graph so we can catch any potential cycles - // in the validate stage - graph, err := c.Graph(&ContextGraphOpts{ - Validate: true, - Verbose: false, - }) + // Build the graph so we can walk it and run Validate on nodes. + // We also validate the graph generated here, but this graph doesn't + // necessarily match the graph that Plan will generate, so we'll validate the + // graph again later after Planning. + graph, err := c.Graph(&ContextGraphOpts{Validate: true}) if err != nil { return nil, []error{err} } diff --git a/terraform/graph_config_node_resource.go b/terraform/graph_config_node_resource.go index 2ad91d18d508..7c94f098e76f 100644 --- a/terraform/graph_config_node_resource.go +++ b/terraform/graph_config_node_resource.go @@ -426,10 +426,13 @@ func (n *graphNodeResourceDestroy) destroyIncludePrimary( // to include this resource. prefix := n.Original.Resource.Id() - // If we're present in the diff proper, then keep it. + // If we're present in the diff proper, then keep it. We're looking + // only for resources in the diff that match our resource or a count-index + // of our resource that are marked for destroy. if d != nil { - for k, _ := range d.Resources { - if strings.HasPrefix(k, prefix) { + for k, d := range d.Resources { + match := k == prefix || strings.HasPrefix(k, prefix+".") + if match && d.Destroy { return true } } diff --git a/terraform/transform_destroy_test.go b/terraform/transform_destroy_test.go index 56acec0494c2..1a50ce0538c8 100644 --- a/terraform/transform_destroy_test.go +++ b/terraform/transform_destroy_test.go @@ -166,7 +166,7 @@ func TestPruneDestroyTransformer_diff(t *testing.T) { actual := strings.TrimSpace(g.String()) expected := strings.TrimSpace(testTransformPruneDestroyBasicDiffStr) if actual != expected { - t.Fatalf("bad:\n\n%s", actual) + t.Fatalf("expected:\n\n%s\n\nbad:\n\n%s", expected, actual) } } @@ -421,9 +421,7 @@ aws_instance.foo const testTransformPruneDestroyBasicDiffStr = ` aws_instance.bar - aws_instance.bar (destroy) aws_instance.foo -aws_instance.bar (destroy) aws_instance.foo `