| // Copyright IBM Corp. 2014, 2026 | |
| // 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/genconfig" | |
| "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 | |
| Queries []*QueryInstanceSrc | |
| // ActionInvocations tracks planned action invocations, which may have | |
| // embedded resource instance changes. | |
| ActionInvocations []*ActionInvocationInstanceSrc | |
| // 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 | |
| } | |
| } | |
| if len(c.ActionInvocations) > 0 { | |
| // action invocations can be applied | |
| 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 _, qis := range c.Queries { | |
| p, ok := schemas.Providers[qis.ProviderAddr.Provider] | |
| if !ok { | |
| return nil, fmt.Errorf("ChangesSrc.Decode: missing provider %s for %s", qis.ProviderAddr, qis.Addr) | |
| } | |
| schema := p.ListResourceTypes[qis.Addr.Resource.Resource.Type] | |
| if schema.Body == nil { | |
| return nil, fmt.Errorf("ChangesSrc.Decode: missing schema for %s", qis.Addr) | |
| } | |
| query, err := qis.Decode(schema) | |
| if err != nil { | |
| return nil, err | |
| } | |
| changes.Queries = append(changes.Queries, query) | |
| } | |
| for _, ais := range c.ActionInvocations { | |
| p, ok := schemas.Providers[ais.ProviderAddr.Provider] | |
| if !ok { | |
| return nil, fmt.Errorf("ChangesSrc.Decode: missing provider %s for %s", ais.ProviderAddr, ais.Addr) | |
| } | |
| schema, ok := p.Actions[ais.Addr.Action.Action.Type] | |
| if !ok { | |
| return nil, fmt.Errorf("ChangesSrc.Decode: missing schema for %s", ais.Addr.Action.Action.Type) | |
| } | |
| action, err := ais.Decode(&schema) | |
| if err != nil { | |
| return nil, err | |
| } | |
| changes.ActionInvocations = append(changes.ActionInvocations, action) | |
| } | |
| 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) | |
| } | |
| type QueryInstanceSrc struct { | |
| Addr addrs.AbsResourceInstance | |
| ProviderAddr addrs.AbsProviderConfig | |
| Results DynamicValue | |
| Generated genconfig.ImportGroup | |
| } | |
| func (qis *QueryInstanceSrc) Decode(schema providers.Schema) (*QueryInstance, error) { | |
| query, err := qis.Results.Decode(schema.Body.ImpliedType()) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &QueryInstance{ | |
| Addr: qis.Addr, | |
| Results: QueryResults{ | |
| Value: query, | |
| Generated: qis.Generated, | |
| }, | |
| ProviderAddr: qis.ProviderAddr, | |
| }, nil | |
| } | |
| // 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 | |
| } | |
| // AppendActionInvocationInstanceChange records the given resource instance change in | |
| // the set of planned resource changes. | |
| func (c *ChangesSrc) AppendActionInvocationInstanceChange(action *ActionInvocationInstanceSrc) { | |
| if c == nil { | |
| panic("AppendActionInvocationInstanceChange on nil ChangesSync") | |
| } | |
| a := action.DeepCopy() | |
| c.ActionInvocations = append(c.ActionInvocations, a) | |
| } | |
| type ActionInvocationInstanceSrc struct { | |
| Addr addrs.AbsActionInstance | |
| ActionTrigger ActionTrigger | |
| ConfigValue DynamicValue | |
| SensitiveConfigPaths []cty.Path | |
| ProviderAddr addrs.AbsProviderConfig | |
| } | |
| // Decode unmarshals the raw representation of actions. | |
| func (acs *ActionInvocationInstanceSrc) Decode(schema *providers.ActionSchema) (*ActionInvocationInstance, error) { | |
| ty := cty.DynamicPseudoType | |
| if schema != nil { | |
| ty = schema.ConfigSchema.ImpliedType() | |
| } | |
| config, err := acs.ConfigValue.Decode(ty) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding 'config' value: %s", err) | |
| } | |
| markedConfigValue := marks.MarkPaths(config, marks.Sensitive, acs.SensitiveConfigPaths) | |
| ai := &ActionInvocationInstance{ | |
| Addr: acs.Addr, | |
| ActionTrigger: acs.ActionTrigger, | |
| ProviderAddr: acs.ProviderAddr, | |
| ConfigValue: markedConfigValue, | |
| } | |
| return ai, nil | |
| } | |
| func (acs *ActionInvocationInstanceSrc) DeepCopy() *ActionInvocationInstanceSrc { | |
| if acs == nil { | |
| return acs | |
| } | |
| ret := *acs | |
| ret.ConfigValue = ret.ConfigValue.Copy() | |
| return &ret | |
| } | |
| func (acs *ActionInvocationInstanceSrc) Less(other *ActionInvocationInstanceSrc) bool { | |
| if acs.ActionTrigger.Equals(other.ActionTrigger) { | |
| return acs.Addr.Less(other.Addr) | |
| } | |
| return acs.ActionTrigger.Less(other.ActionTrigger) | |
| } |