mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
- Transfer Component.ActionInvocations to plan.Changes.ActionInvocations in ForModulesRuntime() - Populate plan.ActionTargetAddrs with action invocations - Skip action invocations when config lacks action trigger definitions
179 lines
7.1 KiB
Go
179 lines
7.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackplan
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/collections"
|
|
"github.com/hashicorp/terraform/internal/lang"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
)
|
|
|
|
// Component is a container for a set of changes that all belong to the same
|
|
// component instance as declared in a stack configuration.
|
|
//
|
|
// Each instance of component essentially maps to one call into the main
|
|
// Terraform language runtime to apply all of the described changes together as
|
|
// a single operation.
|
|
type Component struct {
|
|
PlannedAction plans.Action
|
|
Mode plans.Mode
|
|
|
|
// These fields echo the [plans.Plan.Applyable] and [plans.Plan.Complete]
|
|
// field respectively. See the docs for those fields for more information.
|
|
PlanApplyable, PlanComplete bool
|
|
|
|
// ResourceInstancePlanned describes the changes that Terraform is proposing
|
|
// to make to try to converge the real system state with the desired state
|
|
// as described by the configuration.
|
|
ResourceInstancePlanned addrs.Map[addrs.AbsResourceInstanceObject, *plans.ResourceInstanceChangeSrc]
|
|
|
|
// ResourceInstancePriorState describes the state as it was when making
|
|
// the proposals described in [Component.ResourceInstancePlanned].
|
|
//
|
|
// Elements of this map have nil values if the planned action is "create",
|
|
// since in that case there is no prior object.
|
|
ResourceInstancePriorState addrs.Map[addrs.AbsResourceInstanceObject, *states.ResourceInstanceObjectSrc]
|
|
|
|
// ResourceInstanceProviderConfig is a lookup table from resource instance
|
|
// object address to the address of the provider configuration that
|
|
// will handle any apply-time actions for that object.
|
|
ResourceInstanceProviderConfig addrs.Map[addrs.AbsResourceInstanceObject, addrs.AbsProviderConfig]
|
|
|
|
// DeferredResourceInstanceChanges is a set of resource instance objects
|
|
// that have changes that are deferred to a later plan and apply cycle.
|
|
DeferredResourceInstanceChanges addrs.Map[addrs.AbsResourceInstanceObject, *plans.DeferredResourceInstanceChangeSrc]
|
|
|
|
// ActionInvocations is a set of planned action invocations for this
|
|
// component.
|
|
ActionInvocations addrs.Map[addrs.AbsActionInstance, *plans.ActionInvocationInstanceSrc]
|
|
|
|
// PlanTimestamp is the time Terraform Core recorded as the single "plan
|
|
// timestamp", which is used only for the result of the "plantimestamp"
|
|
// function during apply and must not be used for any other purpose.
|
|
PlanTimestamp time.Time
|
|
|
|
// Dependencies is a set of addresses of other components that this one
|
|
// expects to exist for as long as this one exists.
|
|
Dependencies collections.Set[stackaddrs.AbsComponent]
|
|
|
|
// Dependents is the reverse of [Component.Dependencies], describing
|
|
// the other components that must be destroyed before this one could
|
|
// be destroyed.
|
|
Dependents collections.Set[stackaddrs.AbsComponent]
|
|
|
|
// PlannedFunctionResults is a shared table of results from calling
|
|
// provider functions. This is stored and loaded from during the planning
|
|
// stage to use during apply operations.
|
|
PlannedFunctionResults []lang.FunctionResultHash
|
|
|
|
// PlannedInputValues and PlannedInputValueMarks are the values that
|
|
// Terraform has planned to use for input variables in this component.
|
|
PlannedInputValues map[addrs.InputVariable]plans.DynamicValue
|
|
PlannedInputValueMarks map[addrs.InputVariable][]cty.PathValueMarks
|
|
|
|
PlannedOutputValues map[addrs.OutputValue]cty.Value
|
|
|
|
PlannedChecks *states.CheckResults
|
|
}
|
|
|
|
// ForModulesRuntime translates the component instance plan into the form
|
|
// expected by the modules runtime, which is what would ultimately be used
|
|
// to apply the plan.
|
|
//
|
|
// The stack component planning model preserves only the most crucial details
|
|
// of a component plan produced by the modules runtime, and so the result
|
|
// will not exactly match the [plans.Plan] that the component plan was produced
|
|
// from, but should be complete enough to successfully apply the plan.
|
|
//
|
|
// Conversion with this method should always succeed if the given previous
|
|
// run state is truly the one that the plan was created from. If this method
|
|
// returns an error then that suggests that the recieving plan is inconsistent
|
|
// with the given previous run state, which should not happen if the caller
|
|
// is using Terraform Core correctly.
|
|
func (c *Component) ForModulesRuntime() (*plans.Plan, error) {
|
|
changes := &plans.ChangesSrc{}
|
|
plan := &plans.Plan{
|
|
UIMode: c.Mode,
|
|
Changes: changes,
|
|
Timestamp: c.PlanTimestamp,
|
|
Applyable: c.PlanApplyable,
|
|
Complete: c.PlanComplete,
|
|
Checks: c.PlannedChecks,
|
|
FunctionResults: c.PlannedFunctionResults,
|
|
}
|
|
|
|
for _, elem := range c.ResourceInstancePlanned.Elems {
|
|
changeSrc := elem.Value
|
|
if changeSrc != nil {
|
|
changes.Resources = append(changes.Resources, changeSrc)
|
|
}
|
|
}
|
|
|
|
// Add all action invocations to the plan
|
|
for _, elem := range c.ActionInvocations.Elems {
|
|
actionInvocation := elem.Value
|
|
if actionInvocation != nil {
|
|
changes.ActionInvocations = append(changes.ActionInvocations, actionInvocation)
|
|
}
|
|
}
|
|
|
|
priorState := states.NewState()
|
|
ss := priorState.SyncWrapper()
|
|
for _, elem := range c.ResourceInstancePriorState.Elems {
|
|
addr := elem.Key
|
|
providerConfigAddr, ok := c.ResourceInstanceProviderConfig.GetOk(addr)
|
|
if !ok {
|
|
return nil, fmt.Errorf("no provider config address for %s", addr)
|
|
}
|
|
stateSrc := elem.Value
|
|
if addr.IsCurrent() {
|
|
ss.SetResourceInstanceCurrent(addr.ResourceInstance, stateSrc, providerConfigAddr)
|
|
} else {
|
|
ss.SetResourceInstanceDeposed(addr.ResourceInstance, addr.DeposedKey, stateSrc, providerConfigAddr)
|
|
}
|
|
}
|
|
|
|
variableValues := make(map[string]plans.DynamicValue, len(c.PlannedInputValues))
|
|
variableMarks := make(map[string][]cty.PathValueMarks, len(c.PlannedInputValueMarks))
|
|
for k, v := range c.PlannedInputValues {
|
|
variableValues[k.Name] = v
|
|
}
|
|
plan.VariableValues = variableValues
|
|
for k, v := range c.PlannedInputValueMarks {
|
|
variableMarks[k.Name] = v
|
|
}
|
|
plan.VariableMarks = variableMarks
|
|
|
|
plan.PriorState = priorState
|
|
plan.PrevRunState = priorState.DeepCopy() // This is just here to complete the data structure; we don't really do anything with it
|
|
|
|
return plan, nil
|
|
}
|
|
|
|
// RequiredProviderInstances returns a description of all the provider instance
|
|
// slots that are required to satisfy the resource instances planned for this
|
|
// component.
|
|
//
|
|
// See also stackstate.State.RequiredProviderInstances and
|
|
// stackeval.ComponentConfig.RequiredProviderInstances for similar functions
|
|
// that retrieve the provider instances for a components in the config and in
|
|
// the state.
|
|
func (c *Component) RequiredProviderInstances() addrs.Set[addrs.RootProviderConfig] {
|
|
providerInstances := addrs.MakeSet[addrs.RootProviderConfig]()
|
|
for _, elem := range c.ResourceInstanceProviderConfig.Elems {
|
|
providerInstances.Add(addrs.RootProviderConfig{
|
|
Provider: elem.Value.Provider,
|
|
Alias: elem.Value.Alias,
|
|
})
|
|
}
|
|
return providerInstances
|
|
}
|