github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/node_resource_refresh.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform-plugin-sdk/internal/plans" 8 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 9 10 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 11 12 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/dag" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 15 ) 16 17 // NodeRefreshableManagedResource represents a resource that is expanabled into 18 // NodeRefreshableManagedResourceInstance. Resource count orphans are also added. 19 type NodeRefreshableManagedResource struct { 20 *NodeAbstractResource 21 } 22 23 var ( 24 _ GraphNodeSubPath = (*NodeRefreshableManagedResource)(nil) 25 _ GraphNodeDynamicExpandable = (*NodeRefreshableManagedResource)(nil) 26 _ GraphNodeReferenceable = (*NodeRefreshableManagedResource)(nil) 27 _ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil) 28 _ GraphNodeResource = (*NodeRefreshableManagedResource)(nil) 29 _ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil) 30 ) 31 32 // GraphNodeDynamicExpandable 33 func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) { 34 var diags tfdiags.Diagnostics 35 36 count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx) 37 diags = diags.Append(countDiags) 38 if countDiags.HasErrors() { 39 return nil, diags.Err() 40 } 41 42 forEachMap, forEachDiags := evaluateResourceForEachExpression(n.Config.ForEach, ctx) 43 if forEachDiags.HasErrors() { 44 return nil, diags.Err() 45 } 46 47 // Next we need to potentially rename an instance address in the state 48 // if we're transitioning whether "count" is set at all. 49 fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1) 50 51 // Our graph transformers require access to the full state, so we'll 52 // temporarily lock it while we work on this. 53 state := ctx.State().Lock() 54 defer ctx.State().Unlock() 55 56 // The concrete resource factory we'll use 57 concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex { 58 // Add the config and state since we don't do that via transforms 59 a.Config = n.Config 60 a.ResolvedProvider = n.ResolvedProvider 61 62 return &NodeRefreshableManagedResourceInstance{ 63 NodeAbstractResourceInstance: a, 64 } 65 } 66 67 // Start creating the steps 68 steps := []GraphTransformer{ 69 // Expand the count. 70 &ResourceCountTransformer{ 71 Concrete: concreteResource, 72 Schema: n.Schema, 73 Count: count, 74 ForEach: forEachMap, 75 Addr: n.ResourceAddr(), 76 }, 77 78 // Add the count orphans to make sure these resources are accounted for 79 // during a scale in. 80 &OrphanResourceCountTransformer{ 81 Concrete: concreteResource, 82 Count: count, 83 ForEach: forEachMap, 84 Addr: n.ResourceAddr(), 85 State: state, 86 }, 87 88 // Attach the state 89 &AttachStateTransformer{State: state}, 90 91 // Targeting 92 &TargetsTransformer{Targets: n.Targets}, 93 94 // Connect references so ordering is correct 95 &ReferenceTransformer{}, 96 97 // Make sure there is a single root 98 &RootTransformer{}, 99 } 100 101 // Build the graph 102 b := &BasicGraphBuilder{ 103 Steps: steps, 104 Validate: true, 105 Name: "NodeRefreshableManagedResource", 106 } 107 108 graph, diags := b.Build(ctx.Path()) 109 return graph, diags.ErrWithWarnings() 110 } 111 112 // NodeRefreshableManagedResourceInstance represents a resource that is "applyable": 113 // it is ready to be applied and is represented by a diff. 114 type NodeRefreshableManagedResourceInstance struct { 115 *NodeAbstractResourceInstance 116 } 117 118 var ( 119 _ GraphNodeSubPath = (*NodeRefreshableManagedResourceInstance)(nil) 120 _ GraphNodeReferenceable = (*NodeRefreshableManagedResourceInstance)(nil) 121 _ GraphNodeReferencer = (*NodeRefreshableManagedResourceInstance)(nil) 122 _ GraphNodeDestroyer = (*NodeRefreshableManagedResourceInstance)(nil) 123 _ GraphNodeResource = (*NodeRefreshableManagedResourceInstance)(nil) 124 _ GraphNodeResourceInstance = (*NodeRefreshableManagedResourceInstance)(nil) 125 _ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResourceInstance)(nil) 126 _ GraphNodeAttachResourceState = (*NodeRefreshableManagedResourceInstance)(nil) 127 _ GraphNodeEvalable = (*NodeRefreshableManagedResourceInstance)(nil) 128 ) 129 130 // GraphNodeDestroyer 131 func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance { 132 addr := n.ResourceInstanceAddr() 133 return &addr 134 } 135 136 // GraphNodeEvalable 137 func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode { 138 addr := n.ResourceInstanceAddr() 139 140 // Eval info is different depending on what kind of resource this is 141 switch addr.Resource.Resource.Mode { 142 case addrs.ManagedResourceMode: 143 if n.ResourceState == nil { 144 log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s has no existing state to refresh", addr) 145 return n.evalTreeManagedResourceNoState() 146 } 147 log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s will be refreshed", addr) 148 return n.evalTreeManagedResource() 149 150 case addrs.DataResourceMode: 151 // Get the data source node. If we don't have a configuration 152 // then it is an orphan so we destroy it (remove it from the state). 153 var dn GraphNodeEvalable 154 if n.Config != nil { 155 dn = &NodeRefreshableDataResourceInstance{ 156 NodeAbstractResourceInstance: n.NodeAbstractResourceInstance, 157 } 158 } else { 159 dn = &NodeDestroyableDataResourceInstance{ 160 NodeAbstractResourceInstance: n.NodeAbstractResourceInstance, 161 } 162 } 163 164 return dn.EvalTree() 165 default: 166 panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode)) 167 } 168 } 169 170 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode { 171 addr := n.ResourceInstanceAddr() 172 173 // Declare a bunch of variables that are used for state during 174 // evaluation. Most of this are written to by-address below. 175 var provider providers.Interface 176 var providerSchema *ProviderSchema 177 var state *states.ResourceInstanceObject 178 179 // This happened during initial development. All known cases were 180 // fixed and tested but as a sanity check let's assert here. 181 if n.ResourceState == nil { 182 err := fmt.Errorf( 183 "No resource state attached for addr: %s\n\n"+ 184 "This is a bug. Please report this to Terraform with your configuration\n"+ 185 "and state attached. Please be careful to scrub any sensitive information.", 186 addr) 187 return &EvalReturnError{Error: &err} 188 } 189 190 return &EvalSequence{ 191 Nodes: []EvalNode{ 192 &EvalGetProvider{ 193 Addr: n.ResolvedProvider, 194 Output: &provider, 195 Schema: &providerSchema, 196 }, 197 198 &EvalReadState{ 199 Addr: addr.Resource, 200 Provider: &provider, 201 ProviderSchema: &providerSchema, 202 203 Output: &state, 204 }, 205 206 &EvalRefresh{ 207 Addr: addr.Resource, 208 ProviderAddr: n.ResolvedProvider, 209 Provider: &provider, 210 ProviderSchema: &providerSchema, 211 State: &state, 212 Output: &state, 213 }, 214 215 &EvalWriteState{ 216 Addr: addr.Resource, 217 ProviderAddr: n.ResolvedProvider, 218 ProviderSchema: &providerSchema, 219 State: &state, 220 }, 221 }, 222 } 223 } 224 225 // evalTreeManagedResourceNoState produces an EvalSequence for refresh resource 226 // nodes that don't have state attached. An example of where this functionality 227 // is useful is when a resource that already exists in state is being scaled 228 // out, ie: has its resource count increased. In this case, the scaled out node 229 // needs to be available to other nodes (namely data sources) that may depend 230 // on it for proper interpolation, or confusing "index out of range" errors can 231 // occur. 232 // 233 // The steps in this sequence are very similar to the steps carried out in 234 // plan, but nothing is done with the diff after it is created - it is dropped, 235 // and its changes are not counted in the UI. 236 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode { 237 addr := n.ResourceInstanceAddr() 238 239 // Declare a bunch of variables that are used for state during 240 // evaluation. Most of this are written to by-address below. 241 var provider providers.Interface 242 var providerSchema *ProviderSchema 243 var change *plans.ResourceInstanceChange 244 var state *states.ResourceInstanceObject 245 246 return &EvalSequence{ 247 Nodes: []EvalNode{ 248 &EvalGetProvider{ 249 Addr: n.ResolvedProvider, 250 Output: &provider, 251 Schema: &providerSchema, 252 }, 253 254 &EvalReadState{ 255 Addr: addr.Resource, 256 Provider: &provider, 257 ProviderSchema: &providerSchema, 258 259 Output: &state, 260 }, 261 262 &EvalDiff{ 263 Addr: addr.Resource, 264 Config: n.Config, 265 Provider: &provider, 266 ProviderAddr: n.ResolvedProvider, 267 ProviderSchema: &providerSchema, 268 State: &state, 269 OutputChange: &change, 270 OutputState: &state, 271 Stub: true, 272 }, 273 274 &EvalWriteState{ 275 Addr: addr.Resource, 276 ProviderAddr: n.ResolvedProvider, 277 ProviderSchema: &providerSchema, 278 State: &state, 279 }, 280 281 // We must also save the planned change, so that expressions in 282 // other nodes, such as provider configurations and data resources, 283 // can work with the planned new value. 284 // 285 // This depends on the fact that Context.Refresh creates a 286 // temporary new empty changeset for the duration of its graph 287 // walk, and so this recorded change will be discarded immediately 288 // after the refresh walk completes. 289 &EvalWriteDiff{ 290 Addr: addr.Resource, 291 Change: &change, 292 ProviderSchema: &providerSchema, 293 }, 294 }, 295 } 296 }