7 "github.com/hashicorp/terraform/plans"
8 "github.com/hashicorp/terraform/providers"
10 "github.com/hashicorp/terraform/states"
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/dag"
14 "github.com/hashicorp/terraform/tfdiags"
17 // NodeRefreshableManagedResource represents a resource that is expanabled into
18 // NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
19 type NodeRefreshableManagedResource struct {
24 _ GraphNodeSubPath = (*NodeRefreshableManagedResource)(nil)
25 _ GraphNodeDynamicExpandable = (*NodeRefreshableManagedResource)(nil)
26 _ GraphNodeReferenceable = (*NodeRefreshableManagedResource)(nil)
27 _ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil)
28 _ GraphNodeResource = (*NodeRefreshableManagedResource)(nil)
29 _ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil)
32 // GraphNodeDynamicExpandable
33 func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
34 var diags tfdiags.Diagnostics
36 count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
37 diags = diags.Append(countDiags)
38 if countDiags.HasErrors() {
39 return nil, diags.Err()
42 // Next we need to potentially rename an instance address in the state
43 // if we're transitioning whether "count" is set at all.
44 fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1)
46 // Our graph transformers require access to the full state, so we'll
47 // temporarily lock it while we work on this.
48 state := ctx.State().Lock()
49 defer ctx.State().Unlock()
51 // The concrete resource factory we'll use
52 concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
53 // Add the config and state since we don't do that via transforms
55 a.ResolvedProvider = n.ResolvedProvider
57 return &NodeRefreshableManagedResourceInstance{
58 NodeAbstractResourceInstance: a,
62 // Start creating the steps
63 steps := []GraphTransformer{
65 &ResourceCountTransformer{
66 Concrete: concreteResource,
69 Addr: n.ResourceAddr(),
72 // Add the count orphans to make sure these resources are accounted for
74 &OrphanResourceCountTransformer{
75 Concrete: concreteResource,
77 Addr: n.ResourceAddr(),
82 &AttachStateTransformer{State: state},
85 &TargetsTransformer{Targets: n.Targets},
87 // Connect references so ordering is correct
88 &ReferenceTransformer{},
90 // Make sure there is a single root
95 b := &BasicGraphBuilder{
98 Name: "NodeRefreshableManagedResource",
101 graph, diags := b.Build(ctx.Path())
102 return graph, diags.ErrWithWarnings()
105 // NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
106 // it is ready to be applied and is represented by a diff.
107 type NodeRefreshableManagedResourceInstance struct {
108 *NodeAbstractResourceInstance
112 _ GraphNodeSubPath = (*NodeRefreshableManagedResourceInstance)(nil)
113 _ GraphNodeReferenceable = (*NodeRefreshableManagedResourceInstance)(nil)
114 _ GraphNodeReferencer = (*NodeRefreshableManagedResourceInstance)(nil)
115 _ GraphNodeDestroyer = (*NodeRefreshableManagedResourceInstance)(nil)
116 _ GraphNodeResource = (*NodeRefreshableManagedResourceInstance)(nil)
117 _ GraphNodeResourceInstance = (*NodeRefreshableManagedResourceInstance)(nil)
118 _ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResourceInstance)(nil)
119 _ GraphNodeAttachResourceState = (*NodeRefreshableManagedResourceInstance)(nil)
120 _ GraphNodeEvalable = (*NodeRefreshableManagedResourceInstance)(nil)
123 // GraphNodeDestroyer
124 func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance {
125 addr := n.ResourceInstanceAddr()
130 func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode {
131 addr := n.ResourceInstanceAddr()
133 // Eval info is different depending on what kind of resource this is
134 switch addr.Resource.Resource.Mode {
135 case addrs.ManagedResourceMode:
136 if n.ResourceState == nil {
137 log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s has no existing state to refresh", addr)
138 return n.evalTreeManagedResourceNoState()
140 log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s will be refreshed", addr)
141 return n.evalTreeManagedResource()
143 case addrs.DataResourceMode:
144 // Get the data source node. If we don't have a configuration
145 // then it is an orphan so we destroy it (remove it from the state).
146 var dn GraphNodeEvalable
148 dn = &NodeRefreshableDataResourceInstance{
149 NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
152 dn = &NodeDestroyableDataResourceInstance{
153 NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
159 panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode))
163 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
164 addr := n.ResourceInstanceAddr()
166 // Declare a bunch of variables that are used for state during
167 // evaluation. Most of this are written to by-address below.
168 var provider providers.Interface
169 var providerSchema *ProviderSchema
170 var state *states.ResourceInstanceObject
172 // This happened during initial development. All known cases were
173 // fixed and tested but as a sanity check let's assert here.
174 if n.ResourceState == nil {
176 "No resource state attached for addr: %s\n\n"+
177 "This is a bug. Please report this to Terraform with your configuration\n"+
178 "and state attached. Please be careful to scrub any sensitive information.",
180 return &EvalReturnError{Error: &err}
183 return &EvalSequence{
186 Addr: n.ResolvedProvider,
188 Schema: &providerSchema,
194 ProviderSchema: &providerSchema,
201 ProviderAddr: n.ResolvedProvider,
203 ProviderSchema: &providerSchema,
210 ProviderAddr: n.ResolvedProvider,
211 ProviderSchema: &providerSchema,
218 // evalTreeManagedResourceNoState produces an EvalSequence for refresh resource
219 // nodes that don't have state attached. An example of where this functionality
220 // is useful is when a resource that already exists in state is being scaled
221 // out, ie: has its resource count increased. In this case, the scaled out node
222 // needs to be available to other nodes (namely data sources) that may depend
223 // on it for proper interpolation, or confusing "index out of range" errors can
226 // The steps in this sequence are very similar to the steps carried out in
227 // plan, but nothing is done with the diff after it is created - it is dropped,
228 // and its changes are not counted in the UI.
229 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode {
230 addr := n.ResourceInstanceAddr()
232 // Declare a bunch of variables that are used for state during
233 // evaluation. Most of this are written to by-address below.
234 var provider providers.Interface
235 var providerSchema *ProviderSchema
236 var change *plans.ResourceInstanceChange
237 var state *states.ResourceInstanceObject
239 return &EvalSequence{
242 Addr: n.ResolvedProvider,
244 Schema: &providerSchema,
250 ProviderSchema: &providerSchema,
259 ProviderAddr: n.ResolvedProvider,
260 ProviderSchema: &providerSchema,
262 OutputChange: &change,
269 ProviderAddr: n.ResolvedProvider,
270 ProviderSchema: &providerSchema,
274 // We must also save the planned change, so that expressions in
275 // other nodes, such as provider configurations and data resources,
276 // can work with the planned new value.
278 // This depends on the fact that Context.Refresh creates a
279 // temporary new empty changeset for the duration of its graph
280 // walk, and so this recorded change will be discarded immediately
281 // after the refresh walk completes.
285 ProviderSchema: &providerSchema,