github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/terraform/node_data_refresh.go (about) 1 package terraform 2 3 import ( 4 "github.com/hashicorp/terraform/dag" 5 ) 6 7 // NodeRefreshableDataResource represents a resource that is "plannable": 8 // it is ready to be planned in order to create a diff. 9 type NodeRefreshableDataResource struct { 10 *NodeAbstractCountResource 11 } 12 13 // GraphNodeDynamicExpandable 14 func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) { 15 // Grab the state which we read 16 state, lock := ctx.State() 17 lock.RLock() 18 defer lock.RUnlock() 19 20 // Expand the resource count which must be available by now from EvalTree 21 count, err := n.Config.Count() 22 if err != nil { 23 return nil, err 24 } 25 26 // The concrete resource factory we'll use 27 concreteResource := func(a *NodeAbstractResource) dag.Vertex { 28 // Add the config and state since we don't do that via transforms 29 a.Config = n.Config 30 31 return &NodeRefreshableDataResourceInstance{ 32 NodeAbstractResource: a, 33 } 34 } 35 36 // We also need a destroyable resource for orphans that are a result of a 37 // scaled-in count. 38 concreteResourceDestroyable := func(a *NodeAbstractResource) dag.Vertex { 39 // Add the config since we don't do that via transforms 40 a.Config = n.Config 41 42 return &NodeDestroyableDataResource{ 43 NodeAbstractResource: a, 44 } 45 } 46 47 // Start creating the steps 48 steps := []GraphTransformer{ 49 // Expand the count. 50 &ResourceCountTransformer{ 51 Concrete: concreteResource, 52 Count: count, 53 Addr: n.ResourceAddr(), 54 }, 55 56 // Add the count orphans. As these are orphaned refresh nodes, we add them 57 // directly as NodeDestroyableDataResource. 58 &OrphanResourceCountTransformer{ 59 Concrete: concreteResourceDestroyable, 60 Count: count, 61 Addr: n.ResourceAddr(), 62 State: state, 63 }, 64 65 // Attach the state 66 &AttachStateTransformer{State: state}, 67 68 // Targeting 69 &TargetsTransformer{ParsedTargets: n.Targets}, 70 71 // Connect references so ordering is correct 72 &ReferenceTransformer{}, 73 74 // Make sure there is a single root 75 &RootTransformer{}, 76 } 77 78 // Build the graph 79 b := &BasicGraphBuilder{ 80 Steps: steps, 81 Validate: true, 82 Name: "NodeRefreshableDataResource", 83 } 84 85 return b.Build(ctx.Path()) 86 } 87 88 // NodeRefreshableDataResourceInstance represents a _single_ resource instance 89 // that is refreshable. 90 type NodeRefreshableDataResourceInstance struct { 91 *NodeAbstractResource 92 } 93 94 // GraphNodeEvalable 95 func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode { 96 addr := n.NodeAbstractResource.Addr 97 98 // stateId is the ID to put into the state 99 stateId := addr.stateId() 100 101 // Build the instance info. More of this will be populated during eval 102 info := &InstanceInfo{ 103 Id: stateId, 104 Type: addr.Type, 105 } 106 107 // Get the state if we have it, if not we build it 108 rs := n.ResourceState 109 if rs == nil { 110 rs = &ResourceState{} 111 } 112 113 // If the config isn't empty we update the state 114 if n.Config != nil { 115 rs = &ResourceState{ 116 Type: n.Config.Type, 117 Provider: n.Config.Provider, 118 Dependencies: n.StateReferences(), 119 } 120 } 121 122 // Build the resource for eval 123 resource := &Resource{ 124 Name: addr.Name, 125 Type: addr.Type, 126 CountIndex: addr.Index, 127 } 128 if resource.CountIndex < 0 { 129 resource.CountIndex = 0 130 } 131 132 // Declare a bunch of variables that are used for state during 133 // evaluation. Most of this are written to by-address below. 134 var config *ResourceConfig 135 var diff *InstanceDiff 136 var provider ResourceProvider 137 var state *InstanceState 138 139 return &EvalSequence{ 140 Nodes: []EvalNode{ 141 // Always destroy the existing state first, since we must 142 // make sure that values from a previous read will not 143 // get interpolated if we end up needing to defer our 144 // loading until apply time. 145 &EvalWriteState{ 146 Name: stateId, 147 ResourceType: rs.Type, 148 Provider: rs.Provider, 149 Dependencies: rs.Dependencies, 150 State: &state, // state is nil here 151 }, 152 153 &EvalInterpolate{ 154 Config: n.Config.RawConfig.Copy(), 155 Resource: resource, 156 Output: &config, 157 }, 158 159 // The rest of this pass can proceed only if there are no 160 // computed values in our config. 161 // (If there are, we'll deal with this during the plan and 162 // apply phases.) 163 &EvalIf{ 164 If: func(ctx EvalContext) (bool, error) { 165 if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 { 166 return true, EvalEarlyExitError{} 167 } 168 169 // If the config explicitly has a depends_on for this 170 // data source, assume the intention is to prevent 171 // refreshing ahead of that dependency. 172 if len(n.Config.DependsOn) > 0 { 173 return true, EvalEarlyExitError{} 174 } 175 176 return true, nil 177 }, 178 179 Then: EvalNoop{}, 180 }, 181 182 // The remainder of this pass is the same as running 183 // a "plan" pass immediately followed by an "apply" pass, 184 // populating the state early so it'll be available to 185 // provider configurations that need this data during 186 // refresh/plan. 187 &EvalGetProvider{ 188 Name: n.ProvidedBy()[0], 189 Output: &provider, 190 }, 191 192 &EvalReadDataDiff{ 193 Info: info, 194 Config: &config, 195 Provider: &provider, 196 Output: &diff, 197 OutputState: &state, 198 }, 199 200 &EvalReadDataApply{ 201 Info: info, 202 Diff: &diff, 203 Provider: &provider, 204 Output: &state, 205 }, 206 207 &EvalWriteState{ 208 Name: stateId, 209 ResourceType: rs.Type, 210 Provider: rs.Provider, 211 Dependencies: rs.Dependencies, 212 State: &state, 213 }, 214 215 &EvalUpdateStateHook{}, 216 }, 217 } 218 }