github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/command/jsonplan/plan.go (about)

     1  package jsonplan
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sort"
     7  
     8  	"github.com/zclconf/go-cty/cty"
     9  	ctyjson "github.com/zclconf/go-cty/cty/json"
    10  
    11  	"github.com/muratcelep/terraform/not-internal/addrs"
    12  	"github.com/muratcelep/terraform/not-internal/command/jsonconfig"
    13  	"github.com/muratcelep/terraform/not-internal/command/jsonstate"
    14  	"github.com/muratcelep/terraform/not-internal/configs"
    15  	"github.com/muratcelep/terraform/not-internal/plans"
    16  	"github.com/muratcelep/terraform/not-internal/states"
    17  	"github.com/muratcelep/terraform/not-internal/states/statefile"
    18  	"github.com/muratcelep/terraform/not-internal/terraform"
    19  	"github.com/muratcelep/terraform/version"
    20  )
    21  
    22  // FormatVersion represents the version of the json format and will be
    23  // incremented for any change to this format that requires changes to a
    24  // consuming parser.
    25  const FormatVersion = "1.0"
    26  
    27  // Plan is the top-level representation of the json format of a plan. It includes
    28  // the complete config and current state.
    29  type plan struct {
    30  	FormatVersion    string      `json:"format_version,omitempty"`
    31  	TerraformVersion string      `json:"terraform_version,omitempty"`
    32  	Variables        variables   `json:"variables,omitempty"`
    33  	PlannedValues    stateValues `json:"planned_values,omitempty"`
    34  	// ResourceDrift and ResourceChanges are sorted in a user-friendly order
    35  	// that is undefined at this time, but consistent.
    36  	ResourceDrift   []resourceChange  `json:"resource_drift,omitempty"`
    37  	ResourceChanges []resourceChange  `json:"resource_changes,omitempty"`
    38  	OutputChanges   map[string]change `json:"output_changes,omitempty"`
    39  	PriorState      json.RawMessage   `json:"prior_state,omitempty"`
    40  	Config          json.RawMessage   `json:"configuration,omitempty"`
    41  }
    42  
    43  func newPlan() *plan {
    44  	return &plan{
    45  		FormatVersion: FormatVersion,
    46  	}
    47  }
    48  
    49  // Change is the representation of a proposed change for an object.
    50  type change struct {
    51  	// Actions are the actions that will be taken on the object selected by the
    52  	// properties below. Valid actions values are:
    53  	//    ["no-op"]
    54  	//    ["create"]
    55  	//    ["read"]
    56  	//    ["update"]
    57  	//    ["delete", "create"]
    58  	//    ["create", "delete"]
    59  	//    ["delete"]
    60  	// The two "replace" actions are represented in this way to allow callers to
    61  	// e.g. just scan the list for "delete" to recognize all three situations
    62  	// where the object will be deleted, allowing for any new deletion
    63  	// combinations that might be added in future.
    64  	Actions []string `json:"actions,omitempty"`
    65  
    66  	// Before and After are representations of the object value both before and
    67  	// after the action. For ["create"] and ["delete"] actions, either "before"
    68  	// or "after" is unset (respectively). For ["no-op"], the before and after
    69  	// values are identical. The "after" value will be incomplete if there are
    70  	// values within it that won't be known until after apply.
    71  	Before json.RawMessage `json:"before,omitempty"`
    72  	After  json.RawMessage `json:"after,omitempty"`
    73  
    74  	// AfterUnknown is an object value with similar structure to After, but
    75  	// with all unknown leaf values replaced with true, and all known leaf
    76  	// values omitted.  This can be combined with After to reconstruct a full
    77  	// value after the action, including values which will only be known after
    78  	// apply.
    79  	AfterUnknown json.RawMessage `json:"after_unknown,omitempty"`
    80  
    81  	// BeforeSensitive and AfterSensitive are object values with similar
    82  	// structure to Before and After, but with all sensitive leaf values
    83  	// replaced with true, and all non-sensitive leaf values omitted. These
    84  	// objects should be combined with Before and After to prevent accidental
    85  	// display of sensitive values in user interfaces.
    86  	BeforeSensitive json.RawMessage `json:"before_sensitive,omitempty"`
    87  	AfterSensitive  json.RawMessage `json:"after_sensitive,omitempty"`
    88  
    89  	// ReplacePaths is an array of arrays representing a set of paths into the
    90  	// object value which resulted in the action being "replace". This will be
    91  	// omitted if the action is not replace, or if no paths caused the
    92  	// replacement (for example, if the resource was tainted). Each path
    93  	// consists of one or more steps, each of which will be a number or a
    94  	// string.
    95  	ReplacePaths json.RawMessage `json:"replace_paths,omitempty"`
    96  }
    97  
    98  type output struct {
    99  	Sensitive bool            `json:"sensitive"`
   100  	Value     json.RawMessage `json:"value,omitempty"`
   101  }
   102  
   103  // variables is the JSON representation of the variables provided to the current
   104  // plan.
   105  type variables map[string]*variable
   106  
   107  type variable struct {
   108  	Value json.RawMessage `json:"value,omitempty"`
   109  }
   110  
   111  // Marshal returns the json encoding of a terraform plan.
   112  func Marshal(
   113  	config *configs.Config,
   114  	p *plans.Plan,
   115  	sf *statefile.File,
   116  	schemas *terraform.Schemas,
   117  ) ([]byte, error) {
   118  	output := newPlan()
   119  	output.TerraformVersion = version.String()
   120  
   121  	err := output.marshalPlanVariables(p.VariableValues, schemas)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("error in marshalPlanVariables: %s", err)
   124  	}
   125  
   126  	// output.PlannedValues
   127  	err = output.marshalPlannedValues(p.Changes, schemas)
   128  	if err != nil {
   129  		return nil, fmt.Errorf("error in marshalPlannedValues: %s", err)
   130  	}
   131  
   132  	// output.ResourceDrift
   133  	if len(p.DriftedResources) > 0 {
   134  		// In refresh-only mode, we render all resources marked as drifted,
   135  		// including those which have moved without other changes. In other plan
   136  		// modes, move-only changes will be included in the planned changes, so
   137  		// we skip them here.
   138  		var driftedResources []*plans.ResourceInstanceChangeSrc
   139  		if p.UIMode == plans.RefreshOnlyMode {
   140  			driftedResources = p.DriftedResources
   141  		} else {
   142  			for _, dr := range p.DriftedResources {
   143  				if dr.Action != plans.NoOp {
   144  					driftedResources = append(driftedResources, dr)
   145  				}
   146  			}
   147  		}
   148  		output.ResourceDrift, err = output.marshalResourceChanges(driftedResources, schemas)
   149  		if err != nil {
   150  			return nil, fmt.Errorf("error in marshaling resource drift: %s", err)
   151  		}
   152  	}
   153  
   154  	// output.ResourceChanges
   155  	if p.Changes != nil {
   156  		output.ResourceChanges, err = output.marshalResourceChanges(p.Changes.Resources, schemas)
   157  		if err != nil {
   158  			return nil, fmt.Errorf("error in marshaling resource changes: %s", err)
   159  		}
   160  	}
   161  
   162  	// output.OutputChanges
   163  	err = output.marshalOutputChanges(p.Changes)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("error in marshaling output changes: %s", err)
   166  	}
   167  
   168  	// output.PriorState
   169  	if sf != nil && !sf.State.Empty() {
   170  		output.PriorState, err = jsonstate.Marshal(sf, schemas)
   171  		if err != nil {
   172  			return nil, fmt.Errorf("error marshaling prior state: %s", err)
   173  		}
   174  	}
   175  
   176  	// output.Config
   177  	output.Config, err = jsonconfig.Marshal(config, schemas)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("error marshaling config: %s", err)
   180  	}
   181  
   182  	ret, err := json.Marshal(output)
   183  	return ret, err
   184  }
   185  
   186  func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, schemas *terraform.Schemas) error {
   187  	if len(vars) == 0 {
   188  		return nil
   189  	}
   190  
   191  	p.Variables = make(variables, len(vars))
   192  
   193  	for k, v := range vars {
   194  		val, err := v.Decode(cty.DynamicPseudoType)
   195  		if err != nil {
   196  			return err
   197  		}
   198  		valJSON, err := ctyjson.Marshal(val, val.Type())
   199  		if err != nil {
   200  			return err
   201  		}
   202  		p.Variables[k] = &variable{
   203  			Value: valJSON,
   204  		}
   205  	}
   206  	return nil
   207  }
   208  
   209  func (p *plan) marshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schemas *terraform.Schemas) ([]resourceChange, error) {
   210  	var ret []resourceChange
   211  
   212  	for _, rc := range resources {
   213  		var r resourceChange
   214  		addr := rc.Addr
   215  		r.Address = addr.String()
   216  		if !addr.Equal(rc.PrevRunAddr) {
   217  			r.PreviousAddress = rc.PrevRunAddr.String()
   218  		}
   219  
   220  		dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
   221  		// We create "delete" actions for data resources so we can clean up
   222  		// their entries in state, but this is an implementation detail that
   223  		// users shouldn't see.
   224  		if dataSource && rc.Action == plans.Delete {
   225  			continue
   226  		}
   227  
   228  		schema, _ := schemas.ResourceTypeConfig(
   229  			rc.ProviderAddr.Provider,
   230  			addr.Resource.Resource.Mode,
   231  			addr.Resource.Resource.Type,
   232  		)
   233  		if schema == nil {
   234  			return nil, fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider)
   235  		}
   236  
   237  		changeV, err := rc.Decode(schema.ImpliedType())
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  		// We drop the marks from the change, as decoding is only an
   242  		// intermediate step to re-encode the values as json
   243  		changeV.Before, _ = changeV.Before.UnmarkDeep()
   244  		changeV.After, _ = changeV.After.UnmarkDeep()
   245  
   246  		var before, after []byte
   247  		var beforeSensitive, afterSensitive []byte
   248  		var afterUnknown cty.Value
   249  
   250  		if changeV.Before != cty.NilVal {
   251  			before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
   252  			if err != nil {
   253  				return nil, err
   254  			}
   255  			marks := rc.BeforeValMarks
   256  			if schema.ContainsSensitive() {
   257  				marks = append(marks, schema.ValueMarks(changeV.Before, nil)...)
   258  			}
   259  			bs := jsonstate.SensitiveAsBool(changeV.Before.MarkWithPaths(marks))
   260  			beforeSensitive, err = ctyjson.Marshal(bs, bs.Type())
   261  			if err != nil {
   262  				return nil, err
   263  			}
   264  		}
   265  		if changeV.After != cty.NilVal {
   266  			if changeV.After.IsWhollyKnown() {
   267  				after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
   268  				if err != nil {
   269  					return nil, err
   270  				}
   271  				afterUnknown = cty.EmptyObjectVal
   272  			} else {
   273  				filteredAfter := omitUnknowns(changeV.After)
   274  				if filteredAfter.IsNull() {
   275  					after = nil
   276  				} else {
   277  					after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type())
   278  					if err != nil {
   279  						return nil, err
   280  					}
   281  				}
   282  				afterUnknown = unknownAsBool(changeV.After)
   283  			}
   284  			marks := rc.AfterValMarks
   285  			if schema.ContainsSensitive() {
   286  				marks = append(marks, schema.ValueMarks(changeV.After, nil)...)
   287  			}
   288  			as := jsonstate.SensitiveAsBool(changeV.After.MarkWithPaths(marks))
   289  			afterSensitive, err = ctyjson.Marshal(as, as.Type())
   290  			if err != nil {
   291  				return nil, err
   292  			}
   293  		}
   294  
   295  		a, err := ctyjson.Marshal(afterUnknown, afterUnknown.Type())
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  		replacePaths, err := encodePaths(rc.RequiredReplace)
   300  		if err != nil {
   301  			return nil, err
   302  		}
   303  
   304  		r.Change = change{
   305  			Actions:         actionString(rc.Action.String()),
   306  			Before:          json.RawMessage(before),
   307  			After:           json.RawMessage(after),
   308  			AfterUnknown:    a,
   309  			BeforeSensitive: json.RawMessage(beforeSensitive),
   310  			AfterSensitive:  json.RawMessage(afterSensitive),
   311  			ReplacePaths:    replacePaths,
   312  		}
   313  
   314  		if rc.DeposedKey != states.NotDeposed {
   315  			r.Deposed = rc.DeposedKey.String()
   316  		}
   317  
   318  		key := addr.Resource.Key
   319  		if key != nil {
   320  			r.Index = key
   321  		}
   322  
   323  		switch addr.Resource.Resource.Mode {
   324  		case addrs.ManagedResourceMode:
   325  			r.Mode = "managed"
   326  		case addrs.DataResourceMode:
   327  			r.Mode = "data"
   328  		default:
   329  			return nil, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, addr.Resource.Resource.Mode.String())
   330  		}
   331  		r.ModuleAddress = addr.Module.String()
   332  		r.Name = addr.Resource.Resource.Name
   333  		r.Type = addr.Resource.Resource.Type
   334  		r.ProviderName = rc.ProviderAddr.Provider.String()
   335  
   336  		switch rc.ActionReason {
   337  		case plans.ResourceInstanceChangeNoReason:
   338  			r.ActionReason = "" // will be omitted in output
   339  		case plans.ResourceInstanceReplaceBecauseCannotUpdate:
   340  			r.ActionReason = "replace_because_cannot_update"
   341  		case plans.ResourceInstanceReplaceBecauseTainted:
   342  			r.ActionReason = "replace_because_tainted"
   343  		case plans.ResourceInstanceReplaceByRequest:
   344  			r.ActionReason = "replace_by_request"
   345  		case plans.ResourceInstanceDeleteBecauseNoResourceConfig:
   346  			r.ActionReason = "delete_because_no_resource_config"
   347  		case plans.ResourceInstanceDeleteBecauseWrongRepetition:
   348  			r.ActionReason = "delete_because_wrong_repetition"
   349  		case plans.ResourceInstanceDeleteBecauseCountIndex:
   350  			r.ActionReason = "delete_because_count_index"
   351  		case plans.ResourceInstanceDeleteBecauseEachKey:
   352  			r.ActionReason = "delete_because_each_key"
   353  		case plans.ResourceInstanceDeleteBecauseNoModule:
   354  			r.ActionReason = "delete_because_no_module"
   355  		default:
   356  			return nil, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason)
   357  		}
   358  
   359  		ret = append(ret, r)
   360  
   361  	}
   362  
   363  	sort.Slice(ret, func(i, j int) bool {
   364  		return ret[i].Address < ret[j].Address
   365  	})
   366  
   367  	return ret, nil
   368  }
   369  
   370  func (p *plan) marshalOutputChanges(changes *plans.Changes) error {
   371  	if changes == nil {
   372  		// Nothing to do!
   373  		return nil
   374  	}
   375  
   376  	p.OutputChanges = make(map[string]change, len(changes.Outputs))
   377  	for _, oc := range changes.Outputs {
   378  		changeV, err := oc.Decode()
   379  		if err != nil {
   380  			return err
   381  		}
   382  		// We drop the marks from the change, as decoding is only an
   383  		// intermediate step to re-encode the values as json
   384  		changeV.Before, _ = changeV.Before.UnmarkDeep()
   385  		changeV.After, _ = changeV.After.UnmarkDeep()
   386  
   387  		var before, after []byte
   388  		afterUnknown := cty.False
   389  		if changeV.Before != cty.NilVal {
   390  			before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
   391  			if err != nil {
   392  				return err
   393  			}
   394  		}
   395  		if changeV.After != cty.NilVal {
   396  			if changeV.After.IsWhollyKnown() {
   397  				after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
   398  				if err != nil {
   399  					return err
   400  				}
   401  			} else {
   402  				afterUnknown = cty.True
   403  			}
   404  		}
   405  
   406  		// The only information we have in the plan about output sensitivity is
   407  		// a boolean which is true if the output was or is marked sensitive. As
   408  		// a result, BeforeSensitive and AfterSensitive will be identical, and
   409  		// either false or true.
   410  		outputSensitive := cty.False
   411  		if oc.Sensitive {
   412  			outputSensitive = cty.True
   413  		}
   414  		sensitive, err := ctyjson.Marshal(outputSensitive, outputSensitive.Type())
   415  		if err != nil {
   416  			return err
   417  		}
   418  
   419  		a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type())
   420  
   421  		c := change{
   422  			Actions:         actionString(oc.Action.String()),
   423  			Before:          json.RawMessage(before),
   424  			After:           json.RawMessage(after),
   425  			AfterUnknown:    a,
   426  			BeforeSensitive: json.RawMessage(sensitive),
   427  			AfterSensitive:  json.RawMessage(sensitive),
   428  		}
   429  
   430  		p.OutputChanges[oc.Addr.OutputValue.Name] = c
   431  	}
   432  
   433  	return nil
   434  }
   435  
   436  func (p *plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) error {
   437  	// marshal the planned changes into a module
   438  	plan, err := marshalPlannedValues(changes, schemas)
   439  	if err != nil {
   440  		return err
   441  	}
   442  	p.PlannedValues.RootModule = plan
   443  
   444  	// marshalPlannedOutputs
   445  	outputs, err := marshalPlannedOutputs(changes)
   446  	if err != nil {
   447  		return err
   448  	}
   449  	p.PlannedValues.Outputs = outputs
   450  
   451  	return nil
   452  }
   453  
   454  // omitUnknowns recursively walks the src cty.Value and returns a new cty.Value,
   455  // omitting any unknowns.
   456  //
   457  // The result also normalizes some types: all sequence types are turned into
   458  // tuple types and all mapping types are converted to object types, since we
   459  // assume the result of this is just going to be serialized as JSON (and thus
   460  // lose those distinctions) anyway.
   461  func omitUnknowns(val cty.Value) cty.Value {
   462  	ty := val.Type()
   463  	switch {
   464  	case val.IsNull():
   465  		return val
   466  	case !val.IsKnown():
   467  		return cty.NilVal
   468  	case ty.IsPrimitiveType():
   469  		return val
   470  	case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
   471  		var vals []cty.Value
   472  		it := val.ElementIterator()
   473  		for it.Next() {
   474  			_, v := it.Element()
   475  			newVal := omitUnknowns(v)
   476  			if newVal != cty.NilVal {
   477  				vals = append(vals, newVal)
   478  			} else if newVal == cty.NilVal && ty.IsListType() {
   479  				// list length may be significant, so we will turn unknowns into nulls
   480  				vals = append(vals, cty.NullVal(v.Type()))
   481  			}
   482  		}
   483  		// We use tuple types always here, because the work we did above
   484  		// may have caused the individual elements to have different types,
   485  		// and we're doing this work to produce JSON anyway and JSON marshalling
   486  		// represents all of these sequence types as an array.
   487  		return cty.TupleVal(vals)
   488  	case ty.IsMapType() || ty.IsObjectType():
   489  		vals := make(map[string]cty.Value)
   490  		it := val.ElementIterator()
   491  		for it.Next() {
   492  			k, v := it.Element()
   493  			newVal := omitUnknowns(v)
   494  			if newVal != cty.NilVal {
   495  				vals[k.AsString()] = newVal
   496  			}
   497  		}
   498  		// We use object types always here, because the work we did above
   499  		// may have caused the individual elements to have different types,
   500  		// and we're doing this work to produce JSON anyway and JSON marshalling
   501  		// represents both of these mapping types as an object.
   502  		return cty.ObjectVal(vals)
   503  	default:
   504  		// Should never happen, since the above should cover all types
   505  		panic(fmt.Sprintf("omitUnknowns cannot handle %#v", val))
   506  	}
   507  }
   508  
   509  // recursively iterate through a cty.Value, replacing unknown values (including
   510  // null) with cty.True and known values with cty.False.
   511  //
   512  // The result also normalizes some types: all sequence types are turned into
   513  // tuple types and all mapping types are converted to object types, since we
   514  // assume the result of this is just going to be serialized as JSON (and thus
   515  // lose those distinctions) anyway.
   516  //
   517  // For map/object values, all known attribute values will be omitted instead of
   518  // returning false, as this results in a more compact serialization.
   519  func unknownAsBool(val cty.Value) cty.Value {
   520  	ty := val.Type()
   521  	switch {
   522  	case val.IsNull():
   523  		return cty.False
   524  	case !val.IsKnown():
   525  		if ty.IsPrimitiveType() || ty.Equals(cty.DynamicPseudoType) {
   526  			return cty.True
   527  		}
   528  		fallthrough
   529  	case ty.IsPrimitiveType():
   530  		return cty.BoolVal(!val.IsKnown())
   531  	case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
   532  		length := val.LengthInt()
   533  		if length == 0 {
   534  			// If there are no elements then we can't have unknowns
   535  			return cty.EmptyTupleVal
   536  		}
   537  		vals := make([]cty.Value, 0, length)
   538  		it := val.ElementIterator()
   539  		for it.Next() {
   540  			_, v := it.Element()
   541  			vals = append(vals, unknownAsBool(v))
   542  		}
   543  		// The above transform may have changed the types of some of the
   544  		// elements, so we'll always use a tuple here in case we've now made
   545  		// different elements have different types. Our ultimate goal is to
   546  		// marshal to JSON anyway, and all of these sequence types are
   547  		// indistinguishable in JSON.
   548  		return cty.TupleVal(vals)
   549  	case ty.IsMapType() || ty.IsObjectType():
   550  		var length int
   551  		switch {
   552  		case ty.IsMapType():
   553  			length = val.LengthInt()
   554  		default:
   555  			length = len(val.Type().AttributeTypes())
   556  		}
   557  		if length == 0 {
   558  			// If there are no elements then we can't have unknowns
   559  			return cty.EmptyObjectVal
   560  		}
   561  		vals := make(map[string]cty.Value)
   562  		it := val.ElementIterator()
   563  		for it.Next() {
   564  			k, v := it.Element()
   565  			vAsBool := unknownAsBool(v)
   566  			// Omit all of the "false"s for known values for more compact
   567  			// serialization
   568  			if !vAsBool.RawEquals(cty.False) {
   569  				vals[k.AsString()] = unknownAsBool(v)
   570  			}
   571  		}
   572  		// The above transform may have changed the types of some of the
   573  		// elements, so we'll always use an object here in case we've now made
   574  		// different elements have different types. Our ultimate goal is to
   575  		// marshal to JSON anyway, and all of these mapping types are
   576  		// indistinguishable in JSON.
   577  		return cty.ObjectVal(vals)
   578  	default:
   579  		// Should never happen, since the above should cover all types
   580  		panic(fmt.Sprintf("unknownAsBool cannot handle %#v", val))
   581  	}
   582  }
   583  
   584  func actionString(action string) []string {
   585  	switch {
   586  	case action == "NoOp":
   587  		return []string{"no-op"}
   588  	case action == "Create":
   589  		return []string{"create"}
   590  	case action == "Delete":
   591  		return []string{"delete"}
   592  	case action == "Update":
   593  		return []string{"update"}
   594  	case action == "CreateThenDelete":
   595  		return []string{"create", "delete"}
   596  	case action == "Read":
   597  		return []string{"read"}
   598  	case action == "DeleteThenCreate":
   599  		return []string{"delete", "create"}
   600  	default:
   601  		return []string{action}
   602  	}
   603  }
   604  
   605  // encodePaths lossily encodes a cty.PathSet into an array of arrays of step
   606  // values, such as:
   607  //
   608  //   [["length"],["triggers",0,"value"]]
   609  //
   610  // The lossiness is that we cannot distinguish between an IndexStep with string
   611  // key and a GetAttr step. This is fine with JSON output, because JSON's type
   612  // system means that those two steps are equivalent anyway: both are object
   613  // indexes.
   614  //
   615  // JavaScript (or similar dynamic language) consumers of these values can
   616  // recursively apply the steps to a given object using an index operation for
   617  // each step.
   618  func encodePaths(pathSet cty.PathSet) (json.RawMessage, error) {
   619  	if pathSet.Empty() {
   620  		return nil, nil
   621  	}
   622  
   623  	pathList := pathSet.List()
   624  	jsonPaths := make([]json.RawMessage, 0, len(pathList))
   625  
   626  	for _, path := range pathList {
   627  		steps := make([]json.RawMessage, 0, len(path))
   628  		for _, step := range path {
   629  			switch s := step.(type) {
   630  			case cty.IndexStep:
   631  				key, err := ctyjson.Marshal(s.Key, s.Key.Type())
   632  				if err != nil {
   633  					return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err)
   634  				}
   635  				steps = append(steps, key)
   636  			case cty.GetAttrStep:
   637  				name, err := json.Marshal(s.Name)
   638  				if err != nil {
   639  					return nil, fmt.Errorf("Failed to marshal get attr step name %#v: %s", s.Name, err)
   640  				}
   641  				steps = append(steps, name)
   642  			default:
   643  				return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step)
   644  			}
   645  		}
   646  		jsonPath, err := json.Marshal(steps)
   647  		if err != nil {
   648  			return nil, err
   649  		}
   650  		jsonPaths = append(jsonPaths, jsonPath)
   651  	}
   652  
   653  	return json.Marshal(jsonPaths)
   654  }