// 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/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) } // FilterLaterActionInvocations returns the list of action invocations that // should be triggered after this one. This function assumes the supplied list // of action invocations has already been filtered to invocations against the // same resource as the current invocation. func (acs *ActionInvocationInstanceSrc) FilterLaterActionInvocations(actionInvocations []*ActionInvocationInstanceSrc) []*ActionInvocationInstanceSrc { needleLat := acs.ActionTrigger.(*LifecycleActionTrigger) var laterInvocations []*ActionInvocationInstanceSrc for _, invocation := range actionInvocations { if lat, ok := invocation.ActionTrigger.(*LifecycleActionTrigger); ok { if sameTriggerEvent(lat, needleLat) && triggersLater(lat, needleLat) { laterInvocations = append(laterInvocations, invocation) } } } return laterInvocations } func sameTriggerEvent(one, two *LifecycleActionTrigger) bool { return one.ActionTriggerEvent == two.ActionTriggerEvent } func triggersLater(one, two *LifecycleActionTrigger) bool { return one.ActionTriggerBlockIndex > two.ActionTriggerBlockIndex || (one.ActionTriggerBlockIndex == two.ActionTriggerBlockIndex && one.ActionsListIndex > two.ActionsListIndex) }