github.com/pulumi/terraform@v1.4.0/pkg/command/jsonplan/plan.go (about)

     1  package jsonplan
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  	ctyjson "github.com/zclconf/go-cty/cty/json"
    11  
    12  	"github.com/pulumi/terraform/pkg/addrs"
    13  	"github.com/pulumi/terraform/pkg/command/jsonchecks"
    14  	"github.com/pulumi/terraform/pkg/command/jsonconfig"
    15  	"github.com/pulumi/terraform/pkg/command/jsonstate"
    16  	"github.com/pulumi/terraform/pkg/configs"
    17  	"github.com/pulumi/terraform/pkg/plans"
    18  	"github.com/pulumi/terraform/pkg/states"
    19  	"github.com/pulumi/terraform/pkg/states/statefile"
    20  	"github.com/pulumi/terraform/pkg/terraform"
    21  	"github.com/pulumi/terraform/version"
    22  )
    23  
    24  // FormatVersion represents the version of the json format and will be
    25  // incremented for any change to this format that requires changes to a
    26  // consuming parser.
    27  const (
    28  	FormatVersion = "1.1"
    29  
    30  	ResourceInstanceReplaceBecauseCannotUpdate    = "replace_because_cannot_update"
    31  	ResourceInstanceReplaceBecauseTainted         = "replace_because_tainted"
    32  	ResourceInstanceReplaceByRequest              = "replace_by_request"
    33  	ResourceInstanceReplaceByTriggers             = "replace_by_triggers"
    34  	ResourceInstanceDeleteBecauseNoResourceConfig = "delete_because_no_resource_config"
    35  	ResourceInstanceDeleteBecauseWrongRepetition  = "delete_because_wrong_repetition"
    36  	ResourceInstanceDeleteBecauseCountIndex       = "delete_because_count_index"
    37  	ResourceInstanceDeleteBecauseEachKey          = "delete_because_each_key"
    38  	ResourceInstanceDeleteBecauseNoModule         = "delete_because_no_module"
    39  	ResourceInstanceDeleteBecauseNoMoveTarget     = "delete_because_no_move_target"
    40  	ResourceInstanceReadBecauseConfigUnknown      = "read_because_config_unknown"
    41  	ResourceInstanceReadBecauseDependencyPending  = "read_because_dependency_pending"
    42  )
    43  
    44  // Plan is the top-level representation of the json format of a plan. It includes
    45  // the complete config and current state.
    46  type plan struct {
    47  	FormatVersion    string      `json:"format_version,omitempty"`
    48  	TerraformVersion string      `json:"terraform_version,omitempty"`
    49  	Variables        variables   `json:"variables,omitempty"`
    50  	PlannedValues    stateValues `json:"planned_values,omitempty"`
    51  	// ResourceDrift and ResourceChanges are sorted in a user-friendly order
    52  	// that is undefined at this time, but consistent.
    53  	ResourceDrift      []ResourceChange  `json:"resource_drift,omitempty"`
    54  	ResourceChanges    []ResourceChange  `json:"resource_changes,omitempty"`
    55  	OutputChanges      map[string]Change `json:"output_changes,omitempty"`
    56  	PriorState         json.RawMessage   `json:"prior_state,omitempty"`
    57  	Config             json.RawMessage   `json:"configuration,omitempty"`
    58  	RelevantAttributes []ResourceAttr    `json:"relevant_attributes,omitempty"`
    59  	Checks             json.RawMessage   `json:"checks,omitempty"`
    60  }
    61  
    62  func newPlan() *plan {
    63  	return &plan{
    64  		FormatVersion: FormatVersion,
    65  	}
    66  }
    67  
    68  // ResourceAttr contains the address and attribute of an external for the
    69  // RelevantAttributes in the plan.
    70  type ResourceAttr struct {
    71  	Resource string          `json:"resource"`
    72  	Attr     json.RawMessage `json:"attribute"`
    73  }
    74  
    75  // Change is the representation of a proposed change for an object.
    76  type Change struct {
    77  	// Actions are the actions that will be taken on the object selected by the
    78  	// properties below. Valid actions values are:
    79  	//    ["no-op"]
    80  	//    ["create"]
    81  	//    ["read"]
    82  	//    ["update"]
    83  	//    ["delete", "create"]
    84  	//    ["create", "delete"]
    85  	//    ["delete"]
    86  	// The two "replace" actions are represented in this way to allow callers to
    87  	// e.g. just scan the list for "delete" to recognize all three situations
    88  	// where the object will be deleted, allowing for any new deletion
    89  	// combinations that might be added in future.
    90  	Actions []string `json:"actions,omitempty"`
    91  
    92  	// Before and After are representations of the object value both before and
    93  	// after the action. For ["create"] and ["delete"] actions, either "before"
    94  	// or "after" is unset (respectively). For ["no-op"], the before and after
    95  	// values are identical. The "after" value will be incomplete if there are
    96  	// values within it that won't be known until after apply.
    97  	Before json.RawMessage `json:"before,omitempty"`
    98  	After  json.RawMessage `json:"after,omitempty"`
    99  
   100  	// AfterUnknown is an object value with similar structure to After, but
   101  	// with all unknown leaf values replaced with true, and all known leaf
   102  	// values omitted.  This can be combined with After to reconstruct a full
   103  	// value after the action, including values which will only be known after
   104  	// apply.
   105  	AfterUnknown json.RawMessage `json:"after_unknown,omitempty"`
   106  
   107  	// BeforeSensitive and AfterSensitive are object values with similar
   108  	// structure to Before and After, but with all sensitive leaf values
   109  	// replaced with true, and all non-sensitive leaf values omitted. These
   110  	// objects should be combined with Before and After to prevent accidental
   111  	// display of sensitive values in user interfaces.
   112  	BeforeSensitive json.RawMessage `json:"before_sensitive,omitempty"`
   113  	AfterSensitive  json.RawMessage `json:"after_sensitive,omitempty"`
   114  
   115  	// ReplacePaths is an array of arrays representing a set of paths into the
   116  	// object value which resulted in the action being "replace". This will be
   117  	// omitted if the action is not replace, or if no paths caused the
   118  	// replacement (for example, if the resource was tainted). Each path
   119  	// consists of one or more steps, each of which will be a number or a
   120  	// string.
   121  	ReplacePaths json.RawMessage `json:"replace_paths,omitempty"`
   122  }
   123  
   124  type output struct {
   125  	Sensitive bool            `json:"sensitive"`
   126  	Type      json.RawMessage `json:"type,omitempty"`
   127  	Value     json.RawMessage `json:"value,omitempty"`
   128  }
   129  
   130  // variables is the JSON representation of the variables provided to the current
   131  // plan.
   132  type variables map[string]*variable
   133  
   134  type variable struct {
   135  	Value json.RawMessage `json:"value,omitempty"`
   136  }
   137  
   138  // MarshalForRenderer returns the pre-json encoding changes of the requested
   139  // plan, in a format available to the structured renderer.
   140  //
   141  // This function does a small part of the Marshal function, as it only returns
   142  // the part of the plan required by the jsonformat.Plan renderer.
   143  func MarshalForRenderer(
   144  	p *plans.Plan,
   145  	schemas *terraform.Schemas,
   146  ) (map[string]Change, []ResourceChange, []ResourceChange, []ResourceAttr, error) {
   147  	output := newPlan()
   148  
   149  	var err error
   150  	if output.OutputChanges, err = MarshalOutputChanges(p.Changes); err != nil {
   151  		return nil, nil, nil, nil, err
   152  	}
   153  
   154  	if output.ResourceChanges, err = MarshalResourceChanges(p.Changes.Resources, schemas); err != nil {
   155  		return nil, nil, nil, nil, err
   156  	}
   157  
   158  	if len(p.DriftedResources) > 0 {
   159  		// In refresh-only mode, we render all resources marked as drifted,
   160  		// including those which have moved without other changes. In other plan
   161  		// modes, move-only changes will be included in the planned changes, so
   162  		// we skip them here.
   163  		var driftedResources []*plans.ResourceInstanceChangeSrc
   164  		if p.UIMode == plans.RefreshOnlyMode {
   165  			driftedResources = p.DriftedResources
   166  		} else {
   167  			for _, dr := range p.DriftedResources {
   168  				if dr.Action != plans.NoOp {
   169  					driftedResources = append(driftedResources, dr)
   170  				}
   171  			}
   172  		}
   173  		output.ResourceDrift, err = MarshalResourceChanges(driftedResources, schemas)
   174  		if err != nil {
   175  			return nil, nil, nil, nil, err
   176  		}
   177  	}
   178  
   179  	if err := output.marshalRelevantAttrs(p); err != nil {
   180  		return nil, nil, nil, nil, err
   181  	}
   182  
   183  	return output.OutputChanges, output.ResourceChanges, output.ResourceDrift, output.RelevantAttributes, nil
   184  }
   185  
   186  // Marshal returns the json encoding of a terraform plan.
   187  func Marshal(
   188  	config *configs.Config,
   189  	p *plans.Plan,
   190  	sf *statefile.File,
   191  	schemas *terraform.Schemas,
   192  ) ([]byte, error) {
   193  	output := newPlan()
   194  	output.TerraformVersion = version.String()
   195  
   196  	err := output.marshalPlanVariables(p.VariableValues, config.Module.Variables)
   197  	if err != nil {
   198  		return nil, fmt.Errorf("error in marshalPlanVariables: %s", err)
   199  	}
   200  
   201  	// output.PlannedValues
   202  	err = output.marshalPlannedValues(p.Changes, schemas)
   203  	if err != nil {
   204  		return nil, fmt.Errorf("error in marshalPlannedValues: %s", err)
   205  	}
   206  
   207  	// output.ResourceDrift
   208  	if len(p.DriftedResources) > 0 {
   209  		// In refresh-only mode, we render all resources marked as drifted,
   210  		// including those which have moved without other changes. In other plan
   211  		// modes, move-only changes will be included in the planned changes, so
   212  		// we skip them here.
   213  		var driftedResources []*plans.ResourceInstanceChangeSrc
   214  		if p.UIMode == plans.RefreshOnlyMode {
   215  			driftedResources = p.DriftedResources
   216  		} else {
   217  			for _, dr := range p.DriftedResources {
   218  				if dr.Action != plans.NoOp {
   219  					driftedResources = append(driftedResources, dr)
   220  				}
   221  			}
   222  		}
   223  		output.ResourceDrift, err = MarshalResourceChanges(driftedResources, schemas)
   224  		if err != nil {
   225  			return nil, fmt.Errorf("error in marshaling resource drift: %s", err)
   226  		}
   227  	}
   228  
   229  	if err := output.marshalRelevantAttrs(p); err != nil {
   230  		return nil, fmt.Errorf("error marshaling relevant attributes for external changes: %s", err)
   231  	}
   232  
   233  	// output.ResourceChanges
   234  	if p.Changes != nil {
   235  		output.ResourceChanges, err = MarshalResourceChanges(p.Changes.Resources, schemas)
   236  		if err != nil {
   237  			return nil, fmt.Errorf("error in marshaling resource changes: %s", err)
   238  		}
   239  	}
   240  
   241  	// output.OutputChanges
   242  	if output.OutputChanges, err = MarshalOutputChanges(p.Changes); err != nil {
   243  		return nil, fmt.Errorf("error in marshaling output changes: %s", err)
   244  	}
   245  
   246  	// output.Checks
   247  	if p.Checks != nil && p.Checks.ConfigResults.Len() > 0 {
   248  		output.Checks = jsonchecks.MarshalCheckStates(p.Checks)
   249  	}
   250  
   251  	// output.PriorState
   252  	if sf != nil && !sf.State.Empty() {
   253  		output.PriorState, err = jsonstate.Marshal(sf, schemas)
   254  		if err != nil {
   255  			return nil, fmt.Errorf("error marshaling prior state: %s", err)
   256  		}
   257  	}
   258  
   259  	// output.Config
   260  	output.Config, err = jsonconfig.Marshal(config, schemas)
   261  	if err != nil {
   262  		return nil, fmt.Errorf("error marshaling config: %s", err)
   263  	}
   264  
   265  	ret, err := json.Marshal(output)
   266  	return ret, err
   267  }
   268  
   269  func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, decls map[string]*configs.Variable) error {
   270  	p.Variables = make(variables, len(vars))
   271  
   272  	for k, v := range vars {
   273  		val, err := v.Decode(cty.DynamicPseudoType)
   274  		if err != nil {
   275  			return err
   276  		}
   277  		valJSON, err := ctyjson.Marshal(val, val.Type())
   278  		if err != nil {
   279  			return err
   280  		}
   281  		p.Variables[k] = &variable{
   282  			Value: valJSON,
   283  		}
   284  	}
   285  
   286  	// In Terraform v1.1 and earlier we had some confusion about which subsystem
   287  	// of Terraform was the one responsible for substituting in default values
   288  	// for unset module variables, with root module variables being handled in
   289  	// three different places while child module variables were only handled
   290  	// during the Terraform Core graph walk.
   291  	//
   292  	// For Terraform v1.2 and later we rationalized that by having the Terraform
   293  	// Core graph walk always be responsible for selecting defaults regardless
   294  	// of root vs. child module, but unfortunately our earlier accidental
   295  	// misbehavior bled out into the public interface by making the defaults
   296  	// show up in the "vars" map to this function. Those are now correctly
   297  	// omitted (so that the plan file only records the variables _actually_
   298  	// set by the caller) but consumers of the JSON plan format may be depending
   299  	// on our old behavior and so we'll fake it here just in time so that
   300  	// outside consumers won't see a behavior change.
   301  	for name, decl := range decls {
   302  		if _, ok := p.Variables[name]; ok {
   303  			continue
   304  		}
   305  		if val := decl.Default; val != cty.NilVal {
   306  			valJSON, err := ctyjson.Marshal(val, val.Type())
   307  			if err != nil {
   308  				return err
   309  			}
   310  			p.Variables[name] = &variable{
   311  				Value: valJSON,
   312  			}
   313  		}
   314  	}
   315  
   316  	if len(p.Variables) == 0 {
   317  		p.Variables = nil // omit this property if there are no variables to describe
   318  	}
   319  
   320  	return nil
   321  }
   322  
   323  // MarshalResourceChanges converts the provided internal representation of
   324  // ResourceInstanceChangeSrc objects into the public structured JSON changes.
   325  //
   326  // This function is referenced directly from the structured renderer tests, to
   327  // ensure parity between the renderers. It probably shouldn't be used anywhere
   328  // else.
   329  func MarshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schemas *terraform.Schemas) ([]ResourceChange, error) {
   330  	var ret []ResourceChange
   331  
   332  	var sortedResources []*plans.ResourceInstanceChangeSrc
   333  	sortedResources = append(sortedResources, resources...)
   334  	sort.Slice(sortedResources, func(i, j int) bool {
   335  		if !sortedResources[i].Addr.Equal(sortedResources[j].Addr) {
   336  			return sortedResources[i].Addr.Less(sortedResources[j].Addr)
   337  		}
   338  		return sortedResources[i].DeposedKey < sortedResources[j].DeposedKey
   339  	})
   340  
   341  	for _, rc := range sortedResources {
   342  		var r ResourceChange
   343  		addr := rc.Addr
   344  		r.Address = addr.String()
   345  		if !addr.Equal(rc.PrevRunAddr) {
   346  			r.PreviousAddress = rc.PrevRunAddr.String()
   347  		}
   348  
   349  		dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
   350  		// We create "delete" actions for data resources so we can clean up
   351  		// their entries in state, but this is an implementation detail that
   352  		// users shouldn't see.
   353  		if dataSource && rc.Action == plans.Delete {
   354  			continue
   355  		}
   356  
   357  		schema, _ := schemas.ResourceTypeConfig(
   358  			rc.ProviderAddr.Provider,
   359  			addr.Resource.Resource.Mode,
   360  			addr.Resource.Resource.Type,
   361  		)
   362  		if schema == nil {
   363  			return nil, fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider)
   364  		}
   365  
   366  		changeV, err := rc.Decode(schema.ImpliedType())
   367  		if err != nil {
   368  			return nil, err
   369  		}
   370  		// We drop the marks from the change, as decoding is only an
   371  		// intermediate step to re-encode the values as json
   372  		changeV.Before, _ = changeV.Before.UnmarkDeep()
   373  		changeV.After, _ = changeV.After.UnmarkDeep()
   374  
   375  		var before, after []byte
   376  		var beforeSensitive, afterSensitive []byte
   377  		var afterUnknown cty.Value
   378  
   379  		if changeV.Before != cty.NilVal {
   380  			before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
   381  			if err != nil {
   382  				return nil, err
   383  			}
   384  			marks := rc.BeforeValMarks
   385  			if schema.ContainsSensitive() {
   386  				marks = append(marks, schema.ValueMarks(changeV.Before, nil)...)
   387  			}
   388  			bs := jsonstate.SensitiveAsBool(changeV.Before.MarkWithPaths(marks))
   389  			beforeSensitive, err = ctyjson.Marshal(bs, bs.Type())
   390  			if err != nil {
   391  				return nil, err
   392  			}
   393  		}
   394  		if changeV.After != cty.NilVal {
   395  			if changeV.After.IsWhollyKnown() {
   396  				after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
   397  				if err != nil {
   398  					return nil, err
   399  				}
   400  				afterUnknown = cty.EmptyObjectVal
   401  			} else {
   402  				filteredAfter := omitUnknowns(changeV.After)
   403  				if filteredAfter.IsNull() {
   404  					after = nil
   405  				} else {
   406  					after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type())
   407  					if err != nil {
   408  						return nil, err
   409  					}
   410  				}
   411  				afterUnknown = unknownAsBool(changeV.After)
   412  			}
   413  			marks := rc.AfterValMarks
   414  			if schema.ContainsSensitive() {
   415  				marks = append(marks, schema.ValueMarks(changeV.After, nil)...)
   416  			}
   417  			as := jsonstate.SensitiveAsBool(changeV.After.MarkWithPaths(marks))
   418  			afterSensitive, err = ctyjson.Marshal(as, as.Type())
   419  			if err != nil {
   420  				return nil, err
   421  			}
   422  		}
   423  
   424  		a, err := ctyjson.Marshal(afterUnknown, afterUnknown.Type())
   425  		if err != nil {
   426  			return nil, err
   427  		}
   428  		replacePaths, err := encodePaths(rc.RequiredReplace)
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  
   433  		r.Change = Change{
   434  			Actions:         actionString(rc.Action.String()),
   435  			Before:          json.RawMessage(before),
   436  			After:           json.RawMessage(after),
   437  			AfterUnknown:    a,
   438  			BeforeSensitive: json.RawMessage(beforeSensitive),
   439  			AfterSensitive:  json.RawMessage(afterSensitive),
   440  			ReplacePaths:    replacePaths,
   441  		}
   442  
   443  		if rc.DeposedKey != states.NotDeposed {
   444  			r.Deposed = rc.DeposedKey.String()
   445  		}
   446  
   447  		key := addr.Resource.Key
   448  		if key != nil {
   449  			value := key.Value()
   450  			if r.Index, err = ctyjson.Marshal(value, value.Type()); err != nil {
   451  				return nil, err
   452  			}
   453  		}
   454  
   455  		switch addr.Resource.Resource.Mode {
   456  		case addrs.ManagedResourceMode:
   457  			r.Mode = jsonstate.ManagedResourceMode
   458  		case addrs.DataResourceMode:
   459  			r.Mode = jsonstate.DataResourceMode
   460  		default:
   461  			return nil, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, addr.Resource.Resource.Mode.String())
   462  		}
   463  		r.ModuleAddress = addr.Module.String()
   464  		r.Name = addr.Resource.Resource.Name
   465  		r.Type = addr.Resource.Resource.Type
   466  		r.ProviderName = rc.ProviderAddr.Provider.String()
   467  
   468  		switch rc.ActionReason {
   469  		case plans.ResourceInstanceChangeNoReason:
   470  			r.ActionReason = "" // will be omitted in output
   471  		case plans.ResourceInstanceReplaceBecauseCannotUpdate:
   472  			r.ActionReason = ResourceInstanceReplaceBecauseCannotUpdate
   473  		case plans.ResourceInstanceReplaceBecauseTainted:
   474  			r.ActionReason = ResourceInstanceReplaceBecauseTainted
   475  		case plans.ResourceInstanceReplaceByRequest:
   476  			r.ActionReason = ResourceInstanceReplaceByRequest
   477  		case plans.ResourceInstanceReplaceByTriggers:
   478  			r.ActionReason = ResourceInstanceReplaceByTriggers
   479  		case plans.ResourceInstanceDeleteBecauseNoResourceConfig:
   480  			r.ActionReason = ResourceInstanceDeleteBecauseNoResourceConfig
   481  		case plans.ResourceInstanceDeleteBecauseWrongRepetition:
   482  			r.ActionReason = ResourceInstanceDeleteBecauseWrongRepetition
   483  		case plans.ResourceInstanceDeleteBecauseCountIndex:
   484  			r.ActionReason = ResourceInstanceDeleteBecauseCountIndex
   485  		case plans.ResourceInstanceDeleteBecauseEachKey:
   486  			r.ActionReason = ResourceInstanceDeleteBecauseEachKey
   487  		case plans.ResourceInstanceDeleteBecauseNoModule:
   488  			r.ActionReason = ResourceInstanceDeleteBecauseNoModule
   489  		case plans.ResourceInstanceDeleteBecauseNoMoveTarget:
   490  			r.ActionReason = ResourceInstanceDeleteBecauseNoMoveTarget
   491  		case plans.ResourceInstanceReadBecauseConfigUnknown:
   492  			r.ActionReason = ResourceInstanceReadBecauseConfigUnknown
   493  		case plans.ResourceInstanceReadBecauseDependencyPending:
   494  			r.ActionReason = ResourceInstanceReadBecauseDependencyPending
   495  		default:
   496  			return nil, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason)
   497  		}
   498  
   499  		ret = append(ret, r)
   500  
   501  	}
   502  
   503  	return ret, nil
   504  }
   505  
   506  // MarshalOutputChanges converts the provided internal representation of
   507  // Changes objects into the structured JSON representation.
   508  //
   509  // This function is referenced directly from the structured renderer tests, to
   510  // ensure parity between the renderers. It probably shouldn't be used anywhere
   511  // else.
   512  func MarshalOutputChanges(changes *plans.Changes) (map[string]Change, error) {
   513  	if changes == nil {
   514  		// Nothing to do!
   515  		return nil, nil
   516  	}
   517  
   518  	outputChanges := make(map[string]Change, len(changes.Outputs))
   519  	for _, oc := range changes.Outputs {
   520  
   521  		// Skip output changes that are not from the root module.
   522  		// These are automatically stripped from plans that are written to disk
   523  		// elsewhere, we just need to duplicate the logic here in case anyone
   524  		// is converting this plan directly from memory.
   525  		if !oc.Addr.Module.IsRoot() {
   526  			continue
   527  		}
   528  
   529  		changeV, err := oc.Decode()
   530  		if err != nil {
   531  			return nil, err
   532  		}
   533  		// We drop the marks from the change, as decoding is only an
   534  		// intermediate step to re-encode the values as json
   535  		changeV.Before, _ = changeV.Before.UnmarkDeep()
   536  		changeV.After, _ = changeV.After.UnmarkDeep()
   537  
   538  		var before, after []byte
   539  		var afterUnknown cty.Value
   540  
   541  		if changeV.Before != cty.NilVal {
   542  			before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
   543  			if err != nil {
   544  				return nil, err
   545  			}
   546  		}
   547  		if changeV.After != cty.NilVal {
   548  			if changeV.After.IsWhollyKnown() {
   549  				after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
   550  				if err != nil {
   551  					return nil, err
   552  				}
   553  				afterUnknown = cty.False
   554  			} else {
   555  				filteredAfter := omitUnknowns(changeV.After)
   556  				if filteredAfter.IsNull() {
   557  					after = nil
   558  				} else {
   559  					after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type())
   560  					if err != nil {
   561  						return nil, err
   562  					}
   563  				}
   564  				afterUnknown = unknownAsBool(changeV.After)
   565  			}
   566  		}
   567  
   568  		// The only information we have in the plan about output sensitivity is
   569  		// a boolean which is true if the output was or is marked sensitive. As
   570  		// a result, BeforeSensitive and AfterSensitive will be identical, and
   571  		// either false or true.
   572  		outputSensitive := cty.False
   573  		if oc.Sensitive {
   574  			outputSensitive = cty.True
   575  		}
   576  		sensitive, err := ctyjson.Marshal(outputSensitive, outputSensitive.Type())
   577  		if err != nil {
   578  			return nil, err
   579  		}
   580  
   581  		a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type())
   582  
   583  		c := Change{
   584  			Actions:         actionString(oc.Action.String()),
   585  			Before:          json.RawMessage(before),
   586  			After:           json.RawMessage(after),
   587  			AfterUnknown:    a,
   588  			BeforeSensitive: json.RawMessage(sensitive),
   589  			AfterSensitive:  json.RawMessage(sensitive),
   590  		}
   591  
   592  		outputChanges[oc.Addr.OutputValue.Name] = c
   593  	}
   594  
   595  	return outputChanges, nil
   596  }
   597  
   598  func (p *plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) error {
   599  	// marshal the planned changes into a module
   600  	plan, err := marshalPlannedValues(changes, schemas)
   601  	if err != nil {
   602  		return err
   603  	}
   604  	p.PlannedValues.RootModule = plan
   605  
   606  	// marshalPlannedOutputs
   607  	outputs, err := marshalPlannedOutputs(changes)
   608  	if err != nil {
   609  		return err
   610  	}
   611  	p.PlannedValues.Outputs = outputs
   612  
   613  	return nil
   614  }
   615  
   616  func (p *plan) marshalRelevantAttrs(plan *plans.Plan) error {
   617  	for _, ra := range plan.RelevantAttributes {
   618  		addr := ra.Resource.String()
   619  		path, err := encodePath(ra.Attr)
   620  		if err != nil {
   621  			return err
   622  		}
   623  
   624  		p.RelevantAttributes = append(p.RelevantAttributes, ResourceAttr{addr, path})
   625  	}
   626  	return nil
   627  }
   628  
   629  // omitUnknowns recursively walks the src cty.Value and returns a new cty.Value,
   630  // omitting any unknowns.
   631  //
   632  // The result also normalizes some types: all sequence types are turned into
   633  // tuple types and all mapping types are converted to object types, since we
   634  // assume the result of this is just going to be serialized as JSON (and thus
   635  // lose those distinctions) anyway.
   636  func omitUnknowns(val cty.Value) cty.Value {
   637  	ty := val.Type()
   638  	switch {
   639  	case val.IsNull():
   640  		return val
   641  	case !val.IsKnown():
   642  		return cty.NilVal
   643  	case ty.IsPrimitiveType():
   644  		return val
   645  	case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
   646  		var vals []cty.Value
   647  		it := val.ElementIterator()
   648  		for it.Next() {
   649  			_, v := it.Element()
   650  			newVal := omitUnknowns(v)
   651  			if newVal != cty.NilVal {
   652  				vals = append(vals, newVal)
   653  			} else if newVal == cty.NilVal {
   654  				// element order is how we correlate unknownness, so we must
   655  				// replace unknowns with nulls
   656  				vals = append(vals, cty.NullVal(v.Type()))
   657  			}
   658  		}
   659  		// We use tuple types always here, because the work we did above
   660  		// may have caused the individual elements to have different types,
   661  		// and we're doing this work to produce JSON anyway and JSON marshalling
   662  		// represents all of these sequence types as an array.
   663  		return cty.TupleVal(vals)
   664  	case ty.IsMapType() || ty.IsObjectType():
   665  		vals := make(map[string]cty.Value)
   666  		it := val.ElementIterator()
   667  		for it.Next() {
   668  			k, v := it.Element()
   669  			newVal := omitUnknowns(v)
   670  			if newVal != cty.NilVal {
   671  				vals[k.AsString()] = newVal
   672  			}
   673  		}
   674  		// We use object types always here, because the work we did above
   675  		// may have caused the individual elements to have different types,
   676  		// and we're doing this work to produce JSON anyway and JSON marshalling
   677  		// represents both of these mapping types as an object.
   678  		return cty.ObjectVal(vals)
   679  	default:
   680  		// Should never happen, since the above should cover all types
   681  		panic(fmt.Sprintf("omitUnknowns cannot handle %#v", val))
   682  	}
   683  }
   684  
   685  // recursively iterate through a cty.Value, replacing unknown values (including
   686  // null) with cty.True and known values with cty.False.
   687  //
   688  // The result also normalizes some types: all sequence types are turned into
   689  // tuple types and all mapping types are converted to object types, since we
   690  // assume the result of this is just going to be serialized as JSON (and thus
   691  // lose those distinctions) anyway.
   692  //
   693  // For map/object values, all known attribute values will be omitted instead of
   694  // returning false, as this results in a more compact serialization.
   695  func unknownAsBool(val cty.Value) cty.Value {
   696  	ty := val.Type()
   697  	switch {
   698  	case val.IsNull():
   699  		return cty.False
   700  	case !val.IsKnown():
   701  		if ty.IsPrimitiveType() || ty.Equals(cty.DynamicPseudoType) {
   702  			return cty.True
   703  		}
   704  		fallthrough
   705  	case ty.IsPrimitiveType():
   706  		return cty.BoolVal(!val.IsKnown())
   707  	case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
   708  		length := val.LengthInt()
   709  		if length == 0 {
   710  			// If there are no elements then we can't have unknowns
   711  			return cty.EmptyTupleVal
   712  		}
   713  		vals := make([]cty.Value, 0, length)
   714  		it := val.ElementIterator()
   715  		for it.Next() {
   716  			_, v := it.Element()
   717  			vals = append(vals, unknownAsBool(v))
   718  		}
   719  		// The above transform may have changed the types of some of the
   720  		// elements, so we'll always use a tuple here in case we've now made
   721  		// different elements have different types. Our ultimate goal is to
   722  		// marshal to JSON anyway, and all of these sequence types are
   723  		// indistinguishable in JSON.
   724  		return cty.TupleVal(vals)
   725  	case ty.IsMapType() || ty.IsObjectType():
   726  		var length int
   727  		switch {
   728  		case ty.IsMapType():
   729  			length = val.LengthInt()
   730  		default:
   731  			length = len(val.Type().AttributeTypes())
   732  		}
   733  		if length == 0 {
   734  			// If there are no elements then we can't have unknowns
   735  			return cty.EmptyObjectVal
   736  		}
   737  		vals := make(map[string]cty.Value)
   738  		it := val.ElementIterator()
   739  		for it.Next() {
   740  			k, v := it.Element()
   741  			vAsBool := unknownAsBool(v)
   742  			// Omit all of the "false"s for known values for more compact
   743  			// serialization
   744  			if !vAsBool.RawEquals(cty.False) {
   745  				vals[k.AsString()] = vAsBool
   746  			}
   747  		}
   748  		// The above transform may have changed the types of some of the
   749  		// elements, so we'll always use an object here in case we've now made
   750  		// different elements have different types. Our ultimate goal is to
   751  		// marshal to JSON anyway, and all of these mapping types are
   752  		// indistinguishable in JSON.
   753  		return cty.ObjectVal(vals)
   754  	default:
   755  		// Should never happen, since the above should cover all types
   756  		panic(fmt.Sprintf("unknownAsBool cannot handle %#v", val))
   757  	}
   758  }
   759  
   760  func actionString(action string) []string {
   761  	switch {
   762  	case action == "NoOp":
   763  		return []string{"no-op"}
   764  	case action == "Create":
   765  		return []string{"create"}
   766  	case action == "Delete":
   767  		return []string{"delete"}
   768  	case action == "Update":
   769  		return []string{"update"}
   770  	case action == "CreateThenDelete":
   771  		return []string{"create", "delete"}
   772  	case action == "Read":
   773  		return []string{"read"}
   774  	case action == "DeleteThenCreate":
   775  		return []string{"delete", "create"}
   776  	default:
   777  		return []string{action}
   778  	}
   779  }
   780  
   781  // UnmarshalActions reverses the actionString function.
   782  func UnmarshalActions(actions []string) plans.Action {
   783  	if len(actions) == 2 {
   784  		if actions[0] == "create" && actions[1] == "delete" {
   785  			return plans.CreateThenDelete
   786  		}
   787  
   788  		if actions[0] == "delete" && actions[1] == "create" {
   789  			return plans.DeleteThenCreate
   790  		}
   791  	}
   792  
   793  	if len(actions) == 1 {
   794  		switch actions[0] {
   795  		case "create":
   796  			return plans.Create
   797  		case "delete":
   798  			return plans.Delete
   799  		case "update":
   800  			return plans.Update
   801  		case "read":
   802  			return plans.Read
   803  		case "no-op":
   804  			return plans.NoOp
   805  		}
   806  	}
   807  
   808  	panic("unrecognized action slice: " + strings.Join(actions, ", "))
   809  }
   810  
   811  // encodePaths lossily encodes a cty.PathSet into an array of arrays of step
   812  // values, such as:
   813  //
   814  //	[["length"],["triggers",0,"value"]]
   815  //
   816  // The lossiness is that we cannot distinguish between an IndexStep with string
   817  // key and a GetAttr step. This is fine with JSON output, because JSON's type
   818  // system means that those two steps are equivalent anyway: both are object
   819  // indexes.
   820  //
   821  // JavaScript (or similar dynamic language) consumers of these values can
   822  // iterate over the the steps starting from the root object to reach the
   823  // value that each path is describing.
   824  func encodePaths(pathSet cty.PathSet) (json.RawMessage, error) {
   825  	if pathSet.Empty() {
   826  		return nil, nil
   827  	}
   828  
   829  	pathList := pathSet.List()
   830  	jsonPaths := make([]json.RawMessage, 0, len(pathList))
   831  
   832  	for _, path := range pathList {
   833  		jsonPath, err := encodePath(path)
   834  		if err != nil {
   835  			return nil, err
   836  		}
   837  		jsonPaths = append(jsonPaths, jsonPath)
   838  	}
   839  
   840  	return json.Marshal(jsonPaths)
   841  }
   842  
   843  func encodePath(path cty.Path) (json.RawMessage, error) {
   844  	steps := make([]json.RawMessage, 0, len(path))
   845  	for _, step := range path {
   846  		switch s := step.(type) {
   847  		case cty.IndexStep:
   848  			key, err := ctyjson.Marshal(s.Key, s.Key.Type())
   849  			if err != nil {
   850  				return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err)
   851  			}
   852  			steps = append(steps, key)
   853  		case cty.GetAttrStep:
   854  			name, err := json.Marshal(s.Name)
   855  			if err != nil {
   856  				return nil, fmt.Errorf("Failed to marshal get attr step name %#v: %s", s.Name, err)
   857  			}
   858  			steps = append(steps, name)
   859  		default:
   860  			return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step)
   861  		}
   862  	}
   863  	return json.Marshal(steps)
   864  }