terraform: logic for shadowing the original graph

This introduces failing tests. How many is unknown since shadow graph
errors cause a panic.
This commit is contained in:
Mitchell Hashimoto 2016-10-19 14:17:12 -07:00
parent 7d36e991da
commit 13b9007474
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
1 changed files with 66 additions and 22 deletions

View File

@ -363,35 +363,79 @@ func (c *Context) Apply() (*State, error) {
// Copy our own state // Copy our own state
c.state = c.state.DeepCopy() c.state = c.state.DeepCopy()
// Build the graph. If it is a destroy operation, we use the // Build the original graph. This is before the new graph builders
// standard graph builder. If it is an apply operation, we use // coming in 0.8. We do this for shadow graphing.
// our new graph builder. oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true})
var graph *Graph if err != nil && X_newApply {
var err error // If we had an error graphing but we're using the new graph,
if c.destroy || !X_newApply { // just set it to nil and let it go. There are some features that
graph, err = c.Graph(&ContextGraphOpts{Validate: true}) // may work with the new graph that don't with the old.
} else { oldGraph = nil
graph, err = (&ApplyGraphBuilder{ err = nil
Module: c.module,
Diff: c.diff,
State: c.state,
Providers: c.components.ResourceProviders(),
Provisioners: c.components.ResourceProvisioners(),
}).Build(RootModulePath)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Do the walk // Build the new graph. We do this no matter what so we can shadow it.
var walker *ContextGraphWalker newGraph, err := (&ApplyGraphBuilder{
if c.destroy { Module: c.module,
walker, err = c.walk(graph, graph, walkDestroy) Diff: c.diff,
} else { State: c.state,
//walker, err = c.walk(graph, nil, walkApply) Providers: c.components.ResourceProviders(),
walker, err = c.walk(graph, graph, walkApply) 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 { if len(walker.ValidationErrors) > 0 {
err = multierror.Append(err, walker.ValidationErrors...) err = multierror.Append(err, walker.ValidationErrors...)
} }