github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/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/hashicorp/terraform/internal/addrs"
    11  	"github.com/hashicorp/terraform/internal/checks"
    12  	"github.com/hashicorp/terraform/internal/lang/globalref"
    13  	"github.com/hashicorp/terraform/internal/lang/marks"
    14  	"github.com/hashicorp/terraform/internal/plans"
    15  	"github.com/hashicorp/terraform/internal/plans/internal/planproto"
    16  	"github.com/hashicorp/terraform/internal/states"
    17  	"github.com/hashicorp/terraform/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.TerraformVersion != version.String() {
    52  		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())
    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  		Checks:           &states.CheckResults{},
    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  	plan.Checks.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *states.CheckResultAggregate]()
    94  	for _, rawCRs := range rawPlan.CheckResults {
    95  		aggr := &states.CheckResultAggregate{}
    96  		switch rawCRs.Status {
    97  		case planproto.CheckResults_UNKNOWN:
    98  			aggr.Status = checks.StatusUnknown
    99  		case planproto.CheckResults_PASS:
   100  			aggr.Status = checks.StatusPass
   101  		case planproto.CheckResults_FAIL:
   102  			aggr.Status = checks.StatusFail
   103  		case planproto.CheckResults_ERROR:
   104  			aggr.Status = checks.StatusError
   105  		default:
   106  			return nil, fmt.Errorf("aggregate check results for %s have unsupported status %#v", rawCRs.ConfigAddr, rawCRs.Status)
   107  		}
   108  
   109  		var objKind addrs.CheckableKind
   110  		switch rawCRs.Kind {
   111  		case planproto.CheckResults_RESOURCE:
   112  			objKind = addrs.CheckableResource
   113  		case planproto.CheckResults_OUTPUT_VALUE:
   114  			objKind = addrs.CheckableOutputValue
   115  		default:
   116  			return nil, fmt.Errorf("aggregate check results for %s have unsupported object kind %s", rawCRs.ConfigAddr, objKind)
   117  		}
   118  
   119  		// Some trickiness here: we only have an address parser for
   120  		// addrs.Checkable and not for addrs.ConfigCheckable, but that's okay
   121  		// because once we have an addrs.Checkable we can always derive an
   122  		// addrs.ConfigCheckable from it, and a ConfigCheckable should always
   123  		// be the same syntax as a Checkable with no index information and
   124  		// thus we can reuse the same parser for both here.
   125  		configAddrProxy, diags := addrs.ParseCheckableStr(objKind, rawCRs.ConfigAddr)
   126  		if diags.HasErrors() {
   127  			return nil, diags.Err()
   128  		}
   129  		configAddr := configAddrProxy.ConfigCheckable()
   130  		if configAddr.String() != configAddrProxy.String() {
   131  			// This is how we catch if the config address included index
   132  			// information that would be allowed in a Checkable but not
   133  			// in a ConfigCheckable.
   134  			return nil, fmt.Errorf("invalid checkable config address %s", rawCRs.ConfigAddr)
   135  		}
   136  
   137  		aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *states.CheckResultObject]()
   138  		for _, rawCR := range rawCRs.Objects {
   139  			objectAddr, diags := addrs.ParseCheckableStr(objKind, rawCR.ObjectAddr)
   140  			if diags.HasErrors() {
   141  				return nil, diags.Err()
   142  			}
   143  			if !addrs.Equivalent(objectAddr.ConfigCheckable(), configAddr) {
   144  				return nil, fmt.Errorf("checkable object %s should not be grouped under %s", objectAddr, configAddr)
   145  			}
   146  
   147  			obj := &states.CheckResultObject{
   148  				FailureMessages: rawCR.FailureMessages,
   149  			}
   150  			switch rawCR.Status {
   151  			case planproto.CheckResults_UNKNOWN:
   152  				obj.Status = checks.StatusUnknown
   153  			case planproto.CheckResults_PASS:
   154  				obj.Status = checks.StatusPass
   155  			case planproto.CheckResults_FAIL:
   156  				obj.Status = checks.StatusFail
   157  			case planproto.CheckResults_ERROR:
   158  				obj.Status = checks.StatusError
   159  			default:
   160  				return nil, fmt.Errorf("object check results for %s has unsupported status %#v", rawCR.ObjectAddr, rawCR.Status)
   161  			}
   162  
   163  			aggr.ObjectResults.Put(objectAddr, obj)
   164  		}
   165  		// If we ended up with no elements in the map then we'll just nil it,
   166  		// primarily just to make life easier for our round-trip tests.
   167  		if aggr.ObjectResults.Len() == 0 {
   168  			aggr.ObjectResults.Elems = nil
   169  		}
   170  
   171  		plan.Checks.ConfigResults.Put(configAddr, aggr)
   172  	}
   173  	// If we ended up with no elements in the map then we'll just nil it,
   174  	// primarily just to make life easier for our round-trip tests.
   175  	if plan.Checks.ConfigResults.Len() == 0 {
   176  		plan.Checks.ConfigResults.Elems = nil
   177  	}
   178  
   179  	for _, rawRC := range rawPlan.ResourceChanges {
   180  		change, err := resourceChangeFromTfplan(rawRC)
   181  		if err != nil {
   182  			// errors from resourceChangeFromTfplan already include context
   183  			return nil, err
   184  		}
   185  
   186  		plan.Changes.Resources = append(plan.Changes.Resources, change)
   187  	}
   188  
   189  	for _, rawRC := range rawPlan.ResourceDrift {
   190  		change, err := resourceChangeFromTfplan(rawRC)
   191  		if err != nil {
   192  			// errors from resourceChangeFromTfplan already include context
   193  			return nil, err
   194  		}
   195  
   196  		plan.DriftedResources = append(plan.DriftedResources, change)
   197  	}
   198  
   199  	for _, rawRA := range rawPlan.RelevantAttributes {
   200  		ra, err := resourceAttrFromTfplan(rawRA)
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  		plan.RelevantAttributes = append(plan.RelevantAttributes, ra)
   205  	}
   206  
   207  	for _, rawTargetAddr := range rawPlan.TargetAddrs {
   208  		target, diags := addrs.ParseTargetStr(rawTargetAddr)
   209  		if diags.HasErrors() {
   210  			return nil, fmt.Errorf("plan contains invalid target address %q: %s", target, diags.Err())
   211  		}
   212  		plan.TargetAddrs = append(plan.TargetAddrs, target.Subject)
   213  	}
   214  
   215  	for _, rawReplaceAddr := range rawPlan.ForceReplaceAddrs {
   216  		addr, diags := addrs.ParseAbsResourceInstanceStr(rawReplaceAddr)
   217  		if diags.HasErrors() {
   218  			return nil, fmt.Errorf("plan contains invalid force-replace address %q: %s", addr, diags.Err())
   219  		}
   220  		plan.ForceReplaceAddrs = append(plan.ForceReplaceAddrs, addr)
   221  	}
   222  
   223  	for name, rawVal := range rawPlan.Variables {
   224  		val, err := valueFromTfplan(rawVal)
   225  		if err != nil {
   226  			return nil, fmt.Errorf("invalid value for input variable %q: %s", name, err)
   227  		}
   228  		plan.VariableValues[name] = val
   229  	}
   230  
   231  	if rawBackend := rawPlan.Backend; rawBackend == nil {
   232  		return nil, fmt.Errorf("plan file has no backend settings; backend settings are required")
   233  	} else {
   234  		config, err := valueFromTfplan(rawBackend.Config)
   235  		if err != nil {
   236  			return nil, fmt.Errorf("plan file has invalid backend configuration: %s", err)
   237  		}
   238  		plan.Backend = plans.Backend{
   239  			Type:      rawBackend.Type,
   240  			Config:    config,
   241  			Workspace: rawBackend.Workspace,
   242  		}
   243  	}
   244  
   245  	return plan, nil
   246  }
   247  
   248  func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*plans.ResourceInstanceChangeSrc, error) {
   249  	if rawChange == nil {
   250  		// Should never happen in practice, since protobuf can't represent
   251  		// a nil value in a list.
   252  		return nil, fmt.Errorf("resource change object is absent")
   253  	}
   254  
   255  	ret := &plans.ResourceInstanceChangeSrc{}
   256  
   257  	if rawChange.Addr == "" {
   258  		// If "Addr" isn't populated then seems likely that this is a plan
   259  		// file created by an earlier version of Terraform, which had the
   260  		// same information spread over various other fields:
   261  		// ModulePath, Mode, Name, Type, and InstanceKey.
   262  		return nil, fmt.Errorf("no instance address for resource instance change; perhaps this plan was created by a different version of Terraform?")
   263  	}
   264  
   265  	instAddr, diags := addrs.ParseAbsResourceInstanceStr(rawChange.Addr)
   266  	if diags.HasErrors() {
   267  		return nil, fmt.Errorf("invalid resource instance address %q: %w", rawChange.Addr, diags.Err())
   268  	}
   269  	prevRunAddr := instAddr
   270  	if rawChange.PrevRunAddr != "" {
   271  		prevRunAddr, diags = addrs.ParseAbsResourceInstanceStr(rawChange.PrevRunAddr)
   272  		if diags.HasErrors() {
   273  			return nil, fmt.Errorf("invalid resource instance previous run address %q: %w", rawChange.PrevRunAddr, diags.Err())
   274  		}
   275  	}
   276  
   277  	providerAddr, diags := addrs.ParseAbsProviderConfigStr(rawChange.Provider)
   278  	if diags.HasErrors() {
   279  		return nil, diags.Err()
   280  	}
   281  	ret.ProviderAddr = providerAddr
   282  
   283  	ret.Addr = instAddr
   284  	ret.PrevRunAddr = prevRunAddr
   285  
   286  	if rawChange.DeposedKey != "" {
   287  		if len(rawChange.DeposedKey) != 8 {
   288  			return nil, fmt.Errorf("deposed object for %s has invalid deposed key %q", ret.Addr, rawChange.DeposedKey)
   289  		}
   290  		ret.DeposedKey = states.DeposedKey(rawChange.DeposedKey)
   291  	}
   292  
   293  	ret.RequiredReplace = cty.NewPathSet()
   294  	for _, p := range rawChange.RequiredReplace {
   295  		path, err := pathFromTfplan(p)
   296  		if err != nil {
   297  			return nil, fmt.Errorf("invalid path in required replace: %s", err)
   298  		}
   299  		ret.RequiredReplace.Add(path)
   300  	}
   301  
   302  	change, err := changeFromTfplan(rawChange.Change)
   303  	if err != nil {
   304  		return nil, fmt.Errorf("invalid plan for resource %s: %s", ret.Addr, err)
   305  	}
   306  
   307  	ret.ChangeSrc = *change
   308  
   309  	switch rawChange.ActionReason {
   310  	case planproto.ResourceInstanceActionReason_NONE:
   311  		ret.ActionReason = plans.ResourceInstanceChangeNoReason
   312  	case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE:
   313  		ret.ActionReason = plans.ResourceInstanceReplaceBecauseCannotUpdate
   314  	case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED:
   315  		ret.ActionReason = plans.ResourceInstanceReplaceBecauseTainted
   316  	case planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST:
   317  		ret.ActionReason = plans.ResourceInstanceReplaceByRequest
   318  	case planproto.ResourceInstanceActionReason_REPLACE_BY_TRIGGERS:
   319  		ret.ActionReason = plans.ResourceInstanceReplaceByTriggers
   320  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG:
   321  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoResourceConfig
   322  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION:
   323  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseWrongRepetition
   324  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX:
   325  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseCountIndex
   326  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY:
   327  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseEachKey
   328  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE:
   329  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoModule
   330  	case planproto.ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN:
   331  		ret.ActionReason = plans.ResourceInstanceReadBecauseConfigUnknown
   332  	case planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING:
   333  		ret.ActionReason = plans.ResourceInstanceReadBecauseDependencyPending
   334  	case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MOVE_TARGET:
   335  		ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoMoveTarget
   336  	default:
   337  		return nil, fmt.Errorf("resource has invalid action reason %s", rawChange.ActionReason)
   338  	}
   339  
   340  	if len(rawChange.Private) != 0 {
   341  		ret.Private = rawChange.Private
   342  	}
   343  
   344  	return ret, nil
   345  }
   346  
   347  func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) {
   348  	if rawChange == nil {
   349  		return nil, fmt.Errorf("change object is absent")
   350  	}
   351  
   352  	ret := &plans.ChangeSrc{}
   353  
   354  	// -1 indicates that there is no index. We'll customize these below
   355  	// depending on the change action, and then decode.
   356  	beforeIdx, afterIdx := -1, -1
   357  
   358  	switch rawChange.Action {
   359  	case planproto.Action_NOOP:
   360  		ret.Action = plans.NoOp
   361  		beforeIdx = 0
   362  		afterIdx = 0
   363  	case planproto.Action_CREATE:
   364  		ret.Action = plans.Create
   365  		afterIdx = 0
   366  	case planproto.Action_READ:
   367  		ret.Action = plans.Read
   368  		beforeIdx = 0
   369  		afterIdx = 1
   370  	case planproto.Action_UPDATE:
   371  		ret.Action = plans.Update
   372  		beforeIdx = 0
   373  		afterIdx = 1
   374  	case planproto.Action_DELETE:
   375  		ret.Action = plans.Delete
   376  		beforeIdx = 0
   377  	case planproto.Action_CREATE_THEN_DELETE:
   378  		ret.Action = plans.CreateThenDelete
   379  		beforeIdx = 0
   380  		afterIdx = 1
   381  	case planproto.Action_DELETE_THEN_CREATE:
   382  		ret.Action = plans.DeleteThenCreate
   383  		beforeIdx = 0
   384  		afterIdx = 1
   385  	default:
   386  		return nil, fmt.Errorf("invalid change action %s", rawChange.Action)
   387  	}
   388  
   389  	if beforeIdx != -1 {
   390  		if l := len(rawChange.Values); l <= beforeIdx {
   391  			return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action)
   392  		}
   393  		var err error
   394  		ret.Before, err = valueFromTfplan(rawChange.Values[beforeIdx])
   395  		if err != nil {
   396  			return nil, fmt.Errorf("invalid \"before\" value: %s", err)
   397  		}
   398  		if ret.Before == nil {
   399  			return nil, fmt.Errorf("missing \"before\" value: %s", err)
   400  		}
   401  	}
   402  	if afterIdx != -1 {
   403  		if l := len(rawChange.Values); l <= afterIdx {
   404  			return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action)
   405  		}
   406  		var err error
   407  		ret.After, err = valueFromTfplan(rawChange.Values[afterIdx])
   408  		if err != nil {
   409  			return nil, fmt.Errorf("invalid \"after\" value: %s", err)
   410  		}
   411  		if ret.After == nil {
   412  			return nil, fmt.Errorf("missing \"after\" value: %s", err)
   413  		}
   414  	}
   415  
   416  	sensitive := cty.NewValueMarks(marks.Sensitive)
   417  	beforeValMarks, err := pathValueMarksFromTfplan(rawChange.BeforeSensitivePaths, sensitive)
   418  	if err != nil {
   419  		return nil, fmt.Errorf("failed to decode before sensitive paths: %s", err)
   420  	}
   421  	afterValMarks, err := pathValueMarksFromTfplan(rawChange.AfterSensitivePaths, sensitive)
   422  	if err != nil {
   423  		return nil, fmt.Errorf("failed to decode after sensitive paths: %s", err)
   424  	}
   425  	if len(beforeValMarks) > 0 {
   426  		ret.BeforeValMarks = beforeValMarks
   427  	}
   428  	if len(afterValMarks) > 0 {
   429  		ret.AfterValMarks = afterValMarks
   430  	}
   431  
   432  	return ret, nil
   433  }
   434  
   435  func valueFromTfplan(rawV *planproto.DynamicValue) (plans.DynamicValue, error) {
   436  	if len(rawV.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf
   437  		return nil, fmt.Errorf("dynamic value does not have msgpack serialization")
   438  	}
   439  
   440  	return plans.DynamicValue(rawV.Msgpack), nil
   441  }
   442  
   443  // writeTfplan serializes the given plan into the protobuf-based format used
   444  // for the "tfplan" portion of a plan file.
   445  func writeTfplan(plan *plans.Plan, w io.Writer) error {
   446  	if plan == nil {
   447  		return fmt.Errorf("cannot write plan file for nil plan")
   448  	}
   449  	if plan.Changes == nil {
   450  		return fmt.Errorf("cannot write plan file with nil changeset")
   451  	}
   452  
   453  	rawPlan := &planproto.Plan{
   454  		Version:          tfplanFormatVersion,
   455  		TerraformVersion: version.String(),
   456  
   457  		Variables:       map[string]*planproto.DynamicValue{},
   458  		OutputChanges:   []*planproto.OutputChange{},
   459  		CheckResults:    []*planproto.CheckResults{},
   460  		ResourceChanges: []*planproto.ResourceInstanceChange{},
   461  		ResourceDrift:   []*planproto.ResourceInstanceChange{},
   462  	}
   463  
   464  	switch plan.UIMode {
   465  	case plans.NormalMode:
   466  		rawPlan.UiMode = planproto.Mode_NORMAL
   467  	case plans.DestroyMode:
   468  		rawPlan.UiMode = planproto.Mode_DESTROY
   469  	case plans.RefreshOnlyMode:
   470  		rawPlan.UiMode = planproto.Mode_REFRESH_ONLY
   471  	default:
   472  		return fmt.Errorf("plan has unsupported mode %s", plan.UIMode)
   473  	}
   474  
   475  	for _, oc := range plan.Changes.Outputs {
   476  		// When serializing a plan we only retain the root outputs, since
   477  		// changes to these are externally-visible side effects (e.g. via
   478  		// terraform_remote_state).
   479  		if !oc.Addr.Module.IsRoot() {
   480  			continue
   481  		}
   482  
   483  		name := oc.Addr.OutputValue.Name
   484  
   485  		// Writing outputs as cty.DynamicPseudoType forces the stored values
   486  		// to also contain dynamic type information, so we can recover the
   487  		// original type when we read the values back in readTFPlan.
   488  		protoChange, err := changeToTfplan(&oc.ChangeSrc)
   489  		if err != nil {
   490  			return fmt.Errorf("cannot write output value %q: %s", name, err)
   491  		}
   492  
   493  		rawPlan.OutputChanges = append(rawPlan.OutputChanges, &planproto.OutputChange{
   494  			Name:      name,
   495  			Change:    protoChange,
   496  			Sensitive: oc.Sensitive,
   497  		})
   498  	}
   499  
   500  	if plan.Checks != nil {
   501  		for _, configElem := range plan.Checks.ConfigResults.Elems {
   502  			crs := configElem.Value
   503  			pcrs := &planproto.CheckResults{
   504  				ConfigAddr: configElem.Key.String(),
   505  			}
   506  			switch crs.Status {
   507  			case checks.StatusUnknown:
   508  				pcrs.Status = planproto.CheckResults_UNKNOWN
   509  			case checks.StatusPass:
   510  				pcrs.Status = planproto.CheckResults_PASS
   511  			case checks.StatusFail:
   512  				pcrs.Status = planproto.CheckResults_FAIL
   513  			case checks.StatusError:
   514  				pcrs.Status = planproto.CheckResults_ERROR
   515  			default:
   516  				return fmt.Errorf("checkable configuration %s has unsupported aggregate status %s", configElem.Key, crs.Status)
   517  			}
   518  			switch kind := configElem.Key.CheckableKind(); kind {
   519  			case addrs.CheckableResource:
   520  				pcrs.Kind = planproto.CheckResults_RESOURCE
   521  			case addrs.CheckableOutputValue:
   522  				pcrs.Kind = planproto.CheckResults_OUTPUT_VALUE
   523  			default:
   524  				return fmt.Errorf("checkable configuration %s has unsupported object type kind %s", configElem.Key, kind)
   525  			}
   526  
   527  			for _, objectElem := range configElem.Value.ObjectResults.Elems {
   528  				cr := objectElem.Value
   529  				pcr := &planproto.CheckResults_ObjectResult{
   530  					ObjectAddr:      objectElem.Key.String(),
   531  					FailureMessages: objectElem.Value.FailureMessages,
   532  				}
   533  				switch cr.Status {
   534  				case checks.StatusUnknown:
   535  					pcr.Status = planproto.CheckResults_UNKNOWN
   536  				case checks.StatusPass:
   537  					pcr.Status = planproto.CheckResults_PASS
   538  				case checks.StatusFail:
   539  					pcr.Status = planproto.CheckResults_FAIL
   540  				case checks.StatusError:
   541  					pcr.Status = planproto.CheckResults_ERROR
   542  				default:
   543  					return fmt.Errorf("checkable object %s has unsupported status %s", objectElem.Key, crs.Status)
   544  				}
   545  				pcrs.Objects = append(pcrs.Objects, pcr)
   546  			}
   547  
   548  			rawPlan.CheckResults = append(rawPlan.CheckResults, pcrs)
   549  		}
   550  	}
   551  
   552  	for _, rc := range plan.Changes.Resources {
   553  		rawRC, err := resourceChangeToTfplan(rc)
   554  		if err != nil {
   555  			return err
   556  		}
   557  		rawPlan.ResourceChanges = append(rawPlan.ResourceChanges, rawRC)
   558  	}
   559  
   560  	for _, rc := range plan.DriftedResources {
   561  		rawRC, err := resourceChangeToTfplan(rc)
   562  		if err != nil {
   563  			return err
   564  		}
   565  		rawPlan.ResourceDrift = append(rawPlan.ResourceDrift, rawRC)
   566  	}
   567  
   568  	for _, ra := range plan.RelevantAttributes {
   569  		rawRA, err := resourceAttrToTfplan(ra)
   570  		if err != nil {
   571  			return err
   572  		}
   573  		rawPlan.RelevantAttributes = append(rawPlan.RelevantAttributes, rawRA)
   574  	}
   575  
   576  	for _, targetAddr := range plan.TargetAddrs {
   577  		rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String())
   578  	}
   579  
   580  	for _, replaceAddr := range plan.ForceReplaceAddrs {
   581  		rawPlan.ForceReplaceAddrs = append(rawPlan.ForceReplaceAddrs, replaceAddr.String())
   582  	}
   583  
   584  	for name, val := range plan.VariableValues {
   585  		rawPlan.Variables[name] = valueToTfplan(val)
   586  	}
   587  
   588  	if plan.Backend.Type == "" || plan.Backend.Config == nil {
   589  		// This suggests a bug in the code that created the plan, since it
   590  		// ought to always have a backend populated, even if it's the default
   591  		// "local" backend with a local state file.
   592  		return fmt.Errorf("plan does not have a backend configuration")
   593  	}
   594  
   595  	rawPlan.Backend = &planproto.Backend{
   596  		Type:      plan.Backend.Type,
   597  		Config:    valueToTfplan(plan.Backend.Config),
   598  		Workspace: plan.Backend.Workspace,
   599  	}
   600  
   601  	src, err := proto.Marshal(rawPlan)
   602  	if err != nil {
   603  		return fmt.Errorf("serialization error: %s", err)
   604  	}
   605  
   606  	_, err = w.Write(src)
   607  	if err != nil {
   608  		return fmt.Errorf("failed to write plan to plan file: %s", err)
   609  	}
   610  
   611  	return nil
   612  }
   613  
   614  func resourceAttrToTfplan(ra globalref.ResourceAttr) (*planproto.PlanResourceAttr, error) {
   615  	res := &planproto.PlanResourceAttr{}
   616  
   617  	res.Resource = ra.Resource.String()
   618  	attr, err := pathToTfplan(ra.Attr)
   619  	if err != nil {
   620  		return res, err
   621  	}
   622  	res.Attr = attr
   623  	return res, nil
   624  }
   625  
   626  func resourceAttrFromTfplan(ra *planproto.PlanResourceAttr) (globalref.ResourceAttr, error) {
   627  	var res globalref.ResourceAttr
   628  	if ra.Resource == "" {
   629  		return res, fmt.Errorf("missing resource address from relevant attribute")
   630  	}
   631  
   632  	instAddr, diags := addrs.ParseAbsResourceInstanceStr(ra.Resource)
   633  	if diags.HasErrors() {
   634  		return res, fmt.Errorf("invalid resource instance address %q in relevant attributes: %w", ra.Resource, diags.Err())
   635  	}
   636  
   637  	res.Resource = instAddr
   638  	path, err := pathFromTfplan(ra.Attr)
   639  	if err != nil {
   640  		return res, fmt.Errorf("invalid path in %q relevant attribute: %s", res.Resource, err)
   641  	}
   642  
   643  	res.Attr = path
   644  	return res, nil
   645  }
   646  
   647  func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) {
   648  	ret := &planproto.ResourceInstanceChange{}
   649  
   650  	if change.PrevRunAddr.Resource.Resource.Type == "" {
   651  		// Suggests that an old caller wasn't yet updated to populate this
   652  		// properly. All code that generates plans should populate this field,
   653  		// even if it's just to write in the same value as in change.Addr.
   654  		change.PrevRunAddr = change.Addr
   655  	}
   656  
   657  	ret.Addr = change.Addr.String()
   658  	ret.PrevRunAddr = change.PrevRunAddr.String()
   659  	if ret.PrevRunAddr == ret.Addr {
   660  		// In the on-disk format we leave PrevRunAddr unpopulated in the common
   661  		// case where it's the same as Addr, and then fill it back in again on
   662  		// read.
   663  		ret.PrevRunAddr = ""
   664  	}
   665  
   666  	ret.DeposedKey = string(change.DeposedKey)
   667  	ret.Provider = change.ProviderAddr.String()
   668  
   669  	requiredReplace := change.RequiredReplace.List()
   670  	ret.RequiredReplace = make([]*planproto.Path, 0, len(requiredReplace))
   671  	for _, p := range requiredReplace {
   672  		path, err := pathToTfplan(p)
   673  		if err != nil {
   674  			return nil, fmt.Errorf("invalid path in required replace: %s", err)
   675  		}
   676  		ret.RequiredReplace = append(ret.RequiredReplace, path)
   677  	}
   678  
   679  	valChange, err := changeToTfplan(&change.ChangeSrc)
   680  	if err != nil {
   681  		return nil, fmt.Errorf("failed to serialize resource %s change: %s", change.Addr, err)
   682  	}
   683  	ret.Change = valChange
   684  
   685  	switch change.ActionReason {
   686  	case plans.ResourceInstanceChangeNoReason:
   687  		ret.ActionReason = planproto.ResourceInstanceActionReason_NONE
   688  	case plans.ResourceInstanceReplaceBecauseCannotUpdate:
   689  		ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE
   690  	case plans.ResourceInstanceReplaceBecauseTainted:
   691  		ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED
   692  	case plans.ResourceInstanceReplaceByRequest:
   693  		ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST
   694  	case plans.ResourceInstanceReplaceByTriggers:
   695  		ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BY_TRIGGERS
   696  	case plans.ResourceInstanceDeleteBecauseNoResourceConfig:
   697  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG
   698  	case plans.ResourceInstanceDeleteBecauseWrongRepetition:
   699  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION
   700  	case plans.ResourceInstanceDeleteBecauseCountIndex:
   701  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX
   702  	case plans.ResourceInstanceDeleteBecauseEachKey:
   703  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY
   704  	case plans.ResourceInstanceDeleteBecauseNoModule:
   705  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE
   706  	case plans.ResourceInstanceReadBecauseConfigUnknown:
   707  		ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN
   708  	case plans.ResourceInstanceReadBecauseDependencyPending:
   709  		ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING
   710  	case plans.ResourceInstanceDeleteBecauseNoMoveTarget:
   711  		ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MOVE_TARGET
   712  	default:
   713  		return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason)
   714  	}
   715  
   716  	if len(change.Private) > 0 {
   717  		ret.Private = change.Private
   718  	}
   719  
   720  	return ret, nil
   721  }
   722  
   723  func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) {
   724  	ret := &planproto.Change{}
   725  
   726  	before := valueToTfplan(change.Before)
   727  	after := valueToTfplan(change.After)
   728  
   729  	beforeSensitivePaths, err := pathValueMarksToTfplan(change.BeforeValMarks)
   730  	if err != nil {
   731  		return nil, err
   732  	}
   733  	afterSensitivePaths, err := pathValueMarksToTfplan(change.AfterValMarks)
   734  	if err != nil {
   735  		return nil, err
   736  	}
   737  	ret.BeforeSensitivePaths = beforeSensitivePaths
   738  	ret.AfterSensitivePaths = afterSensitivePaths
   739  
   740  	switch change.Action {
   741  	case plans.NoOp:
   742  		ret.Action = planproto.Action_NOOP
   743  		ret.Values = []*planproto.DynamicValue{before} // before and after should be identical
   744  	case plans.Create:
   745  		ret.Action = planproto.Action_CREATE
   746  		ret.Values = []*planproto.DynamicValue{after}
   747  	case plans.Read:
   748  		ret.Action = planproto.Action_READ
   749  		ret.Values = []*planproto.DynamicValue{before, after}
   750  	case plans.Update:
   751  		ret.Action = planproto.Action_UPDATE
   752  		ret.Values = []*planproto.DynamicValue{before, after}
   753  	case plans.Delete:
   754  		ret.Action = planproto.Action_DELETE
   755  		ret.Values = []*planproto.DynamicValue{before}
   756  	case plans.DeleteThenCreate:
   757  		ret.Action = planproto.Action_DELETE_THEN_CREATE
   758  		ret.Values = []*planproto.DynamicValue{before, after}
   759  	case plans.CreateThenDelete:
   760  		ret.Action = planproto.Action_CREATE_THEN_DELETE
   761  		ret.Values = []*planproto.DynamicValue{before, after}
   762  	default:
   763  		return nil, fmt.Errorf("invalid change action %s", change.Action)
   764  	}
   765  
   766  	return ret, nil
   767  }
   768  
   769  func valueToTfplan(val plans.DynamicValue) *planproto.DynamicValue {
   770  	if val == nil {
   771  		// protobuf can't represent nil, so we'll represent it as a
   772  		// DynamicValue that has no serializations at all.
   773  		return &planproto.DynamicValue{}
   774  	}
   775  	return &planproto.DynamicValue{
   776  		Msgpack: []byte(val),
   777  	}
   778  }
   779  
   780  func pathValueMarksFromTfplan(paths []*planproto.Path, marks cty.ValueMarks) ([]cty.PathValueMarks, error) {
   781  	ret := make([]cty.PathValueMarks, 0, len(paths))
   782  	for _, p := range paths {
   783  		path, err := pathFromTfplan(p)
   784  		if err != nil {
   785  			return nil, err
   786  		}
   787  		ret = append(ret, cty.PathValueMarks{
   788  			Path:  path,
   789  			Marks: marks,
   790  		})
   791  	}
   792  	return ret, nil
   793  }
   794  
   795  func pathValueMarksToTfplan(pvm []cty.PathValueMarks) ([]*planproto.Path, error) {
   796  	ret := make([]*planproto.Path, 0, len(pvm))
   797  	for _, p := range pvm {
   798  		path, err := pathToTfplan(p.Path)
   799  		if err != nil {
   800  			return nil, err
   801  		}
   802  		ret = append(ret, path)
   803  	}
   804  	return ret, nil
   805  }
   806  
   807  func pathFromTfplan(path *planproto.Path) (cty.Path, error) {
   808  	ret := make([]cty.PathStep, 0, len(path.Steps))
   809  	for _, step := range path.Steps {
   810  		switch s := step.Selector.(type) {
   811  		case *planproto.Path_Step_ElementKey:
   812  			dynamicVal, err := valueFromTfplan(s.ElementKey)
   813  			if err != nil {
   814  				return nil, fmt.Errorf("error decoding path index step: %s", err)
   815  			}
   816  			ty, err := dynamicVal.ImpliedType()
   817  			if err != nil {
   818  				return nil, fmt.Errorf("error determining path index type: %s", err)
   819  			}
   820  			val, err := dynamicVal.Decode(ty)
   821  			if err != nil {
   822  				return nil, fmt.Errorf("error decoding path index value: %s", err)
   823  			}
   824  			ret = append(ret, cty.IndexStep{Key: val})
   825  		case *planproto.Path_Step_AttributeName:
   826  			ret = append(ret, cty.GetAttrStep{Name: s.AttributeName})
   827  		default:
   828  			return nil, fmt.Errorf("Unsupported path step %t", step.Selector)
   829  		}
   830  	}
   831  	return ret, nil
   832  }
   833  
   834  func pathToTfplan(path cty.Path) (*planproto.Path, error) {
   835  	steps := make([]*planproto.Path_Step, 0, len(path))
   836  	for _, step := range path {
   837  		switch s := step.(type) {
   838  		case cty.IndexStep:
   839  			value, err := plans.NewDynamicValue(s.Key, s.Key.Type())
   840  			if err != nil {
   841  				return nil, fmt.Errorf("Error encoding path step: %s", err)
   842  			}
   843  			steps = append(steps, &planproto.Path_Step{
   844  				Selector: &planproto.Path_Step_ElementKey{
   845  					ElementKey: valueToTfplan(value),
   846  				},
   847  			})
   848  		case cty.GetAttrStep:
   849  			steps = append(steps, &planproto.Path_Step{
   850  				Selector: &planproto.Path_Step_AttributeName{
   851  					AttributeName: s.Name,
   852  				},
   853  			})
   854  		default:
   855  			return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step)
   856  		}
   857  	}
   858  	return &planproto.Path{
   859  		Steps: steps,
   860  	}, nil
   861  }