mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
13 KiB
340 lines
13 KiB
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package plans
|
|
|
|
import (
|
|
"sort"
|
|
"time"
|
|
|
|
version "github.com/hashicorp/go-version"
|
|
tfaddr "github.com/hashicorp/terraform-registry-address"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/collections"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/lang"
|
|
"github.com/hashicorp/terraform/internal/lang/globalref"
|
|
"github.com/hashicorp/terraform/internal/moduletest/mocking"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
)
|
|
|
|
// Plan is the top-level type representing a planned set of changes.
|
|
//
|
|
// A plan is a summary of the set of changes required to move from a current
|
|
// state to a goal state derived from configuration. The described changes
|
|
// are not applied directly, but contain an approximation of the final
|
|
// result that will be completed during apply by resolving any values that
|
|
// cannot be predicted.
|
|
//
|
|
// A plan must always be accompanied by the configuration it was built from,
|
|
// since the plan does not itself include all of the information required to
|
|
// make the changes indicated.
|
|
type Plan struct {
|
|
// Mode is the mode under which this plan was created.
|
|
//
|
|
// This is only recorded to allow for UI differences when presenting plans
|
|
// to the end-user, and so it must not be used to influence apply-time
|
|
// behavior. The actions during apply must be described entirely by
|
|
// the Changes field, regardless of how the plan was created.
|
|
//
|
|
// FIXME: destroy operations still rely on DestroyMode being set, because
|
|
// there is no other source of this information in the plan. New behavior
|
|
// should not be added based on this flag, and changing the flag should be
|
|
// checked carefully against existing destroy behaviors.
|
|
UIMode Mode
|
|
|
|
// VariableValues, VariableMarks, and ApplyTimeVariables together describe
|
|
// how Terraform should decide the input variable values for the apply
|
|
// phase if this plan is to be applied.
|
|
//
|
|
// VariableValues and VariableMarks describe persisted (non-ephemeral)
|
|
// values that were set as part of the planning options and are to be
|
|
// re-used during the apply phase. VariableValues can potentially contain
|
|
// unknown values for a speculative plan, but the variable values must
|
|
// all be known for a plan that will subsequently be applied.
|
|
//
|
|
// ApplyTimeVariables retains the names of any ephemeral variables that were
|
|
// set (non-null) during the planning phase and must therefore be
|
|
// re-supplied by the caller (potentially with different values) during
|
|
// the apply phase. Ephemeral input variables are intended for populating
|
|
// arguments for other ephemeral objects in the configuration, such as
|
|
// provider configurations. Although the values for these variables can
|
|
// change between plan and apply, their "nullness" may not.
|
|
VariableValues map[string]DynamicValue
|
|
VariableMarks map[string][]cty.PathValueMarks
|
|
ApplyTimeVariables collections.Set[string]
|
|
|
|
Changes *ChangesSrc
|
|
DriftedResources []*ResourceInstanceChangeSrc
|
|
DeferredResources []*DeferredResourceInstanceChangeSrc
|
|
DeferredActionInvocations []*DeferredActionInvocationSrc
|
|
TargetAddrs []addrs.Targetable
|
|
ActionTargetAddrs []addrs.Targetable
|
|
ForceReplaceAddrs []addrs.AbsResourceInstance
|
|
|
|
Backend *Backend
|
|
StateStore *StateStore
|
|
|
|
// Complete is true if Terraform considers this to be a "complete" plan,
|
|
// which is to say that it includes a planned action (even if no-op)
|
|
// for every resource instance object that was mentioned across both
|
|
// the desired state and prior state.
|
|
//
|
|
// If Complete is false then the plan might still be applyable (check
|
|
// [Plan.Applyable]) but after applying it the operator should be reminded
|
|
// to plan and apply again to hopefully make more progress towards
|
|
// convergence.
|
|
//
|
|
// For an incomplete plan, other fields of this type may give more context
|
|
// about why the plan is incomplete, which a UI layer could present to
|
|
// the user as part of a warning that the plan is incomplete.
|
|
Complete bool
|
|
|
|
// Applyable is true if both Terraform was able to create a plan
|
|
// successfully and if the plan calls for making some sort of meaningful
|
|
// change.
|
|
//
|
|
// If [Plan.Errored] is also set then that means the plan is non-applyable
|
|
// due to an error. If not then the plan was created successfully but found
|
|
// no material differences between desired and prior state, and so
|
|
// applying this plan would achieve nothing.
|
|
Applyable bool
|
|
|
|
// Errored is true if the Changes information is incomplete because
|
|
// the planning operation failed. An errored plan cannot be applied,
|
|
// but can be cautiously inspected for debugging purposes.
|
|
Errored bool
|
|
|
|
// Checks captures a snapshot of the (probably-incomplete) check results
|
|
// at the end of the planning process.
|
|
//
|
|
// If this plan is applyable (that is, if the planning process completed
|
|
// without errors) then the set of checks here should be complete even
|
|
// though some of them will likely have StatusUnknown where the check
|
|
// condition depends on values we won't know until the apply step.
|
|
Checks *states.CheckResults
|
|
|
|
// RelevantAttributes is a set of resource instance addresses and
|
|
// attributes that are either directly affected by proposed changes or may
|
|
// have indirectly contributed to them via references in expressions.
|
|
//
|
|
// This is the result of a heuristic and is intended only as a hint to
|
|
// the UI layer in case it wants to emphasize or de-emphasize certain
|
|
// resources. Don't use this to drive any non-cosmetic behavior, especially
|
|
// including anything that would be subject to compatibility constraints.
|
|
RelevantAttributes []globalref.ResourceAttr
|
|
|
|
// PrevRunState and PriorState both describe the situation that the plan
|
|
// was derived from:
|
|
//
|
|
// PrevRunState is a representation of the outcome of the previous
|
|
// Terraform operation, without any updates from the remote system but
|
|
// potentially including some changes that resulted from state upgrade
|
|
// actions.
|
|
//
|
|
// PriorState is a representation of the current state of remote objects,
|
|
// which will differ from PrevRunState if the "refresh" step returned
|
|
// different data, which might reflect drift.
|
|
//
|
|
// PriorState is the main snapshot we use for actions during apply.
|
|
// PrevRunState is only here so that we can diff PriorState against it in
|
|
// order to report to the user any out-of-band changes we've detected.
|
|
PrevRunState *states.State
|
|
PriorState *states.State
|
|
|
|
// ExternalReferences are references that are being made to resources within
|
|
// the plan from external sources.
|
|
//
|
|
// This is never recorded outside of Terraform. It is not written into the
|
|
// binary plan file, and it is not written into the JSON structured outputs.
|
|
// The testing framework never writes the plans out but holds everything in
|
|
// memory as it executes, so there is no need to add any kind of
|
|
// serialization for this field. This does mean that you shouldn't rely on
|
|
// this field existing unless you have just generated the plan.
|
|
ExternalReferences []*addrs.Reference
|
|
|
|
// Overrides contains the set of overrides that were applied while making
|
|
// this plan. We need to provide the same set of overrides when applying
|
|
// the plan so we preserve them here. As with ExternalReferences, this is
|
|
// only used by the testing framework and so isn't written into any external
|
|
// representation of the plan.
|
|
Overrides *mocking.Overrides
|
|
|
|
// Timestamp is the record of truth for when the plan happened.
|
|
Timestamp time.Time
|
|
|
|
// FunctionResults stores hashed results from all providers function calls
|
|
// and builtin calls which may access external state so that calls during
|
|
// apply can be checked for consistency.
|
|
FunctionResults []lang.FunctionResultHash
|
|
}
|
|
|
|
// ProviderAddrs returns a list of all of the provider configuration addresses
|
|
// referenced throughout the receiving plan.
|
|
//
|
|
// The result is de-duplicated so that each distinct address appears only once.
|
|
func (p *Plan) ProviderAddrs() []addrs.AbsProviderConfig {
|
|
if p == nil || p.Changes == nil {
|
|
return nil
|
|
}
|
|
|
|
m := map[string]addrs.AbsProviderConfig{}
|
|
|
|
// Get all provider requirements from resources.
|
|
for _, rc := range p.Changes.Resources {
|
|
m[rc.ProviderAddr.String()] = rc.ProviderAddr
|
|
}
|
|
|
|
// Get the provider required for pluggable state storage, if that's in use.
|
|
//
|
|
// This check should be redundant as:
|
|
// 1) Any provider used for state storage would be in required_providers, which is checked separately elsewhere.
|
|
// 2) An apply operation that uses the planfile only checks the providers needed for the plan _after_ the operations backend
|
|
// for the operation is set up, and that process will detect if the provider needed for state storage is missing.
|
|
//
|
|
// However, for completeness when describing the providers needed by a plan, it is included here.
|
|
if p.StateStore != nil {
|
|
address := addrs.AbsProviderConfig{
|
|
Module: addrs.RootModule, // A state_store block is only ever in the root module
|
|
Provider: *p.StateStore.Provider.Source,
|
|
// Alias: aliases are not permitted when using a provider for PSS.
|
|
}
|
|
|
|
m[p.StateStore.Provider.Source.String()] = address
|
|
}
|
|
|
|
if len(m) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// This is mainly just so we'll get stable results for testing purposes.
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
ret := make([]addrs.AbsProviderConfig, len(keys))
|
|
for i, key := range keys {
|
|
ret[i] = m[key]
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// Backend represents the backend-related configuration and other data as it
|
|
// existed when a plan was created.
|
|
type Backend struct {
|
|
// Type is the type of backend that the plan will apply against.
|
|
Type string
|
|
|
|
// Config is the configuration of the backend, whose schema is decided by
|
|
// the backend Type.
|
|
Config DynamicValue
|
|
|
|
// Workspace is the name of the workspace that was active when the plan
|
|
// was created. It is illegal to apply a plan created for one workspace
|
|
// to the state of another workspace.
|
|
// (This constraint is already enforced by the statefile lineage mechanism,
|
|
// but storing this explicitly allows us to return a better error message
|
|
// in the situation where the user has the wrong workspace selected.)
|
|
Workspace string
|
|
}
|
|
|
|
func NewBackend(typeName string, config cty.Value, configSchema *configschema.Block, workspaceName string) (*Backend, error) {
|
|
dv, err := NewDynamicValue(config, configSchema.ImpliedType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Backend{
|
|
Type: typeName,
|
|
Config: dv,
|
|
Workspace: workspaceName,
|
|
}, nil
|
|
}
|
|
|
|
// StateStore represents the state store-related configuration and other data as it
|
|
// existed when a plan was created.
|
|
type StateStore struct {
|
|
// Type is the type of state store that the plan will apply against.
|
|
Type string
|
|
|
|
Provider *Provider
|
|
|
|
// Config is the configuration of the state store, whose schema is obtained
|
|
// from the host provider's GetProviderSchema response.
|
|
Config DynamicValue
|
|
|
|
// Workspace is the name of the workspace that was active when the plan
|
|
// was created. It is illegal to apply a plan created for one workspace
|
|
// to the state of another workspace.
|
|
// (This constraint is already enforced by the statefile lineage mechanism,
|
|
// but storing this explicitly allows us to return a better error message
|
|
// in the situation where the user has the wrong workspace selected.)
|
|
Workspace string
|
|
}
|
|
|
|
type Provider struct {
|
|
Version *version.Version // The specific provider version used for the state store. Should be set using a getproviders.Version, etc.
|
|
Source *tfaddr.Provider // The FQN/fully-qualified name of the provider.
|
|
|
|
// Config is the configuration of the state store, whose schema is obtained
|
|
// from the host provider's GetProviderSchema response.
|
|
Config DynamicValue
|
|
}
|
|
|
|
func NewStateStore(typeName string, ver *version.Version, source *tfaddr.Provider, storeConfig cty.Value, storeSchema *configschema.Block, providerConfig cty.Value, providerSchema *configschema.Block, workspaceName string) (*StateStore, error) {
|
|
sdv, err := NewDynamicValue(storeConfig, storeSchema.ImpliedType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pdv, err := NewDynamicValue(providerConfig, providerSchema.ImpliedType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
provider := &Provider{
|
|
Version: ver,
|
|
Source: source,
|
|
Config: pdv,
|
|
}
|
|
|
|
return &StateStore{
|
|
Type: typeName,
|
|
Provider: provider,
|
|
Config: sdv,
|
|
Workspace: workspaceName,
|
|
}, nil
|
|
}
|
|
|
|
// SetVersion includes logic for parsing a string representation of a version,
|
|
// for example data read from a plan file.
|
|
// If an error occurs it is returned and the receiver's Version field is unchanged.
|
|
// If there are no errors then the receiver's Version field is updated.
|
|
func (p *Provider) SetVersion(input string) error {
|
|
ver, err := version.NewVersion(input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.Version = ver
|
|
return nil
|
|
}
|
|
|
|
// SetSource includes logic for parsing a string representation of a provider source,
|
|
// for example data read from a plan file.
|
|
// If an error occurs it is returned and the receiver's Source field is unchanged.
|
|
// If there are no errors then the receiver's Source field is updated.
|
|
func (p *Provider) SetSource(input string) error {
|
|
source, diags := addrs.ParseProviderSourceString(input)
|
|
if diags.HasErrors() {
|
|
return diags.ErrWithWarnings()
|
|
}
|
|
|
|
p.Source = &source
|
|
return nil
|
|
}
|