github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/terraform/shadow_context.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/go-multierror" 8 "github.com/mitchellh/copystructure" 9 ) 10 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_ 13 // for a graph walk. 14 // 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. 17 // 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) { 22 // Copy the targets 23 targetRaw, err := copystructure.Copy(c.targets) 24 if err != nil { 25 panic(err) 26 } 27 28 // Copy the variables 29 varRaw, err := copystructure.Copy(c.variables) 30 if err != nil { 31 panic(err) 32 } 33 34 // Copy the provider inputs 35 providerInputRaw, err := copystructure.Copy(c.providerInputConfig) 36 if err != nil { 37 panic(err) 38 } 39 40 // The factories 41 componentsReal, componentsShadow := newShadowComponentFactory(c.components) 42 43 // Create the shadow 44 shadow := &Context{ 45 components: componentsShadow, 46 destroy: c.destroy, 47 diff: c.diff.DeepCopy(), 48 hooks: nil, 49 meta: c.meta, 50 module: c.module, 51 state: c.state.DeepCopy(), 52 targets: targetRaw.([]string), 53 variables: varRaw.(map[string]interface{}), 54 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), 61 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{}), 67 } 68 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. 72 real := &Context{ 73 // The fields below are changed. 74 components: componentsReal, 75 76 // The fields below are direct copies 77 destroy: c.destroy, 78 diff: c.diff, 79 // diffLock - no copy 80 hooks: c.hooks, 81 meta: c.meta, 82 module: c.module, 83 sh: c.sh, 84 state: c.state, 85 // stateLock - no copy 86 targets: c.targets, 87 uiInput: c.uiInput, 88 variables: c.variables, 89 90 // l - no copy 91 parallelSem: c.parallelSem, 92 providerInputConfig: c.providerInputConfig, 93 runContext: c.runContext, 94 runContextCancel: c.runContextCancel, 95 shadowErr: c.shadowErr, 96 } 97 98 return real, shadow, &shadowContextCloser{ 99 Components: componentsShadow, 100 } 101 } 102 103 // shadowContextVerify takes the real and shadow context and verifies they 104 // have equal diffs and states. 105 func shadowContextVerify(real, shadow *Context) error { 106 var result error 107 108 // The states compared must be pruned so they're minimal/clean 109 real.state.prune() 110 shadow.state.prune() 111 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)) 119 } 120 121 // Compare the diffs 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)) 128 } 129 130 return result 131 } 132 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 137 } 138 139 // Close closes the shadow context. 140 func (c *shadowContextCloser) CloseShadow() error { 141 return c.Components.CloseShadow() 142 } 143 144 func (c *shadowContextCloser) ShadowError() error { 145 err := c.Components.ShadowError() 146 if err == nil { 147 return nil 148 } 149 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()") { 154 err = nil 155 } 156 157 return err 158 }