github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/plans/planfile/tfplan.go (about)

     1  package planfile
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans/internal/planproto"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    14  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    15  	"github.com/hashicorp/terraform-plugin-sdk/internal/version"
    16  )
    17  
    18  const tfplanFormatVersion = 3
    19  const tfplanFilename = "tfplan"
    20  
    21  // ---------------------------------------------------------------------------
    22  // This file deals with the internal structure of the "tfplan" sub-file within
    23  // the plan file format. It's all private API, wrapped by methods defined
    24  // elsewhere. This is the only file that should import the
    25  // ../internal/planproto package, which contains the ugly stubs generated
    26  // by the protobuf compiler.
    27  // ---------------------------------------------------------------------------
    28  
    29  // readTfplan reads a protobuf-encoded description from the plan portion of
    30  // a plan file, which is stored in a special file in the archive called
    31  // "tfplan".
    32  func readTfplan(r io.Reader) (*plans.Plan, error) {
    33  	src, err := ioutil.ReadAll(r)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	var rawPlan planproto.Plan
    39  	err = proto.Unmarshal(src, &rawPlan)
    40  	if err != nil {
    41  		return nil, fmt.Errorf("parse error: %s", err)
    42  	}
    43  
    44  	if rawPlan.Version != tfplanFormatVersion {
    45  		return nil, fmt.Errorf("unsupported plan file format version %d; only version %d is supported", rawPlan.Version, tfplanFormatVersion)
    46  	}
    47  
    48  	if rawPlan.TerraformVersion != version.String() {
    49  		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())
    50  	}
    51  
    52  	plan := &plans.Plan{
    53  		VariableValues: map[string]plans.DynamicValue{},
    54  		Changes: &plans.Changes{
    55  			Outputs:   []*plans.OutputChangeSrc{},
    56  			Resources: []*plans.ResourceInstanceChangeSrc{},
    57  		},
    58  
    59  		ProviderSHA256s: map[string][]byte{},
    60  	}
    61  
    62  	for _, rawOC := range rawPlan.OutputChanges {
    63  		name := rawOC.Name
    64  		change, err := changeFromTfplan(rawOC.Change)
    65  		if err != nil {
    66  			return nil, fmt.Errorf("invalid plan for output %q: %s", name, err)
    67  		}
    68  
    69  		plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{
    70  			// All output values saved in the plan file are root module outputs,
    71  			// since we don't retain others. (They can be easily recomputed
    72  			// during apply).
    73  			Addr:      addrs.OutputValue{Name: name}.Absolute(addrs.RootModuleInstance),
    74  			ChangeSrc: *change,
    75  			Sensitive: rawOC.Sensitive,
    76  		})
    77  	}
    78  
    79  	for _, rawRC := range rawPlan.ResourceChanges {
    80  		change, err := resourceChangeFromTfplan(rawRC)
    81  		if err != nil {
    82  			// errors from resourceChangeFromTfplan already include context
    83  			return nil, err
    84  		}
    85  
    86  		plan.Changes.Resources = append(plan.Changes.Resources, change)
    87  	}
    88  
    89  	for _, rawTargetAddr := range rawPlan.TargetAddrs {
    90  		target, diags := addrs.ParseTargetStr(rawTargetAddr)
    91  		if diags.HasErrors() {
    92  			return nil, fmt.Errorf("plan contains invalid target address %q: %s", target, diags.Err())
    93  		}
    94  		plan.TargetAddrs = append(plan.TargetAddrs, target.Subject)
    95  	}
    96  
    97  	for name, rawHashObj := range rawPlan.ProviderHashes {
    98  		if len(rawHashObj.Sha256) == 0 {
    99  			return nil, fmt.Errorf("no SHA256 hash for provider %q plugin", name)
   100  		}
   101  
   102  		plan.ProviderSHA256s[name] = rawHashObj.Sha256
   103  	}
   104  
   105  	for name, rawVal := range rawPlan.Variables {
   106  		val, err := valueFromTfplan(rawVal)
   107  		if err != nil {
   108  			return nil, fmt.Errorf("invalid value for input variable %q: %s", name, err)
   109  		}
   110  		plan.VariableValues[name] = val
   111  	}
   112  
   113  	if rawBackend := rawPlan.Backend; rawBackend == nil {
   114  		return nil, fmt.Errorf("plan file has no backend settings; backend settings are required")
   115  	} else {
   116  		config, err := valueFromTfplan(rawBackend.Config)
   117  		if err != nil {
   118  			return nil, fmt.Errorf("plan file has invalid backend configuration: %s", err)
   119  		}
   120  		plan.Backend = plans.Backend{
   121  			Type:      rawBackend.Type,
   122  			Config:    config,
   123  			Workspace: rawBackend.Workspace,
   124  		}
   125  	}
   126  
   127  	return plan, nil
   128  }
   129  
   130  func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*plans.ResourceInstanceChangeSrc, error) {
   131  	if rawChange == nil {
   132  		// Should never happen in practice, since protobuf can't represent
   133  		// a nil value in a list.
   134  		return nil, fmt.Errorf("resource change object is absent")
   135  	}
   136  
   137  	ret := &plans.ResourceInstanceChangeSrc{}
   138  
   139  	moduleAddr := addrs.RootModuleInstance
   140  	if rawChange.ModulePath != "" {
   141  		var diags tfdiags.Diagnostics
   142  		moduleAddr, diags = addrs.ParseModuleInstanceStr(rawChange.ModulePath)
   143  		if diags.HasErrors() {
   144  			return nil, diags.Err()
   145  		}
   146  	}
   147  
   148  	providerAddr, diags := addrs.ParseAbsProviderConfigStr(rawChange.Provider)
   149  	if diags.HasErrors() {
   150  		return nil, diags.Err()
   151  	}
   152  	ret.ProviderAddr = providerAddr
   153  
   154  	var mode addrs.ResourceMode
   155  	switch rawChange.Mode {
   156  	case planproto.ResourceInstanceChange_managed:
   157  		mode = addrs.ManagedResourceMode
   158  	case planproto.ResourceInstanceChange_data:
   159  		mode = addrs.DataResourceMode
   160  	default:
   161  		return nil, fmt.Errorf("resource has invalid mode %s", rawChange.Mode)
   162  	}
   163  
   164  	typeName := rawChange.Type
   165  	name := rawChange.Name
   166  
   167  	resAddr := addrs.Resource{
   168  		Mode: mode,
   169  		Type: typeName,
   170  		Name: name,
   171  	}
   172  
   173  	var instKey addrs.InstanceKey
   174  	switch rawTk := rawChange.InstanceKey.(type) {
   175  	case nil:
   176  	case *planproto.ResourceInstanceChange_Int:
   177  		instKey = addrs.IntKey(rawTk.Int)
   178  	case *planproto.ResourceInstanceChange_Str:
   179  		instKey = addrs.StringKey(rawTk.Str)
   180  	default:
   181  		return nil, fmt.Errorf("instance of %s has invalid key type %T", resAddr.Absolute(moduleAddr), rawChange.InstanceKey)
   182  	}
   183  
   184  	ret.Addr = resAddr.Instance(instKey).Absolute(moduleAddr)
   185  
   186  	if rawChange.DeposedKey != "" {
   187  		if len(rawChange.DeposedKey) != 8 {
   188  			return nil, fmt.Errorf("deposed object for %s has invalid deposed key %q", ret.Addr, rawChange.DeposedKey)
   189  		}
   190  		ret.DeposedKey = states.DeposedKey(rawChange.DeposedKey)
   191  	}
   192  
   193  	change, err := changeFromTfplan(rawChange.Change)
   194  	if err != nil {
   195  		return nil, fmt.Errorf("invalid plan for resource %s: %s", ret.Addr, err)
   196  	}
   197  
   198  	ret.ChangeSrc = *change
   199  
   200  	if len(rawChange.Private) != 0 {
   201  		ret.Private = rawChange.Private
   202  	}
   203  
   204  	return ret, nil
   205  }
   206  
   207  func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) {
   208  	if rawChange == nil {
   209  		return nil, fmt.Errorf("change object is absent")
   210  	}
   211  
   212  	ret := &plans.ChangeSrc{}
   213  
   214  	// -1 indicates that there is no index. We'll customize these below
   215  	// depending on the change action, and then decode.
   216  	beforeIdx, afterIdx := -1, -1
   217  
   218  	switch rawChange.Action {
   219  	case planproto.Action_NOOP:
   220  		ret.Action = plans.NoOp
   221  		beforeIdx = 0
   222  		afterIdx = 0
   223  	case planproto.Action_CREATE:
   224  		ret.Action = plans.Create
   225  		afterIdx = 0
   226  	case planproto.Action_READ:
   227  		ret.Action = plans.Read
   228  		beforeIdx = 0
   229  		afterIdx = 1
   230  	case planproto.Action_UPDATE:
   231  		ret.Action = plans.Update
   232  		beforeIdx = 0
   233  		afterIdx = 1
   234  	case planproto.Action_DELETE:
   235  		ret.Action = plans.Delete
   236  		beforeIdx = 0
   237  	case planproto.Action_CREATE_THEN_DELETE:
   238  		ret.Action = plans.CreateThenDelete
   239  		beforeIdx = 0
   240  		afterIdx = 1
   241  	case planproto.Action_DELETE_THEN_CREATE:
   242  		ret.Action = plans.DeleteThenCreate
   243  		beforeIdx = 0
   244  		afterIdx = 1
   245  	default:
   246  		return nil, fmt.Errorf("invalid change action %s", rawChange.Action)
   247  	}
   248  
   249  	if beforeIdx != -1 {
   250  		if l := len(rawChange.Values); l <= beforeIdx {
   251  			return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action)
   252  		}
   253  		var err error
   254  		ret.Before, err = valueFromTfplan(rawChange.Values[beforeIdx])
   255  		if err != nil {
   256  			return nil, fmt.Errorf("invalid \"before\" value: %s", err)
   257  		}
   258  		if ret.Before == nil {
   259  			return nil, fmt.Errorf("missing \"before\" value: %s", err)
   260  		}
   261  	}
   262  	if afterIdx != -1 {
   263  		if l := len(rawChange.Values); l <= afterIdx {
   264  			return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action)
   265  		}
   266  		var err error
   267  		ret.After, err = valueFromTfplan(rawChange.Values[afterIdx])
   268  		if err != nil {
   269  			return nil, fmt.Errorf("invalid \"after\" value: %s", err)
   270  		}
   271  		if ret.After == nil {
   272  			return nil, fmt.Errorf("missing \"after\" value: %s", err)
   273  		}
   274  	}
   275  
   276  	return ret, nil
   277  }
   278  
   279  func valueFromTfplan(rawV *planproto.DynamicValue) (plans.DynamicValue, error) {
   280  	if len(rawV.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf
   281  		return nil, fmt.Errorf("dynamic value does not have msgpack serialization")
   282  	}
   283  
   284  	return plans.DynamicValue(rawV.Msgpack), nil
   285  }
   286  
   287  // writeTfplan serializes the given plan into the protobuf-based format used
   288  // for the "tfplan" portion of a plan file.
   289  func writeTfplan(plan *plans.Plan, w io.Writer) error {
   290  	if plan == nil {
   291  		return fmt.Errorf("cannot write plan file for nil plan")
   292  	}
   293  	if plan.Changes == nil {
   294  		return fmt.Errorf("cannot write plan file with nil changeset")
   295  	}
   296  
   297  	rawPlan := &planproto.Plan{
   298  		Version:          tfplanFormatVersion,
   299  		TerraformVersion: version.String(),
   300  		ProviderHashes:   map[string]*planproto.Hash{},
   301  
   302  		Variables:       map[string]*planproto.DynamicValue{},
   303  		OutputChanges:   []*planproto.OutputChange{},
   304  		ResourceChanges: []*planproto.ResourceInstanceChange{},
   305  	}
   306  
   307  	for _, oc := range plan.Changes.Outputs {
   308  		// When serializing a plan we only retain the root outputs, since
   309  		// changes to these are externally-visible side effects (e.g. via
   310  		// terraform_remote_state).
   311  		if !oc.Addr.Module.IsRoot() {
   312  			continue
   313  		}
   314  
   315  		name := oc.Addr.OutputValue.Name
   316  
   317  		// Writing outputs as cty.DynamicPseudoType forces the stored values
   318  		// to also contain dynamic type information, so we can recover the
   319  		// original type when we read the values back in readTFPlan.
   320  		protoChange, err := changeToTfplan(&oc.ChangeSrc)
   321  		if err != nil {
   322  			return fmt.Errorf("cannot write output value %q: %s", name, err)
   323  		}
   324  
   325  		rawPlan.OutputChanges = append(rawPlan.OutputChanges, &planproto.OutputChange{
   326  			Name:      name,
   327  			Change:    protoChange,
   328  			Sensitive: oc.Sensitive,
   329  		})
   330  	}
   331  
   332  	for _, rc := range plan.Changes.Resources {
   333  		rawRC, err := resourceChangeToTfplan(rc)
   334  		if err != nil {
   335  			return err
   336  		}
   337  		rawPlan.ResourceChanges = append(rawPlan.ResourceChanges, rawRC)
   338  	}
   339  
   340  	for _, targetAddr := range plan.TargetAddrs {
   341  		rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String())
   342  	}
   343  
   344  	for name, hash := range plan.ProviderSHA256s {
   345  		rawPlan.ProviderHashes[name] = &planproto.Hash{
   346  			Sha256: hash,
   347  		}
   348  	}
   349  
   350  	for name, val := range plan.VariableValues {
   351  		rawPlan.Variables[name] = valueToTfplan(val)
   352  	}
   353  
   354  	if plan.Backend.Type == "" || plan.Backend.Config == nil {
   355  		// This suggests a bug in the code that created the plan, since it
   356  		// ought to always have a backend populated, even if it's the default
   357  		// "local" backend with a local state file.
   358  		return fmt.Errorf("plan does not have a backend configuration")
   359  	}
   360  
   361  	rawPlan.Backend = &planproto.Backend{
   362  		Type:      plan.Backend.Type,
   363  		Config:    valueToTfplan(plan.Backend.Config),
   364  		Workspace: plan.Backend.Workspace,
   365  	}
   366  
   367  	src, err := proto.Marshal(rawPlan)
   368  	if err != nil {
   369  		return fmt.Errorf("serialization error: %s", err)
   370  	}
   371  
   372  	_, err = w.Write(src)
   373  	if err != nil {
   374  		return fmt.Errorf("failed to write plan to plan file: %s", err)
   375  	}
   376  
   377  	return nil
   378  }
   379  
   380  func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) {
   381  	ret := &planproto.ResourceInstanceChange{}
   382  
   383  	ret.ModulePath = change.Addr.Module.String()
   384  
   385  	relAddr := change.Addr.Resource
   386  
   387  	switch relAddr.Resource.Mode {
   388  	case addrs.ManagedResourceMode:
   389  		ret.Mode = planproto.ResourceInstanceChange_managed
   390  	case addrs.DataResourceMode:
   391  		ret.Mode = planproto.ResourceInstanceChange_data
   392  	default:
   393  		return nil, fmt.Errorf("resource %s has unsupported mode %s", relAddr, relAddr.Resource.Mode)
   394  	}
   395  
   396  	ret.Type = relAddr.Resource.Type
   397  	ret.Name = relAddr.Resource.Name
   398  
   399  	switch tk := relAddr.Key.(type) {
   400  	case nil:
   401  		// Nothing to do, then.
   402  	case addrs.IntKey:
   403  		ret.InstanceKey = &planproto.ResourceInstanceChange_Int{
   404  			Int: int64(tk),
   405  		}
   406  	case addrs.StringKey:
   407  		ret.InstanceKey = &planproto.ResourceInstanceChange_Str{
   408  			Str: string(tk),
   409  		}
   410  	default:
   411  		return nil, fmt.Errorf("resource %s has unsupported instance key type %T", relAddr, relAddr.Key)
   412  	}
   413  
   414  	ret.DeposedKey = string(change.DeposedKey)
   415  	ret.Provider = change.ProviderAddr.String()
   416  
   417  	valChange, err := changeToTfplan(&change.ChangeSrc)
   418  	if err != nil {
   419  		return nil, fmt.Errorf("failed to serialize resource %s change: %s", relAddr, err)
   420  	}
   421  	ret.Change = valChange
   422  
   423  	if len(change.Private) > 0 {
   424  		ret.Private = change.Private
   425  	}
   426  
   427  	return ret, nil
   428  }
   429  
   430  func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) {
   431  	ret := &planproto.Change{}
   432  
   433  	before := valueToTfplan(change.Before)
   434  	after := valueToTfplan(change.After)
   435  
   436  	switch change.Action {
   437  	case plans.NoOp:
   438  		ret.Action = planproto.Action_NOOP
   439  		ret.Values = []*planproto.DynamicValue{before} // before and after should be identical
   440  	case plans.Create:
   441  		ret.Action = planproto.Action_CREATE
   442  		ret.Values = []*planproto.DynamicValue{after}
   443  	case plans.Read:
   444  		ret.Action = planproto.Action_READ
   445  		ret.Values = []*planproto.DynamicValue{before, after}
   446  	case plans.Update:
   447  		ret.Action = planproto.Action_UPDATE
   448  		ret.Values = []*planproto.DynamicValue{before, after}
   449  	case plans.Delete:
   450  		ret.Action = planproto.Action_DELETE
   451  		ret.Values = []*planproto.DynamicValue{before}
   452  	case plans.DeleteThenCreate:
   453  		ret.Action = planproto.Action_DELETE_THEN_CREATE
   454  		ret.Values = []*planproto.DynamicValue{before, after}
   455  	case plans.CreateThenDelete:
   456  		ret.Action = planproto.Action_CREATE_THEN_DELETE
   457  		ret.Values = []*planproto.DynamicValue{before, after}
   458  	default:
   459  		return nil, fmt.Errorf("invalid change action %s", change.Action)
   460  	}
   461  
   462  	return ret, nil
   463  }
   464  
   465  func valueToTfplan(val plans.DynamicValue) *planproto.DynamicValue {
   466  	if val == nil {
   467  		// protobuf can't represent nil, so we'll represent it as a
   468  		// DynamicValue that has no serializations at all.
   469  		return &planproto.DynamicValue{}
   470  	}
   471  	return &planproto.DynamicValue{
   472  		Msgpack: []byte(val),
   473  	}
   474  }