// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package stackplan import ( "fmt" "time" "github.com/hashicorp/go-version" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/msgpack" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/collections" "github.com/hashicorp/terraform/internal/lang" "github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/plans/planfile" "github.com/hashicorp/terraform/internal/plans/planproto" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks" "github.com/hashicorp/terraform/internal/stacks/stackaddrs" "github.com/hashicorp/terraform/internal/stacks/stackutils" "github.com/hashicorp/terraform/internal/stacks/tfstackdata1" "github.com/hashicorp/terraform/internal/states" ) // PlannedChange represents a single isolated planned changed, emitted as // part of a stream of planned changes during the PlanStackChanges RPC API // operation. // // Each PlannedChange becomes a single event in the RPC API, which itself // has zero or more opaque raw plan messages that the caller must collect and // provide verbatim during planning and zero or one "description" messages // that are to give the caller realtime updates about the planning process. // // The aggregated sequence of "raw" messages can be provided later to // [LoadFromProto] to obtain a [Plan] object containing the information // Terraform Core would need to apply the plan. type PlannedChange interface { // PlannedChangeProto returns the protocol buffers representation of // the change, ready to be sent verbatim to an RPC API client. PlannedChangeProto() (*stacks.PlannedChange, error) } // PlannedChangeRootInputValue announces the existence of a root stack input // variable and captures its plan-time value so we can make sure to use // the same value during the apply phase. type PlannedChangeRootInputValue struct { Addr stackaddrs.InputVariable // Action is the change being applied to this input variable. Action plans.Action // Before and After provide the values for before and after this plan. // Both could be cty.NilValue if the before or after was ephemeral at the // time it was set. Before will be cty.NullVal if Action is plans.Create. Before cty.Value After cty.Value // RequiredOnApply is true if a non-null value for this variable // must be supplied during the apply phase. // // If this field is false then the variable must either be left unset // or must be set to the same value during the apply phase, both of // which are equivalent. // // This is set for an input variable that was declared as ephemeral // and was set to a non-null value during the planning phase. The // "null-ness" of an ephemeral value is not allowed to change between // plan and apply, but a value set during planning can have a different // value during apply. RequiredOnApply bool // DeleteOnApply is true if this variable should be removed from the state // on apply even if it was not actively removed from the configuration in // a delete action. This is typically the case during a destroy only plan // in which we want to update the state to remove everything. DeleteOnApply bool } var _ PlannedChange = (*PlannedChangeRootInputValue)(nil) // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangeRootInputValue) PlannedChangeProto() (*stacks.PlannedChange, error) { protoChangeTypes, err := stacks.ChangeTypesForPlanAction(pc.Action) if err != nil { return nil, err } var raws []*anypb.Any if pc.Action == plans.Delete || pc.DeleteOnApply { var raw anypb.Any if err := anypb.MarshalFrom(&raw, &tfstackdata1.DeletedRootInputVariable{ Name: pc.Addr.Name, }, proto.MarshalOptions{}); err != nil { return nil, fmt.Errorf("failed to encode raw state for %s: %w", pc.Addr, err) } raws = append(raws, &raw) } before, err := stacks.ToDynamicValue(pc.Before, cty.DynamicPseudoType) if err != nil { return nil, fmt.Errorf("failed to encode before planned input variable %s: %w", pc.Addr, err) } after, err := stacks.ToDynamicValue(pc.After, cty.DynamicPseudoType) if err != nil { return nil, fmt.Errorf("failed to encode after planned input variable %s: %w", pc.Addr, err) } if pc.Action != plans.Delete { var raw anypb.Any if err := anypb.MarshalFrom(&raw, &tfstackdata1.PlanRootInputValue{ Name: pc.Addr.Name, Value: tfstackdata1.Terraform1ToStackDataDynamicValue(after), RequiredOnApply: pc.RequiredOnApply, }, proto.MarshalOptions{}); err != nil { return nil, err } raws = append(raws, &raw) } return &stacks.PlannedChange{ Raw: raws, Descriptions: []*stacks.PlannedChange_ChangeDescription{ { Description: &stacks.PlannedChange_ChangeDescription_InputVariablePlanned{ InputVariablePlanned: &stacks.PlannedChange_InputVariable{ Name: pc.Addr.Name, Actions: protoChangeTypes, Values: &stacks.DynamicValueChange{ Old: before, New: after, }, RequiredDuringApply: pc.RequiredOnApply, }, }, }, }, }, nil } // PlannedChangeComponentInstanceRemoved is just a reminder for the apply // operation to delete this component from the state because it's not in // the configuration and is empty. type PlannedChangeComponentInstanceRemoved struct { Addr stackaddrs.AbsComponentInstance } var _ PlannedChange = (*PlannedChangeComponentInstanceRemoved)(nil) func (pc *PlannedChangeComponentInstanceRemoved) PlannedChangeProto() (*stacks.PlannedChange, error) { var raw anypb.Any if err := anypb.MarshalFrom(&raw, &tfstackdata1.DeletedComponent{ ComponentInstanceAddr: pc.Addr.String(), }, proto.MarshalOptions{}); err != nil { return nil, err } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, }, nil } // PlannedChangeComponentInstance announces the existence of a component // instance and describes (using a plan action) whether it is being added // or removed. type PlannedChangeComponentInstance struct { Addr stackaddrs.AbsComponentInstance // PlanApplyable is true if the modules runtime ruled that this particular // component's plan is applyable. // // See the documentation for [plans.Plan.Applyable] for details on what // exactly this represents. PlanApplyable bool // PlanApplyable is true if the modules runtime ruled that this particular // component's plan is complete. // // See the documentation for [plans.Plan.Complete] for details on what // exactly this represents. PlanComplete bool // Action describes any difference in the existence of this component // instance compared to the prior state. // // Currently it can only be "Create", "Delete", or "NoOp". This action // relates to the existence of the component instance itself and does // not consider the resource instances inside, whose change actions // are tracked in their own [PlannedChange] objects. Action plans.Action // Mode describes the mode that the component instance is being planned // in. Mode plans.Mode // RequiredComponents is a set of the addresses of all of the components // that provide infrastructure that this one's infrastructure will // depend on. Any component named here must exist for the entire lifespan // of this component instance. RequiredComponents collections.Set[stackaddrs.AbsComponent] // PlannedInputValues records our best approximation of the component's // topmost input values during the planning phase. This could contain // unknown values if one component is configured from results of another. // This therefore won't be used directly as the input values during apply, // but the final set of input values during apply should be consistent // with what's captured here. PlannedInputValues map[string]plans.DynamicValue PlannedInputValueMarks map[string][]cty.PathValueMarks PlannedOutputValues map[string]cty.Value PlannedCheckResults *states.CheckResults PlannedProviderFunctionResults []lang.FunctionResultHash // PlanTimestamp is the timestamp that would be returned from the // "plantimestamp" function in modules inside this component. We // must preserve this in the raw plan data to ensure that we can // return the same timestamp again during the apply phase. PlanTimestamp time.Time } var _ PlannedChange = (*PlannedChangeComponentInstance)(nil) // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangeComponentInstance) PlannedChangeProto() (*stacks.PlannedChange, error) { var plannedInputValues map[string]*tfstackdata1.DynamicValue if n := len(pc.PlannedInputValues); n != 0 { plannedInputValues = make(map[string]*tfstackdata1.DynamicValue, n) for k, v := range pc.PlannedInputValues { var sensitivePaths []*planproto.Path if pvm, ok := pc.PlannedInputValueMarks[k]; ok { for _, p := range pvm { path, err := planproto.NewPath(p.Path) if err != nil { return nil, err } sensitivePaths = append(sensitivePaths, path) } } plannedInputValues[k] = &tfstackdata1.DynamicValue{ Value: &planproto.DynamicValue{ Msgpack: v, }, SensitivePaths: sensitivePaths, } } } var planTimestampStr string var zeroTime time.Time if pc.PlanTimestamp != zeroTime { planTimestampStr = pc.PlanTimestamp.Format(time.RFC3339) } componentAddrsRaw := make([]string, 0, pc.RequiredComponents.Len()) for componentAddr := range pc.RequiredComponents.All() { componentAddrsRaw = append(componentAddrsRaw, componentAddr.String()) } plannedOutputValues := make(map[string]*tfstackdata1.DynamicValue) for k, v := range pc.PlannedOutputValues { dv, err := stacks.ToDynamicValue(v, cty.DynamicPseudoType) if err != nil { return nil, fmt.Errorf("encoding output value %q: %w", k, err) } plannedOutputValues[k] = tfstackdata1.Terraform1ToStackDataDynamicValue(dv) } plannedCheckResults, err := planfile.CheckResultsToPlanProto(pc.PlannedCheckResults) if err != nil { return nil, fmt.Errorf("failed to encode check results: %s", err) } var plannedFunctionResults []*planproto.FunctionCallHash for _, result := range pc.PlannedProviderFunctionResults { plannedFunctionResults = append(plannedFunctionResults, &planproto.FunctionCallHash{ Key: result.Key, Result: result.Result, }) } mode, err := planproto.NewMode(pc.Mode) if err != nil { return nil, fmt.Errorf("failed to encode mode: %s", err) } var raw anypb.Any err = anypb.MarshalFrom(&raw, &tfstackdata1.PlanComponentInstance{ ComponentInstanceAddr: pc.Addr.String(), PlanTimestamp: planTimestampStr, PlannedInputValues: plannedInputValues, PlannedAction: planproto.NewAction(pc.Action), Mode: mode, PlanApplyable: pc.PlanApplyable, PlanComplete: pc.PlanComplete, DependsOnComponentAddrs: componentAddrsRaw, PlannedOutputValues: plannedOutputValues, PlannedCheckResults: plannedCheckResults, FunctionResults: plannedFunctionResults, }, proto.MarshalOptions{}) if err != nil { return nil, err } protoChangeTypes, err := stacks.ChangeTypesForPlanAction(pc.Action) if err != nil { return nil, err } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, Descriptions: []*stacks.PlannedChange_ChangeDescription{ { Description: &stacks.PlannedChange_ChangeDescription_ComponentInstancePlanned{ ComponentInstancePlanned: &stacks.PlannedChange_ComponentInstance{ Addr: &stacks.ComponentInstanceInStackAddr{ ComponentAddr: stackaddrs.ConfigComponentForAbsInstance(pc.Addr).String(), ComponentInstanceAddr: pc.Addr.String(), }, Actions: protoChangeTypes, PlanComplete: pc.PlanComplete, // We don't include "applyable" in here since for a // stack operation it's the overall stack plan applyable // flag that matters, and the per-component flags // are just an implementation detail. }, }, }, }, }, nil } // PlannedChangeResourceInstancePlanned announces an action that Terraform // is proposing to take if this plan is applied. type PlannedChangeResourceInstancePlanned struct { ResourceInstanceObjectAddr stackaddrs.AbsResourceInstanceObject // ChangeSrc describes the planned change, if any. This can be nil if // we're only intending to update the state to match PriorStateSrc. ChangeSrc *plans.ResourceInstanceChangeSrc // PriorStateSrc describes the "prior state" that the planned change, if // any, was generated against. // // This can be nil if the object didn't previously exist. If both // PriorStateSrc and ChangeSrc are nil then that suggests that the // object existed in the previous run's state but was found to no // longer exist while refreshing during plan. PriorStateSrc *states.ResourceInstanceObjectSrc // ProviderConfigAddr is the address of the provider configuration // that planned this change, resolved in terms of the configuration for // the component this resource instance object belongs to. ProviderConfigAddr addrs.AbsProviderConfig // Schema MUST be the same schema that was used to encode the dynamic // values inside ChangeSrc and PriorStateSrc. // // Can be empty if and only if ChangeSrc and PriorStateSrc are both nil // themselves. Schema providers.Schema } var _ PlannedChange = (*PlannedChangeResourceInstancePlanned)(nil) func (pc *PlannedChangeResourceInstancePlanned) PlanResourceInstanceChangePlannedProto() (*tfstackdata1.PlanResourceInstanceChangePlanned, error) { rioAddr := pc.ResourceInstanceObjectAddr if pc.ChangeSrc == nil && pc.PriorStateSrc == nil { // This is just a stubby placeholder to remind us to drop the // apparently-deleted-outside-of-Terraform object from the state // if this plan later gets applied. return &tfstackdata1.PlanResourceInstanceChangePlanned{ ComponentInstanceAddr: rioAddr.Component.String(), ResourceInstanceAddr: rioAddr.Item.ResourceInstance.String(), DeposedKey: rioAddr.Item.DeposedKey.String(), ProviderConfigAddr: pc.ProviderConfigAddr.String(), }, nil } // We include the prior state as part of the raw plan because that // contains the result of upgrading the state to the provider's latest // schema version and incorporating any changes detected in the refresh // step, which we'll rely on during the apply step to make sure that // the final plan is consistent, etc. priorStateProto := tfstackdata1.ResourceInstanceObjectStateToTFStackData1(pc.PriorStateSrc, pc.ProviderConfigAddr) changeProto, err := planfile.ResourceChangeToProto(pc.ChangeSrc) if err != nil { return nil, fmt.Errorf("converting resource instance change to proto: %w", err) } return &tfstackdata1.PlanResourceInstanceChangePlanned{ ComponentInstanceAddr: rioAddr.Component.String(), ResourceInstanceAddr: rioAddr.Item.ResourceInstance.String(), DeposedKey: rioAddr.Item.DeposedKey.String(), ProviderConfigAddr: pc.ProviderConfigAddr.String(), Change: changeProto, PriorState: priorStateProto, }, nil } func (pc *PlannedChangeResourceInstancePlanned) ChangeDescription() (*stacks.PlannedChange_ChangeDescription, error) { rioAddr := pc.ResourceInstanceObjectAddr // We only emit an external description if there's a change to describe. // Otherwise, we just emit a raw to remind us to update the state for // this object during the apply step, to match the prior state. if pc.ChangeSrc == nil { return nil, nil } protoChangeTypes, err := stacks.ChangeTypesForPlanAction(pc.ChangeSrc.Action) if err != nil { return nil, err } replacePaths, err := encodePathSet(pc.ChangeSrc.RequiredReplace) if err != nil { return nil, err } var moved *stacks.PlannedChange_ResourceInstance_Moved var imported *stacks.PlannedChange_ResourceInstance_Imported if pc.ChangeSrc.Moved() { moved = &stacks.PlannedChange_ResourceInstance_Moved{ PrevAddr: stacks.NewResourceInstanceInStackAddr(stackaddrs.AbsResourceInstance{ Component: rioAddr.Component, Item: pc.ChangeSrc.PrevRunAddr, }), } } if pc.ChangeSrc.Importing != nil { imported = &stacks.PlannedChange_ResourceInstance_Imported{ ImportId: pc.ChangeSrc.Importing.ID, Unknown: pc.ChangeSrc.Importing.Unknown, } } var index *stacks.PlannedChange_ResourceInstance_Index if pc.ChangeSrc.Addr.Resource.Key != nil { key := pc.ChangeSrc.Addr.Resource.Key if key == addrs.WildcardKey { index = &stacks.PlannedChange_ResourceInstance_Index{ Unknown: true, } } else { value, err := DynamicValueToTerraform1(key.Value(), cty.DynamicPseudoType) if err != nil { return nil, err } index = &stacks.PlannedChange_ResourceInstance_Index{ Value: value, } } } return &stacks.PlannedChange_ChangeDescription{ Description: &stacks.PlannedChange_ChangeDescription_ResourceInstancePlanned{ ResourceInstancePlanned: &stacks.PlannedChange_ResourceInstance{ Addr: stacks.NewResourceInstanceObjectInStackAddr(rioAddr), ResourceName: pc.ChangeSrc.Addr.Resource.Resource.Name, Index: index, ModuleAddr: pc.ChangeSrc.Addr.Module.String(), ResourceMode: stackutils.ResourceModeForProto(pc.ChangeSrc.Addr.Resource.Resource.Mode), ResourceType: pc.ChangeSrc.Addr.Resource.Resource.Type, ProviderAddr: pc.ChangeSrc.ProviderAddr.Provider.String(), ActionReason: pc.ChangeSrc.ActionReason.String(), Actions: protoChangeTypes, Values: &stacks.DynamicValueChange{ Old: stacks.NewDynamicValue( pc.ChangeSrc.Before, pc.ChangeSrc.BeforeSensitivePaths, ), New: stacks.NewDynamicValue( pc.ChangeSrc.After, pc.ChangeSrc.AfterSensitivePaths, ), }, ReplacePaths: replacePaths, Moved: moved, Imported: imported, }, }, }, nil } func DynamicValueToTerraform1(val cty.Value, ty cty.Type) (*stacks.DynamicValue, error) { unmarkedVal, markPaths := val.UnmarkDeepWithPaths() sensitivePaths, withOtherMarks := marks.PathsWithMark(markPaths, marks.Sensitive) _, withOtherMarks = marks.PathsWithMark(withOtherMarks, marks.Sensitive) if len(withOtherMarks) != 0 { return nil, withOtherMarks[0].Path.NewErrorf( "can't serialize value marked with %#v (this is a bug in Terraform)", withOtherMarks[0].Marks, ) } rawVal, err := msgpack.Marshal(unmarkedVal, ty) if err != nil { return nil, err } ret := &stacks.DynamicValue{ Msgpack: rawVal, } if len(markPaths) == 0 { return ret, nil } ret.Sensitive = make([]*stacks.AttributePath, 0, len(markPaths)) for _, path := range sensitivePaths { ret.Sensitive = append(ret.Sensitive, stacks.NewAttributePath(path)) } return ret, nil } // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangeResourceInstancePlanned) PlannedChangeProto() (*stacks.PlannedChange, error) { pric, err := pc.PlanResourceInstanceChangePlannedProto() if err != nil { return nil, err } var raw anypb.Any err = anypb.MarshalFrom(&raw, pric, proto.MarshalOptions{}) if err != nil { return nil, err } if pc.ChangeSrc == nil && pc.PriorStateSrc == nil { // We only emit a "raw" in this case, because this is a relatively // uninteresting edge-case. The PlanResourceInstanceChangePlannedProto // function should have returned a placeholder value for this use case. return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, }, nil } var descs []*stacks.PlannedChange_ChangeDescription desc, err := pc.ChangeDescription() if err != nil { return nil, err } if desc != nil { descs = append(descs, desc) } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, Descriptions: descs, }, nil } // PlannedChangeDeferredResourceInstancePlanned announces that an action that Terraform // is proposing to take if this plan is applied is being deferred. type PlannedChangeDeferredResourceInstancePlanned struct { // ResourceInstancePlanned is the planned change that is being deferred. ResourceInstancePlanned PlannedChangeResourceInstancePlanned // DeferredReason is the reason why the change is being deferred. DeferredReason providers.DeferredReason } var _ PlannedChange = (*PlannedChangeDeferredResourceInstancePlanned)(nil) // PlannedChangeProto implements PlannedChange. func (dpc *PlannedChangeDeferredResourceInstancePlanned) PlannedChangeProto() (*stacks.PlannedChange, error) { change, err := dpc.ResourceInstancePlanned.PlanResourceInstanceChangePlannedProto() if err != nil { return nil, err } // We'll ignore the error here. We certainly should not have got this far // if we have a deferred reason that the Terraform Core runtime doesn't // recognise. There will be diagnostics elsewhere to reflect this, as we // can just use INVALID to capture this. This also makes us forwards and // backwards compatible, as we'll return INVALID for any new deferred // reasons that are added in the future without erroring. deferredReason, _ := planfile.DeferredReasonToProto(dpc.DeferredReason) var raw anypb.Any err = anypb.MarshalFrom(&raw, &tfstackdata1.PlanDeferredResourceInstanceChange{ Change: change, Deferred: &planproto.Deferred{ Reason: deferredReason, }, }, proto.MarshalOptions{}) if err != nil { return nil, err } ricd, err := dpc.ResourceInstancePlanned.ChangeDescription() if err != nil { return nil, err } var descs []*stacks.PlannedChange_ChangeDescription descs = append(descs, &stacks.PlannedChange_ChangeDescription{ Description: &stacks.PlannedChange_ChangeDescription_ResourceInstanceDeferred{ ResourceInstanceDeferred: &stacks.PlannedChange_ResourceInstanceDeferred{ ResourceInstance: ricd.GetResourceInstancePlanned(), Deferred: EncodeDeferred(dpc.DeferredReason), }, }, }) return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, Descriptions: descs, }, nil } func EncodeDeferred(reason providers.DeferredReason) *stacks.Deferred { deferred := new(stacks.Deferred) switch reason { case providers.DeferredReasonInstanceCountUnknown: deferred.Reason = stacks.Deferred_INSTANCE_COUNT_UNKNOWN case providers.DeferredReasonResourceConfigUnknown: deferred.Reason = stacks.Deferred_RESOURCE_CONFIG_UNKNOWN case providers.DeferredReasonProviderConfigUnknown: deferred.Reason = stacks.Deferred_PROVIDER_CONFIG_UNKNOWN case providers.DeferredReasonAbsentPrereq: deferred.Reason = stacks.Deferred_ABSENT_PREREQ case providers.DeferredReasonDeferredPrereq: deferred.Reason = stacks.Deferred_DEFERRED_PREREQ default: deferred.Reason = stacks.Deferred_INVALID } return deferred } func encodePathSet(pathSet cty.PathSet) ([]*stacks.AttributePath, error) { if pathSet.Empty() { return nil, nil } pathList := pathSet.List() paths := make([]*stacks.AttributePath, 0, len(pathList)) for _, path := range pathList { paths = append(paths, stacks.NewAttributePath(path)) } return paths, nil } // PlannedChangeOutputValue announces the change action for one output value // declared in the top-level stack configuration. // // This change type only includes an external description, and does not // contribute anything to the raw plan sequence. type PlannedChangeOutputValue struct { Addr stackaddrs.OutputValue // Covers only root stack output values Action plans.Action Before, After cty.Value } var _ PlannedChange = (*PlannedChangeOutputValue)(nil) // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangeOutputValue) PlannedChangeProto() (*stacks.PlannedChange, error) { protoChangeTypes, err := stacks.ChangeTypesForPlanAction(pc.Action) if err != nil { return nil, err } before, err := stacks.ToDynamicValue(pc.Before, cty.DynamicPseudoType) if err != nil { return nil, fmt.Errorf("failed to encode planned output value %s: %w", pc.Addr, err) } after, err := stacks.ToDynamicValue(pc.After, cty.DynamicPseudoType) if err != nil { return nil, fmt.Errorf("failed to encode planned output value %s: %w", pc.Addr, err) } var raw []*anypb.Any if pc.Action == plans.Delete { var r anypb.Any if err := anypb.MarshalFrom(&r, &tfstackdata1.DeletedRootOutputValue{ Name: pc.Addr.Name, }, proto.MarshalOptions{}); err != nil { return nil, fmt.Errorf("failed to encode raw state for %s: %w", pc.Addr, err) } raw = []*anypb.Any{&r} } return &stacks.PlannedChange{ Raw: raw, Descriptions: []*stacks.PlannedChange_ChangeDescription{ { Description: &stacks.PlannedChange_ChangeDescription_OutputValuePlanned{ OutputValuePlanned: &stacks.PlannedChange_OutputValue{ Name: pc.Addr.Name, Actions: protoChangeTypes, Values: &stacks.DynamicValueChange{ Old: before, New: after, }, }, }, }, }, }, nil } // PlannedChangeHeader is a special change type we typically emit before any // others to capture overall metadata about a plan. [LoadFromProto] fails if // asked to decode a plan sequence that doesn't include at least one raw // message generated from this change type. // // PlannedChangeHeader has only a raw message and does not contribute to // the external-facing plan description. type PlannedChangeHeader struct { TerraformVersion *version.Version } var _ PlannedChange = (*PlannedChangeHeader)(nil) // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangeHeader) PlannedChangeProto() (*stacks.PlannedChange, error) { var raw anypb.Any err := anypb.MarshalFrom(&raw, &tfstackdata1.PlanHeader{ TerraformVersion: pc.TerraformVersion.String(), }, proto.MarshalOptions{}) if err != nil { return nil, err } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, }, nil } // PlannedChangePriorStateElement is a special change type we emit to capture // each element of the prior state. // // PlannedChangePriorStateElement has only a raw message and does not // contribute to the external-facing plan description, since it's really just // an implementation detail that allows us to deal with various state cleanup // concerns during the apply phase; this isn't really a "planned change" in // the typical sense. type PlannedChangePriorStateElement struct { Key string Raw *anypb.Any } var _ PlannedChange = (*PlannedChangePriorStateElement)(nil) // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangePriorStateElement) PlannedChangeProto() (*stacks.PlannedChange, error) { var raw anypb.Any err := anypb.MarshalFrom(&raw, &tfstackdata1.PlanPriorStateElem{ Key: pc.Key, Raw: pc.Raw, }, proto.MarshalOptions{}) if err != nil { return nil, err } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, }, nil } // PlannedChangePlannedTimestamp is a special change type we emit to record the timestamp // of when the plan was generated. This is being used in the plantimestamp function. type PlannedChangePlannedTimestamp struct { PlannedTimestamp time.Time } var _ PlannedChange = (*PlannedChangePlannedTimestamp)(nil) // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangePlannedTimestamp) PlannedChangeProto() (*stacks.PlannedChange, error) { var raw anypb.Any err := anypb.MarshalFrom(&raw, &tfstackdata1.PlanTimestamp{ PlanTimestamp: pc.PlannedTimestamp.Format(time.RFC3339), }, proto.MarshalOptions{}) if err != nil { return nil, err } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, }, nil } // PlannedChangeApplyable is a special change type we typically append at the // end of the raw plan stream to represent that the planning process ran to // completion without encountering any errors, and therefore the plan could // potentially be applied. type PlannedChangeApplyable struct { Applyable bool } var _ PlannedChange = (*PlannedChangeApplyable)(nil) // PlannedChangeProto implements PlannedChange. func (pc *PlannedChangeApplyable) PlannedChangeProto() (*stacks.PlannedChange, error) { var raw anypb.Any err := anypb.MarshalFrom(&raw, &tfstackdata1.PlanApplyable{ Applyable: pc.Applyable, }, proto.MarshalOptions{}) if err != nil { return nil, err } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, Descriptions: []*stacks.PlannedChange_ChangeDescription{ { Description: &stacks.PlannedChange_ChangeDescription_PlanApplyable{ PlanApplyable: pc.Applyable, }, }, }, }, nil } type PlannedChangeProviderFunctionResults struct { Results []lang.FunctionResultHash } var _ PlannedChange = (*PlannedChangeProviderFunctionResults)(nil) func (pc *PlannedChangeProviderFunctionResults) PlannedChangeProto() (*stacks.PlannedChange, error) { var results tfstackdata1.FunctionResults for _, result := range pc.Results { results.FunctionResults = append(results.FunctionResults, &planproto.FunctionCallHash{ Key: result.Key, Result: result.Result, }) } var raw anypb.Any err := anypb.MarshalFrom(&raw, &results, proto.MarshalOptions{}) if err != nil { return nil, err } return &stacks.PlannedChange{ Raw: []*anypb.Any{&raw}, }, nil }