mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-05-17 01:03:30 -04:00
277 lines
11 KiB
Go
277 lines
11 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tofu
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"sync"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/dag"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// nodeExpandApplyableResource handles the first layer of resource
|
|
// expansion during apply. Even though the resource instances themselves are already expanded from the plan,
|
|
// we still need to register resource nodes in instances.Expander with their absolute addresses
|
|
// based on the expanded module instances.
|
|
//
|
|
// "Expand" in the name refers to instances.Expander usage. This node doesn't generate a dynamic subgraph.
|
|
type nodeExpandApplyableResource struct {
|
|
*NodeAbstractResource
|
|
|
|
// This slice is meant to keep references to the resourceCloser's of the expanded instances.
|
|
// Later, this will be called from nodeCloseableResource.
|
|
// At the time of introducing this, it was strictly meant for ephemeral resources, but if there
|
|
// will be other closeable resources, this could be used for those too.
|
|
closers []resourceCloser
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeExecutable = (*nodeExpandApplyableResource)(nil)
|
|
_ GraphNodeDynamicExpandable = (*nodeExpandApplyableResource)(nil)
|
|
_ GraphNodeReferenceable = (*nodeExpandApplyableResource)(nil)
|
|
_ GraphNodeReferencer = (*nodeExpandApplyableResource)(nil)
|
|
_ GraphNodeConfigResource = (*nodeExpandApplyableResource)(nil)
|
|
_ GraphNodeAttachResourceConfig = (*nodeExpandApplyableResource)(nil)
|
|
// nodeExpandApplyableResource needs to be retained during unused nodes pruning
|
|
// to register the resource for expanded module instances in `instances.Expander`
|
|
_ graphNodeRetainedByPruneUnusedNodesTransformer = (*nodeExpandApplyableResource)(nil)
|
|
_ GraphNodeTargetable = (*nodeExpandApplyableResource)(nil)
|
|
_ closableResource = (*nodeExpandApplyableResource)(nil)
|
|
)
|
|
|
|
func (n *nodeExpandApplyableResource) retainDuringUnusedPruning() {
|
|
}
|
|
|
|
func (n *nodeExpandApplyableResource) References() []*addrs.Reference {
|
|
refs := n.NodeAbstractResource.References()
|
|
|
|
// The expand node needs to connect to the individual resource instances it
|
|
// references, but cannot refer to it's own instances without causing
|
|
// cycles. It would be preferable to entirely disallow self references
|
|
// without the `self` identifier, but those were allowed in provisioners
|
|
// for compatibility with legacy configuration. We also can't always just
|
|
// filter them out for all resource node types, because the only method we
|
|
// have for catching certain invalid configurations are the cycles that
|
|
// result from these inter-instance references.
|
|
return filterSelfRefs(n.Addr.Resource, refs)
|
|
}
|
|
|
|
func (n *nodeExpandApplyableResource) Name() string {
|
|
return n.NodeAbstractResource.Name() + " (expand)"
|
|
}
|
|
|
|
func (n *nodeExpandApplyableResource) Execute(ctx context.Context, evalCtx EvalContext, op walkOperation) tfdiags.Diagnostics {
|
|
// Because ephemeral resources are not stored in plan or state, the apply expansion needs
|
|
// to produce a new graph too.
|
|
// Therefore, for ephemeral resources, we want to skip the logic from this method since
|
|
// the same thing will be executed during nodeExpandApplyableResource.DynamicExpand.
|
|
if n.Addr.Resource.Mode == addrs.EphemeralResourceMode {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
expander := evalCtx.InstanceExpander()
|
|
moduleInstances := expander.ExpandModule(n.Addr.Module)
|
|
for _, module := range moduleInstances {
|
|
evalCtx = evalCtx.WithPath(module)
|
|
diags = diags.Append(n.writeResourceState(ctx, evalCtx, n.Addr.Resource.Absolute(module)))
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
func (n *nodeExpandApplyableResource) DynamicExpand(evalCtx EvalContext) (*Graph, error) {
|
|
// All of the other resource types have their information stored in the plan, but not ephemeral.
|
|
// Resource types with information in the plan have the associated instance nodes created during a
|
|
// separate transformer (ie: DiffTransfomer).
|
|
// For ephemeral resources, we need to create the instance nodes again during the apply graph
|
|
// building.
|
|
if n.Addr.Resource.Mode != addrs.EphemeralResourceMode {
|
|
return nil, nil
|
|
}
|
|
g, diags := n.expandEphemeralResource(context.TODO(), evalCtx)
|
|
return g, diags.ErrWithWarnings()
|
|
}
|
|
|
|
// expandEphemeralResource logic is mostly identical with the nodeExpandPlannableResource.DynamicExpand one.
|
|
func (n *nodeExpandApplyableResource) expandEphemeralResource(ctx context.Context, evalCtx EvalContext) (*Graph, tfdiags.Diagnostics) {
|
|
var (
|
|
diags tfdiags.Diagnostics
|
|
g Graph
|
|
)
|
|
expander := evalCtx.InstanceExpander()
|
|
moduleInstances := expander.ExpandModule(n.Addr.Module)
|
|
|
|
// The above dealt with the expansion of the containing module, so now
|
|
// we need to deal with the expansion of the resource itself across all
|
|
// instances of the module.
|
|
//
|
|
// We'll gather up all of the leaf instances we learn about along the way
|
|
// so that we can inform the checks subsystem of which instances it should
|
|
// be expecting check results for, below.
|
|
instAddrs := addrs.MakeSet[addrs.Checkable]()
|
|
for _, module := range moduleInstances {
|
|
resAddr := n.Addr.Resource.Absolute(module)
|
|
expDiags := n.expandEphemeralInstances(ctx, evalCtx, resAddr, &g, instAddrs)
|
|
diags = diags.Append(expDiags)
|
|
}
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
// If this is a resource that participates in custom condition checks
|
|
// (i.e. it has preconditions or postconditions) then the check state
|
|
// wants to know the addresses of the checkable objects so that it can
|
|
// treat them as unknown status if we encounter an error before actually
|
|
// visiting the checks.
|
|
if checkState := evalCtx.Checks(); checkState.ConfigHasChecks(n.NodeAbstractResource.Addr) {
|
|
checkState.ReportCheckableObjects(n.NodeAbstractResource.Addr, instAddrs)
|
|
}
|
|
|
|
addRootNodeToGraph(&g)
|
|
return &g, diags
|
|
}
|
|
|
|
// expandEphemeralInstances calculates the dynamic expansion for the ephemeral resource
|
|
// itself in the context of a particular module instance.
|
|
//
|
|
// It has several side-effects:
|
|
// - Adds a node to Graph g for each leaf resource instance it discovers.
|
|
// - Registers the expansion of the resource in the "expander" object embedded inside EvalContext ctx.
|
|
// - Adds each present (non-orphaned) resource instance address to instAddrs (guaranteed to always be addrs.AbsResourceInstance, despite being declared as addrs.Checkable).
|
|
//
|
|
// After calling this for each of the module instances the resource appears
|
|
// within, the caller must register the final superset instAddrs with the
|
|
// checks subsystem so that it knows the fully expanded set of checkable
|
|
// object instances for this resource instance.
|
|
func (n *nodeExpandApplyableResource) expandEphemeralInstances(ctx context.Context, globalCtx EvalContext, resAddr addrs.AbsResource, g *Graph, instAddrs addrs.Set[addrs.Checkable]) (diags tfdiags.Diagnostics) {
|
|
// The rest of our work here needs to know which module instance it's
|
|
// working in, so that it can evaluate expressions in the appropriate scope.
|
|
moduleCtx := globalCtx.WithPath(resAddr.Module)
|
|
|
|
// writeResourceState is responsible for informing the expander of what
|
|
// repetition mode this resource has, which allows expander.ExpandResource
|
|
// to work below.
|
|
moreDiags := n.writeResourceState(ctx, moduleCtx, resAddr)
|
|
diags = diags.Append(moreDiags)
|
|
if moreDiags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
expander := moduleCtx.InstanceExpander()
|
|
instanceAddrs := expander.ExpandResource(resAddr)
|
|
|
|
for _, addr := range instanceAddrs {
|
|
// If this resource is participating in the "checks" mechanism then our
|
|
// caller will need to know all of our expanded instance addresses as
|
|
// checkable object instances.
|
|
// (NOTE: instAddrs probably already has other instance addresses in it
|
|
// from earlier calls to this function with different resource addresses,
|
|
// because its purpose is to aggregate them all together into a single set.)
|
|
instAddrs.Add(addr)
|
|
}
|
|
|
|
// Our graph builder mechanism expects to always be constructing new
|
|
// graphs rather than adding to existing ones, so we'll first
|
|
// construct a subgraph just for this individual modules's instances and
|
|
// then we'll steal all of its nodes and edges to incorporate into our
|
|
// main graph which contains all of the resource instances together.
|
|
instG, err := n.ephemeralInstanceSubgraph(ctx, resAddr, instanceAddrs)
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return diags
|
|
}
|
|
g.Subsume(&instG.AcyclicGraph.Graph)
|
|
|
|
return diags
|
|
}
|
|
|
|
// ephemeralInstanceSubgraph creates the subgraph for the given ephemeral resource in the context of
|
|
// a module.
|
|
// This also records the closableResource.Close method into nodeExpandApplyableResource.closers to be later used
|
|
// to close the ephemeral resources.
|
|
func (n *nodeExpandApplyableResource) ephemeralInstanceSubgraph(ctx context.Context, addr addrs.AbsResource, instanceAddrs []addrs.AbsResourceInstance) (*Graph, error) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
// The concrete resource factory we'll use
|
|
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
var m *NodeApplyableResourceInstance
|
|
|
|
// Add the config and state since we don't do that via transforms
|
|
a.Config = n.Config
|
|
a.ResolvedProvider = n.ResolvedProvider
|
|
a.Schema = n.Schema
|
|
a.ProvisionerSchemas = n.ProvisionerSchemas
|
|
a.ProviderMetas = n.ProviderMetas
|
|
a.dependsOn = n.dependsOn
|
|
a.generateConfigPath = n.generateConfigPath
|
|
|
|
m = &NodeApplyableResourceInstance{
|
|
NodeAbstractResourceInstance: a,
|
|
}
|
|
// When creating concrete instance nodes for the ephemeral resources we want to collect all the
|
|
// resourceCloser callbacks from the nodes to be able to close the resources at the end of the graph walk.
|
|
n.closers = append(n.closers, m.Close)
|
|
|
|
return m
|
|
}
|
|
|
|
// NOTE: The logic of creating a new subgraph, is also present in nodeExpandPlannableResource.
|
|
// If there will be any transformes added here, we need to double check
|
|
// the other similar implementation and mirror the changes there if needed.
|
|
// Start creating the steps
|
|
steps := []GraphTransformer{
|
|
// Expand the count or for_each (if present)
|
|
&ResourceCountTransformer{
|
|
Concrete: concreteResource,
|
|
Schema: n.Schema,
|
|
Addr: n.ResourceAddr(),
|
|
InstanceAddrs: instanceAddrs,
|
|
},
|
|
|
|
// Targeting
|
|
&TargetingTransformer{Targets: n.Targets, Excludes: n.Excludes},
|
|
|
|
// Connect references so ordering is correct
|
|
&ReferenceTransformer{},
|
|
|
|
// Make sure there is a single root
|
|
&RootTransformer{},
|
|
}
|
|
|
|
// Build the graph
|
|
b := &BasicGraphBuilder{
|
|
Steps: steps,
|
|
Name: "nodeExpandEphemeralApplyableResource",
|
|
}
|
|
graph, graphDiags := b.Build(ctx, addr.Module)
|
|
return graph, diags.Append(graphDiags).ErrWithWarnings()
|
|
}
|
|
|
|
// Close implements closableResource
|
|
func (n *nodeExpandApplyableResource) Close() (diags tfdiags.Diagnostics) {
|
|
if n.Addr.Resource.Mode != addrs.EphemeralResourceMode {
|
|
return diags
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
diagsCh := make(chan tfdiags.Diagnostics, len(n.closers))
|
|
log.Printf("[TRACE] nodeExpandApplyableResource - scheduling %d closing operations for of ephemeral resource %s", len(n.closers), n.Addr.String())
|
|
for _, cb := range n.closers {
|
|
wg.Go(func() {
|
|
diagsCh <- cb()
|
|
})
|
|
}
|
|
wg.Wait()
|
|
close(diagsCh)
|
|
for d := range diagsCh {
|
|
diags = diags.Append(d)
|
|
}
|
|
return diags
|
|
}
|