| // 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/plans" |
| "github.com/hashicorp/terraform/internal/providers" |
| "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 []providers.FunctionHash |
| |
| // 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, |
| ProviderFunctionResults: 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 |
| } |