mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
857 lines
29 KiB
857 lines
29 KiB
// 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
|
|
}
|