From 13b900747421aa3eb720cef5ce5fdaee62e4fc6e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 19 Oct 2016 14:17:12 -0700 Subject: [PATCH] terraform: logic for shadowing the original graph This introduces failing tests. How many is unknown since shadow graph errors cause a panic. --- terraform/context.go | 88 +++++++++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index eebae1c15..d15b9f6c2 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -363,35 +363,79 @@ func (c *Context) Apply() (*State, error) { // Copy our own state c.state = c.state.DeepCopy() - // Build the graph. If it is a destroy operation, we use the - // standard graph builder. If it is an apply operation, we use - // our new graph builder. - var graph *Graph - var err error - if c.destroy || !X_newApply { - graph, err = c.Graph(&ContextGraphOpts{Validate: true}) - } else { - graph, err = (&ApplyGraphBuilder{ - Module: c.module, - Diff: c.diff, - State: c.state, - Providers: c.components.ResourceProviders(), - Provisioners: c.components.ResourceProvisioners(), - }).Build(RootModulePath) + // Build the original graph. This is before the new graph builders + // coming in 0.8. We do this for shadow graphing. + oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true}) + if err != nil && X_newApply { + // If we had an error graphing but we're using the new graph, + // just set it to nil and let it go. There are some features that + // may work with the new graph that don't with the old. + oldGraph = nil + err = nil } if err != nil { return nil, err } - // Do the walk - var walker *ContextGraphWalker - if c.destroy { - walker, err = c.walk(graph, graph, walkDestroy) - } else { - //walker, err = c.walk(graph, nil, walkApply) - walker, err = c.walk(graph, graph, walkApply) + // Build the new graph. We do this no matter what so we can shadow it. + newGraph, err := (&ApplyGraphBuilder{ + Module: c.module, + Diff: c.diff, + State: c.state, + Providers: c.components.ResourceProviders(), + Provisioners: c.components.ResourceProvisioners(), + }).Build(RootModulePath) + if err != nil && !X_newApply { + // If we had an error graphing but we're not using this graph, just + // set it to nil and record it as a shadow error. + c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf( + "Error building new apply graph: %s", err)) + + newGraph = nil + err = nil + } + if err != nil { + return nil, err } + // Determine what is the real and what is the shadow. The logic here + // is straightforward though the if statements are not: + // + // * Destroy mode - always use original, shadow with nothing because + // we're only testing the new APPLY graph. + // * Apply with new apply - use new graph, shadow is new graph. We can't + // shadow with the old graph because the old graph does a lot more + // that it shouldn't. + // * Apply with old apply - use old graph, shadow with new graph. + // + real := oldGraph + shadow := newGraph + if c.destroy { + log.Printf("[WARN] terraform: real graph is original, shadow is nil") + shadow = nil + } else { + if X_newApply { + log.Printf("[WARN] terraform: real graph is Xnew-apply, shadow is Xnew-apply") + real = shadow + } else { + log.Printf("[WARN] terraform: real graph is original, shadow is Xnew-apply") + } + } + + // Determine the operation + operation := walkApply + if c.destroy { + operation = walkDestroy + } + + // This shouldn't happen, so assert it. This is before any state changes + // so it is safe to crash here. + if real == nil { + panic("nil real graph") + } + + // Walk the graph + walker, err := c.walk(real, shadow, operation) if len(walker.ValidationErrors) > 0 { err = multierror.Append(err, walker.ValidationErrors...) }