github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/node_data_refresh.go (about) 1 package terraform 2 3 import ( 4 "github.com/hashicorp/terraform-plugin-sdk/internal/dag" 5 "github.com/hashicorp/terraform-plugin-sdk/internal/plans" 6 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 7 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 8 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 9 "github.com/zclconf/go-cty/cty" 10 ) 11 12 // NodeRefreshableDataResource represents a resource that is "refreshable". 13 type NodeRefreshableDataResource struct { 14 *NodeAbstractResource 15 } 16 17 var ( 18 _ GraphNodeSubPath = (*NodeRefreshableDataResource)(nil) 19 _ GraphNodeDynamicExpandable = (*NodeRefreshableDataResource)(nil) 20 _ GraphNodeReferenceable = (*NodeRefreshableDataResource)(nil) 21 _ GraphNodeReferencer = (*NodeRefreshableDataResource)(nil) 22 _ GraphNodeResource = (*NodeRefreshableDataResource)(nil) 23 _ GraphNodeAttachResourceConfig = (*NodeRefreshableDataResource)(nil) 24 ) 25 26 // GraphNodeDynamicExpandable 27 func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) { 28 var diags tfdiags.Diagnostics 29 30 count, countKnown, countDiags := evaluateResourceCountExpressionKnown(n.Config.Count, ctx) 31 diags = diags.Append(countDiags) 32 if countDiags.HasErrors() { 33 return nil, diags.Err() 34 } 35 if !countKnown { 36 // If the count isn't known yet, we'll skip refreshing and try expansion 37 // again during the plan walk. 38 return nil, nil 39 } 40 41 forEachMap, forEachKnown, forEachDiags := evaluateResourceForEachExpressionKnown(n.Config.ForEach, ctx) 42 diags = diags.Append(forEachDiags) 43 if forEachDiags.HasErrors() { 44 return nil, diags.Err() 45 } 46 if !forEachKnown { 47 // If the for_each isn't known yet, we'll skip refreshing and try expansion 48 // again during the plan walk. 49 return nil, nil 50 } 51 52 // Next we need to potentially rename an instance address in the state 53 // if we're transitioning whether "count" is set at all. 54 fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1) 55 56 // Our graph transformers require access to the full state, so we'll 57 // temporarily lock it while we work on this. 58 state := ctx.State().Lock() 59 defer ctx.State().Unlock() 60 61 // The concrete resource factory we'll use 62 concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex { 63 // Add the config and state since we don't do that via transforms 64 a.Config = n.Config 65 a.ResolvedProvider = n.ResolvedProvider 66 67 return &NodeRefreshableDataResourceInstance{ 68 NodeAbstractResourceInstance: a, 69 } 70 } 71 72 // We also need a destroyable resource for orphans that are a result of a 73 // scaled-in count. 74 concreteResourceDestroyable := func(a *NodeAbstractResourceInstance) dag.Vertex { 75 // Add the config and provider since we don't do that via transforms 76 a.Config = n.Config 77 a.ResolvedProvider = n.ResolvedProvider 78 79 return &NodeDestroyableDataResourceInstance{ 80 NodeAbstractResourceInstance: a, 81 } 82 } 83 84 // Start creating the steps 85 steps := []GraphTransformer{ 86 // Expand the count. 87 &ResourceCountTransformer{ 88 Concrete: concreteResource, 89 Schema: n.Schema, 90 Count: count, 91 ForEach: forEachMap, 92 Addr: n.ResourceAddr(), 93 }, 94 95 // Add the count orphans. As these are orphaned refresh nodes, we add them 96 // directly as NodeDestroyableDataResource. 97 &OrphanResourceCountTransformer{ 98 Concrete: concreteResourceDestroyable, 99 Count: count, 100 ForEach: forEachMap, 101 Addr: n.ResourceAddr(), 102 State: state, 103 }, 104 105 // Attach the state 106 &AttachStateTransformer{State: state}, 107 108 // Targeting 109 &TargetsTransformer{Targets: n.Targets}, 110 111 // Connect references so ordering is correct 112 &ReferenceTransformer{}, 113 114 // Make sure there is a single root 115 &RootTransformer{}, 116 } 117 118 // Build the graph 119 b := &BasicGraphBuilder{ 120 Steps: steps, 121 Validate: true, 122 Name: "NodeRefreshableDataResource", 123 } 124 125 graph, diags := b.Build(ctx.Path()) 126 return graph, diags.ErrWithWarnings() 127 } 128 129 // NodeRefreshableDataResourceInstance represents a single resource instance 130 // that is refreshable. 131 type NodeRefreshableDataResourceInstance struct { 132 *NodeAbstractResourceInstance 133 } 134 135 // GraphNodeEvalable 136 func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode { 137 addr := n.ResourceInstanceAddr() 138 139 // These variables are the state for the eval sequence below, and are 140 // updated through pointers. 141 var provider providers.Interface 142 var providerSchema *ProviderSchema 143 var change *plans.ResourceInstanceChange 144 var state *states.ResourceInstanceObject 145 var configVal cty.Value 146 147 return &EvalSequence{ 148 Nodes: []EvalNode{ 149 &EvalGetProvider{ 150 Addr: n.ResolvedProvider, 151 Output: &provider, 152 Schema: &providerSchema, 153 }, 154 155 // Always destroy the existing state first, since we must 156 // make sure that values from a previous read will not 157 // get interpolated if we end up needing to defer our 158 // loading until apply time. 159 &EvalWriteState{ 160 Addr: addr.Resource, 161 ProviderAddr: n.ResolvedProvider, 162 State: &state, // a pointer to nil, here 163 ProviderSchema: &providerSchema, 164 }, 165 166 // EvalReadData will _attempt_ to read the data source, but may 167 // generate an incomplete planned object if the configuration 168 // includes values that won't be known until apply. 169 &EvalReadData{ 170 Addr: addr.Resource, 171 Config: n.Config, 172 Dependencies: n.StateReferences(), 173 Provider: &provider, 174 ProviderAddr: n.ResolvedProvider, 175 ProviderSchema: &providerSchema, 176 OutputChange: &change, 177 OutputConfigValue: &configVal, 178 OutputState: &state, 179 // If the config explicitly has a depends_on for this data 180 // source, assume the intention is to prevent refreshing ahead 181 // of that dependency, and therefore we need to deal with this 182 // resource during the apply phase. We do that by forcing this 183 // read to result in a plan. 184 ForcePlanRead: len(n.Config.DependsOn) > 0, 185 }, 186 187 &EvalIf{ 188 If: func(ctx EvalContext) (bool, error) { 189 return (*state).Status != states.ObjectPlanned, nil 190 }, 191 Then: &EvalSequence{ 192 Nodes: []EvalNode{ 193 &EvalWriteState{ 194 Addr: addr.Resource, 195 ProviderAddr: n.ResolvedProvider, 196 State: &state, 197 ProviderSchema: &providerSchema, 198 }, 199 &EvalUpdateStateHook{}, 200 }, 201 }, 202 Else: &EvalSequence{ 203 // We can't deal with this yet, so we'll repeat this step 204 // during the plan walk to produce a planned change to read 205 // this during the apply walk. However, we do still need to 206 // save the generated change and partial state so that 207 // results from it can be included in other data resources 208 // or provider configurations during the refresh walk. 209 // (The planned object we save in the state here will be 210 // pruned out at the end of the refresh walk, returning 211 // it back to being unset again for subsequent walks.) 212 Nodes: []EvalNode{ 213 &EvalWriteDiff{ 214 Addr: addr.Resource, 215 Change: &change, 216 ProviderSchema: &providerSchema, 217 }, 218 &EvalWriteState{ 219 Addr: addr.Resource, 220 ProviderAddr: n.ResolvedProvider, 221 State: &state, 222 ProviderSchema: &providerSchema, 223 }, 224 }, 225 }, 226 }, 227 }, 228 } 229 }