7 "github.com/hashicorp/go-multierror"
8 "github.com/mitchellh/copystructure"
11 // newShadowContext creates a new context that will shadow the given context
12 // when walking the graph. The resulting context should be used _only once_
15 // The returned Shadow should be closed after the graph walk with the
16 // real context is complete. Errors from the shadow can be retrieved there.
18 // Most importantly, any operations done on the shadow context (the returned
19 // context) will NEVER affect the real context. All structures are deep
20 // copied, no real providers or resources are used, etc.
21 func newShadowContext(c *Context) (*Context, *Context, Shadow) {
23 targetRaw, err := copystructure.Copy(c.targets)
29 varRaw, err := copystructure.Copy(c.variables)
34 // Copy the provider inputs
35 providerInputRaw, err := copystructure.Copy(c.providerInputConfig)
41 componentsReal, componentsShadow := newShadowComponentFactory(c.components)
45 components: componentsShadow,
47 diff: c.diff.DeepCopy(),
51 state: c.state.DeepCopy(),
52 targets: targetRaw.([]string),
53 variables: varRaw.(map[string]interface{}),
55 // NOTE(mitchellh): This is not going to work for shadows that are
56 // testing that input results in the proper end state. At the time
57 // of writing, input is not used in any state-changing graph
58 // walks anyways, so this checks nothing. We set it to this to avoid
59 // any panics but even a "nil" value worked here.
60 uiInput: new(MockUIInput),
62 // Hardcoded to 4 since parallelism in the shadow doesn't matter
63 // a ton since we're doing far less compared to the real side
64 // and our operations are MUCH faster.
65 parallelSem: NewSemaphore(4),
66 providerInputConfig: providerInputRaw.(map[string]map[string]interface{}),
69 // Create the real context. This is effectively just a copy of
70 // the context given except we need to modify some of the values
71 // to point to the real side of a shadow so the shadow can compare values.
73 // The fields below are changed.
74 components: componentsReal,
76 // The fields below are direct copies
85 // stateLock - no copy
88 variables: c.variables,
91 parallelSem: c.parallelSem,
92 providerInputConfig: c.providerInputConfig,
93 runContext: c.runContext,
94 runContextCancel: c.runContextCancel,
95 shadowErr: c.shadowErr,
98 return real, shadow, &shadowContextCloser{
99 Components: componentsShadow,
103 // shadowContextVerify takes the real and shadow context and verifies they
104 // have equal diffs and states.
105 func shadowContextVerify(real, shadow *Context) error {
108 // The states compared must be pruned so they're minimal/clean
112 // Compare the states
113 if !real.state.Equal(shadow.state) {
114 result = multierror.Append(result, fmt.Errorf(
115 "Real and shadow states do not match! "+
116 "Real state:\n\n%s\n\n"+
117 "Shadow state:\n\n%s\n\n",
118 real.state, shadow.state))
122 if !real.diff.Equal(shadow.diff) {
123 result = multierror.Append(result, fmt.Errorf(
124 "Real and shadow diffs do not match! "+
125 "Real diff:\n\n%s\n\n"+
126 "Shadow diff:\n\n%s\n\n",
127 real.diff, shadow.diff))
133 // shadowContextCloser is the io.Closer returned by newShadowContext that
134 // closes all the shadows and returns the results.
135 type shadowContextCloser struct {
136 Components *shadowComponentFactory
139 // Close closes the shadow context.
140 func (c *shadowContextCloser) CloseShadow() error {
141 return c.Components.CloseShadow()
144 func (c *shadowContextCloser) ShadowError() error {
145 err := c.Components.ShadowError()
150 // This is a sad edge case: if the configuration contains uuid() at
151 // any point, we cannot reason aboyt the shadow execution. Tested
152 // with Context2Plan_shadowUuid.
153 if strings.Contains(err.Error(), "uuid()") {