4 "github.com/hashicorp/terraform/dag"
5 "github.com/hashicorp/terraform/plans"
6 "github.com/hashicorp/terraform/providers"
7 "github.com/hashicorp/terraform/states"
8 "github.com/hashicorp/terraform/tfdiags"
9 "github.com/zclconf/go-cty/cty"
12 // NodeRefreshableDataResource represents a resource that is "refreshable".
13 type NodeRefreshableDataResource struct {
18 _ GraphNodeSubPath = (*NodeRefreshableDataResource)(nil)
19 _ GraphNodeDynamicExpandable = (*NodeRefreshableDataResource)(nil)
20 _ GraphNodeReferenceable = (*NodeRefreshableDataResource)(nil)
21 _ GraphNodeReferencer = (*NodeRefreshableDataResource)(nil)
22 _ GraphNodeResource = (*NodeRefreshableDataResource)(nil)
23 _ GraphNodeAttachResourceConfig = (*NodeRefreshableDataResource)(nil)
26 // GraphNodeDynamicExpandable
27 func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
28 var diags tfdiags.Diagnostics
30 count, countKnown, countDiags := evaluateResourceCountExpressionKnown(n.Config.Count, ctx)
31 diags = diags.Append(countDiags)
32 if countDiags.HasErrors() {
33 return nil, diags.Err()
36 // If the count isn't known yet, we'll skip refreshing and try expansion
37 // again during the plan walk.
41 forEachMap, forEachKnown, forEachDiags := evaluateResourceForEachExpressionKnown(n.Config.ForEach, ctx)
42 if forEachDiags.HasErrors() {
43 return nil, diags.Err()
46 // If the for_each isn't known yet, we'll skip refreshing and try expansion
47 // again during the plan walk.
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)
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()
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
64 a.ResolvedProvider = n.ResolvedProvider
66 return &NodeRefreshableDataResourceInstance{
67 NodeAbstractResourceInstance: a,
71 // We also need a destroyable resource for orphans that are a result of a
73 concreteResourceDestroyable := func(a *NodeAbstractResourceInstance) dag.Vertex {
74 // Add the config and provider since we don't do that via transforms
76 a.ResolvedProvider = n.ResolvedProvider
78 return &NodeDestroyableDataResourceInstance{
79 NodeAbstractResourceInstance: a,
83 // Start creating the steps
84 steps := []GraphTransformer{
86 &ResourceCountTransformer{
87 Concrete: concreteResource,
91 Addr: n.ResourceAddr(),
94 // Add the count orphans. As these are orphaned refresh nodes, we add them
95 // directly as NodeDestroyableDataResource.
96 &OrphanResourceCountTransformer{
97 Concrete: concreteResourceDestroyable,
100 Addr: n.ResourceAddr(),
105 &AttachStateTransformer{State: state},
108 &TargetsTransformer{Targets: n.Targets},
110 // Connect references so ordering is correct
111 &ReferenceTransformer{},
113 // Make sure there is a single root
118 b := &BasicGraphBuilder{
121 Name: "NodeRefreshableDataResource",
124 graph, diags := b.Build(ctx.Path())
125 return graph, diags.ErrWithWarnings()
128 // NodeRefreshableDataResourceInstance represents a single resource instance
129 // that is refreshable.
130 type NodeRefreshableDataResourceInstance struct {
131 *NodeAbstractResourceInstance
135 func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
136 addr := n.ResourceInstanceAddr()
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
146 return &EvalSequence{
149 Addr: n.ResolvedProvider,
151 Schema: &providerSchema,
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.
160 ProviderAddr: n.ResolvedProvider,
161 State: &state, // a pointer to nil, here
162 ProviderSchema: &providerSchema,
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
172 if len(n.Config.DependsOn) > 0 {
173 return true, EvalEarlyExitError{}
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.
187 Dependencies: n.StateReferences(),
189 ProviderAddr: n.ResolvedProvider,
190 ProviderSchema: &providerSchema,
191 OutputChange: &change,
192 OutputConfigValue: &configVal,
197 If: func(ctx EvalContext) (bool, error) {
198 return (*state).Status != states.ObjectPlanned, nil
204 ProviderAddr: n.ResolvedProvider,
206 ProviderSchema: &providerSchema,
208 &EvalUpdateStateHook{},
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.)
225 ProviderSchema: &providerSchema,
229 ProviderAddr: n.ResolvedProvider,
231 ProviderSchema: &providerSchema,