github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/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. 74 stateDeps := n.StateReferences() 75 76 // Eval info is different depending on what kind of resource this is 77 switch n.Config.Mode { 78 case config.ManagedResourceMode: 79 return n.evalTreeManagedResource( 80 stateId, info, resource, stateDeps, 81 ) 82 case config.DataResourceMode: 83 return n.evalTreeDataResource( 84 stateId, info, resource, stateDeps) 85 default: 86 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) 87 } 88 } 89 90 func (n *NodeApplyableResource) evalTreeDataResource( 91 stateId string, info *InstanceInfo, 92 resource *Resource, stateDeps []string) EvalNode { 93 var provider ResourceProvider 94 var config *ResourceConfig 95 var diff *InstanceDiff 96 var state *InstanceState 97 98 return &EvalSequence{ 99 Nodes: []EvalNode{ 100 // Build the instance info 101 &EvalInstanceInfo{ 102 Info: info, 103 }, 104 105 // Get the saved diff for apply 106 &EvalReadDiff{ 107 Name: stateId, 108 Diff: &diff, 109 }, 110 111 // Stop here if we don't actually have a diff 112 &EvalIf{ 113 If: func(ctx EvalContext) (bool, error) { 114 if diff == nil { 115 return true, EvalEarlyExitError{} 116 } 117 118 if diff.GetAttributesLen() == 0 { 119 return true, EvalEarlyExitError{} 120 } 121 122 return true, nil 123 }, 124 Then: EvalNoop{}, 125 }, 126 127 // We need to re-interpolate the config here, rather than 128 // just using the diff's values directly, because we've 129 // potentially learned more variable values during the 130 // apply pass that weren't known when the diff was produced. 131 &EvalInterpolate{ 132 Config: n.Config.RawConfig.Copy(), 133 Resource: resource, 134 Output: &config, 135 }, 136 137 &EvalGetProvider{ 138 Name: n.ProvidedBy()[0], 139 Output: &provider, 140 }, 141 142 // Make a new diff with our newly-interpolated config. 143 &EvalReadDataDiff{ 144 Info: info, 145 Config: &config, 146 Previous: &diff, 147 Provider: &provider, 148 Output: &diff, 149 }, 150 151 &EvalReadDataApply{ 152 Info: info, 153 Diff: &diff, 154 Provider: &provider, 155 Output: &state, 156 }, 157 158 &EvalWriteState{ 159 Name: stateId, 160 ResourceType: n.Config.Type, 161 Provider: n.Config.Provider, 162 Dependencies: stateDeps, 163 State: &state, 164 }, 165 166 // Clear the diff now that we've applied it, so 167 // later nodes won't see a diff that's now a no-op. 168 &EvalWriteDiff{ 169 Name: stateId, 170 Diff: nil, 171 }, 172 173 &EvalUpdateStateHook{}, 174 }, 175 } 176 } 177 178 func (n *NodeApplyableResource) evalTreeManagedResource( 179 stateId string, info *InstanceInfo, 180 resource *Resource, stateDeps []string) EvalNode { 181 // Declare a bunch of variables that are used for state during 182 // evaluation. Most of this are written to by-address below. 183 var provider ResourceProvider 184 var diff, diffApply *InstanceDiff 185 var state *InstanceState 186 var resourceConfig *ResourceConfig 187 var err error 188 var createNew bool 189 var createBeforeDestroyEnabled bool 190 191 return &EvalSequence{ 192 Nodes: []EvalNode{ 193 // Build the instance info 194 &EvalInstanceInfo{ 195 Info: info, 196 }, 197 198 // Get the saved diff for apply 199 &EvalReadDiff{ 200 Name: stateId, 201 Diff: &diffApply, 202 }, 203 204 // We don't want to do any destroys 205 &EvalIf{ 206 If: func(ctx EvalContext) (bool, error) { 207 if diffApply == nil { 208 return true, EvalEarlyExitError{} 209 } 210 211 if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 { 212 return true, EvalEarlyExitError{} 213 } 214 215 diffApply.SetDestroy(false) 216 return true, nil 217 }, 218 Then: EvalNoop{}, 219 }, 220 221 &EvalIf{ 222 If: func(ctx EvalContext) (bool, error) { 223 destroy := false 224 if diffApply != nil { 225 destroy = diffApply.GetDestroy() || diffApply.RequiresNew() 226 } 227 228 createBeforeDestroyEnabled = 229 n.Config.Lifecycle.CreateBeforeDestroy && 230 destroy 231 232 return createBeforeDestroyEnabled, nil 233 }, 234 Then: &EvalDeposeState{ 235 Name: stateId, 236 }, 237 }, 238 239 &EvalInterpolate{ 240 Config: n.Config.RawConfig.Copy(), 241 Resource: resource, 242 Output: &resourceConfig, 243 }, 244 &EvalGetProvider{ 245 Name: n.ProvidedBy()[0], 246 Output: &provider, 247 }, 248 &EvalReadState{ 249 Name: stateId, 250 Output: &state, 251 }, 252 // Re-run validation to catch any errors we missed, e.g. type 253 // mismatches on computed values. 254 &EvalValidateResource{ 255 Provider: &provider, 256 Config: &resourceConfig, 257 ResourceName: n.Config.Name, 258 ResourceType: n.Config.Type, 259 ResourceMode: n.Config.Mode, 260 IgnoreWarnings: true, 261 }, 262 &EvalDiff{ 263 Info: info, 264 Config: &resourceConfig, 265 Resource: n.Config, 266 Provider: &provider, 267 Diff: &diffApply, 268 State: &state, 269 OutputDiff: &diffApply, 270 }, 271 272 // Get the saved diff 273 &EvalReadDiff{ 274 Name: stateId, 275 Diff: &diff, 276 }, 277 278 // Compare the diffs 279 &EvalCompareDiff{ 280 Info: info, 281 One: &diff, 282 Two: &diffApply, 283 }, 284 285 &EvalGetProvider{ 286 Name: n.ProvidedBy()[0], 287 Output: &provider, 288 }, 289 &EvalReadState{ 290 Name: stateId, 291 Output: &state, 292 }, 293 // Call pre-apply hook 294 &EvalApplyPre{ 295 Info: info, 296 State: &state, 297 Diff: &diffApply, 298 }, 299 &EvalApply{ 300 Info: info, 301 State: &state, 302 Diff: &diffApply, 303 Provider: &provider, 304 Output: &state, 305 Error: &err, 306 CreateNew: &createNew, 307 }, 308 &EvalWriteState{ 309 Name: stateId, 310 ResourceType: n.Config.Type, 311 Provider: n.Config.Provider, 312 Dependencies: stateDeps, 313 State: &state, 314 }, 315 &EvalApplyProvisioners{ 316 Info: info, 317 State: &state, 318 Resource: n.Config, 319 InterpResource: resource, 320 CreateNew: &createNew, 321 Error: &err, 322 When: config.ProvisionerWhenCreate, 323 }, 324 &EvalIf{ 325 If: func(ctx EvalContext) (bool, error) { 326 return createBeforeDestroyEnabled && err != nil, nil 327 }, 328 Then: &EvalUndeposeState{ 329 Name: stateId, 330 State: &state, 331 }, 332 Else: &EvalWriteState{ 333 Name: stateId, 334 ResourceType: n.Config.Type, 335 Provider: n.Config.Provider, 336 Dependencies: stateDeps, 337 State: &state, 338 }, 339 }, 340 341 // We clear the diff out here so that future nodes 342 // don't see a diff that is already complete. There 343 // is no longer a diff! 344 &EvalWriteDiff{ 345 Name: stateId, 346 Diff: nil, 347 }, 348 349 &EvalApplyPost{ 350 Info: info, 351 State: &state, 352 Error: &err, 353 }, 354 &EvalUpdateStateHook{}, 355 }, 356 } 357 }