2018-06-20 01:30:59 +02:00
package planfile
import (
"fmt"
"io"
"io/ioutil"
2020-12-01 22:07:05 +01:00
"google.golang.org/protobuf/proto"
2018-06-20 01:30:59 +02:00
2021-05-17 21:00:50 +02:00
"github.com/hashicorp/terraform/internal/addrs"
2021-06-24 23:53:43 +02:00
"github.com/hashicorp/terraform/internal/lang/marks"
2021-05-17 21:33:17 +02:00
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/internal/planproto"
2021-05-17 21:43:35 +02:00
"github.com/hashicorp/terraform/internal/states"
2018-06-20 01:30:59 +02:00
"github.com/hashicorp/terraform/version"
2021-03-24 21:45:46 +01:00
"github.com/zclconf/go-cty/cty"
2018-06-20 01:30:59 +02:00
)
const tfplanFormatVersion = 3
const tfplanFilename = "tfplan"
// ---------------------------------------------------------------------------
// This file deals with the internal structure of the "tfplan" sub-file within
// the plan file format. It's all private API, wrapped by methods defined
// elsewhere. This is the only file that should import the
// ../internal/planproto package, which contains the ugly stubs generated
// by the protobuf compiler.
// ---------------------------------------------------------------------------
// readTfplan reads a protobuf-encoded description from the plan portion of
// a plan file, which is stored in a special file in the archive called
// "tfplan".
func readTfplan ( r io . Reader ) ( * plans . Plan , error ) {
src , err := ioutil . ReadAll ( r )
if err != nil {
return nil , err
}
var rawPlan planproto . Plan
err = proto . Unmarshal ( src , & rawPlan )
if err != nil {
return nil , fmt . Errorf ( "parse error: %s" , err )
}
if rawPlan . Version != tfplanFormatVersion {
return nil , fmt . Errorf ( "unsupported plan file format version %d; only version %d is supported" , rawPlan . Version , tfplanFormatVersion )
}
if rawPlan . TerraformVersion != version . String ( ) {
return nil , fmt . Errorf ( "plan file was created by Terraform %s, but this is %s; plan files cannot be transferred between different Terraform versions" , rawPlan . TerraformVersion , version . String ( ) )
}
plan := & plans . Plan {
VariableValues : map [ string ] plans . DynamicValue { } ,
Changes : & plans . Changes {
2018-09-11 01:26:55 +02:00
Outputs : [ ] * plans . OutputChangeSrc { } ,
Resources : [ ] * plans . ResourceInstanceChangeSrc { } ,
2018-06-20 01:30:59 +02:00
} ,
2021-09-13 22:49:19 +02:00
DriftedResources : [ ] * plans . ResourceInstanceChangeSrc { } ,
2018-06-20 01:30:59 +02:00
}
2021-04-30 19:14:09 +02:00
switch rawPlan . UiMode {
2021-04-07 02:37:38 +02:00
case planproto . Mode_NORMAL :
2021-04-30 19:14:09 +02:00
plan . UIMode = plans . NormalMode
2021-04-07 02:37:38 +02:00
case planproto . Mode_DESTROY :
2021-04-30 19:14:09 +02:00
plan . UIMode = plans . DestroyMode
2021-04-07 02:37:38 +02:00
case planproto . Mode_REFRESH_ONLY :
2021-04-30 19:14:09 +02:00
plan . UIMode = plans . RefreshOnlyMode
2021-04-07 02:37:38 +02:00
default :
2021-04-30 19:14:09 +02:00
return nil , fmt . Errorf ( "plan has invalid mode %s" , rawPlan . UiMode )
2021-04-07 02:37:38 +02:00
}
2018-06-20 01:30:59 +02:00
for _ , rawOC := range rawPlan . OutputChanges {
name := rawOC . Name
change , err := changeFromTfplan ( rawOC . Change )
if err != nil {
return nil , fmt . Errorf ( "invalid plan for output %q: %s" , name , err )
}
2018-09-11 01:26:55 +02:00
plan . Changes . Outputs = append ( plan . Changes . Outputs , & plans . OutputChangeSrc {
// All output values saved in the plan file are root module outputs,
// since we don't retain others. (They can be easily recomputed
// during apply).
Addr : addrs . OutputValue { Name : name } . Absolute ( addrs . RootModuleInstance ) ,
2018-07-21 02:15:03 +02:00
ChangeSrc : * change ,
2018-06-20 01:30:59 +02:00
Sensitive : rawOC . Sensitive ,
2018-09-11 01:26:55 +02:00
} )
2018-06-20 01:30:59 +02:00
}
for _ , rawRC := range rawPlan . ResourceChanges {
change , err := resourceChangeFromTfplan ( rawRC )
if err != nil {
// errors from resourceChangeFromTfplan already include context
return nil , err
}
plan . Changes . Resources = append ( plan . Changes . Resources , change )
}
2021-09-13 22:49:19 +02:00
for _ , rawRC := range rawPlan . ResourceDrift {
change , err := resourceChangeFromTfplan ( rawRC )
if err != nil {
// errors from resourceChangeFromTfplan already include context
return nil , err
}
plan . DriftedResources = append ( plan . DriftedResources , change )
}
2018-07-10 23:34:32 +02:00
for _ , rawTargetAddr := range rawPlan . TargetAddrs {
target , diags := addrs . ParseTargetStr ( rawTargetAddr )
if diags . HasErrors ( ) {
return nil , fmt . Errorf ( "plan contains invalid target address %q: %s" , target , diags . Err ( ) )
}
plan . TargetAddrs = append ( plan . TargetAddrs , target . Subject )
}
2021-04-07 02:37:38 +02:00
for _ , rawReplaceAddr := range rawPlan . ForceReplaceAddrs {
addr , diags := addrs . ParseAbsResourceInstanceStr ( rawReplaceAddr )
if diags . HasErrors ( ) {
return nil , fmt . Errorf ( "plan contains invalid force-replace address %q: %s" , addr , diags . Err ( ) )
}
plan . ForceReplaceAddrs = append ( plan . ForceReplaceAddrs , addr )
}
2018-06-20 01:30:59 +02:00
for name , rawVal := range rawPlan . Variables {
val , err := valueFromTfplan ( rawVal )
if err != nil {
return nil , fmt . Errorf ( "invalid value for input variable %q: %s" , name , err )
}
plan . VariableValues [ name ] = val
}
2018-07-05 22:21:38 +02:00
if rawBackend := rawPlan . Backend ; rawBackend == nil {
return nil , fmt . Errorf ( "plan file has no backend settings; backend settings are required" )
} else {
config , err := valueFromTfplan ( rawBackend . Config )
if err != nil {
return nil , fmt . Errorf ( "plan file has invalid backend configuration: %s" , err )
}
plan . Backend = plans . Backend {
Type : rawBackend . Type ,
Config : config ,
Workspace : rawBackend . Workspace ,
}
}
2018-06-20 01:30:59 +02:00
return plan , nil
}
2018-07-21 02:15:03 +02:00
func resourceChangeFromTfplan ( rawChange * planproto . ResourceInstanceChange ) ( * plans . ResourceInstanceChangeSrc , error ) {
2018-06-20 01:30:59 +02:00
if rawChange == nil {
// Should never happen in practice, since protobuf can't represent
// a nil value in a list.
return nil , fmt . Errorf ( "resource change object is absent" )
}
2018-07-21 02:15:03 +02:00
ret := & plans . ResourceInstanceChangeSrc { }
2018-06-20 01:30:59 +02:00
2021-08-21 01:08:12 +02:00
if rawChange . Addr == "" {
// If "Addr" isn't populated then seems likely that this is a plan
// file created by an earlier version of Terraform, which had the
// same information spread over various other fields:
// ModulePath, Mode, Name, Type, and InstanceKey.
return nil , fmt . Errorf ( "no instance address for resource instance change; perhaps this plan was created by a different version of Terraform?" )
}
instAddr , diags := addrs . ParseAbsResourceInstanceStr ( rawChange . Addr )
if diags . HasErrors ( ) {
return nil , fmt . Errorf ( "invalid resource instance address %q: %w" , rawChange . Addr , diags . Err ( ) )
}
prevRunAddr := instAddr
if rawChange . PrevRunAddr != "" {
prevRunAddr , diags = addrs . ParseAbsResourceInstanceStr ( rawChange . PrevRunAddr )
2018-06-20 01:30:59 +02:00
if diags . HasErrors ( ) {
2021-08-21 01:08:12 +02:00
return nil , fmt . Errorf ( "invalid resource instance previous run address %q: %w" , rawChange . PrevRunAddr , diags . Err ( ) )
2018-06-20 01:30:59 +02:00
}
}
2018-06-22 00:26:37 +02:00
providerAddr , diags := addrs . ParseAbsProviderConfigStr ( rawChange . Provider )
if diags . HasErrors ( ) {
return nil , diags . Err ( )
}
ret . ProviderAddr = providerAddr
2021-08-21 01:08:12 +02:00
ret . Addr = instAddr
ret . PrevRunAddr = prevRunAddr
2018-06-20 01:30:59 +02:00
if rawChange . DeposedKey != "" {
if len ( rawChange . DeposedKey ) != 8 {
return nil , fmt . Errorf ( "deposed object for %s has invalid deposed key %q" , ret . Addr , rawChange . DeposedKey )
}
ret . DeposedKey = states . DeposedKey ( rawChange . DeposedKey )
}
2021-03-24 21:45:46 +01:00
ret . RequiredReplace = cty . NewPathSet ( )
for _ , p := range rawChange . RequiredReplace {
path , err := pathFromTfplan ( p )
if err != nil {
return nil , fmt . Errorf ( "invalid path in required replace: %s" , err )
}
ret . RequiredReplace . Add ( path )
}
2018-06-20 01:30:59 +02:00
change , err := changeFromTfplan ( rawChange . Change )
if err != nil {
return nil , fmt . Errorf ( "invalid plan for resource %s: %s" , ret . Addr , err )
}
2018-07-21 02:15:03 +02:00
ret . ChangeSrc = * change
2018-06-20 01:30:59 +02:00
2021-04-28 21:02:34 +02:00
switch rawChange . ActionReason {
case planproto . ResourceInstanceActionReason_NONE :
ret . ActionReason = plans . ResourceInstanceChangeNoReason
case planproto . ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE :
ret . ActionReason = plans . ResourceInstanceReplaceBecauseCannotUpdate
case planproto . ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED :
ret . ActionReason = plans . ResourceInstanceReplaceBecauseTainted
case planproto . ResourceInstanceActionReason_REPLACE_BY_REQUEST :
ret . ActionReason = plans . ResourceInstanceReplaceByRequest
2021-09-23 02:58:41 +02:00
case planproto . ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG :
ret . ActionReason = plans . ResourceInstanceDeleteBecauseNoResourceConfig
case planproto . ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION :
ret . ActionReason = plans . ResourceInstanceDeleteBecauseWrongRepetition
case planproto . ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX :
ret . ActionReason = plans . ResourceInstanceDeleteBecauseCountIndex
case planproto . ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY :
ret . ActionReason = plans . ResourceInstanceDeleteBecauseEachKey
case planproto . ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE :
ret . ActionReason = plans . ResourceInstanceDeleteBecauseNoModule
2021-04-28 21:02:34 +02:00
default :
return nil , fmt . Errorf ( "resource has invalid action reason %s" , rawChange . ActionReason )
}
2018-08-18 02:22:18 +02:00
if len ( rawChange . Private ) != 0 {
ret . Private = rawChange . Private
}
2018-06-20 01:30:59 +02:00
return ret , nil
}
2018-07-21 02:15:03 +02:00
func changeFromTfplan ( rawChange * planproto . Change ) ( * plans . ChangeSrc , error ) {
2018-06-20 01:30:59 +02:00
if rawChange == nil {
return nil , fmt . Errorf ( "change object is absent" )
}
2018-07-21 02:15:03 +02:00
ret := & plans . ChangeSrc { }
2018-06-20 01:30:59 +02:00
// -1 indicates that there is no index. We'll customize these below
// depending on the change action, and then decode.
beforeIdx , afterIdx := - 1 , - 1
switch rawChange . Action {
case planproto . Action_NOOP :
ret . Action = plans . NoOp
beforeIdx = 0
afterIdx = 0
case planproto . Action_CREATE :
ret . Action = plans . Create
afterIdx = 0
case planproto . Action_READ :
ret . Action = plans . Read
beforeIdx = 0
afterIdx = 1
case planproto . Action_UPDATE :
ret . Action = plans . Update
beforeIdx = 0
afterIdx = 1
case planproto . Action_DELETE :
ret . Action = plans . Delete
beforeIdx = 0
2018-09-22 02:08:52 +02:00
case planproto . Action_CREATE_THEN_DELETE :
ret . Action = plans . CreateThenDelete
beforeIdx = 0
afterIdx = 1
case planproto . Action_DELETE_THEN_CREATE :
ret . Action = plans . DeleteThenCreate
beforeIdx = 0
afterIdx = 1
2018-06-20 01:30:59 +02:00
default :
return nil , fmt . Errorf ( "invalid change action %s" , rawChange . Action )
}
if beforeIdx != - 1 {
if l := len ( rawChange . Values ) ; l <= beforeIdx {
return nil , fmt . Errorf ( "incorrect number of values (%d) for %s change" , l , rawChange . Action )
}
var err error
ret . Before , err = valueFromTfplan ( rawChange . Values [ beforeIdx ] )
if err != nil {
return nil , fmt . Errorf ( "invalid \"before\" value: %s" , err )
}
if ret . Before == nil {
return nil , fmt . Errorf ( "missing \"before\" value: %s" , err )
}
}
if afterIdx != - 1 {
if l := len ( rawChange . Values ) ; l <= afterIdx {
return nil , fmt . Errorf ( "incorrect number of values (%d) for %s change" , l , rawChange . Action )
}
var err error
ret . After , err = valueFromTfplan ( rawChange . Values [ afterIdx ] )
if err != nil {
return nil , fmt . Errorf ( "invalid \"after\" value: %s" , err )
}
if ret . After == nil {
return nil , fmt . Errorf ( "missing \"after\" value: %s" , err )
}
}
2021-06-24 23:53:43 +02:00
sensitive := cty . NewValueMarks ( marks . Sensitive )
2021-03-24 21:45:46 +01:00
beforeValMarks , err := pathValueMarksFromTfplan ( rawChange . BeforeSensitivePaths , sensitive )
if err != nil {
return nil , fmt . Errorf ( "failed to decode before sensitive paths: %s" , err )
}
afterValMarks , err := pathValueMarksFromTfplan ( rawChange . AfterSensitivePaths , sensitive )
if err != nil {
return nil , fmt . Errorf ( "failed to decode after sensitive paths: %s" , err )
}
if len ( beforeValMarks ) > 0 {
ret . BeforeValMarks = beforeValMarks
}
if len ( afterValMarks ) > 0 {
ret . AfterValMarks = afterValMarks
}
2018-06-20 01:30:59 +02:00
return ret , nil
}
func valueFromTfplan ( rawV * planproto . DynamicValue ) ( plans . DynamicValue , error ) {
if len ( rawV . Msgpack ) == 0 { // len(0) because that's the default value for a "bytes" in protobuf
return nil , fmt . Errorf ( "dynamic value does not have msgpack serialization" )
}
return plans . DynamicValue ( rawV . Msgpack ) , nil
}
// writeTfplan serializes the given plan into the protobuf-based format used
// for the "tfplan" portion of a plan file.
func writeTfplan ( plan * plans . Plan , w io . Writer ) error {
2018-09-30 18:29:51 +02:00
if plan == nil {
return fmt . Errorf ( "cannot write plan file for nil plan" )
}
if plan . Changes == nil {
return fmt . Errorf ( "cannot write plan file with nil changeset" )
}
2018-06-20 01:30:59 +02:00
rawPlan := & planproto . Plan {
Version : tfplanFormatVersion ,
TerraformVersion : version . String ( ) ,
Variables : map [ string ] * planproto . DynamicValue { } ,
OutputChanges : [ ] * planproto . OutputChange { } ,
ResourceChanges : [ ] * planproto . ResourceInstanceChange { } ,
2021-09-13 22:49:19 +02:00
ResourceDrift : [ ] * planproto . ResourceInstanceChange { } ,
2018-06-20 01:30:59 +02:00
}
2021-04-30 19:14:09 +02:00
switch plan . UIMode {
2021-04-07 02:37:38 +02:00
case plans . NormalMode :
2021-04-30 19:14:09 +02:00
rawPlan . UiMode = planproto . Mode_NORMAL
2021-04-07 02:37:38 +02:00
case plans . DestroyMode :
2021-04-30 19:14:09 +02:00
rawPlan . UiMode = planproto . Mode_DESTROY
2021-04-07 02:37:38 +02:00
case plans . RefreshOnlyMode :
2021-04-30 19:14:09 +02:00
rawPlan . UiMode = planproto . Mode_REFRESH_ONLY
2021-04-07 02:37:38 +02:00
default :
2021-04-30 19:14:09 +02:00
return fmt . Errorf ( "plan has unsupported mode %s" , plan . UIMode )
2021-04-07 02:37:38 +02:00
}
2018-09-11 01:26:55 +02:00
for _ , oc := range plan . Changes . Outputs {
// When serializing a plan we only retain the root outputs, since
// changes to these are externally-visible side effects (e.g. via
// terraform_remote_state).
if ! oc . Addr . Module . IsRoot ( ) {
continue
}
name := oc . Addr . OutputValue . Name
2018-06-20 01:30:59 +02:00
// Writing outputs as cty.DynamicPseudoType forces the stored values
// to also contain dynamic type information, so we can recover the
// original type when we read the values back in readTFPlan.
2018-07-21 02:15:03 +02:00
protoChange , err := changeToTfplan ( & oc . ChangeSrc )
2018-06-20 01:30:59 +02:00
if err != nil {
return fmt . Errorf ( "cannot write output value %q: %s" , name , err )
}
rawPlan . OutputChanges = append ( rawPlan . OutputChanges , & planproto . OutputChange {
Name : name ,
Change : protoChange ,
Sensitive : oc . Sensitive ,
} )
}
for _ , rc := range plan . Changes . Resources {
rawRC , err := resourceChangeToTfplan ( rc )
if err != nil {
return err
}
rawPlan . ResourceChanges = append ( rawPlan . ResourceChanges , rawRC )
}
2021-09-13 22:49:19 +02:00
for _ , rc := range plan . DriftedResources {
rawRC , err := resourceChangeToTfplan ( rc )
if err != nil {
return err
}
rawPlan . ResourceDrift = append ( rawPlan . ResourceDrift , rawRC )
}
2018-07-10 23:34:32 +02:00
for _ , targetAddr := range plan . TargetAddrs {
rawPlan . TargetAddrs = append ( rawPlan . TargetAddrs , targetAddr . String ( ) )
}
2021-04-07 02:37:38 +02:00
for _ , replaceAddr := range plan . ForceReplaceAddrs {
rawPlan . ForceReplaceAddrs = append ( rawPlan . ForceReplaceAddrs , replaceAddr . String ( ) )
}
2018-06-20 01:30:59 +02:00
for name , val := range plan . VariableValues {
rawPlan . Variables [ name ] = valueToTfplan ( val )
}
2018-10-09 21:19:24 +02:00
if plan . Backend . Type == "" || plan . Backend . Config == nil {
// This suggests a bug in the code that created the plan, since it
// ought to always have a backend populated, even if it's the default
// "local" backend with a local state file.
return fmt . Errorf ( "plan does not have a backend configuration" )
}
2018-07-05 22:21:38 +02:00
rawPlan . Backend = & planproto . Backend {
Type : plan . Backend . Type ,
Config : valueToTfplan ( plan . Backend . Config ) ,
Workspace : plan . Backend . Workspace ,
}
2018-06-20 01:30:59 +02:00
src , err := proto . Marshal ( rawPlan )
if err != nil {
return fmt . Errorf ( "serialization error: %s" , err )
}
_ , err = w . Write ( src )
if err != nil {
return fmt . Errorf ( "failed to write plan to plan file: %s" , err )
}
return nil
}
2018-07-21 02:15:03 +02:00
func resourceChangeToTfplan ( change * plans . ResourceInstanceChangeSrc ) ( * planproto . ResourceInstanceChange , error ) {
2018-06-20 01:30:59 +02:00
ret := & planproto . ResourceInstanceChange { }
2021-08-21 01:08:12 +02:00
if change . PrevRunAddr . Resource . Resource . Type == "" {
// Suggests that an old caller wasn't yet updated to populate this
// properly. All code that generates plans should populate this field,
// even if it's just to write in the same value as in change.Addr.
change . PrevRunAddr = change . Addr
2018-06-20 01:30:59 +02:00
}
2021-08-21 01:08:12 +02:00
ret . Addr = change . Addr . String ( )
ret . PrevRunAddr = change . PrevRunAddr . String ( )
if ret . PrevRunAddr == ret . Addr {
// In the on-disk format we leave PrevRunAddr unpopulated in the common
// case where it's the same as Addr, and then fill it back in again on
// read.
ret . PrevRunAddr = ""
2018-06-20 01:30:59 +02:00
}
ret . DeposedKey = string ( change . DeposedKey )
2018-06-22 00:26:37 +02:00
ret . Provider = change . ProviderAddr . String ( )
2018-06-20 01:30:59 +02:00
2021-03-24 21:45:46 +01:00
requiredReplace := change . RequiredReplace . List ( )
ret . RequiredReplace = make ( [ ] * planproto . Path , 0 , len ( requiredReplace ) )
for _ , p := range requiredReplace {
path , err := pathToTfplan ( p )
if err != nil {
return nil , fmt . Errorf ( "invalid path in required replace: %s" , err )
}
ret . RequiredReplace = append ( ret . RequiredReplace , path )
}
2018-07-21 02:15:03 +02:00
valChange , err := changeToTfplan ( & change . ChangeSrc )
2018-06-20 01:30:59 +02:00
if err != nil {
2021-08-21 01:08:12 +02:00
return nil , fmt . Errorf ( "failed to serialize resource %s change: %s" , change . Addr , err )
2018-06-20 01:30:59 +02:00
}
ret . Change = valChange
2021-04-28 21:02:34 +02:00
switch change . ActionReason {
case plans . ResourceInstanceChangeNoReason :
ret . ActionReason = planproto . ResourceInstanceActionReason_NONE
case plans . ResourceInstanceReplaceBecauseCannotUpdate :
ret . ActionReason = planproto . ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE
case plans . ResourceInstanceReplaceBecauseTainted :
ret . ActionReason = planproto . ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED
case plans . ResourceInstanceReplaceByRequest :
ret . ActionReason = planproto . ResourceInstanceActionReason_REPLACE_BY_REQUEST
2021-09-23 02:58:41 +02:00
case plans . ResourceInstanceDeleteBecauseNoResourceConfig :
ret . ActionReason = planproto . ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG
case plans . ResourceInstanceDeleteBecauseWrongRepetition :
ret . ActionReason = planproto . ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION
case plans . ResourceInstanceDeleteBecauseCountIndex :
ret . ActionReason = planproto . ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX
case plans . ResourceInstanceDeleteBecauseEachKey :
ret . ActionReason = planproto . ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY
case plans . ResourceInstanceDeleteBecauseNoModule :
ret . ActionReason = planproto . ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE
2021-04-28 21:02:34 +02:00
default :
2021-08-21 01:08:12 +02:00
return nil , fmt . Errorf ( "resource %s has unsupported action reason %s" , change . Addr , change . ActionReason )
2021-04-28 21:02:34 +02:00
}
2018-08-18 02:22:18 +02:00
if len ( change . Private ) > 0 {
ret . Private = change . Private
}
2018-06-20 01:30:59 +02:00
return ret , nil
}
2018-07-21 02:15:03 +02:00
func changeToTfplan ( change * plans . ChangeSrc ) ( * planproto . Change , error ) {
2018-06-20 01:30:59 +02:00
ret := & planproto . Change { }
before := valueToTfplan ( change . Before )
after := valueToTfplan ( change . After )
2021-03-24 21:45:46 +01:00
beforeSensitivePaths , err := pathValueMarksToTfplan ( change . BeforeValMarks )
if err != nil {
return nil , err
}
afterSensitivePaths , err := pathValueMarksToTfplan ( change . AfterValMarks )
if err != nil {
return nil , err
}
ret . BeforeSensitivePaths = beforeSensitivePaths
ret . AfterSensitivePaths = afterSensitivePaths
2018-06-20 01:30:59 +02:00
switch change . Action {
case plans . NoOp :
ret . Action = planproto . Action_NOOP
ret . Values = [ ] * planproto . DynamicValue { before } // before and after should be identical
case plans . Create :
ret . Action = planproto . Action_CREATE
ret . Values = [ ] * planproto . DynamicValue { after }
case plans . Read :
ret . Action = planproto . Action_READ
ret . Values = [ ] * planproto . DynamicValue { before , after }
case plans . Update :
ret . Action = planproto . Action_UPDATE
ret . Values = [ ] * planproto . DynamicValue { before , after }
case plans . Delete :
ret . Action = planproto . Action_DELETE
ret . Values = [ ] * planproto . DynamicValue { before }
2018-09-22 02:08:52 +02:00
case plans . DeleteThenCreate :
ret . Action = planproto . Action_DELETE_THEN_CREATE
ret . Values = [ ] * planproto . DynamicValue { before , after }
case plans . CreateThenDelete :
ret . Action = planproto . Action_CREATE_THEN_DELETE
ret . Values = [ ] * planproto . DynamicValue { before , after }
2018-06-20 01:30:59 +02:00
default :
return nil , fmt . Errorf ( "invalid change action %s" , change . Action )
}
return ret , nil
}
func valueToTfplan ( val plans . DynamicValue ) * planproto . DynamicValue {
if val == nil {
// protobuf can't represent nil, so we'll represent it as a
// DynamicValue that has no serializations at all.
return & planproto . DynamicValue { }
}
return & planproto . DynamicValue {
Msgpack : [ ] byte ( val ) ,
}
}
2021-03-24 21:45:46 +01:00
func pathValueMarksFromTfplan ( paths [ ] * planproto . Path , marks cty . ValueMarks ) ( [ ] cty . PathValueMarks , error ) {
ret := make ( [ ] cty . PathValueMarks , 0 , len ( paths ) )
for _ , p := range paths {
path , err := pathFromTfplan ( p )
if err != nil {
return nil , err
}
ret = append ( ret , cty . PathValueMarks {
Path : path ,
Marks : marks ,
} )
}
return ret , nil
}
func pathValueMarksToTfplan ( pvm [ ] cty . PathValueMarks ) ( [ ] * planproto . Path , error ) {
ret := make ( [ ] * planproto . Path , 0 , len ( pvm ) )
for _ , p := range pvm {
path , err := pathToTfplan ( p . Path )
if err != nil {
return nil , err
}
ret = append ( ret , path )
}
return ret , nil
}
func pathFromTfplan ( path * planproto . Path ) ( cty . Path , error ) {
ret := make ( [ ] cty . PathStep , 0 , len ( path . Steps ) )
for _ , step := range path . Steps {
switch s := step . Selector . ( type ) {
case * planproto . Path_Step_ElementKey :
dynamicVal , err := valueFromTfplan ( s . ElementKey )
if err != nil {
return nil , fmt . Errorf ( "error decoding path index step: %s" , err )
}
ty , err := dynamicVal . ImpliedType ( )
if err != nil {
return nil , fmt . Errorf ( "error determining path index type: %s" , err )
}
val , err := dynamicVal . Decode ( ty )
if err != nil {
return nil , fmt . Errorf ( "error decoding path index value: %s" , err )
}
ret = append ( ret , cty . IndexStep { Key : val } )
case * planproto . Path_Step_AttributeName :
ret = append ( ret , cty . GetAttrStep { Name : s . AttributeName } )
default :
return nil , fmt . Errorf ( "Unsupported path step %t" , step . Selector )
}
}
return ret , nil
}
func pathToTfplan ( path cty . Path ) ( * planproto . Path , error ) {
steps := make ( [ ] * planproto . Path_Step , 0 , len ( path ) )
for _ , step := range path {
switch s := step . ( type ) {
case cty . IndexStep :
value , err := plans . NewDynamicValue ( s . Key , s . Key . Type ( ) )
if err != nil {
return nil , fmt . Errorf ( "Error encoding path step: %s" , err )
}
steps = append ( steps , & planproto . Path_Step {
Selector : & planproto . Path_Step_ElementKey {
ElementKey : valueToTfplan ( value ) ,
} ,
} )
case cty . GetAttrStep :
steps = append ( steps , & planproto . Path_Step {
Selector : & planproto . Path_Step_AttributeName {
AttributeName : s . Name ,
} ,
} )
default :
return nil , fmt . Errorf ( "Unsupported path step %#v (%t)" , step , step )
}
}
return & planproto . Path {
Steps : steps ,
} , nil
}