| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package stackplan |
| |
| import ( |
| "iter" |
| "time" |
| |
| "github.com/zclconf/go-cty/cty" |
| "google.golang.org/protobuf/types/known/anypb" |
| |
| "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" |
| ) |
| |
| // Plan is the main type in this package, representing an entire stack plan, |
| // or at least the subset of the information that Terraform needs to reliably |
| // apply the plan and detect any inconsistencies during the apply process. |
| // |
| // However, the process of _creating_ a plan doesn't actually produce a single |
| // object of this type, and instead produces fragments of it gradually as the |
| // planning process proceeds. The caller of the stack runtime must retain |
| // all of the raw parts in the order they were emitted and provide them back |
| // during the apply phase, and then we will finally construct a single instance |
| // of Plan covering the entire set of changes before we begin applying it. |
| type Plan struct { |
| // Applyable is true for a plan that was successfully created in full and |
| // is sufficient to be applied, or false if the plan is incomplete for |
| // some reason, such as if an error occurred during planning and so |
| // the planning process did not entirely run. |
| Applyable bool |
| |
| // Complete is true for a plan that shouldn't need any follow-up plans to |
| // converge. |
| Complete bool |
| |
| // Mode is the original mode of the plan. |
| Mode plans.Mode |
| |
| // Root is the root StackInstance for the configuration being planned. |
| // The StackInstance object wraps the specific components for each stack |
| // instance. |
| Root *StackInstance |
| |
| // The raw representation of the raw state that was provided in the request |
| // to create the plan. We use this primarily to perform mundane state |
| // data structure maintenence operations, such as discarding keys that |
| // are no longer needed or replacing data in old formats with the |
| // equivalent new representations. |
| PrevRunStateRaw map[string]*anypb.Any |
| |
| // RootInputValues are the input variable values provided to calculate |
| // the plan. We must use the same values during the apply step to |
| // sure that the actions taken can be consistent with what was planned. |
| RootInputValues map[stackaddrs.InputVariable]cty.Value |
| |
| // ApplyTimeInputVariables are the names of the root input variable |
| // values whose values must be re-supplied during the apply phase, |
| // instead of being persisted in [Plan.RootInputValues]. |
| ApplyTimeInputVariables collections.Set[stackaddrs.InputVariable] |
| |
| // DeletedInputVariables tracks the set of input variables that are being |
| // deleted by this plan. The apply operation will miss any values |
| // that are not defined in the configuration, but should still emit |
| // deletion events to remove them from the state. |
| DeletedInputVariables collections.Set[stackaddrs.InputVariable] |
| |
| // DeletedOutputValues tracks the set of output values that are being |
| // deleted by this plan. The apply operation will miss any output values |
| // that are not defined in the configuration, but should still emit |
| // deletion events to remove them from the state. Output values not being |
| // deleted will be recomputed during the apply so are not needed. |
| DeletedOutputValues collections.Set[stackaddrs.OutputValue] |
| |
| // DeletedComponents are a set of components that are in the state that |
| // should just be removed without any apply operation. This is typically |
| // because they are not referenced in the configuration and have no |
| // associated resources. |
| DeletedComponents collections.Set[stackaddrs.AbsComponentInstance] |
| |
| // FunctionResults 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. |
| FunctionResults []lang.FunctionResultHash |
| |
| // PlanTimestamp is the time at which the plan was created. |
| PlanTimestamp time.Time |
| } |
| |
| func (p *Plan) AllComponents() iter.Seq2[stackaddrs.AbsComponentInstance, *Component] { |
| return func(yield func(stackaddrs.AbsComponentInstance, *Component) bool) { |
| p.Root.iterate(yield) |
| } |
| } |
| |
| func (p *Plan) ComponentInstanceAddresses(addr stackaddrs.AbsComponent) iter.Seq[stackaddrs.ComponentInstance] { |
| return func(yield func(stackaddrs.ComponentInstance) bool) { |
| stack := p.Root.GetDescendentStack(addr.Stack) |
| if stack != nil { |
| components := stack.Components[addr.Item] |
| for key := range components { |
| proceed := yield(stackaddrs.ComponentInstance{ |
| Component: addr.Item, |
| Key: key, |
| }) |
| if !proceed { |
| return |
| } |
| } |
| } |
| } |
| } |
| |
| // ComponentInstances returns a set of the component instances that belong to |
| // the given component. |
| func (p *Plan) ComponentInstances(addr stackaddrs.AbsComponent) iter.Seq2[stackaddrs.ComponentInstance, *Component] { |
| return func(yield func(stackaddrs.ComponentInstance, *Component) bool) { |
| stack := p.Root.GetDescendentStack(addr.Stack) |
| if stack != nil { |
| components := stack.Components[addr.Item] |
| for key, component := range components { |
| proceed := yield(stackaddrs.ComponentInstance{ |
| Component: addr.Item, |
| Key: key, |
| }, component) |
| if !proceed { |
| return |
| } |
| } |
| } |
| } |
| } |
| |
| func (p *Plan) StackInstances(addr stackaddrs.AbsStackCall) iter.Seq[stackaddrs.StackInstance] { |
| return func(yield func(stackaddrs.StackInstance) bool) { |
| stack := p.Root.GetDescendentStack(addr.Stack) |
| if stack != nil { |
| stacks := stack.Children[addr.Item.Name] |
| for key := range stacks { |
| proceed := yield(append(addr.Stack, stackaddrs.StackInstanceStep{ |
| Name: addr.Item.Name, |
| Key: key, |
| })) |
| if !proceed { |
| return |
| } |
| } |
| } |
| } |
| } |
| |
| func (p *Plan) GetOrCreate(addr stackaddrs.AbsComponentInstance, component *Component) *Component { |
| targetStackInstance := p.Root.GetOrCreateDescendentStack(addr.Stack) |
| return targetStackInstance.GetOrCreateComponent(addr.Item, component) |
| } |
| |
| func (p *Plan) GetComponent(addr stackaddrs.AbsComponentInstance) *Component { |
| targetStackInstance := p.Root.GetDescendentStack(addr.Stack) |
| return targetStackInstance.GetComponent(addr.Item) |
| } |
| |
| func (p *Plan) GetStack(addr stackaddrs.StackInstance) *StackInstance { |
| return p.Root.GetDescendentStack(addr) |
| } |
| |
| // RequiredProviderInstances returns a description of all of the provider |
| // instance slots that are required to satisfy the resource instances |
| // belonging to the given component instance. |
| // |
| // See also stackeval.ComponentConfig.RequiredProviderInstances for a similar |
| // function that operates on the configuration of a component instance rather |
| // than the plan of one. |
| func (p *Plan) RequiredProviderInstances(addr stackaddrs.AbsComponentInstance) addrs.Set[addrs.RootProviderConfig] { |
| stack := p.Root.GetDescendentStack(addr.Stack) |
| if stack == nil { |
| return addrs.MakeSet[addrs.RootProviderConfig]() |
| } |
| |
| components, ok := stack.Components[addr.Item.Component] |
| if !ok { |
| return addrs.MakeSet[addrs.RootProviderConfig]() |
| } |
| |
| component, ok := components[addr.Item.Key] |
| if !ok { |
| return addrs.MakeSet[addrs.RootProviderConfig]() |
| } |
| return component.RequiredProviderInstances() |
| } |
| |
| // StackInstance stores the components and embedded stacks for a single stack |
| // instance. |
| type StackInstance struct { |
| Address stackaddrs.StackInstance |
| Children map[string]map[addrs.InstanceKey]*StackInstance |
| Components map[stackaddrs.Component]map[addrs.InstanceKey]*Component |
| } |
| |
| func newStackInstance(address stackaddrs.StackInstance) *StackInstance { |
| return &StackInstance{ |
| Address: address, |
| Components: make(map[stackaddrs.Component]map[addrs.InstanceKey]*Component), |
| Children: make(map[string]map[addrs.InstanceKey]*StackInstance), |
| } |
| } |
| |
| func (stack *StackInstance) GetComponent(addr stackaddrs.ComponentInstance) *Component { |
| components, ok := stack.Components[addr.Component] |
| if !ok { |
| return nil |
| } |
| return components[addr.Key] |
| } |
| |
| func (stack *StackInstance) GetOrCreateComponent(addr stackaddrs.ComponentInstance, component *Component) *Component { |
| components, ok := stack.Components[addr.Component] |
| if !ok { |
| components = make(map[addrs.InstanceKey]*Component) |
| } |
| existing, ok := components[addr.Key] |
| if ok { |
| return existing |
| } |
| components[addr.Key] = component |
| stack.Components[addr.Component] = components |
| return component |
| } |
| |
| func (stack *StackInstance) GetOrCreateDescendentStack(addr stackaddrs.StackInstance) *StackInstance { |
| if len(addr) == 0 { |
| return stack |
| } |
| next := stack.GetOrCreateChildStack(addr[0]) |
| return next.GetOrCreateDescendentStack(addr[1:]) |
| } |
| |
| func (stack *StackInstance) GetOrCreateChildStack(step stackaddrs.StackInstanceStep) *StackInstance { |
| child := stack.GetChildStack(step) |
| if child == nil { |
| child = stack.CreateChildStack(step) |
| } |
| return child |
| } |
| |
| func (stack *StackInstance) GetDescendentStack(addr stackaddrs.StackInstance) *StackInstance { |
| if len(addr) == 0 { |
| return stack |
| } |
| |
| next := stack.GetChildStack(addr[0]) |
| if next == nil { |
| return nil |
| } |
| return next.GetDescendentStack(addr[1:]) |
| } |
| |
| func (stack *StackInstance) GetChildStack(step stackaddrs.StackInstanceStep) *StackInstance { |
| insts, ok := stack.Children[step.Name] |
| if !ok { |
| return nil |
| } |
| return insts[step.Key] |
| } |
| |
| func (stack *StackInstance) CreateChildStack(step stackaddrs.StackInstanceStep) *StackInstance { |
| stacks, ok := stack.Children[step.Name] |
| if !ok { |
| stacks = make(map[addrs.InstanceKey]*StackInstance) |
| } |
| stacks[step.Key] = newStackInstance(append(stack.Address, step)) |
| stack.Children[step.Name] = stacks |
| return stacks[step.Key] |
| } |
| |
| func (stack *StackInstance) GetOk(addr stackaddrs.AbsComponentInstance) (*Component, bool) { |
| if len(addr.Stack) == 0 { |
| component, ok := stack.Components[addr.Item.Component] |
| if !ok { |
| return nil, false |
| } |
| |
| instance, ok := component[addr.Item.Key] |
| return instance, ok |
| } |
| |
| stacks, ok := stack.Children[addr.Stack[0].Name] |
| if !ok { |
| return nil, false |
| } |
| next, ok := stacks[addr.Stack[0].Key] |
| if !ok { |
| return nil, false |
| } |
| return next.GetOk(stackaddrs.AbsComponentInstance{ |
| Stack: addr.Stack[1:], |
| Item: addr.Item, |
| }) |
| } |
| |
| func (stack *StackInstance) iterate(yield func(stackaddrs.AbsComponentInstance, *Component) bool) bool { |
| for name, components := range stack.Components { |
| for key, component := range components { |
| proceed := yield(stackaddrs.AbsComponentInstance{ |
| Stack: stack.Address, |
| Item: stackaddrs.ComponentInstance{ |
| Component: name, |
| Key: key, |
| }, |
| }, component) |
| if !proceed { |
| return false |
| } |
| } |
| } |
| |
| for _, stacks := range stack.Children { |
| for _, inst := range stacks { |
| proceed := inst.iterate(yield) |
| if !proceed { |
| return false |
| } |
| } |
| } |
| |
| return true |
| } |