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