diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/shadow_context.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/terraform/shadow_context.go | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/shadow_context.go b/vendor/github.com/hashicorp/terraform/terraform/shadow_context.go new file mode 100644 index 00000000..5588af25 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/terraform/shadow_context.go @@ -0,0 +1,158 @@ +package terraform + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/mitchellh/copystructure" +) + +// newShadowContext creates a new context that will shadow the given context +// when walking the graph. The resulting context should be used _only once_ +// for a graph walk. +// +// The returned Shadow should be closed after the graph walk with the +// real context is complete. Errors from the shadow can be retrieved there. +// +// Most importantly, any operations done on the shadow context (the returned +// context) will NEVER affect the real context. All structures are deep +// copied, no real providers or resources are used, etc. +func newShadowContext(c *Context) (*Context, *Context, Shadow) { + // Copy the targets + targetRaw, err := copystructure.Copy(c.targets) + if err != nil { + panic(err) + } + + // Copy the variables + varRaw, err := copystructure.Copy(c.variables) + if err != nil { + panic(err) + } + + // Copy the provider inputs + providerInputRaw, err := copystructure.Copy(c.providerInputConfig) + if err != nil { + panic(err) + } + + // The factories + componentsReal, componentsShadow := newShadowComponentFactory(c.components) + + // Create the shadow + shadow := &Context{ + components: componentsShadow, + destroy: c.destroy, + diff: c.diff.DeepCopy(), + hooks: nil, + meta: c.meta, + module: c.module, + state: c.state.DeepCopy(), + targets: targetRaw.([]string), + variables: varRaw.(map[string]interface{}), + + // NOTE(mitchellh): This is not going to work for shadows that are + // testing that input results in the proper end state. At the time + // of writing, input is not used in any state-changing graph + // walks anyways, so this checks nothing. We set it to this to avoid + // any panics but even a "nil" value worked here. + uiInput: new(MockUIInput), + + // Hardcoded to 4 since parallelism in the shadow doesn't matter + // a ton since we're doing far less compared to the real side + // and our operations are MUCH faster. + parallelSem: NewSemaphore(4), + providerInputConfig: providerInputRaw.(map[string]map[string]interface{}), + } + + // Create the real context. This is effectively just a copy of + // the context given except we need to modify some of the values + // to point to the real side of a shadow so the shadow can compare values. + real := &Context{ + // The fields below are changed. + components: componentsReal, + + // The fields below are direct copies + destroy: c.destroy, + diff: c.diff, + // diffLock - no copy + hooks: c.hooks, + meta: c.meta, + module: c.module, + sh: c.sh, + state: c.state, + // stateLock - no copy + targets: c.targets, + uiInput: c.uiInput, + variables: c.variables, + + // l - no copy + parallelSem: c.parallelSem, + providerInputConfig: c.providerInputConfig, + runContext: c.runContext, + runContextCancel: c.runContextCancel, + shadowErr: c.shadowErr, + } + + return real, shadow, &shadowContextCloser{ + Components: componentsShadow, + } +} + +// shadowContextVerify takes the real and shadow context and verifies they +// have equal diffs and states. +func shadowContextVerify(real, shadow *Context) error { + var result error + + // The states compared must be pruned so they're minimal/clean + real.state.prune() + shadow.state.prune() + + // Compare the states + if !real.state.Equal(shadow.state) { + result = multierror.Append(result, fmt.Errorf( + "Real and shadow states do not match! "+ + "Real state:\n\n%s\n\n"+ + "Shadow state:\n\n%s\n\n", + real.state, shadow.state)) + } + + // Compare the diffs + if !real.diff.Equal(shadow.diff) { + result = multierror.Append(result, fmt.Errorf( + "Real and shadow diffs do not match! "+ + "Real diff:\n\n%s\n\n"+ + "Shadow diff:\n\n%s\n\n", + real.diff, shadow.diff)) + } + + return result +} + +// shadowContextCloser is the io.Closer returned by newShadowContext that +// closes all the shadows and returns the results. +type shadowContextCloser struct { + Components *shadowComponentFactory +} + +// Close closes the shadow context. +func (c *shadowContextCloser) CloseShadow() error { + return c.Components.CloseShadow() +} + +func (c *shadowContextCloser) ShadowError() error { + err := c.Components.ShadowError() + if err == nil { + return nil + } + + // This is a sad edge case: if the configuration contains uuid() at + // any point, we cannot reason aboyt the shadow execution. Tested + // with Context2Plan_shadowUuid. + if strings.Contains(err.Error(), "uuid()") { + err = nil + } + + return err +} |