github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/states/statefile/version4.go (about)

     1  package statefile
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  
     9  	version "github.com/hashicorp/go-version"
    10  	"github.com/zclconf/go-cty/cty"
    11  	ctyjson "github.com/zclconf/go-cty/cty/json"
    12  
    13  	"github.com/hashicorp/terraform/internal/addrs"
    14  	"github.com/hashicorp/terraform/internal/checks"
    15  	"github.com/hashicorp/terraform/internal/lang/marks"
    16  	"github.com/hashicorp/terraform/internal/states"
    17  	"github.com/hashicorp/terraform/internal/tfdiags"
    18  )
    19  
    20  func readStateV4(src []byte) (*File, tfdiags.Diagnostics) {
    21  	var diags tfdiags.Diagnostics
    22  	sV4 := &stateV4{}
    23  	err := json.Unmarshal(src, sV4)
    24  	if err != nil {
    25  		diags = diags.Append(jsonUnmarshalDiags(err))
    26  		return nil, diags
    27  	}
    28  
    29  	file, prepDiags := prepareStateV4(sV4)
    30  	diags = diags.Append(prepDiags)
    31  	return file, diags
    32  }
    33  
    34  func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) {
    35  	var diags tfdiags.Diagnostics
    36  
    37  	var tfVersion *version.Version
    38  	if sV4.TerraformVersion != "" {
    39  		var err error
    40  		tfVersion, err = version.NewVersion(sV4.TerraformVersion)
    41  		if err != nil {
    42  			diags = diags.Append(tfdiags.Sourceless(
    43  				tfdiags.Error,
    44  				"Invalid Terraform version string",
    45  				fmt.Sprintf("State file claims to have been written by Terraform version %q, which is not a valid version string.", sV4.TerraformVersion),
    46  			))
    47  		}
    48  	}
    49  
    50  	file := &File{
    51  		TerraformVersion: tfVersion,
    52  		Serial:           sV4.Serial,
    53  		Lineage:          sV4.Lineage,
    54  	}
    55  
    56  	state := states.NewState()
    57  
    58  	for _, rsV4 := range sV4.Resources {
    59  		rAddr := addrs.Resource{
    60  			Type: rsV4.Type,
    61  			Name: rsV4.Name,
    62  		}
    63  		switch rsV4.Mode {
    64  		case "managed":
    65  			rAddr.Mode = addrs.ManagedResourceMode
    66  		case "data":
    67  			rAddr.Mode = addrs.DataResourceMode
    68  		default:
    69  			diags = diags.Append(tfdiags.Sourceless(
    70  				tfdiags.Error,
    71  				"Invalid resource mode in state",
    72  				fmt.Sprintf("State contains a resource with mode %q (%q %q) which is not supported.", rsV4.Mode, rAddr.Type, rAddr.Name),
    73  			))
    74  			continue
    75  		}
    76  
    77  		moduleAddr := addrs.RootModuleInstance
    78  		if rsV4.Module != "" {
    79  			var addrDiags tfdiags.Diagnostics
    80  			moduleAddr, addrDiags = addrs.ParseModuleInstanceStr(rsV4.Module)
    81  			diags = diags.Append(addrDiags)
    82  			if addrDiags.HasErrors() {
    83  				continue
    84  			}
    85  		}
    86  
    87  		providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig)
    88  		diags.Append(addrDiags)
    89  		if addrDiags.HasErrors() {
    90  			// If ParseAbsProviderConfigStr returns an error, the state may have
    91  			// been written before Provider FQNs were introduced and the
    92  			// AbsProviderConfig string format will need normalization. If so,
    93  			// we treat it like a legacy provider (namespace "-") and let the
    94  			// provider installer handle detecting the FQN.
    95  			var legacyAddrDiags tfdiags.Diagnostics
    96  			providerAddr, legacyAddrDiags = addrs.ParseLegacyAbsProviderConfigStr(rsV4.ProviderConfig)
    97  			if legacyAddrDiags.HasErrors() {
    98  				continue
    99  			}
   100  		}
   101  
   102  		ms := state.EnsureModule(moduleAddr)
   103  
   104  		// Ensure the resource container object is present in the state.
   105  		ms.SetResourceProvider(rAddr, providerAddr)
   106  
   107  		for _, isV4 := range rsV4.Instances {
   108  			keyRaw := isV4.IndexKey
   109  			var key addrs.InstanceKey
   110  			switch tk := keyRaw.(type) {
   111  			case int:
   112  				key = addrs.IntKey(tk)
   113  			case float64:
   114  				// Since JSON only has one number type, reading from encoding/json
   115  				// gives us a float64 here even if the number is whole.
   116  				// float64 has a smaller integer range than int, but in practice
   117  				// we rarely have more than a few tens of instances and so
   118  				// it's unlikely that we'll exhaust the 52 bits in a float64.
   119  				key = addrs.IntKey(int(tk))
   120  			case string:
   121  				key = addrs.StringKey(tk)
   122  			default:
   123  				if keyRaw != nil {
   124  					diags = diags.Append(tfdiags.Sourceless(
   125  						tfdiags.Error,
   126  						"Invalid resource instance metadata in state",
   127  						fmt.Sprintf("Resource %s has an instance with the invalid instance key %#v.", rAddr.Absolute(moduleAddr), keyRaw),
   128  					))
   129  					continue
   130  				}
   131  				key = addrs.NoKey
   132  			}
   133  
   134  			instAddr := rAddr.Instance(key)
   135  
   136  			obj := &states.ResourceInstanceObjectSrc{
   137  				SchemaVersion:       isV4.SchemaVersion,
   138  				CreateBeforeDestroy: isV4.CreateBeforeDestroy,
   139  			}
   140  
   141  			{
   142  				// Instance attributes
   143  				switch {
   144  				case isV4.AttributesRaw != nil:
   145  					obj.AttrsJSON = isV4.AttributesRaw
   146  				case isV4.AttributesFlat != nil:
   147  					obj.AttrsFlat = isV4.AttributesFlat
   148  				default:
   149  					// This is odd, but we'll accept it and just treat the
   150  					// object has being empty. In practice this should arise
   151  					// only from the contrived sort of state objects we tend
   152  					// to hand-write inline in tests.
   153  					obj.AttrsJSON = []byte{'{', '}'}
   154  				}
   155  			}
   156  
   157  			// Sensitive paths
   158  			if isV4.AttributeSensitivePaths != nil {
   159  				paths, pathsDiags := unmarshalPaths([]byte(isV4.AttributeSensitivePaths))
   160  				diags = diags.Append(pathsDiags)
   161  				if pathsDiags.HasErrors() {
   162  					continue
   163  				}
   164  
   165  				var pvm []cty.PathValueMarks
   166  				for _, path := range paths {
   167  					pvm = append(pvm, cty.PathValueMarks{
   168  						Path:  path,
   169  						Marks: cty.NewValueMarks(marks.Sensitive),
   170  					})
   171  				}
   172  				obj.AttrSensitivePaths = pvm
   173  			}
   174  
   175  			{
   176  				// Status
   177  				raw := isV4.Status
   178  				switch raw {
   179  				case "":
   180  					obj.Status = states.ObjectReady
   181  				case "tainted":
   182  					obj.Status = states.ObjectTainted
   183  				default:
   184  					diags = diags.Append(tfdiags.Sourceless(
   185  						tfdiags.Error,
   186  						"Invalid resource instance metadata in state",
   187  						fmt.Sprintf("Instance %s has invalid status %q.", instAddr.Absolute(moduleAddr), raw),
   188  					))
   189  					continue
   190  				}
   191  			}
   192  
   193  			if raw := isV4.PrivateRaw; len(raw) > 0 {
   194  				obj.Private = raw
   195  			}
   196  
   197  			{
   198  				depsRaw := isV4.Dependencies
   199  				deps := make([]addrs.ConfigResource, 0, len(depsRaw))
   200  				for _, depRaw := range depsRaw {
   201  					addr, addrDiags := addrs.ParseAbsResourceStr(depRaw)
   202  					diags = diags.Append(addrDiags)
   203  					if addrDiags.HasErrors() {
   204  						continue
   205  					}
   206  					deps = append(deps, addr.Config())
   207  				}
   208  				obj.Dependencies = deps
   209  			}
   210  
   211  			switch {
   212  			case isV4.Deposed != "":
   213  				dk := states.DeposedKey(isV4.Deposed)
   214  				if len(dk) != 8 {
   215  					diags = diags.Append(tfdiags.Sourceless(
   216  						tfdiags.Error,
   217  						"Invalid resource instance metadata in state",
   218  						fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed),
   219  					))
   220  					continue
   221  				}
   222  				is := ms.ResourceInstance(instAddr)
   223  				if is.HasDeposed(dk) {
   224  					diags = diags.Append(tfdiags.Sourceless(
   225  						tfdiags.Error,
   226  						"Duplicate resource instance in state",
   227  						fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk),
   228  					))
   229  					continue
   230  				}
   231  
   232  				ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr)
   233  			default:
   234  				is := ms.ResourceInstance(instAddr)
   235  				if is.HasCurrent() {
   236  					diags = diags.Append(tfdiags.Sourceless(
   237  						tfdiags.Error,
   238  						"Duplicate resource instance in state",
   239  						fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)),
   240  					))
   241  					continue
   242  				}
   243  
   244  				ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr)
   245  			}
   246  		}
   247  
   248  		// We repeat this after creating the instances because
   249  		// SetResourceInstanceCurrent automatically resets this metadata based
   250  		// on the incoming objects. That behavior is useful when we're making
   251  		// piecemeal updates to the state during an apply, but when we're
   252  		// reading the state file we want to reflect its contents exactly.
   253  		ms.SetResourceProvider(rAddr, providerAddr)
   254  	}
   255  
   256  	// The root module is special in that we persist its attributes and thus
   257  	// need to reload them now. (For descendent modules we just re-calculate
   258  	// them based on the latest configuration on each run.)
   259  	{
   260  		rootModule := state.RootModule()
   261  		for name, fos := range sV4.RootOutputs {
   262  			os := &states.OutputValue{
   263  				Addr: addrs.AbsOutputValue{
   264  					OutputValue: addrs.OutputValue{
   265  						Name: name,
   266  					},
   267  				},
   268  			}
   269  			os.Sensitive = fos.Sensitive
   270  
   271  			ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw))
   272  			if err != nil {
   273  				diags = diags.Append(tfdiags.Sourceless(
   274  					tfdiags.Error,
   275  					"Invalid output value type in state",
   276  					fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err),
   277  				))
   278  				continue
   279  			}
   280  
   281  			val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty)
   282  			if err != nil {
   283  				diags = diags.Append(tfdiags.Sourceless(
   284  					tfdiags.Error,
   285  					"Invalid output value saved in state",
   286  					fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err),
   287  				))
   288  				continue
   289  			}
   290  
   291  			os.Value = val
   292  			rootModule.OutputValues[name] = os
   293  		}
   294  	}
   295  
   296  	// Saved check results from the previous run, if any.
   297  	// We differentiate absense from an empty array here so that we can
   298  	// recognize if the previous run was with a version of Terraform that
   299  	// didn't support checks yet, or if there just weren't any checkable
   300  	// objects to record, in case that's important for certain messaging.
   301  	if sV4.CheckResults != nil {
   302  		var moreDiags tfdiags.Diagnostics
   303  		state.CheckResults, moreDiags = decodeCheckResultsV4(sV4.CheckResults)
   304  		diags = diags.Append(moreDiags)
   305  	}
   306  
   307  	file.State = state
   308  	return file, diags
   309  }
   310  
   311  func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics {
   312  	// Here we'll convert back from the "File" representation to our
   313  	// stateV4 struct representation and write that.
   314  	//
   315  	// While we support legacy state formats for reading, we only support the
   316  	// latest for writing and so if a V5 is added in future then this function
   317  	// should be deleted and replaced with a writeStateV5, even though the
   318  	// read/prepare V4 functions above would stick around.
   319  
   320  	var diags tfdiags.Diagnostics
   321  	if file == nil || file.State == nil {
   322  		panic("attempt to write nil state to file")
   323  	}
   324  
   325  	var terraformVersion string
   326  	if file.TerraformVersion != nil {
   327  		terraformVersion = file.TerraformVersion.String()
   328  	}
   329  
   330  	sV4 := &stateV4{
   331  		TerraformVersion: terraformVersion,
   332  		Serial:           file.Serial,
   333  		Lineage:          file.Lineage,
   334  		RootOutputs:      map[string]outputStateV4{},
   335  		Resources:        []resourceStateV4{},
   336  	}
   337  
   338  	for name, os := range file.State.RootModule().OutputValues {
   339  		src, err := ctyjson.Marshal(os.Value, os.Value.Type())
   340  		if err != nil {
   341  			diags = diags.Append(tfdiags.Sourceless(
   342  				tfdiags.Error,
   343  				"Failed to serialize output value in state",
   344  				fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err),
   345  			))
   346  			continue
   347  		}
   348  
   349  		typeSrc, err := ctyjson.MarshalType(os.Value.Type())
   350  		if err != nil {
   351  			diags = diags.Append(tfdiags.Sourceless(
   352  				tfdiags.Error,
   353  				"Failed to serialize output value in state",
   354  				fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err),
   355  			))
   356  			continue
   357  		}
   358  
   359  		sV4.RootOutputs[name] = outputStateV4{
   360  			Sensitive:    os.Sensitive,
   361  			ValueRaw:     json.RawMessage(src),
   362  			ValueTypeRaw: json.RawMessage(typeSrc),
   363  		}
   364  	}
   365  
   366  	for _, ms := range file.State.Modules {
   367  		moduleAddr := ms.Addr
   368  		for _, rs := range ms.Resources {
   369  			resourceAddr := rs.Addr.Resource
   370  
   371  			var mode string
   372  			switch resourceAddr.Mode {
   373  			case addrs.ManagedResourceMode:
   374  				mode = "managed"
   375  			case addrs.DataResourceMode:
   376  				mode = "data"
   377  			default:
   378  				diags = diags.Append(tfdiags.Sourceless(
   379  					tfdiags.Error,
   380  					"Failed to serialize resource in state",
   381  					fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode),
   382  				))
   383  				continue
   384  			}
   385  
   386  			sV4.Resources = append(sV4.Resources, resourceStateV4{
   387  				Module:         moduleAddr.String(),
   388  				Mode:           mode,
   389  				Type:           resourceAddr.Type,
   390  				Name:           resourceAddr.Name,
   391  				ProviderConfig: rs.ProviderConfig.String(),
   392  				Instances:      []instanceObjectStateV4{},
   393  			})
   394  			rsV4 := &(sV4.Resources[len(sV4.Resources)-1])
   395  
   396  			for key, is := range rs.Instances {
   397  				if is.HasCurrent() {
   398  					var objDiags tfdiags.Diagnostics
   399  					rsV4.Instances, objDiags = appendInstanceObjectStateV4(
   400  						rs, is, key, is.Current, states.NotDeposed,
   401  						rsV4.Instances,
   402  					)
   403  					diags = diags.Append(objDiags)
   404  				}
   405  				for dk, obj := range is.Deposed {
   406  					var objDiags tfdiags.Diagnostics
   407  					rsV4.Instances, objDiags = appendInstanceObjectStateV4(
   408  						rs, is, key, obj, dk,
   409  						rsV4.Instances,
   410  					)
   411  					diags = diags.Append(objDiags)
   412  				}
   413  			}
   414  		}
   415  	}
   416  
   417  	sV4.CheckResults = encodeCheckResultsV4(file.State.CheckResults)
   418  
   419  	sV4.normalize()
   420  
   421  	src, err := json.MarshalIndent(sV4, "", "  ")
   422  	if err != nil {
   423  		// Shouldn't happen if we do our conversion to *stateV4 correctly above.
   424  		diags = diags.Append(tfdiags.Sourceless(
   425  			tfdiags.Error,
   426  			"Failed to serialize state",
   427  			fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err),
   428  		))
   429  		return diags
   430  	}
   431  	src = append(src, '\n')
   432  
   433  	_, err = w.Write(src)
   434  	if err != nil {
   435  		diags = diags.Append(tfdiags.Sourceless(
   436  			tfdiags.Error,
   437  			"Failed to write state",
   438  			fmt.Sprintf("An error occured while writing the serialized state: %s.", err),
   439  		))
   440  		return diags
   441  	}
   442  
   443  	return diags
   444  }
   445  
   446  func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) {
   447  	var diags tfdiags.Diagnostics
   448  
   449  	var status string
   450  	switch obj.Status {
   451  	case states.ObjectReady:
   452  		status = ""
   453  	case states.ObjectTainted:
   454  		status = "tainted"
   455  	default:
   456  		diags = diags.Append(tfdiags.Sourceless(
   457  			tfdiags.Error,
   458  			"Failed to serialize resource instance in state",
   459  			fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status),
   460  		))
   461  	}
   462  
   463  	var privateRaw []byte
   464  	if len(obj.Private) > 0 {
   465  		privateRaw = obj.Private
   466  	}
   467  
   468  	deps := make([]string, len(obj.Dependencies))
   469  	for i, depAddr := range obj.Dependencies {
   470  		deps[i] = depAddr.String()
   471  	}
   472  
   473  	var rawKey interface{}
   474  	switch tk := key.(type) {
   475  	case addrs.IntKey:
   476  		rawKey = int(tk)
   477  	case addrs.StringKey:
   478  		rawKey = string(tk)
   479  	default:
   480  		if key != addrs.NoKey {
   481  			diags = diags.Append(tfdiags.Sourceless(
   482  				tfdiags.Error,
   483  				"Failed to serialize resource instance in state",
   484  				fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key),
   485  			))
   486  		}
   487  	}
   488  
   489  	// Extract paths from path value marks
   490  	var paths []cty.Path
   491  	for _, vm := range obj.AttrSensitivePaths {
   492  		paths = append(paths, vm.Path)
   493  	}
   494  
   495  	// Marshal paths to JSON
   496  	attributeSensitivePaths, pathsDiags := marshalPaths(paths)
   497  	diags = diags.Append(pathsDiags)
   498  
   499  	return append(isV4s, instanceObjectStateV4{
   500  		IndexKey:                rawKey,
   501  		Deposed:                 string(deposed),
   502  		Status:                  status,
   503  		SchemaVersion:           obj.SchemaVersion,
   504  		AttributesFlat:          obj.AttrsFlat,
   505  		AttributesRaw:           obj.AttrsJSON,
   506  		AttributeSensitivePaths: attributeSensitivePaths,
   507  		PrivateRaw:              privateRaw,
   508  		Dependencies:            deps,
   509  		CreateBeforeDestroy:     obj.CreateBeforeDestroy,
   510  	}), diags
   511  }
   512  
   513  func decodeCheckResultsV4(in []checkResultsV4) (*states.CheckResults, tfdiags.Diagnostics) {
   514  	var diags tfdiags.Diagnostics
   515  
   516  	ret := &states.CheckResults{}
   517  	if len(in) == 0 {
   518  		return ret, diags
   519  	}
   520  
   521  	ret.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *states.CheckResultAggregate]()
   522  	for _, aggrIn := range in {
   523  		objectKind := decodeCheckableObjectKindV4(aggrIn.ObjectKind)
   524  		if objectKind == addrs.CheckableKindInvalid {
   525  			diags = diags.Append(fmt.Errorf("unsupported checkable object kind %q", aggrIn.ObjectKind))
   526  			continue
   527  		}
   528  
   529  		// Some trickiness here: we only have an address parser for
   530  		// addrs.Checkable and not for addrs.ConfigCheckable, but that's okay
   531  		// because once we have an addrs.Checkable we can always derive an
   532  		// addrs.ConfigCheckable from it, and a ConfigCheckable should always
   533  		// be the same syntax as a Checkable with no index information and
   534  		// thus we can reuse the same parser for both here.
   535  		configAddrProxy, moreDiags := addrs.ParseCheckableStr(objectKind, aggrIn.ConfigAddr)
   536  		diags = diags.Append(moreDiags)
   537  		if moreDiags.HasErrors() {
   538  			continue
   539  		}
   540  		configAddr := configAddrProxy.ConfigCheckable()
   541  		if configAddr.String() != configAddrProxy.String() {
   542  			// This is how we catch if the config address included index
   543  			// information that would be allowed in a Checkable but not
   544  			// in a ConfigCheckable.
   545  			diags = diags.Append(fmt.Errorf("invalid checkable config address %s", aggrIn.ConfigAddr))
   546  			continue
   547  		}
   548  
   549  		aggr := &states.CheckResultAggregate{
   550  			Status: decodeCheckStatusV4(aggrIn.Status),
   551  		}
   552  
   553  		if len(aggrIn.Objects) != 0 {
   554  			aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *states.CheckResultObject]()
   555  			for _, objectIn := range aggrIn.Objects {
   556  				objectAddr, moreDiags := addrs.ParseCheckableStr(objectKind, objectIn.ObjectAddr)
   557  				diags = diags.Append(moreDiags)
   558  				if moreDiags.HasErrors() {
   559  					continue
   560  				}
   561  
   562  				obj := &states.CheckResultObject{
   563  					Status:          decodeCheckStatusV4(objectIn.Status),
   564  					FailureMessages: objectIn.FailureMessages,
   565  				}
   566  				aggr.ObjectResults.Put(objectAddr, obj)
   567  			}
   568  		}
   569  
   570  		ret.ConfigResults.Put(configAddr, aggr)
   571  	}
   572  
   573  	return ret, diags
   574  }
   575  
   576  func encodeCheckResultsV4(in *states.CheckResults) []checkResultsV4 {
   577  	// normalize empty and nil sets in the serialized state
   578  	if in == nil || in.ConfigResults.Len() == 0 {
   579  		return nil
   580  	}
   581  
   582  	ret := make([]checkResultsV4, 0, in.ConfigResults.Len())
   583  
   584  	for _, configElem := range in.ConfigResults.Elems {
   585  		configResultsOut := checkResultsV4{
   586  			ObjectKind: encodeCheckableObjectKindV4(configElem.Key.CheckableKind()),
   587  			ConfigAddr: configElem.Key.String(),
   588  			Status:     encodeCheckStatusV4(configElem.Value.Status),
   589  		}
   590  		for _, objectElem := range configElem.Value.ObjectResults.Elems {
   591  			configResultsOut.Objects = append(configResultsOut.Objects, checkResultsObjectV4{
   592  				ObjectAddr:      objectElem.Key.String(),
   593  				Status:          encodeCheckStatusV4(objectElem.Value.Status),
   594  				FailureMessages: objectElem.Value.FailureMessages,
   595  			})
   596  		}
   597  
   598  		ret = append(ret, configResultsOut)
   599  	}
   600  
   601  	return ret
   602  }
   603  
   604  func decodeCheckStatusV4(in string) checks.Status {
   605  	switch in {
   606  	case "pass":
   607  		return checks.StatusPass
   608  	case "fail":
   609  		return checks.StatusFail
   610  	case "error":
   611  		return checks.StatusError
   612  	default:
   613  		// We'll treat anything else as unknown just as a concession to
   614  		// forward-compatible parsing, in case a later version of Terraform
   615  		// introduces a new status.
   616  		return checks.StatusUnknown
   617  	}
   618  }
   619  
   620  func encodeCheckStatusV4(in checks.Status) string {
   621  	switch in {
   622  	case checks.StatusPass:
   623  		return "pass"
   624  	case checks.StatusFail:
   625  		return "fail"
   626  	case checks.StatusError:
   627  		return "error"
   628  	case checks.StatusUnknown:
   629  		return "unknown"
   630  	default:
   631  		panic(fmt.Sprintf("unsupported check status %s", in))
   632  	}
   633  }
   634  
   635  func decodeCheckableObjectKindV4(in string) addrs.CheckableKind {
   636  	switch in {
   637  	case "resource":
   638  		return addrs.CheckableResource
   639  	case "output":
   640  		return addrs.CheckableOutputValue
   641  	default:
   642  		// We'll treat anything else as invalid just as a concession to
   643  		// forward-compatible parsing, in case a later version of Terraform
   644  		// introduces a new status.
   645  		return addrs.CheckableKindInvalid
   646  	}
   647  }
   648  
   649  func encodeCheckableObjectKindV4(in addrs.CheckableKind) string {
   650  	switch in {
   651  	case addrs.CheckableResource:
   652  		return "resource"
   653  	case addrs.CheckableOutputValue:
   654  		return "output"
   655  	default:
   656  		panic(fmt.Sprintf("unsupported checkable object kind %s", in))
   657  	}
   658  }
   659  
   660  type stateV4 struct {
   661  	Version          stateVersionV4           `json:"version"`
   662  	TerraformVersion string                   `json:"terraform_version"`
   663  	Serial           uint64                   `json:"serial"`
   664  	Lineage          string                   `json:"lineage"`
   665  	RootOutputs      map[string]outputStateV4 `json:"outputs"`
   666  	Resources        []resourceStateV4        `json:"resources"`
   667  	CheckResults     []checkResultsV4         `json:"check_results"`
   668  }
   669  
   670  // normalize makes some in-place changes to normalize the way items are
   671  // stored to ensure that two functionally-equivalent states will be stored
   672  // identically.
   673  func (s *stateV4) normalize() {
   674  	sort.Stable(sortResourcesV4(s.Resources))
   675  	for _, rs := range s.Resources {
   676  		sort.Stable(sortInstancesV4(rs.Instances))
   677  	}
   678  }
   679  
   680  type outputStateV4 struct {
   681  	ValueRaw     json.RawMessage `json:"value"`
   682  	ValueTypeRaw json.RawMessage `json:"type"`
   683  	Sensitive    bool            `json:"sensitive,omitempty"`
   684  }
   685  
   686  type resourceStateV4 struct {
   687  	Module         string                  `json:"module,omitempty"`
   688  	Mode           string                  `json:"mode"`
   689  	Type           string                  `json:"type"`
   690  	Name           string                  `json:"name"`
   691  	EachMode       string                  `json:"each,omitempty"`
   692  	ProviderConfig string                  `json:"provider"`
   693  	Instances      []instanceObjectStateV4 `json:"instances"`
   694  }
   695  
   696  type instanceObjectStateV4 struct {
   697  	IndexKey interface{} `json:"index_key,omitempty"`
   698  	Status   string      `json:"status,omitempty"`
   699  	Deposed  string      `json:"deposed,omitempty"`
   700  
   701  	SchemaVersion           uint64            `json:"schema_version"`
   702  	AttributesRaw           json.RawMessage   `json:"attributes,omitempty"`
   703  	AttributesFlat          map[string]string `json:"attributes_flat,omitempty"`
   704  	AttributeSensitivePaths json.RawMessage   `json:"sensitive_attributes,omitempty"`
   705  
   706  	PrivateRaw []byte `json:"private,omitempty"`
   707  
   708  	Dependencies []string `json:"dependencies,omitempty"`
   709  
   710  	CreateBeforeDestroy bool `json:"create_before_destroy,omitempty"`
   711  }
   712  
   713  type checkResultsV4 struct {
   714  	ObjectKind string                 `json:"object_kind"`
   715  	ConfigAddr string                 `json:"config_addr"`
   716  	Status     string                 `json:"status"`
   717  	Objects    []checkResultsObjectV4 `json:"objects"`
   718  }
   719  
   720  type checkResultsObjectV4 struct {
   721  	ObjectAddr      string   `json:"object_addr"`
   722  	Status          string   `json:"status"`
   723  	FailureMessages []string `json:"failure_messages,omitempty"`
   724  }
   725  
   726  // stateVersionV4 is a weird special type we use to produce our hard-coded
   727  // "version": 4 in the JSON serialization.
   728  type stateVersionV4 struct{}
   729  
   730  func (sv stateVersionV4) MarshalJSON() ([]byte, error) {
   731  	return []byte{'4'}, nil
   732  }
   733  
   734  func (sv stateVersionV4) UnmarshalJSON([]byte) error {
   735  	// Nothing to do: we already know we're version 4
   736  	return nil
   737  }
   738  
   739  type sortResourcesV4 []resourceStateV4
   740  
   741  func (sr sortResourcesV4) Len() int      { return len(sr) }
   742  func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] }
   743  func (sr sortResourcesV4) Less(i, j int) bool {
   744  	switch {
   745  	case sr[i].Module != sr[j].Module:
   746  		return sr[i].Module < sr[j].Module
   747  	case sr[i].Mode != sr[j].Mode:
   748  		return sr[i].Mode < sr[j].Mode
   749  	case sr[i].Type != sr[j].Type:
   750  		return sr[i].Type < sr[j].Type
   751  	case sr[i].Name != sr[j].Name:
   752  		return sr[i].Name < sr[j].Name
   753  	default:
   754  		return false
   755  	}
   756  }
   757  
   758  type sortInstancesV4 []instanceObjectStateV4
   759  
   760  func (si sortInstancesV4) Len() int      { return len(si) }
   761  func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] }
   762  func (si sortInstancesV4) Less(i, j int) bool {
   763  	ki := si[i].IndexKey
   764  	kj := si[j].IndexKey
   765  	if ki != kj {
   766  		if (ki == nil) != (kj == nil) {
   767  			return ki == nil
   768  		}
   769  		if kii, isInt := ki.(int); isInt {
   770  			if kji, isInt := kj.(int); isInt {
   771  				return kii < kji
   772  			}
   773  			return true
   774  		}
   775  		if kis, isStr := ki.(string); isStr {
   776  			if kjs, isStr := kj.(string); isStr {
   777  				return kis < kjs
   778  			}
   779  			return true
   780  		}
   781  	}
   782  	if si[i].Deposed != si[j].Deposed {
   783  		return si[i].Deposed < si[j].Deposed
   784  	}
   785  	return false
   786  }
   787  
   788  // pathStep is an intermediate representation of a cty.PathStep to facilitate
   789  // consistent JSON serialization. The Value field can either be a cty.Value of
   790  // dynamic type (for index steps), or a string (for get attr steps).
   791  type pathStep struct {
   792  	Type  string          `json:"type"`
   793  	Value json.RawMessage `json:"value"`
   794  }
   795  
   796  const (
   797  	indexPathStepType   = "index"
   798  	getAttrPathStepType = "get_attr"
   799  )
   800  
   801  func unmarshalPaths(buf []byte) ([]cty.Path, tfdiags.Diagnostics) {
   802  	var diags tfdiags.Diagnostics
   803  	var jsonPaths [][]pathStep
   804  
   805  	err := json.Unmarshal(buf, &jsonPaths)
   806  	if err != nil {
   807  		diags = diags.Append(tfdiags.Sourceless(
   808  			tfdiags.Error,
   809  			"Error unmarshaling path steps",
   810  			err.Error(),
   811  		))
   812  	}
   813  
   814  	paths := make([]cty.Path, 0, len(jsonPaths))
   815  
   816  unmarshalOuter:
   817  	for _, jsonPath := range jsonPaths {
   818  		var path cty.Path
   819  		for _, jsonStep := range jsonPath {
   820  			switch jsonStep.Type {
   821  			case indexPathStepType:
   822  				key, err := ctyjson.Unmarshal(jsonStep.Value, cty.DynamicPseudoType)
   823  				if err != nil {
   824  					diags = diags.Append(tfdiags.Sourceless(
   825  						tfdiags.Error,
   826  						"Error unmarshaling path step",
   827  						fmt.Sprintf("Failed to unmarshal index step key: %s", err),
   828  					))
   829  					continue unmarshalOuter
   830  				}
   831  				path = append(path, cty.IndexStep{Key: key})
   832  			case getAttrPathStepType:
   833  				var name string
   834  				if err := json.Unmarshal(jsonStep.Value, &name); err != nil {
   835  					diags = diags.Append(tfdiags.Sourceless(
   836  						tfdiags.Error,
   837  						"Error unmarshaling path step",
   838  						fmt.Sprintf("Failed to unmarshal get attr step name: %s", err),
   839  					))
   840  					continue unmarshalOuter
   841  				}
   842  				path = append(path, cty.GetAttrStep{Name: name})
   843  			default:
   844  				diags = diags.Append(tfdiags.Sourceless(
   845  					tfdiags.Error,
   846  					"Unsupported path step",
   847  					fmt.Sprintf("Unsupported path step type %q", jsonStep.Type),
   848  				))
   849  				continue unmarshalOuter
   850  			}
   851  		}
   852  		paths = append(paths, path)
   853  	}
   854  
   855  	return paths, diags
   856  }
   857  
   858  func marshalPaths(paths []cty.Path) ([]byte, tfdiags.Diagnostics) {
   859  	var diags tfdiags.Diagnostics
   860  
   861  	// cty.Path is a slice of cty.PathSteps, so our representation of a slice
   862  	// of paths is a nested slice of our intermediate pathStep struct
   863  	jsonPaths := make([][]pathStep, 0, len(paths))
   864  
   865  marshalOuter:
   866  	for _, path := range paths {
   867  		jsonPath := make([]pathStep, 0, len(path))
   868  		for _, step := range path {
   869  			var jsonStep pathStep
   870  			switch s := step.(type) {
   871  			case cty.IndexStep:
   872  				key, err := ctyjson.Marshal(s.Key, cty.DynamicPseudoType)
   873  				if err != nil {
   874  					diags = diags.Append(tfdiags.Sourceless(
   875  						tfdiags.Error,
   876  						"Error marshaling path step",
   877  						fmt.Sprintf("Failed to marshal index step key %#v: %s", s.Key, err),
   878  					))
   879  					continue marshalOuter
   880  				}
   881  				jsonStep.Type = indexPathStepType
   882  				jsonStep.Value = key
   883  			case cty.GetAttrStep:
   884  				name, err := json.Marshal(s.Name)
   885  				if err != nil {
   886  					diags = diags.Append(tfdiags.Sourceless(
   887  						tfdiags.Error,
   888  						"Error marshaling path step",
   889  						fmt.Sprintf("Failed to marshal get attr step name %s: %s", s.Name, err),
   890  					))
   891  					continue marshalOuter
   892  				}
   893  				jsonStep.Type = getAttrPathStepType
   894  				jsonStep.Value = name
   895  			default:
   896  				diags = diags.Append(tfdiags.Sourceless(
   897  					tfdiags.Error,
   898  					"Unsupported path step",
   899  					fmt.Sprintf("Unsupported path step %#v (%t)", step, step),
   900  				))
   901  				continue marshalOuter
   902  			}
   903  			jsonPath = append(jsonPath, jsonStep)
   904  		}
   905  		jsonPaths = append(jsonPaths, jsonPath)
   906  	}
   907  
   908  	buf, err := json.Marshal(jsonPaths)
   909  	if err != nil {
   910  		diags = diags.Append(tfdiags.Sourceless(
   911  			tfdiags.Error,
   912  			"Error marshaling path steps",
   913  			fmt.Sprintf("Failed to marshal path steps: %s", err),
   914  		))
   915  	}
   916  
   917  	return buf, diags
   918  }