blob: 9af56cf454013a41870149ceab3e4a96824fa26a [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package plans
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/schemarepo"
"github.com/hashicorp/terraform/internal/states"
)
// ChangesSrc describes various actions that Terraform will attempt to take if
// the corresponding plan is applied.
//
// A Changes object can be rendered into a visual diff (by the caller, using
// code in another package) for display to the user.
type ChangesSrc struct {
// Resources tracks planned changes to resource instance objects.
Resources []*ResourceInstanceChangeSrc
// Outputs tracks planned changes output values.
//
// Note that although an in-memory plan contains planned changes for
// outputs throughout the configuration, a plan serialized
// to disk retains only the root outputs because they are
// externally-visible, while other outputs are implementation details and
// can be easily re-calculated during the apply phase. Therefore only root
// module outputs will survive a round-trip through a plan file.
Outputs []*OutputChangeSrc
}
func NewChangesSrc() *ChangesSrc {
return &ChangesSrc{}
}
func (c *ChangesSrc) Empty() bool {
for _, res := range c.Resources {
if res.Action != NoOp || res.Moved() {
return false
}
if res.Importing != nil {
return false
}
}
for _, out := range c.Outputs {
if out.Addr.Module.IsRoot() && out.Action != NoOp {
return false
}
}
return true
}
// ResourceInstance returns the planned change for the current object of the
// resource instance of the given address, if any. Returns nil if no change is
// planned.
func (c *ChangesSrc) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstanceChangeSrc {
for _, rc := range c.Resources {
if rc.Addr.Equal(addr) && rc.DeposedKey == states.NotDeposed {
return rc
}
}
return nil
}
// ResourceInstanceDeposed returns the plan change of a deposed object of
// the resource instance of the given address, if any. Returns nil if no change
// is planned.
func (c *ChangesSrc) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key states.DeposedKey) *ResourceInstanceChangeSrc {
for _, rc := range c.Resources {
if rc.Addr.Equal(addr) && rc.DeposedKey == key {
return rc
}
}
return nil
}
// OutputValue returns the planned change for the output value with the
//
// given address, if any. Returns nil if no change is planned.
func (c *ChangesSrc) OutputValue(addr addrs.AbsOutputValue) *OutputChangeSrc {
for _, oc := range c.Outputs {
if oc.Addr.Equal(addr) {
return oc
}
}
return nil
}
// Decode decodes all the stored resource and output changes into a new *Changes value.
func (c *ChangesSrc) Decode(schemas *schemarepo.Schemas) (*Changes, error) {
changes := NewChanges()
for _, rcs := range c.Resources {
p, ok := schemas.Providers[rcs.ProviderAddr.Provider]
if !ok {
return nil, fmt.Errorf("ChangesSrc.Decode: missing provider %s for %s", rcs.ProviderAddr, rcs.Addr)
}
var schema providers.Schema
switch rcs.Addr.Resource.Resource.Mode {
case addrs.ManagedResourceMode:
schema = p.ResourceTypes[rcs.Addr.Resource.Resource.Type]
case addrs.DataResourceMode:
schema = p.DataSources[rcs.Addr.Resource.Resource.Type]
default:
panic(fmt.Sprintf("unexpected resource mode %s", rcs.Addr.Resource.Resource.Mode))
}
if schema.Body == nil {
return nil, fmt.Errorf("ChangesSrc.Decode: missing schema for %s", rcs.Addr)
}
rc, err := rcs.Decode(schema)
if err != nil {
return nil, err
}
rc.Before = marks.MarkPaths(rc.Before, marks.Sensitive, rcs.BeforeSensitivePaths)
rc.After = marks.MarkPaths(rc.After, marks.Sensitive, rcs.AfterSensitivePaths)
changes.Resources = append(changes.Resources, rc)
}
for _, ocs := range c.Outputs {
oc, err := ocs.Decode()
if err != nil {
return nil, err
}
changes.Outputs = append(changes.Outputs, oc)
}
return changes, nil
}
// AppendResourceInstanceChange records the given resource instance change in
// the set of planned resource changes.
func (c *ChangesSrc) AppendResourceInstanceChange(change *ResourceInstanceChangeSrc) {
if c == nil {
panic("AppendResourceInstanceChange on nil ChangesSync")
}
s := change.DeepCopy()
c.Resources = append(c.Resources, s)
}
// ResourceInstanceChangeSrc is a not-yet-decoded ResourceInstanceChange.
// Pass the associated resource type's schema type to method Decode to
// obtain a ResourceInstanceChange.
type ResourceInstanceChangeSrc struct {
// Addr is the absolute address of the resource instance that the change
// will apply to.
//
// THIS IS NOT A SUFFICIENT UNIQUE IDENTIFIER! It doesn't consider the
// fact that multiple objects for the same resource instance might be
// present in the same plan; use the ObjectAddr method instead if you
// need a unique address for a particular change.
Addr addrs.AbsResourceInstance
// DeposedKey is the identifier for a deposed object associated with the
// given instance, or states.NotDeposed if this change applies to the
// current object.
//
// A Replace change for a resource with create_before_destroy set will
// create a new DeposedKey temporarily during replacement. In that case,
// DeposedKey in the plan is always states.NotDeposed, representing that
// the current object is being replaced with the deposed.
DeposedKey states.DeposedKey
// PrevRunAddr is the absolute address that this resource instance had at
// the conclusion of a previous run.
//
// This will typically be the same as Addr, but can be different if the
// previous resource instance was subject to a "moved" block that we
// handled in the process of creating this plan.
//
// For the initial creation of a resource instance there isn't really any
// meaningful "previous run address", but PrevRunAddr will still be set
// equal to Addr in that case in order to simplify logic elsewhere which
// aims to detect and react to the movement of instances between addresses.
PrevRunAddr addrs.AbsResourceInstance
// Provider is the address of the provider configuration that was used
// to plan this change, and thus the configuration that must also be
// used to apply it.
ProviderAddr addrs.AbsProviderConfig
// ChangeSrc is an embedded description of the not-yet-decoded change.
ChangeSrc
// ActionReason is an optional extra indication of why we chose the
// action recorded in Change.Action for this particular resource instance.
//
// This is an approximate mechanism only for the purpose of explaining the
// plan to end-users in the UI and is not to be used for any
// decision-making during the apply step; if apply behavior needs to vary
// depending on the "action reason" then the information for that decision
// must be recorded more precisely elsewhere for that purpose.
//
// See the field of the same name in ResourceInstanceChange for more
// details.
ActionReason ResourceInstanceChangeActionReason
// RequiredReplace is a set of paths that caused the change action to be
// Replace rather than Update. Always nil if the change action is not
// Replace.
RequiredReplace cty.PathSet
// Private allows a provider to stash any extra data that is opaque to
// Terraform that relates to this change. Terraform will save this
// byte-for-byte and return it to the provider in the apply call.
Private []byte
}
func (rcs *ResourceInstanceChangeSrc) ObjectAddr() addrs.AbsResourceInstanceObject {
return addrs.AbsResourceInstanceObject{
ResourceInstance: rcs.Addr,
DeposedKey: rcs.DeposedKey,
}
}
// Decode unmarshals the raw representation of the instance object being
// changed. Pass the implied type of the corresponding resource type schema
// for correct operation.
func (rcs *ResourceInstanceChangeSrc) Decode(schema providers.Schema) (*ResourceInstanceChange, error) {
change, err := rcs.ChangeSrc.Decode(&schema)
if err != nil {
return nil, err
}
prevRunAddr := rcs.PrevRunAddr
if prevRunAddr.Resource.Resource.Type == "" {
// Suggests an old caller that hasn't been properly updated to
// populate this yet.
prevRunAddr = rcs.Addr
}
return &ResourceInstanceChange{
Addr: rcs.Addr,
PrevRunAddr: prevRunAddr,
DeposedKey: rcs.DeposedKey,
ProviderAddr: rcs.ProviderAddr,
Change: *change,
ActionReason: rcs.ActionReason,
RequiredReplace: rcs.RequiredReplace,
Private: rcs.Private,
}, nil
}
// DeepCopy creates a copy of the receiver where any pointers to nested mutable
// values are also copied, thus ensuring that future mutations of the receiver
// will not affect the copy.
//
// Some types used within a resource change are immutable by convention even
// though the Go language allows them to be mutated, such as the types from
// the addrs package. These are _not_ copied by this method, under the
// assumption that callers will behave themselves.
func (rcs *ResourceInstanceChangeSrc) DeepCopy() *ResourceInstanceChangeSrc {
if rcs == nil {
return nil
}
ret := *rcs
ret.RequiredReplace = cty.NewPathSet(ret.RequiredReplace.List()...)
if len(ret.Private) != 0 {
private := make([]byte, len(ret.Private))
copy(private, ret.Private)
ret.Private = private
}
ret.ChangeSrc.Before = ret.ChangeSrc.Before.Copy()
ret.ChangeSrc.After = ret.ChangeSrc.After.Copy()
return &ret
}
func (rcs *ResourceInstanceChangeSrc) Moved() bool {
return !rcs.Addr.Equal(rcs.PrevRunAddr)
}
// OutputChangeSrc describes a change to an output value.
type OutputChangeSrc struct {
// Addr is the absolute address of the output value that the change
// will apply to.
Addr addrs.AbsOutputValue
// ChangeSrc is an embedded description of the not-yet-decoded change.
//
// For output value changes, the type constraint for the DynamicValue
// instances is always cty.DynamicPseudoType.
ChangeSrc
// Sensitive, if true, indicates that either the old or new value in the
// change is sensitive and so a rendered version of the plan in the UI
// should elide the actual values while still indicating the action of the
// change.
Sensitive bool
}
// Decode unmarshals the raw representation of the output value being
// changed.
func (ocs *OutputChangeSrc) Decode() (*OutputChange, error) {
change, err := ocs.ChangeSrc.Decode(nil)
if err != nil {
return nil, err
}
return &OutputChange{
Addr: ocs.Addr,
Change: *change,
Sensitive: ocs.Sensitive,
}, nil
}
// DeepCopy creates a copy of the receiver where any pointers to nested mutable
// values are also copied, thus ensuring that future mutations of the receiver
// will not affect the copy.
//
// Some types used within a resource change are immutable by convention even
// though the Go language allows them to be mutated, such as the types from
// the addrs package. These are _not_ copied by this method, under the
// assumption that callers will behave themselves.
func (ocs *OutputChangeSrc) DeepCopy() *OutputChangeSrc {
if ocs == nil {
return nil
}
ret := *ocs
ret.ChangeSrc.Before = ret.ChangeSrc.Before.Copy()
ret.ChangeSrc.After = ret.ChangeSrc.After.Copy()
return &ret
}
// ImportingSrc is the part of a ChangeSrc that describes the embedded import
// action.
//
// The fields in here are subject to change, so downstream consumers should be
// prepared for backwards compatibility in case the contents changes.
type ImportingSrc struct {
// ID is the original ID of the imported resource.
ID string
// Identity is the original identity of the imported resource.
Identity DynamicValue
// Unknown is true if the ID was unknown when we tried to import it. This
// should only be true if the overall change is embedded within a deferred
// action.
Unknown bool
}
// Decode unmarshals the raw representation of the importing action.
func (is *ImportingSrc) Decode(identityType cty.Type) *Importing {
if is == nil {
return nil
}
if is.Unknown {
if is.Identity == nil {
return &Importing{
Target: cty.UnknownVal(cty.String),
}
}
return &Importing{
Target: cty.UnknownVal(cty.EmptyObject),
}
}
if is.Identity == nil {
return &Importing{
Target: cty.StringVal(is.ID),
}
}
target, err := is.Identity.Decode(identityType)
if err != nil {
return &Importing{
Target: target,
}
}
return nil
}
// ChangeSrc is a not-yet-decoded Change.
type ChangeSrc struct {
// Action defines what kind of change is being made.
Action Action
// Before and After correspond to the fields of the same name in Change,
// but have not yet been decoded from the serialized value used for
// storage.
Before, After DynamicValue
// BeforeIdentity and AfterIdentity correspond to the fields of the same name in Change,
// but have not yet been decoded from the serialized value used for
// storage.
BeforeIdentity, AfterIdentity DynamicValue
// BeforeSensitivePaths and AfterSensitivePaths are the paths for any
// values in Before or After (respectively) that are considered to be
// sensitive. The sensitive marks are removed from the in-memory values
// to enable encoding (marked values cannot be marshalled), and so we
// store the sensitive paths to allow re-marking later when we decode
// the serialized change.
BeforeSensitivePaths, AfterSensitivePaths []cty.Path
// Importing is present if the resource is being imported as part of this
// change.
//
// Use the simple presence of this field to detect if a ChangeSrc is to be
// imported, the contents of this structure may be modified going forward.
Importing *ImportingSrc
// GeneratedConfig contains any HCL config generated for this resource
// during planning, as a string. If GeneratedConfig is populated, Importing
// should be true. However, not all Importing changes contain generated
// config.
GeneratedConfig string
}
// Decode unmarshals the raw representations of the before and after values
// to produce a Change object. Pass the type constraint that the result must
// conform to.
//
// Where a ChangeSrc is embedded in some other struct, it's generally better
// to call the corresponding Decode method of that struct rather than working
// directly with its embedded Change.
func (cs *ChangeSrc) Decode(schema *providers.Schema) (*Change, error) {
var err error
ty := cty.DynamicPseudoType
identityType := cty.DynamicPseudoType
if schema != nil {
ty = schema.Body.ImpliedType()
identityType = schema.Identity.ImpliedType()
}
before := cty.NullVal(ty)
beforeIdentity := cty.NullVal(identityType)
after := cty.NullVal(ty)
afterIdentity := cty.NullVal(identityType)
if len(cs.Before) > 0 {
before, err = cs.Before.Decode(ty)
if err != nil {
return nil, fmt.Errorf("error decoding 'before' value: %s", err)
}
}
if len(cs.BeforeIdentity) > 0 {
beforeIdentity, err = cs.BeforeIdentity.Decode(identityType)
if err != nil {
return nil, fmt.Errorf("error decoding 'beforeIdentity' value: %s", err)
}
}
if len(cs.After) > 0 {
after, err = cs.After.Decode(ty)
if err != nil {
return nil, fmt.Errorf("error decoding 'after' value: %s", err)
}
}
if len(cs.AfterIdentity) > 0 {
afterIdentity, err = cs.AfterIdentity.Decode(identityType)
if err != nil {
return nil, fmt.Errorf("error decoding 'afterIdentity' value: %s", err)
}
}
return &Change{
Action: cs.Action,
Before: marks.MarkPaths(before, marks.Sensitive, cs.BeforeSensitivePaths),
BeforeIdentity: beforeIdentity,
After: marks.MarkPaths(after, marks.Sensitive, cs.AfterSensitivePaths),
AfterIdentity: afterIdentity,
Importing: cs.Importing.Decode(identityType),
GeneratedConfig: cs.GeneratedConfig,
}, nil
}