github.com/spirius/terraform@v0.10.0-beta2.0.20170714185654-87b2c0cf8fea/terraform/node_resource_refresh.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform/config" 7 "github.com/hashicorp/terraform/dag" 8 ) 9 10 // NodeRefreshableManagedResource represents a resource that is expanabled into 11 // NodeRefreshableManagedResourceInstance. Resource count orphans are also added. 12 type NodeRefreshableManagedResource struct { 13 *NodeAbstractCountResource 14 } 15 16 // GraphNodeDynamicExpandable 17 func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) { 18 // Grab the state which we read 19 state, lock := ctx.State() 20 lock.RLock() 21 defer lock.RUnlock() 22 23 // Expand the resource count which must be available by now from EvalTree 24 count, err := n.Config.Count() 25 if err != nil { 26 return nil, err 27 } 28 29 // The concrete resource factory we'll use 30 concreteResource := func(a *NodeAbstractResource) dag.Vertex { 31 // Add the config and state since we don't do that via transforms 32 a.Config = n.Config 33 34 return &NodeRefreshableManagedResourceInstance{ 35 NodeAbstractResource: a, 36 } 37 } 38 39 // Start creating the steps 40 steps := []GraphTransformer{ 41 // Expand the count. 42 &ResourceCountTransformer{ 43 Concrete: concreteResource, 44 Count: count, 45 Addr: n.ResourceAddr(), 46 }, 47 48 // Add the count orphans to make sure these resources are accounted for 49 // during a scale in. 50 &OrphanResourceCountTransformer{ 51 Concrete: concreteResource, 52 Count: count, 53 Addr: n.ResourceAddr(), 54 State: state, 55 }, 56 57 // Attach the state 58 &AttachStateTransformer{State: state}, 59 60 // Targeting 61 &TargetsTransformer{ParsedTargets: n.Targets}, 62 63 // Connect references so ordering is correct 64 &ReferenceTransformer{}, 65 66 // Make sure there is a single root 67 &RootTransformer{}, 68 } 69 70 // Build the graph 71 b := &BasicGraphBuilder{ 72 Steps: steps, 73 Validate: true, 74 Name: "NodeRefreshableManagedResource", 75 } 76 77 return b.Build(ctx.Path()) 78 } 79 80 // NodeRefreshableManagedResourceInstance represents a resource that is "applyable": 81 // it is ready to be applied and is represented by a diff. 82 type NodeRefreshableManagedResourceInstance struct { 83 *NodeAbstractResource 84 } 85 86 // GraphNodeDestroyer 87 func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *ResourceAddress { 88 return n.Addr 89 } 90 91 // GraphNodeEvalable 92 func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode { 93 // Eval info is different depending on what kind of resource this is 94 switch mode := n.Addr.Mode; mode { 95 case config.ManagedResourceMode: 96 if n.ResourceState == nil { 97 return n.evalTreeManagedResourceNoState() 98 } 99 return n.evalTreeManagedResource() 100 101 case config.DataResourceMode: 102 // Get the data source node. If we don't have a configuration 103 // then it is an orphan so we destroy it (remove it from the state). 104 var dn GraphNodeEvalable 105 if n.Config != nil { 106 dn = &NodeRefreshableDataResourceInstance{ 107 NodeAbstractResource: n.NodeAbstractResource, 108 } 109 } else { 110 dn = &NodeDestroyableDataResource{ 111 NodeAbstractResource: n.NodeAbstractResource, 112 } 113 } 114 115 return dn.EvalTree() 116 default: 117 panic(fmt.Errorf("unsupported resource mode %s", mode)) 118 } 119 } 120 121 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode { 122 addr := n.NodeAbstractResource.Addr 123 124 // stateId is the ID to put into the state 125 stateId := addr.stateId() 126 127 // Build the instance info. More of this will be populated during eval 128 info := &InstanceInfo{ 129 Id: stateId, 130 Type: addr.Type, 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 provider ResourceProvider 136 var state *InstanceState 137 138 // This happened during initial development. All known cases were 139 // fixed and tested but as a sanity check let's assert here. 140 if n.ResourceState == nil { 141 err := fmt.Errorf( 142 "No resource state attached for addr: %s\n\n"+ 143 "This is a bug. Please report this to Terraform with your configuration\n"+ 144 "and state attached. Please be careful to scrub any sensitive information.", 145 addr) 146 return &EvalReturnError{Error: &err} 147 } 148 149 return &EvalSequence{ 150 Nodes: []EvalNode{ 151 &EvalGetProvider{ 152 Name: n.ProvidedBy()[0], 153 Output: &provider, 154 }, 155 &EvalReadState{ 156 Name: stateId, 157 Output: &state, 158 }, 159 &EvalRefresh{ 160 Info: info, 161 Provider: &provider, 162 State: &state, 163 Output: &state, 164 }, 165 &EvalWriteState{ 166 Name: stateId, 167 ResourceType: n.ResourceState.Type, 168 Provider: n.ResourceState.Provider, 169 Dependencies: n.ResourceState.Dependencies, 170 State: &state, 171 }, 172 }, 173 } 174 } 175 176 // evalTreeManagedResourceNoState produces an EvalSequence for refresh resource 177 // nodes that don't have state attached. An example of where this functionality 178 // is useful is when a resource that already exists in state is being scaled 179 // out, ie: has its resource count increased. In this case, the scaled out node 180 // needs to be available to other nodes (namely data sources) that may depend 181 // on it for proper interpolation, or confusing "index out of range" errors can 182 // occur. 183 // 184 // The steps in this sequence are very similar to the steps carried out in 185 // plan, but nothing is done with the diff after it is created - it is dropped, 186 // and its changes are not counted in the UI. 187 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode { 188 // Declare a bunch of variables that are used for state during 189 // evaluation. Most of this are written to by-address below. 190 var provider ResourceProvider 191 var state *InstanceState 192 var resourceConfig *ResourceConfig 193 194 addr := n.NodeAbstractResource.Addr 195 stateID := addr.stateId() 196 info := &InstanceInfo{ 197 Id: stateID, 198 Type: addr.Type, 199 ModulePath: normalizeModulePath(addr.Path), 200 } 201 202 // Build the resource for eval 203 resource := &Resource{ 204 Name: addr.Name, 205 Type: addr.Type, 206 CountIndex: addr.Index, 207 } 208 if resource.CountIndex < 0 { 209 resource.CountIndex = 0 210 } 211 212 // Determine the dependencies for the state. 213 stateDeps := n.StateReferences() 214 215 return &EvalSequence{ 216 Nodes: []EvalNode{ 217 &EvalInterpolate{ 218 Config: n.Config.RawConfig.Copy(), 219 Resource: resource, 220 Output: &resourceConfig, 221 }, 222 &EvalGetProvider{ 223 Name: n.ProvidedBy()[0], 224 Output: &provider, 225 }, 226 // Re-run validation to catch any errors we missed, e.g. type 227 // mismatches on computed values. 228 &EvalValidateResource{ 229 Provider: &provider, 230 Config: &resourceConfig, 231 ResourceName: n.Config.Name, 232 ResourceType: n.Config.Type, 233 ResourceMode: n.Config.Mode, 234 IgnoreWarnings: true, 235 }, 236 &EvalReadState{ 237 Name: stateID, 238 Output: &state, 239 }, 240 &EvalDiff{ 241 Name: stateID, 242 Info: info, 243 Config: &resourceConfig, 244 Resource: n.Config, 245 Provider: &provider, 246 State: &state, 247 OutputState: &state, 248 Stub: true, 249 }, 250 &EvalWriteState{ 251 Name: stateID, 252 ResourceType: n.Config.Type, 253 Provider: n.Config.Provider, 254 Dependencies: stateDeps, 255 State: &state, 256 }, 257 }, 258 } 259 }