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