6 "github.com/hashicorp/terraform/config"
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 {
16 func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
17 return n.NodeAbstractResource.Addr
20 // GraphNodeReferencer, overriding NodeAbstractResource
21 func (n *NodeApplyableResource) References() []string {
22 result := n.NodeAbstractResource.References()
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.
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.
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.
37 // This behavior is tested in graph_build_apply_test.go to test ordering.
38 cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy
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")
51 func (n *NodeApplyableResource) EvalTree() EvalNode {
52 addr := n.NodeAbstractResource.Addr
54 // stateId is the ID to put into the state
55 stateId := addr.stateId()
57 // Build the instance info. More of this will be populated during eval
58 info := &InstanceInfo{
63 // Build the resource for eval
64 resource := &Resource{
67 CountIndex: addr.Index,
69 if resource.CountIndex < 0 {
70 resource.CountIndex = 0
73 // Determine the dependencies for the state.
74 stateDeps := n.StateReferences()
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,
82 case config.DataResourceMode:
83 return n.evalTreeDataResource(
84 stateId, info, resource, stateDeps)
86 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
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
100 // Build the instance info
105 // Get the saved diff for apply
111 // Stop here if we don't actually have a diff
113 If: func(ctx EvalContext) (bool, error) {
115 return true, EvalEarlyExitError{}
118 if diff.GetAttributesLen() == 0 {
119 return true, EvalEarlyExitError{}
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.
132 Config: n.Config.RawConfig.Copy(),
138 Name: n.ProvidedBy()[0],
142 // Make a new diff with our newly-interpolated config.
160 ResourceType: n.Config.Type,
161 Provider: n.Config.Provider,
162 Dependencies: stateDeps,
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.
173 &EvalUpdateStateHook{},
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
189 var createBeforeDestroyEnabled bool
191 return &EvalSequence{
193 // Build the instance info
198 // Get the saved diff for apply
204 // We don't want to do any destroys
206 If: func(ctx EvalContext) (bool, error) {
207 if diffApply == nil {
208 return true, EvalEarlyExitError{}
211 if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
212 return true, EvalEarlyExitError{}
215 diffApply.SetDestroy(false)
222 If: func(ctx EvalContext) (bool, error) {
224 if diffApply != nil {
225 destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
228 createBeforeDestroyEnabled =
229 n.Config.Lifecycle.CreateBeforeDestroy &&
232 return createBeforeDestroyEnabled, nil
234 Then: &EvalDeposeState{
240 Config: n.Config.RawConfig.Copy(),
242 Output: &resourceConfig,
245 Name: n.ProvidedBy()[0],
252 // Re-run validation to catch any errors we missed, e.g. type
253 // mismatches on computed values.
254 &EvalValidateResource{
256 Config: &resourceConfig,
257 ResourceName: n.Config.Name,
258 ResourceType: n.Config.Type,
259 ResourceMode: n.Config.Mode,
260 IgnoreWarnings: true,
264 Config: &resourceConfig,
269 OutputDiff: &diffApply,
272 // Get the saved diff
286 Name: n.ProvidedBy()[0],
293 // Call pre-apply hook
306 CreateNew: &createNew,
310 ResourceType: n.Config.Type,
311 Provider: n.Config.Provider,
312 Dependencies: stateDeps,
315 &EvalApplyProvisioners{
319 InterpResource: resource,
320 CreateNew: &createNew,
322 When: config.ProvisionerWhenCreate,
325 If: func(ctx EvalContext) (bool, error) {
326 return createBeforeDestroyEnabled && err != nil, nil
328 Then: &EvalUndeposeState{
332 Else: &EvalWriteState{
334 ResourceType: n.Config.Type,
335 Provider: n.Config.Provider,
336 Dependencies: stateDeps,
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!
354 &EvalUpdateStateHook{},