| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package plans | |
| import ( | |
| "fmt" | |
| "github.com/opentofu/opentofu/internal/addrs" | |
| "github.com/opentofu/opentofu/internal/providers" | |
| "github.com/opentofu/opentofu/internal/states" | |
| "github.com/zclconf/go-cty/cty" | |
| ctyjson "github.com/zclconf/go-cty/cty/json" | |
| ) | |
| // 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. | |
| Addr addrs.AbsResourceInstance | |
| // 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 | |
| // 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 | |
| // 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 | |
| // OpenTofu that relates to this change. OpenTofu will save this | |
| // byte-for-byte and return it to the provider in the apply call. | |
| Private []byte | |
| } | |
| // 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() | |
| ret.ChangeSrc.BeforeIdentity = ret.ChangeSrc.BeforeIdentity.Copy() | |
| ret.ChangeSrc.AfterIdentity = ret.ChangeSrc.AfterIdentity.Copy() | |
| if ret.ChangeSrc.Importing != nil { | |
| importing := *ret.ChangeSrc.Importing | |
| importing.Identity = importing.Identity.Copy() | |
| ret.ChangeSrc.Importing = &importing | |
| } | |
| 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() | |
| ret.ChangeSrc.BeforeIdentity = ret.ChangeSrc.BeforeIdentity.Copy() | |
| ret.ChangeSrc.AfterIdentity = ret.ChangeSrc.AfterIdentity.Copy() | |
| if ret.ChangeSrc.Importing != nil { | |
| importing := *ret.ChangeSrc.Importing | |
| importing.Identity = importing.Identity.Copy() | |
| ret.ChangeSrc.Importing = &importing | |
| } | |
| 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 an alternative way to import a resource, using its identity | |
| // attributes instead of its ID. | |
| // | |
| // This is mutually exclusive with ID; and only one of the two should be set. | |
| Identity DynamicValue | |
| } | |
| // String returns a human-readable representation of the import source. | |
| // It returns the ID if set, or a rendered identity value if available. | |
| func (i ImportingSrc) String() string { | |
| if i.ID != "" { | |
| return i.ID | |
| } | |
| if len(i.Identity) == 0 { | |
| return "" | |
| } | |
| ty, err := i.Identity.ImpliedType() | |
| if err != nil { | |
| return "<identity>" | |
| } | |
| val, err := i.Identity.Decode(ty) | |
| if err != nil { | |
| return "<identity>" | |
| } | |
| jsonBytes, err := ctyjson.Marshal(val, ty) | |
| if err != nil { | |
| return "<identity>" | |
| } | |
| return string(jsonBytes) | |
| } | |
| // 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 | |
| // BeforeValMarks and AfterValMarks are stored path+mark combinations | |
| // that might be discovered when encoding a change. Marks are removed | |
| // to enable encoding (marked values cannot be marshalled), and so storing | |
| // the path+mark combinations allow us to re-mark the value later | |
| // when, for example, displaying the diff to the UI. | |
| BeforeValMarks, AfterValMarks []cty.PathValueMarks | |
| // 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 | |
| // BeforeIdentity is the identity value from the known state of the resource instance | |
| // before the plan is executed. | |
| BeforeIdentity DynamicValue | |
| // AfterIdentity is the serialized identity value returned by the provider | |
| // during planning. Only relevant for managed resources, not outputs. | |
| AfterIdentity DynamicValue | |
| } | |
| // Decode unmarshals the raw representations of the before and after values | |
| // to produce a Change object. | |
| // | |
| // 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 ty cty.Type | |
| if schema == nil { | |
| // we've been passed a nil set of schema, we should use a dynamic type here | |
| ty = cty.DynamicPseudoType | |
| } else { | |
| ty = schema.Block.ImpliedType() | |
| } | |
| var err error | |
| before := cty.NullVal(ty) | |
| after := cty.NullVal(ty) | |
| if len(cs.Before) > 0 { | |
| before, err = cs.Before.Decode(ty) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding 'before' value: %w", err) | |
| } | |
| } | |
| if len(cs.After) > 0 { | |
| after, err = cs.After.Decode(ty) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding 'after' value: %w", err) | |
| } | |
| } | |
| var importing *Importing | |
| if cs.Importing != nil { | |
| if len(cs.Importing.Identity) > 0 { | |
| var identityTy cty.Type | |
| if schema != nil && schema.IdentitySchema != nil { | |
| identityTy = schema.IdentitySchema.ImpliedType() | |
| } else { | |
| identityTy, err = cs.Importing.Identity.ImpliedType() | |
| if err != nil { | |
| return nil, fmt.Errorf("error determining importing identity type: %w", err) | |
| } | |
| } | |
| decodedIdentity, err := cs.Importing.Identity.Decode(identityTy) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding importing identity value: %w", err) | |
| } | |
| importing = &Importing{ | |
| Identity: decodedIdentity, | |
| } | |
| } else { | |
| importing = &Importing{ | |
| ID: cs.Importing.ID, | |
| } | |
| } | |
| } | |
| afterIdentity := cty.NullVal(cty.DynamicPseudoType) | |
| if len(cs.AfterIdentity) > 0 { | |
| var identityTy cty.Type | |
| if schema != nil && schema.IdentitySchema != nil { | |
| identityTy = schema.IdentitySchema.ImpliedType() | |
| } else { | |
| identityTy, err = cs.AfterIdentity.ImpliedType() | |
| if err != nil { | |
| return nil, fmt.Errorf("error determining after identity type: %w", err) | |
| } | |
| } | |
| afterIdentity, err = cs.AfterIdentity.Decode(identityTy) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding after identity value: %w", err) | |
| } | |
| } | |
| beforeIdentity := cty.NullVal(cty.DynamicPseudoType) | |
| if len(cs.BeforeIdentity) > 0 { | |
| var identityTy cty.Type | |
| if schema != nil && schema.IdentitySchema != nil { | |
| identityTy = schema.IdentitySchema.ImpliedType() | |
| } else { | |
| identityTy, err = cs.BeforeIdentity.ImpliedType() | |
| if err != nil { | |
| return nil, fmt.Errorf("error determining before identity type: %w", err) | |
| } | |
| } | |
| beforeIdentity, err = cs.BeforeIdentity.Decode(identityTy) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding before identity value: %w", err) | |
| } | |
| } | |
| return &Change{ | |
| Action: cs.Action, | |
| Before: before.MarkWithPaths(cs.BeforeValMarks), | |
| After: after.MarkWithPaths(cs.AfterValMarks), | |
| Importing: importing, | |
| GeneratedConfig: cs.GeneratedConfig, | |
| BeforeIdentity: beforeIdentity, | |
| AfterIdentity: afterIdentity, | |
| }, nil | |
| } |