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