github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/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 // GraphNodeReferencer, overriding NodeAbstractResource 21 func (n *NodeApplyableResource) References() []string { 22 result := n.NodeAbstractResource.References() 23 24 // The "apply" side of a resource generally also depends on the 25 // destruction of its dependencies as well. For example, if a LB 26 // references a set of VMs with ${vm.foo.*.id}, then we must wait for 27 // the destruction so we get the newly updated list of VMs. 28 // 29 // The exception here is CBD. When CBD is set, we don't do this since 30 // it would create a cycle. By not creating a cycle, we require two 31 // applies since the first apply the creation step will use the OLD 32 // values (pre-destroy) and the second step will update. 33 // 34 // This is how Terraform behaved with "legacy" graphs (TF <= 0.7.x). 35 // We mimic that behavior here now and can improve upon it in the future. 36 // 37 // This behavior is tested in graph_build_apply_test.go to test ordering. 38 cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy 39 if !cbd { 40 // The "apply" side of a resource always depends on the destruction 41 // of all its dependencies in addition to the creation. 42 for _, v := range result { 43 result = append(result, v+".destroy") 44 } 45 } 46 47 return result 48 } 49 50 // GraphNodeEvalable 51 func (n *NodeApplyableResource) EvalTree() EvalNode { 52 addr := n.NodeAbstractResource.Addr 53 54 // stateId is the ID to put into the state 55 stateId := addr.stateId() 56 57 // Build the instance info. More of this will be populated during eval 58 info := &InstanceInfo{ 59 Id: stateId, 60 Type: addr.Type, 61 } 62 63 // Build the resource for eval 64 resource := &Resource{ 65 Name: addr.Name, 66 Type: addr.Type, 67 CountIndex: addr.Index, 68 } 69 if resource.CountIndex < 0 { 70 resource.CountIndex = 0 71 } 72 73 // Determine the dependencies for the state. We use some older 74 // code for this that we've used for a long time. 75 var stateDeps []string 76 { 77 oldN := &graphNodeExpandedResource{ 78 Resource: n.Config, 79 Index: addr.Index, 80 } 81 stateDeps = oldN.StateDependencies() 82 } 83 84 // Eval info is different depending on what kind of resource this is 85 switch n.Config.Mode { 86 case config.ManagedResourceMode: 87 return n.evalTreeManagedResource( 88 stateId, info, resource, stateDeps, 89 ) 90 case config.DataResourceMode: 91 return n.evalTreeDataResource( 92 stateId, info, resource, stateDeps) 93 default: 94 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) 95 } 96 } 97 98 func (n *NodeApplyableResource) evalTreeDataResource( 99 stateId string, info *InstanceInfo, 100 resource *Resource, stateDeps []string) EvalNode { 101 var provider ResourceProvider 102 var config *ResourceConfig 103 var diff *InstanceDiff 104 var state *InstanceState 105 106 return &EvalSequence{ 107 Nodes: []EvalNode{ 108 // Build the instance info 109 &EvalInstanceInfo{ 110 Info: info, 111 }, 112 113 // Get the saved diff for apply 114 &EvalReadDiff{ 115 Name: stateId, 116 Diff: &diff, 117 }, 118 119 // Stop here if we don't actually have a diff 120 &EvalIf{ 121 If: func(ctx EvalContext) (bool, error) { 122 if diff == nil { 123 return true, EvalEarlyExitError{} 124 } 125 126 if diff.GetAttributesLen() == 0 { 127 return true, EvalEarlyExitError{} 128 } 129 130 return true, nil 131 }, 132 Then: EvalNoop{}, 133 }, 134 135 // We need to re-interpolate the config here, rather than 136 // just using the diff's values directly, because we've 137 // potentially learned more variable values during the 138 // apply pass that weren't known when the diff was produced. 139 &EvalInterpolate{ 140 Config: n.Config.RawConfig.Copy(), 141 Resource: resource, 142 Output: &config, 143 }, 144 145 &EvalGetProvider{ 146 Name: n.ProvidedBy()[0], 147 Output: &provider, 148 }, 149 150 // Make a new diff with our newly-interpolated config. 151 &EvalReadDataDiff{ 152 Info: info, 153 Config: &config, 154 Previous: &diff, 155 Provider: &provider, 156 Output: &diff, 157 }, 158 159 &EvalReadDataApply{ 160 Info: info, 161 Diff: &diff, 162 Provider: &provider, 163 Output: &state, 164 }, 165 166 &EvalWriteState{ 167 Name: stateId, 168 ResourceType: n.Config.Type, 169 Provider: n.Config.Provider, 170 Dependencies: stateDeps, 171 State: &state, 172 }, 173 174 // Clear the diff now that we've applied it, so 175 // later nodes won't see a diff that's now a no-op. 176 &EvalWriteDiff{ 177 Name: stateId, 178 Diff: nil, 179 }, 180 181 &EvalUpdateStateHook{}, 182 }, 183 } 184 } 185 186 func (n *NodeApplyableResource) evalTreeManagedResource( 187 stateId string, info *InstanceInfo, 188 resource *Resource, stateDeps []string) EvalNode { 189 // Declare a bunch of variables that are used for state during 190 // evaluation. Most of this are written to by-address below. 191 var provider ResourceProvider 192 var diff, diffApply *InstanceDiff 193 var state *InstanceState 194 var resourceConfig *ResourceConfig 195 var err error 196 var createNew bool 197 var createBeforeDestroyEnabled bool 198 199 return &EvalSequence{ 200 Nodes: []EvalNode{ 201 // Build the instance info 202 &EvalInstanceInfo{ 203 Info: info, 204 }, 205 206 // Get the saved diff for apply 207 &EvalReadDiff{ 208 Name: stateId, 209 Diff: &diffApply, 210 }, 211 212 // We don't want to do any destroys 213 &EvalIf{ 214 If: func(ctx EvalContext) (bool, error) { 215 if diffApply == nil { 216 return true, EvalEarlyExitError{} 217 } 218 219 if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 { 220 return true, EvalEarlyExitError{} 221 } 222 223 diffApply.SetDestroy(false) 224 return true, nil 225 }, 226 Then: EvalNoop{}, 227 }, 228 229 &EvalIf{ 230 If: func(ctx EvalContext) (bool, error) { 231 destroy := false 232 if diffApply != nil { 233 destroy = diffApply.GetDestroy() || diffApply.RequiresNew() 234 } 235 236 createBeforeDestroyEnabled = 237 n.Config.Lifecycle.CreateBeforeDestroy && 238 destroy 239 240 return createBeforeDestroyEnabled, nil 241 }, 242 Then: &EvalDeposeState{ 243 Name: stateId, 244 }, 245 }, 246 247 &EvalInterpolate{ 248 Config: n.Config.RawConfig.Copy(), 249 Resource: resource, 250 Output: &resourceConfig, 251 }, 252 &EvalGetProvider{ 253 Name: n.ProvidedBy()[0], 254 Output: &provider, 255 }, 256 &EvalReadState{ 257 Name: stateId, 258 Output: &state, 259 }, 260 // Re-run validation to catch any errors we missed, e.g. type 261 // mismatches on computed values. 262 &EvalValidateResource{ 263 Provider: &provider, 264 Config: &resourceConfig, 265 ResourceName: n.Config.Name, 266 ResourceType: n.Config.Type, 267 ResourceMode: n.Config.Mode, 268 IgnoreWarnings: true, 269 }, 270 &EvalDiff{ 271 Info: info, 272 Config: &resourceConfig, 273 Resource: n.Config, 274 Provider: &provider, 275 Diff: &diffApply, 276 State: &state, 277 OutputDiff: &diffApply, 278 }, 279 280 // Get the saved diff 281 &EvalReadDiff{ 282 Name: stateId, 283 Diff: &diff, 284 }, 285 286 // Compare the diffs 287 &EvalCompareDiff{ 288 Info: info, 289 One: &diff, 290 Two: &diffApply, 291 }, 292 293 &EvalGetProvider{ 294 Name: n.ProvidedBy()[0], 295 Output: &provider, 296 }, 297 &EvalReadState{ 298 Name: stateId, 299 Output: &state, 300 }, 301 // Call pre-apply hook 302 &EvalApplyPre{ 303 Info: info, 304 State: &state, 305 Diff: &diffApply, 306 }, 307 &EvalApply{ 308 Info: info, 309 State: &state, 310 Diff: &diffApply, 311 Provider: &provider, 312 Output: &state, 313 Error: &err, 314 CreateNew: &createNew, 315 }, 316 &EvalWriteState{ 317 Name: stateId, 318 ResourceType: n.Config.Type, 319 Provider: n.Config.Provider, 320 Dependencies: stateDeps, 321 State: &state, 322 }, 323 &EvalApplyProvisioners{ 324 Info: info, 325 State: &state, 326 Resource: n.Config, 327 InterpResource: resource, 328 CreateNew: &createNew, 329 Error: &err, 330 When: config.ProvisionerWhenCreate, 331 }, 332 &EvalIf{ 333 If: func(ctx EvalContext) (bool, error) { 334 return createBeforeDestroyEnabled && err != nil, nil 335 }, 336 Then: &EvalUndeposeState{ 337 Name: stateId, 338 State: &state, 339 }, 340 Else: &EvalWriteState{ 341 Name: stateId, 342 ResourceType: n.Config.Type, 343 Provider: n.Config.Provider, 344 Dependencies: stateDeps, 345 State: &state, 346 }, 347 }, 348 349 // We clear the diff out here so that future nodes 350 // don't see a diff that is already complete. There 351 // is no longer a diff! 352 &EvalWriteDiff{ 353 Name: stateId, 354 Diff: nil, 355 }, 356 357 &EvalApplyPost{ 358 Info: info, 359 State: &state, 360 Error: &err, 361 }, 362 &EvalUpdateStateHook{}, 363 }, 364 } 365 }