// 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] // 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) } } 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 }