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