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