github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/terraform/node_resource_apply.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform/config" 7 ) 8 9 // NodeApplyableResource represents a resource that is "applyable": 10 // it is ready to be applied and is represented by a diff. 11 type NodeApplyableResource struct { 12 *NodeAbstractResource 13 } 14 15 // GraphNodeCreator 16 func (n *NodeApplyableResource) CreateAddr() *ResourceAddress { 17 return n.NodeAbstractResource.Addr 18 } 19 20 // GraphNodeEvalable 21 func (n *NodeApplyableResource) EvalTree() EvalNode { 22 addr := n.NodeAbstractResource.Addr 23 24 // stateId is the ID to put into the state 25 stateId := addr.stateId() 26 if addr.Index > -1 { 27 stateId = fmt.Sprintf("%s.%d", stateId, addr.Index) 28 } 29 30 // Build the instance info. More of this will be populated during eval 31 info := &InstanceInfo{ 32 Id: stateId, 33 Type: addr.Type, 34 } 35 36 // Build the resource for eval 37 resource := &Resource{ 38 Name: addr.Name, 39 Type: addr.Type, 40 CountIndex: addr.Index, 41 } 42 if resource.CountIndex < 0 { 43 resource.CountIndex = 0 44 } 45 46 // Determine the dependencies for the state. We use some older 47 // code for this that we've used for a long time. 48 var stateDeps []string 49 { 50 oldN := &graphNodeExpandedResource{Resource: n.Config} 51 stateDeps = oldN.StateDependencies() 52 } 53 54 // Eval info is different depending on what kind of resource this is 55 switch n.Config.Mode { 56 case config.ManagedResourceMode: 57 return n.evalTreeManagedResource( 58 stateId, info, resource, stateDeps, 59 ) 60 case config.DataResourceMode: 61 return n.evalTreeDataResource( 62 stateId, info, resource, stateDeps) 63 default: 64 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) 65 } 66 } 67 68 func (n *NodeApplyableResource) evalTreeDataResource( 69 stateId string, info *InstanceInfo, 70 resource *Resource, stateDeps []string) EvalNode { 71 var provider ResourceProvider 72 var config *ResourceConfig 73 var diff *InstanceDiff 74 var state *InstanceState 75 76 return &EvalSequence{ 77 Nodes: []EvalNode{ 78 // Get the saved diff for apply 79 &EvalReadDiff{ 80 Name: stateId, 81 Diff: &diff, 82 }, 83 84 // Stop here if we don't actually have a diff 85 &EvalIf{ 86 If: func(ctx EvalContext) (bool, error) { 87 if diff == nil { 88 return true, EvalEarlyExitError{} 89 } 90 91 if diff.GetAttributesLen() == 0 { 92 return true, EvalEarlyExitError{} 93 } 94 95 return true, nil 96 }, 97 Then: EvalNoop{}, 98 }, 99 100 // We need to re-interpolate the config here, rather than 101 // just using the diff's values directly, because we've 102 // potentially learned more variable values during the 103 // apply pass that weren't known when the diff was produced. 104 &EvalInterpolate{ 105 Config: n.Config.RawConfig.Copy(), 106 Resource: resource, 107 Output: &config, 108 }, 109 110 &EvalGetProvider{ 111 Name: n.ProvidedBy()[0], 112 Output: &provider, 113 }, 114 115 // Make a new diff with our newly-interpolated config. 116 &EvalReadDataDiff{ 117 Info: info, 118 Config: &config, 119 Previous: &diff, 120 Provider: &provider, 121 Output: &diff, 122 }, 123 124 &EvalReadDataApply{ 125 Info: info, 126 Diff: &diff, 127 Provider: &provider, 128 Output: &state, 129 }, 130 131 &EvalWriteState{ 132 Name: stateId, 133 ResourceType: n.Config.Type, 134 Provider: n.Config.Provider, 135 Dependencies: stateDeps, 136 State: &state, 137 }, 138 139 // Clear the diff now that we've applied it, so 140 // later nodes won't see a diff that's now a no-op. 141 &EvalWriteDiff{ 142 Name: stateId, 143 Diff: nil, 144 }, 145 146 &EvalUpdateStateHook{}, 147 }, 148 } 149 } 150 151 func (n *NodeApplyableResource) evalTreeManagedResource( 152 stateId string, info *InstanceInfo, 153 resource *Resource, stateDeps []string) EvalNode { 154 // Declare a bunch of variables that are used for state during 155 // evaluation. Most of this are written to by-address below. 156 var provider ResourceProvider 157 var diff, diffApply *InstanceDiff 158 var state *InstanceState 159 var resourceConfig *ResourceConfig 160 var err error 161 var createNew bool 162 var createBeforeDestroyEnabled bool 163 164 return &EvalSequence{ 165 Nodes: []EvalNode{ 166 // Build the instance info 167 &EvalInstanceInfo{ 168 Info: info, 169 }, 170 171 // Get the saved diff for apply 172 &EvalReadDiff{ 173 Name: stateId, 174 Diff: &diffApply, 175 }, 176 177 // We don't want to do any destroys 178 &EvalIf{ 179 If: func(ctx EvalContext) (bool, error) { 180 if diffApply == nil { 181 return true, EvalEarlyExitError{} 182 } 183 184 if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 { 185 return true, EvalEarlyExitError{} 186 } 187 188 diffApply.SetDestroy(false) 189 return true, nil 190 }, 191 Then: EvalNoop{}, 192 }, 193 194 &EvalIf{ 195 If: func(ctx EvalContext) (bool, error) { 196 destroy := false 197 if diffApply != nil { 198 destroy = diffApply.GetDestroy() || diffApply.RequiresNew() 199 } 200 201 createBeforeDestroyEnabled = 202 n.Config.Lifecycle.CreateBeforeDestroy && 203 destroy 204 205 return createBeforeDestroyEnabled, nil 206 }, 207 Then: &EvalDeposeState{ 208 Name: stateId, 209 }, 210 }, 211 212 &EvalInterpolate{ 213 Config: n.Config.RawConfig.Copy(), 214 Resource: resource, 215 Output: &resourceConfig, 216 }, 217 &EvalGetProvider{ 218 Name: n.ProvidedBy()[0], 219 Output: &provider, 220 }, 221 &EvalReadState{ 222 Name: stateId, 223 Output: &state, 224 }, 225 // Re-run validation to catch any errors we missed, e.g. type 226 // mismatches on computed values. 227 &EvalValidateResource{ 228 Provider: &provider, 229 Config: &resourceConfig, 230 ResourceName: n.Config.Name, 231 ResourceType: n.Config.Type, 232 ResourceMode: n.Config.Mode, 233 IgnoreWarnings: true, 234 }, 235 &EvalDiff{ 236 Info: info, 237 Config: &resourceConfig, 238 Resource: n.Config, 239 Provider: &provider, 240 Diff: &diffApply, 241 State: &state, 242 OutputDiff: &diffApply, 243 }, 244 245 // Get the saved diff 246 &EvalReadDiff{ 247 Name: stateId, 248 Diff: &diff, 249 }, 250 251 // Compare the diffs 252 &EvalCompareDiff{ 253 Info: info, 254 One: &diff, 255 Two: &diffApply, 256 }, 257 258 &EvalGetProvider{ 259 Name: n.ProvidedBy()[0], 260 Output: &provider, 261 }, 262 &EvalReadState{ 263 Name: stateId, 264 Output: &state, 265 }, 266 &EvalApply{ 267 Info: info, 268 State: &state, 269 Diff: &diffApply, 270 Provider: &provider, 271 Output: &state, 272 Error: &err, 273 CreateNew: &createNew, 274 }, 275 &EvalWriteState{ 276 Name: stateId, 277 ResourceType: n.Config.Type, 278 Provider: n.Config.Provider, 279 Dependencies: stateDeps, 280 State: &state, 281 }, 282 &EvalApplyProvisioners{ 283 Info: info, 284 State: &state, 285 Resource: n.Config, 286 InterpResource: resource, 287 CreateNew: &createNew, 288 Error: &err, 289 }, 290 &EvalIf{ 291 If: func(ctx EvalContext) (bool, error) { 292 return createBeforeDestroyEnabled && err != nil, nil 293 }, 294 Then: &EvalUndeposeState{ 295 Name: stateId, 296 State: &state, 297 }, 298 Else: &EvalWriteState{ 299 Name: stateId, 300 ResourceType: n.Config.Type, 301 Provider: n.Config.Provider, 302 Dependencies: stateDeps, 303 State: &state, 304 }, 305 }, 306 307 // We clear the diff out here so that future nodes 308 // don't see a diff that is already complete. There 309 // is no longer a diff! 310 &EvalWriteDiff{ 311 Name: stateId, 312 Diff: nil, 313 }, 314 315 &EvalApplyPost{ 316 Info: info, 317 State: &state, 318 Error: &err, 319 }, 320 &EvalUpdateStateHook{}, 321 }, 322 } 323 }