mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
c937c06a03
Due to how deeply the configuration types go into Terraform Core, there isn't a great way to switch out to HCL2 gradually. As a consequence, this huge commit gets us from the old state to a _compilable_ new state, but does not yet attempt to fix any tests and has a number of known missing parts and bugs. We will continue to iterate on this in forthcoming commits, heading back towards passing tests and making Terraform fully-functional again. The three main goals here are: - Use the configuration models from the "configs" package instead of the older models in the "config" package, which is now deprecated and preserved only to help us write our migration tool. - Do expression inspection and evaluation using the functionality of the new "lang" package, instead of the Interpolator type and related functionality in the main "terraform" package. - Represent addresses of various objects using types in the addrs package, rather than hand-constructed strings. This is not critical to support the above, but was a big help during the implementation of these other points since it made it much more explicit what kind of address is expected in each context. Since our new packages are built to accommodate some future planned features that are not yet implemented (e.g. the "for_each" argument on resources, "count"/"for_each" on modules), and since there's still a fair amount of functionality still using old-style APIs, there is a moderate amount of shimming here to connect new assumptions with old, hopefully in a way that makes it easier to find and eliminate these shims later. I apologize in advance to the person who inevitably just found this huge commit while spelunking through the commit history.
269 lines
8.1 KiB
Go
269 lines
8.1 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/dag"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
)
|
|
|
|
// NodeRefreshableManagedResource represents a resource that is expanabled into
|
|
// NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
|
|
type NodeRefreshableManagedResource struct {
|
|
*NodeAbstractResource
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeSubPath = (*NodeRefreshableManagedResource)(nil)
|
|
_ GraphNodeDynamicExpandable = (*NodeRefreshableManagedResource)(nil)
|
|
_ GraphNodeReferenceable = (*NodeRefreshableManagedResource)(nil)
|
|
_ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil)
|
|
_ GraphNodeResource = (*NodeRefreshableManagedResource)(nil)
|
|
_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil)
|
|
)
|
|
|
|
// GraphNodeDynamicExpandable
|
|
func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
|
|
diags = diags.Append(countDiags)
|
|
if countDiags.HasErrors() {
|
|
return nil, diags.Err()
|
|
}
|
|
|
|
// Next we need to potentially rename an instance address in the state
|
|
// if we're transitioning whether "count" is set at all.
|
|
fixResourceCountSetTransition(ctx, n.ResourceAddr().Resource, count != -1)
|
|
|
|
// Grab the state which we read
|
|
state, lock := ctx.State()
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
// The concrete resource factory we'll use
|
|
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
// Add the config and state since we don't do that via transforms
|
|
a.Config = n.Config
|
|
a.ResolvedProvider = n.ResolvedProvider
|
|
|
|
return &NodeRefreshableManagedResourceInstance{
|
|
NodeAbstractResourceInstance: a,
|
|
}
|
|
}
|
|
|
|
// Start creating the steps
|
|
steps := []GraphTransformer{
|
|
// Expand the count.
|
|
&ResourceCountTransformer{
|
|
Concrete: concreteResource,
|
|
Count: count,
|
|
Addr: n.ResourceAddr(),
|
|
},
|
|
|
|
// Add the count orphans to make sure these resources are accounted for
|
|
// during a scale in.
|
|
&OrphanResourceCountTransformer{
|
|
Concrete: concreteResource,
|
|
Count: count,
|
|
Addr: n.ResourceAddr(),
|
|
State: state,
|
|
},
|
|
|
|
// Attach the state
|
|
&AttachStateTransformer{State: state},
|
|
|
|
// Targeting
|
|
&TargetsTransformer{Targets: n.Targets},
|
|
|
|
// Connect references so ordering is correct
|
|
&ReferenceTransformer{},
|
|
|
|
// Make sure there is a single root
|
|
&RootTransformer{},
|
|
}
|
|
|
|
// Build the graph
|
|
b := &BasicGraphBuilder{
|
|
Steps: steps,
|
|
Validate: true,
|
|
Name: "NodeRefreshableManagedResource",
|
|
}
|
|
|
|
graph, diags := b.Build(ctx.Path())
|
|
return graph, diags.ErrWithWarnings()
|
|
}
|
|
|
|
// NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
|
|
// it is ready to be applied and is represented by a diff.
|
|
type NodeRefreshableManagedResourceInstance struct {
|
|
*NodeAbstractResourceInstance
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeSubPath = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeReferenceable = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeReferencer = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeDestroyer = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeResource = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeResourceInstance = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeAttachResourceState = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
_ GraphNodeEvalable = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
)
|
|
|
|
// GraphNodeDestroyer
|
|
func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance {
|
|
addr := n.ResourceInstanceAddr()
|
|
return &addr
|
|
}
|
|
|
|
// GraphNodeEvalable
|
|
func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode {
|
|
addr := n.ResourceInstanceAddr()
|
|
|
|
// Eval info is different depending on what kind of resource this is
|
|
switch addr.Resource.Resource.Mode {
|
|
case addrs.ManagedResourceMode:
|
|
if n.ResourceState == nil {
|
|
return n.evalTreeManagedResourceNoState()
|
|
}
|
|
return n.evalTreeManagedResource()
|
|
|
|
case addrs.DataResourceMode:
|
|
// Get the data source node. If we don't have a configuration
|
|
// then it is an orphan so we destroy it (remove it from the state).
|
|
var dn GraphNodeEvalable
|
|
if n.Config != nil {
|
|
dn = &NodeRefreshableDataResourceInstance{
|
|
NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
|
|
}
|
|
} else {
|
|
dn = &NodeDestroyableDataResource{
|
|
NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
|
|
}
|
|
}
|
|
|
|
return dn.EvalTree()
|
|
default:
|
|
panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode))
|
|
}
|
|
}
|
|
|
|
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
|
|
addr := n.ResourceInstanceAddr()
|
|
|
|
// State still uses legacy-style internal ids, so we need to shim to get
|
|
// a suitable key to use.
|
|
stateId := NewLegacyResourceInstanceAddress(addr).stateId()
|
|
|
|
// Declare a bunch of variables that are used for state during
|
|
// evaluation. Most of this are written to by-address below.
|
|
var provider ResourceProvider
|
|
var state *InstanceState
|
|
|
|
// This happened during initial development. All known cases were
|
|
// fixed and tested but as a sanity check let's assert here.
|
|
if n.ResourceState == nil {
|
|
err := fmt.Errorf(
|
|
"No resource state attached for addr: %s\n\n"+
|
|
"This is a bug. Please report this to Terraform with your configuration\n"+
|
|
"and state attached. Please be careful to scrub any sensitive information.",
|
|
addr)
|
|
return &EvalReturnError{Error: &err}
|
|
}
|
|
|
|
return &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalReadState{
|
|
Name: stateId,
|
|
Output: &state,
|
|
},
|
|
|
|
&EvalGetProvider{
|
|
Addr: n.ResolvedProvider,
|
|
Output: &provider,
|
|
},
|
|
|
|
&EvalRefresh{
|
|
Addr: addr.Resource,
|
|
Provider: &provider,
|
|
State: &state,
|
|
Output: &state,
|
|
},
|
|
|
|
&EvalWriteState{
|
|
Name: stateId,
|
|
ResourceType: n.ResourceState.Type,
|
|
Provider: n.ResolvedProvider,
|
|
Dependencies: n.ResourceState.Dependencies,
|
|
State: &state,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// evalTreeManagedResourceNoState produces an EvalSequence for refresh resource
|
|
// nodes that don't have state attached. An example of where this functionality
|
|
// is useful is when a resource that already exists in state is being scaled
|
|
// out, ie: has its resource count increased. In this case, the scaled out node
|
|
// needs to be available to other nodes (namely data sources) that may depend
|
|
// on it for proper interpolation, or confusing "index out of range" errors can
|
|
// occur.
|
|
//
|
|
// The steps in this sequence are very similar to the steps carried out in
|
|
// plan, but nothing is done with the diff after it is created - it is dropped,
|
|
// and its changes are not counted in the UI.
|
|
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode {
|
|
addr := n.ResourceInstanceAddr()
|
|
|
|
// Declare a bunch of variables that are used for state during
|
|
// evaluation. Most of this are written to by-address below.
|
|
var provider ResourceProvider
|
|
var providerSchema *ProviderSchema
|
|
var diff *InstanceDiff
|
|
var state *InstanceState
|
|
|
|
// State still uses legacy-style internal ids, so we need to shim to get
|
|
// a suitable key to use.
|
|
stateID := NewLegacyResourceInstanceAddress(addr).stateId()
|
|
|
|
// Determine the dependencies for the state.
|
|
stateDeps := n.StateReferences()
|
|
|
|
return &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalReadState{
|
|
Name: stateID,
|
|
Output: &state,
|
|
},
|
|
|
|
&EvalGetProvider{
|
|
Addr: n.ResolvedProvider,
|
|
Output: &provider,
|
|
Schema: &providerSchema,
|
|
},
|
|
|
|
&EvalDiff{
|
|
Addr: addr.Resource,
|
|
Config: n.Config,
|
|
Provider: &provider,
|
|
ProviderSchema: &providerSchema,
|
|
State: &state,
|
|
OutputDiff: &diff,
|
|
OutputState: &state,
|
|
Stub: true,
|
|
},
|
|
|
|
&EvalWriteState{
|
|
Name: stateID,
|
|
ResourceType: n.Config.Type,
|
|
Provider: n.ResolvedProvider,
|
|
Dependencies: stateDeps,
|
|
State: &state,
|
|
},
|
|
},
|
|
}
|
|
}
|