github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/plans/planfile/tfplan.go (about)

     1  package planfile
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  
     8  	"google.golang.org/protobuf/proto"
     9  
    10  	"github.com/muratcelep/terraform/not-internal/addrs"
    11  	"github.com/muratcelep/terraform/not-internal/lang/marks"
    12  	"github.com/muratcelep/terraform/not-internal/plans"
    13  	"github.com/muratcelep/terraform/not-internal/plans/internal/planproto"
    14  	"github.com/muratcelep/terraform/not-internal/states"
    15  	"github.com/muratcelep/terraform/version"
    16  	"github.com/zclconf/go-cty/cty"
    17  )
    18  
    19  const tfplanFormatVersion = 3
    20  const tfplanFilename = "tfplan"
    21  
    22  // ---------------------------------------------------------------------------
    23  // This file deals with the not-internal structure of the "tfplan" sub-file within
    24  // the plan file format. It's all private API, wrapped by methods defined
    25  // elsewhere. This is the only file that should import the
    26  // ../not-internal/planproto package, which contains the ugly stubs generated
    27  // by the protobuf compiler.
    28  // ---------------------------------------------------------------------------
    29  
    30  // readTfplan reads a protobuf-encoded description from the plan portion of
    31  // a plan file, which is stored in a special file in the archive called
    32  // "tfplan".
    33  func readTfplan(r io.Reader) (*plans.Plan, error) {
    34  	src, err := ioutil.ReadAll(r)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	var rawPlan planproto.Plan
    40  	err = proto.Unmarshal(src, &rawPlan)
    41  	if err != nil {
    42  		return nil, fmt.Errorf("parse error: %s", err)
    43  	}
    44  
    45  	if rawPlan.Version != tfplanFormatVersion {
    46  		return nil, fmt.Errorf("unsupported plan file format version %d; only version %d is supported", rawPlan.Version, tfplanFormatVersion)
    47  	}
    48  
    49  	if rawPlan.TerraformVersion != version.String() {
    50  		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())
    51  	}
    52  
    53  	plan := &plans.Plan{
    54  		VariableValues: map[string]plans.DynamicValue{},
    55  		Changes: &plans.Changes{
    56  			Outputs:   []*plans.OutputChangeSrc{},
    57  			Resources: []*plans.ResourceInstanceChangeSrc{},
    58  		},
    59  		DriftedResources: []*plans.ResourceInstanceChangeSrc{},
    60  	}
    61  
    62  	switch rawPlan.UiMode {
    63  	case planproto.Mode_NORMAL:
    64  		plan.UIMode = plans.NormalMode
    65  	case planproto.Mode_DESTROY:
    66  		plan.UIMode = plans.DestroyMode
    67  	case planproto.Mode_REFRESH_ONLY:
    68  		plan.UIMode = plans.RefreshOnlyMode
    69  	default:
    70  		return nil, fmt.Errorf("plan has invalid mode %s", rawPlan.UiMode)
    71  	}
    72  
    73  	for _, rawOC := range rawPlan.OutputChanges {
    74  		name := rawOC.Name
    75  		change, err := changeFromTfplan(rawOC.Change)
    76  		if err != nil {
    77  			return nil, fmt.Errorf("invalid plan for output %q: %s", name, err)
    78  		}
    79  
    80  		plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{
    81  			// All output values saved in the plan file are root module outputs,
    82  			// since we don't retain others. (They can be easily recomputed
    83  			// during apply).
    84  			Addr:      addrs.OutputValue{Name: name}.Absolute(addrs.RootModuleInstance),
    85  			ChangeSrc: *change,
    86  			Sensitive: rawOC.Sensitive,
    87  		})
    88  	}
    89  
    90  	for _, rawRC := range rawPlan.ResourceChanges {
    91  		change, err := resourceChangeFromTfplan(rawRC)
    92  		if err != nil {
    93  			// errors from resourceChangeFromTfplan already include context
    94  			return nil, err
    95  		}
    96  
    97  		plan.Changes.Resources = append(plan.Changes.Resources, change)
    98  	}
    99  
   100  	for _, rawRC := range rawPlan.ResourceDrift {
   101  		change, err := resourceChangeFromTfplan(rawRC)
   102  		if err != nil {
   103  			// errors from resourceChangeFromTfplan already include context
   104  			return nil, err
   105  		}
   106  
   107  		plan.DriftedResources = append(plan.DriftedResources, change)
   108  	}
   109  
   110  	for _, rawTargetAddr := range rawPlan.TargetAddrs {
   111  		target, diags := addrs.ParseTargetStr(rawTargetAddr)
   112  		if diags.HasErrors() {
   113  			return nil, fmt.Errorf("plan contains invalid target address %q: %s", target, diags.Err())
   114  		}
   115  		plan.TargetAddrs = append(plan.TargetAddrs, target.Subject)
   116  	}
   117  
   118  	for _, rawReplaceAddr := range rawPlan.ForceReplaceAddrs {
   119  		addr, diags := addrs.ParseAbsResourceInstanceStr(rawReplaceAddr)
   120  		if diags.HasErrors() {
   121  			return nil, fmt.Errorf("plan contains invalid force-replace address %q: %s", addr, diags.Err())
   122  		}
   123  		plan.ForceReplaceAddrs = append(plan.ForceReplaceAddrs, addr)
   124  	}
   125  
   126  	for name, rawVal := range rawPlan.Variables {
   127  		val, err := valueFromTfplan(rawVal)
   128  		if err != nil {
   129  			return nil, fmt.Errorf("invalid value for input variable %q: %s", name, err)
   130  		}
   131  		plan.VariableValues[name] = val
   132  	}
   133  
   134  	if rawBackend := rawPlan.Backend; rawBackend == nil {
   135  		return nil, fmt.Errorf("plan file has no backend settings; backend settings are required")
   136  	} else {
   137  		config, err := valueFromTfplan(rawBackend.Config)
   138  		if err != nil {
   139  			return nil, fmt.Errorf("plan file has invalid backend configuration: %s", err)
   140  		}
   141  		plan.Backend = plans.Backend{
   142  			Type:      rawBackend.Type,
   143  			Config:    config,
   144  			Workspace: rawBackend.Workspace,
   145  		}
   146  	}
   147  
   148  	return plan, nil
   149  }
   150  
   151  func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*plans.ResourceInstanceChangeSrc, error) {
   152  	if rawChange == nil {
   153  		// Should never happen in practice, since protobuf can't represent
   154  		// a nil value in a list.
   155  		return nil, fmt.Errorf("resource change object is absent")
   156  	}
   157  
   158  	ret := &plans.ResourceInstanceChangeSrc{}
   159  
   160  	if rawChange.Addr == "" {
   161  		// If "Addr" isn't populated then seems likely that this is a plan
   162  		// file created by an earlier version of Terraform, which had the
   163  		// same information spread over various other fields:
   164  		// ModulePath, Mode, Name, Type, and InstanceKey.
   165  		return nil, fmt.Errorf("no instance address for resource instance change; perhaps this plan was created by a different version of Terraform?")
   166  	}
   167  
   168  	instAddr, diags := addrs.ParseAbsResourceInstanceStr(rawChange.Addr)
   169  	if diags.HasErrors() {
   170  		return nil, fmt.Errorf("invalid resource instance address %q: %w", rawChange.Addr, diags.Err())
   171  	}
   172  	prevRunAddr := instAddr
   173  	if rawChange.PrevRunAddr != "" {
   174  		prevRunAddr, diags = addrs.ParseAbsResourceInstanceStr(rawChange.PrevRunAddr)
   175  		if diags.HasErrors() {
   176  			return nil, fmt.Errorf("invalid resource instance previous run address %q: %w", rawChange.PrevRunAddr, diags.Err())
   177  		}
   178  	}
   179  
   180  	providerAddr, diags := addrs.ParseAbsProviderConfigStr(rawChange.Provider)
   181  	if diags.HasErrors() {
   182  		return nil, diags.Err()
   183  	}
   184  	ret.ProviderAddr = providerAddr
   185  
   186  	ret.Addr = instAddr
   187  	ret.PrevRunAddr = prevRunAddr
   188  
   189  	if rawChange.DeposedKey != "" {
   190  		if len(rawChange.DeposedKey) != 8 {
   191  			return nil, fmt.Errorf("deposed object for %s has invalid deposed key %q", ret.Addr, rawChange.DeposedKey)
   192  		}
   193  		ret.DeposedKey = states.DeposedKey(rawChange.DeposedKey)
   194  	}
   195  
   196  	ret.RequiredReplace = cty.NewPathSet()
   197  	for _, p := range rawChange.RequiredReplace {
   198  		path, err := pathFromTfplan(p)
   199  		if err != nil {
   200  			return nil, fmt.Errorf("invalid path in required replace: %s", err)
   201  		}
   202  		ret.RequiredReplace.Add(path)
   203  	}
   204  
   205  	change, err := changeFromTfplan(rawChange.Change)
   206  	if err != nil {
   207  		return nil, fmt.Errorf("invalid plan for resource %s: %s", ret.Addr, err)
   208  	}
   209  
   210  	ret.ChangeSrc = *change
   211  
   212  	switch rawChange.ActionReason {
   213  	case planproto.ResourceInstanceActionReason_NONE:
   214  		ret.ActionReason = plans.ResourceInstanceChangeNoReason
   215  	case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE:
   216  		ret.ActionReason = plans.ResourceInstanceReplaceBecauseCannotUpdate
   217  	case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED:
   218  		ret.ActionReason = plans.ResourceInstanceReplaceBecauseTainted
   219  	case planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST:
   220  		ret.ActionReason = plans.ResourceInstanceReplaceByRequest
   221  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG:
   222  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoResourceConfig
   223  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION:
   224  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseWrongRepetition
   225  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX:
   226  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseCountIndex
   227  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY:
   228  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseEachKey
   229  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE:
   230  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoModule
   231  	default:
   232  		return nil, fmt.Errorf("resource has invalid action reason %s", rawChange.ActionReason)
   233  	}
   234  
   235  	if len(rawChange.Private) != 0 {
   236  		ret.Private = rawChange.Private
   237  	}
   238  
   239  	return ret, nil
   240  }
   241  
   242  func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) {
   243  	if rawChange == nil {
   244  		return nil, fmt.Errorf("change object is absent")
   245  	}
   246  
   247  	ret := &plans.ChangeSrc{}
   248  
   249  	// -1 indicates that there is no index. We'll customize these below
   250  	// depending on the change action, and then decode.
   251  	beforeIdx, afterIdx := -1, -1
   252  
   253  	switch rawChange.Action {
   254  	case planproto.Action_NOOP:
   255  		ret.Action = plans.NoOp
   256  		beforeIdx = 0
   257  		afterIdx = 0
   258  	case planproto.Action_CREATE:
   259  		ret.Action = plans.Create
   260  		afterIdx = 0
   261  	case planproto.Action_READ:
   262  		ret.Action = plans.Read
   263  		beforeIdx = 0
   264  		afterIdx = 1
   265  	case planproto.Action_UPDATE:
   266  		ret.Action = plans.Update
   267  		beforeIdx = 0
   268  		afterIdx = 1
   269  	case planproto.Action_DELETE:
   270  		ret.Action = plans.Delete
   271  		beforeIdx = 0
   272  	case planproto.Action_CREATE_THEN_DELETE:
   273  		ret.Action = plans.CreateThenDelete
   274  		beforeIdx = 0
   275  		afterIdx = 1
   276  	case planproto.Action_DELETE_THEN_CREATE:
   277  		ret.Action = plans.DeleteThenCreate
   278  		beforeIdx = 0
   279  		afterIdx = 1
   280  	default:
   281  		return nil, fmt.Errorf("invalid change action %s", rawChange.Action)
   282  	}
   283  
   284  	if beforeIdx != -1 {
   285  		if l := len(rawChange.Values); l <= beforeIdx {
   286  			return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action)
   287  		}
   288  		var err error
   289  		ret.Before, err = valueFromTfplan(rawChange.Values[beforeIdx])
   290  		if err != nil {
   291  			return nil, fmt.Errorf("invalid \"before\" value: %s", err)
   292  		}
   293  		if ret.Before == nil {
   294  			return nil, fmt.Errorf("missing \"before\" value: %s", err)
   295  		}
   296  	}
   297  	if afterIdx != -1 {
   298  		if l := len(rawChange.Values); l <= afterIdx {
   299  			return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action)
   300  		}
   301  		var err error
   302  		ret.After, err = valueFromTfplan(rawChange.Values[afterIdx])
   303  		if err != nil {
   304  			return nil, fmt.Errorf("invalid \"after\" value: %s", err)
   305  		}
   306  		if ret.After == nil {
   307  			return nil, fmt.Errorf("missing \"after\" value: %s", err)
   308  		}
   309  	}
   310  
   311  	sensitive := cty.NewValueMarks(marks.Sensitive)
   312  	beforeValMarks, err := pathValueMarksFromTfplan(rawChange.BeforeSensitivePaths, sensitive)
   313  	if err != nil {
   314  		return nil, fmt.Errorf("failed to decode before sensitive paths: %s", err)
   315  	}
   316  	afterValMarks, err := pathValueMarksFromTfplan(rawChange.AfterSensitivePaths, sensitive)
   317  	if err != nil {
   318  		return nil, fmt.Errorf("failed to decode after sensitive paths: %s", err)
   319  	}
   320  	if len(beforeValMarks) > 0 {
   321  		ret.BeforeValMarks = beforeValMarks
   322  	}
   323  	if len(afterValMarks) > 0 {
   324  		ret.AfterValMarks = afterValMarks
   325  	}
   326  
   327  	return ret, nil
   328  }
   329  
   330  func valueFromTfplan(rawV *planproto.DynamicValue) (plans.DynamicValue, error) {
   331  	if len(rawV.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf
   332  		return nil, fmt.Errorf("dynamic value does not have msgpack serialization")
   333  	}
   334  
   335  	return plans.DynamicValue(rawV.Msgpack), nil
   336  }
   337  
   338  // writeTfplan serializes the given plan into the protobuf-based format used
   339  // for the "tfplan" portion of a plan file.
   340  func writeTfplan(plan *plans.Plan, w io.Writer) error {
   341  	if plan == nil {
   342  		return fmt.Errorf("cannot write plan file for nil plan")
   343  	}
   344  	if plan.Changes == nil {
   345  		return fmt.Errorf("cannot write plan file with nil changeset")
   346  	}
   347  
   348  	rawPlan := &planproto.Plan{
   349  		Version:          tfplanFormatVersion,
   350  		TerraformVersion: version.String(),
   351  
   352  		Variables:       map[string]*planproto.DynamicValue{},
   353  		OutputChanges:   []*planproto.OutputChange{},
   354  		ResourceChanges: []*planproto.ResourceInstanceChange{},
   355  		ResourceDrift:   []*planproto.ResourceInstanceChange{},
   356  	}
   357  
   358  	switch plan.UIMode {
   359  	case plans.NormalMode:
   360  		rawPlan.UiMode = planproto.Mode_NORMAL
   361  	case plans.DestroyMode:
   362  		rawPlan.UiMode = planproto.Mode_DESTROY
   363  	case plans.RefreshOnlyMode:
   364  		rawPlan.UiMode = planproto.Mode_REFRESH_ONLY
   365  	default:
   366  		return fmt.Errorf("plan has unsupported mode %s", plan.UIMode)
   367  	}
   368  
   369  	for _, oc := range plan.Changes.Outputs {
   370  		// When serializing a plan we only retain the root outputs, since
   371  		// changes to these are externally-visible side effects (e.g. via
   372  		// terraform_remote_state).
   373  		if !oc.Addr.Module.IsRoot() {
   374  			continue
   375  		}
   376  
   377  		name := oc.Addr.OutputValue.Name
   378  
   379  		// Writing outputs as cty.DynamicPseudoType forces the stored values
   380  		// to also contain dynamic type information, so we can recover the
   381  		// original type when we read the values back in readTFPlan.
   382  		protoChange, err := changeToTfplan(&oc.ChangeSrc)
   383  		if err != nil {
   384  			return fmt.Errorf("cannot write output value %q: %s", name, err)
   385  		}
   386  
   387  		rawPlan.OutputChanges = append(rawPlan.OutputChanges, &planproto.OutputChange{
   388  			Name:      name,
   389  			Change:    protoChange,
   390  			Sensitive: oc.Sensitive,
   391  		})
   392  	}
   393  
   394  	for _, rc := range plan.Changes.Resources {
   395  		rawRC, err := resourceChangeToTfplan(rc)
   396  		if err != nil {
   397  			return err
   398  		}
   399  		rawPlan.ResourceChanges = append(rawPlan.ResourceChanges, rawRC)
   400  	}
   401  
   402  	for _, rc := range plan.DriftedResources {
   403  		rawRC, err := resourceChangeToTfplan(rc)
   404  		if err != nil {
   405  			return err
   406  		}
   407  		rawPlan.ResourceDrift = append(rawPlan.ResourceDrift, rawRC)
   408  	}
   409  
   410  	for _, targetAddr := range plan.TargetAddrs {
   411  		rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String())
   412  	}
   413  
   414  	for _, replaceAddr := range plan.ForceReplaceAddrs {
   415  		rawPlan.ForceReplaceAddrs = append(rawPlan.ForceReplaceAddrs, replaceAddr.String())
   416  	}
   417  
   418  	for name, val := range plan.VariableValues {
   419  		rawPlan.Variables[name] = valueToTfplan(val)
   420  	}
   421  
   422  	if plan.Backend.Type == "" || plan.Backend.Config == nil {
   423  		// This suggests a bug in the code that created the plan, since it
   424  		// ought to always have a backend populated, even if it's the default
   425  		// "local" backend with a local state file.
   426  		return fmt.Errorf("plan does not have a backend configuration")
   427  	}
   428  
   429  	rawPlan.Backend = &planproto.Backend{
   430  		Type:      plan.Backend.Type,
   431  		Config:    valueToTfplan(plan.Backend.Config),
   432  		Workspace: plan.Backend.Workspace,
   433  	}
   434  
   435  	src, err := proto.Marshal(rawPlan)
   436  	if err != nil {
   437  		return fmt.Errorf("serialization error: %s", err)
   438  	}
   439  
   440  	_, err = w.Write(src)
   441  	if err != nil {
   442  		return fmt.Errorf("failed to write plan to plan file: %s", err)
   443  	}
   444  
   445  	return nil
   446  }
   447  
   448  func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) {
   449  	ret := &planproto.ResourceInstanceChange{}
   450  
   451  	if change.PrevRunAddr.Resource.Resource.Type == "" {
   452  		// Suggests that an old caller wasn't yet updated to populate this
   453  		// properly. All code that generates plans should populate this field,
   454  		// even if it's just to write in the same value as in change.Addr.
   455  		change.PrevRunAddr = change.Addr
   456  	}
   457  
   458  	ret.Addr = change.Addr.String()
   459  	ret.PrevRunAddr = change.PrevRunAddr.String()
   460  	if ret.PrevRunAddr == ret.Addr {
   461  		// In the on-disk format we leave PrevRunAddr unpopulated in the common
   462  		// case where it's the same as Addr, and then fill it back in again on
   463  		// read.
   464  		ret.PrevRunAddr = ""
   465  	}
   466  
   467  	ret.DeposedKey = string(change.DeposedKey)
   468  	ret.Provider = change.ProviderAddr.String()
   469  
   470  	requiredReplace := change.RequiredReplace.List()
   471  	ret.RequiredReplace = make([]*planproto.Path, 0, len(requiredReplace))
   472  	for _, p := range requiredReplace {
   473  		path, err := pathToTfplan(p)
   474  		if err != nil {
   475  			return nil, fmt.Errorf("invalid path in required replace: %s", err)
   476  		}
   477  		ret.RequiredReplace = append(ret.RequiredReplace, path)
   478  	}
   479  
   480  	valChange, err := changeToTfplan(&change.ChangeSrc)
   481  	if err != nil {
   482  		return nil, fmt.Errorf("failed to serialize resource %s change: %s", change.Addr, err)
   483  	}
   484  	ret.Change = valChange
   485  
   486  	switch change.ActionReason {
   487  	case plans.ResourceInstanceChangeNoReason:
   488  		ret.ActionReason = planproto.ResourceInstanceActionReason_NONE
   489  	case plans.ResourceInstanceReplaceBecauseCannotUpdate:
   490  		ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE
   491  	case plans.ResourceInstanceReplaceBecauseTainted:
   492  		ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED
   493  	case plans.ResourceInstanceReplaceByRequest:
   494  		ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST
   495  	case plans.ResourceInstanceDeleteBecauseNoResourceConfig:
   496  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG
   497  	case plans.ResourceInstanceDeleteBecauseWrongRepetition:
   498  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION
   499  	case plans.ResourceInstanceDeleteBecauseCountIndex:
   500  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX
   501  	case plans.ResourceInstanceDeleteBecauseEachKey:
   502  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY
   503  	case plans.ResourceInstanceDeleteBecauseNoModule:
   504  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE
   505  	default:
   506  		return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason)
   507  	}
   508  
   509  	if len(change.Private) > 0 {
   510  		ret.Private = change.Private
   511  	}
   512  
   513  	return ret, nil
   514  }
   515  
   516  func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) {
   517  	ret := &planproto.Change{}
   518  
   519  	before := valueToTfplan(change.Before)
   520  	after := valueToTfplan(change.After)
   521  
   522  	beforeSensitivePaths, err := pathValueMarksToTfplan(change.BeforeValMarks)
   523  	if err != nil {
   524  		return nil, err
   525  	}
   526  	afterSensitivePaths, err := pathValueMarksToTfplan(change.AfterValMarks)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  	ret.BeforeSensitivePaths = beforeSensitivePaths
   531  	ret.AfterSensitivePaths = afterSensitivePaths
   532  
   533  	switch change.Action {
   534  	case plans.NoOp:
   535  		ret.Action = planproto.Action_NOOP
   536  		ret.Values = []*planproto.DynamicValue{before} // before and after should be identical
   537  	case plans.Create:
   538  		ret.Action = planproto.Action_CREATE
   539  		ret.Values = []*planproto.DynamicValue{after}
   540  	case plans.Read:
   541  		ret.Action = planproto.Action_READ
   542  		ret.Values = []*planproto.DynamicValue{before, after}
   543  	case plans.Update:
   544  		ret.Action = planproto.Action_UPDATE
   545  		ret.Values = []*planproto.DynamicValue{before, after}
   546  	case plans.Delete:
   547  		ret.Action = planproto.Action_DELETE
   548  		ret.Values = []*planproto.DynamicValue{before}
   549  	case plans.DeleteThenCreate:
   550  		ret.Action = planproto.Action_DELETE_THEN_CREATE
   551  		ret.Values = []*planproto.DynamicValue{before, after}
   552  	case plans.CreateThenDelete:
   553  		ret.Action = planproto.Action_CREATE_THEN_DELETE
   554  		ret.Values = []*planproto.DynamicValue{before, after}
   555  	default:
   556  		return nil, fmt.Errorf("invalid change action %s", change.Action)
   557  	}
   558  
   559  	return ret, nil
   560  }
   561  
   562  func valueToTfplan(val plans.DynamicValue) *planproto.DynamicValue {
   563  	if val == nil {
   564  		// protobuf can't represent nil, so we'll represent it as a
   565  		// DynamicValue that has no serializations at all.
   566  		return &planproto.DynamicValue{}
   567  	}
   568  	return &planproto.DynamicValue{
   569  		Msgpack: []byte(val),
   570  	}
   571  }
   572  
   573  func pathValueMarksFromTfplan(paths []*planproto.Path, marks cty.ValueMarks) ([]cty.PathValueMarks, error) {
   574  	ret := make([]cty.PathValueMarks, 0, len(paths))
   575  	for _, p := range paths {
   576  		path, err := pathFromTfplan(p)
   577  		if err != nil {
   578  			return nil, err
   579  		}
   580  		ret = append(ret, cty.PathValueMarks{
   581  			Path:  path,
   582  			Marks: marks,
   583  		})
   584  	}
   585  	return ret, nil
   586  }
   587  
   588  func pathValueMarksToTfplan(pvm []cty.PathValueMarks) ([]*planproto.Path, error) {
   589  	ret := make([]*planproto.Path, 0, len(pvm))
   590  	for _, p := range pvm {
   591  		path, err := pathToTfplan(p.Path)
   592  		if err != nil {
   593  			return nil, err
   594  		}
   595  		ret = append(ret, path)
   596  	}
   597  	return ret, nil
   598  }
   599  
   600  func pathFromTfplan(path *planproto.Path) (cty.Path, error) {
   601  	ret := make([]cty.PathStep, 0, len(path.Steps))
   602  	for _, step := range path.Steps {
   603  		switch s := step.Selector.(type) {
   604  		case *planproto.Path_Step_ElementKey:
   605  			dynamicVal, err := valueFromTfplan(s.ElementKey)
   606  			if err != nil {
   607  				return nil, fmt.Errorf("error decoding path index step: %s", err)
   608  			}
   609  			ty, err := dynamicVal.ImpliedType()
   610  			if err != nil {
   611  				return nil, fmt.Errorf("error determining path index type: %s", err)
   612  			}
   613  			val, err := dynamicVal.Decode(ty)
   614  			if err != nil {
   615  				return nil, fmt.Errorf("error decoding path index value: %s", err)
   616  			}
   617  			ret = append(ret, cty.IndexStep{Key: val})
   618  		case *planproto.Path_Step_AttributeName:
   619  			ret = append(ret, cty.GetAttrStep{Name: s.AttributeName})
   620  		default:
   621  			return nil, fmt.Errorf("Unsupported path step %t", step.Selector)
   622  		}
   623  	}
   624  	return ret, nil
   625  }
   626  
   627  func pathToTfplan(path cty.Path) (*planproto.Path, error) {
   628  	steps := make([]*planproto.Path_Step, 0, len(path))
   629  	for _, step := range path {
   630  		switch s := step.(type) {
   631  		case cty.IndexStep:
   632  			value, err := plans.NewDynamicValue(s.Key, s.Key.Type())
   633  			if err != nil {
   634  				return nil, fmt.Errorf("Error encoding path step: %s", err)
   635  			}
   636  			steps = append(steps, &planproto.Path_Step{
   637  				Selector: &planproto.Path_Step_ElementKey{
   638  					ElementKey: valueToTfplan(value),
   639  				},
   640  			})
   641  		case cty.GetAttrStep:
   642  			steps = append(steps, &planproto.Path_Step{
   643  				Selector: &planproto.Path_Step_AttributeName{
   644  					AttributeName: s.Name,
   645  				},
   646  			})
   647  		default:
   648  			return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step)
   649  		}
   650  	}
   651  	return &planproto.Path{
   652  		Steps: steps,
   653  	}, nil
   654  }