github.com/opentofu/opentofu@v1.7.1/internal/command/jsonplan/plan.go (about)

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