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