github.com/hashicorp/terraform-plugin-sdk@v1.17.2/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  	ctyjson "github.com/zclconf/go-cty/cty/json"
    11  
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    14  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    15  )
    16  
    17  func readStateV4(src []byte) (*File, tfdiags.Diagnostics) {
    18  	var diags tfdiags.Diagnostics
    19  	sV4 := &stateV4{}
    20  	err := json.Unmarshal(src, sV4)
    21  	if err != nil {
    22  		diags = diags.Append(jsonUnmarshalDiags(err))
    23  		return nil, diags
    24  	}
    25  
    26  	file, prepDiags := prepareStateV4(sV4)
    27  	diags = diags.Append(prepDiags)
    28  	return file, diags
    29  }
    30  
    31  func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) {
    32  	var diags tfdiags.Diagnostics
    33  
    34  	var tfVersion *version.Version
    35  	if sV4.TerraformVersion != "" {
    36  		var err error
    37  		tfVersion, err = version.NewVersion(sV4.TerraformVersion)
    38  		if err != nil {
    39  			diags = diags.Append(tfdiags.Sourceless(
    40  				tfdiags.Error,
    41  				"Invalid Terraform version string",
    42  				fmt.Sprintf("State file claims to have been written by Terraform version %q, which is not a valid version string.", sV4.TerraformVersion),
    43  			))
    44  		}
    45  	}
    46  
    47  	file := &File{
    48  		TerraformVersion: tfVersion,
    49  		Serial:           sV4.Serial,
    50  		Lineage:          sV4.Lineage,
    51  	}
    52  
    53  	state := states.NewState()
    54  
    55  	for _, rsV4 := range sV4.Resources {
    56  		rAddr := addrs.Resource{
    57  			Type: rsV4.Type,
    58  			Name: rsV4.Name,
    59  		}
    60  		switch rsV4.Mode {
    61  		case "managed":
    62  			rAddr.Mode = addrs.ManagedResourceMode
    63  		case "data":
    64  			rAddr.Mode = addrs.DataResourceMode
    65  		default:
    66  			diags = diags.Append(tfdiags.Sourceless(
    67  				tfdiags.Error,
    68  				"Invalid resource mode in state",
    69  				fmt.Sprintf("State contains a resource with mode %q (%q %q) which is not supported.", rsV4.Mode, rAddr.Type, rAddr.Name),
    70  			))
    71  			continue
    72  		}
    73  
    74  		moduleAddr := addrs.RootModuleInstance
    75  		if rsV4.Module != "" {
    76  			var addrDiags tfdiags.Diagnostics
    77  			moduleAddr, addrDiags = addrs.ParseModuleInstanceStr(rsV4.Module)
    78  			diags = diags.Append(addrDiags)
    79  			if addrDiags.HasErrors() {
    80  				continue
    81  			}
    82  		}
    83  
    84  		providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig)
    85  		diags.Append(addrDiags)
    86  		if addrDiags.HasErrors() {
    87  			continue
    88  		}
    89  
    90  		var eachMode states.EachMode
    91  		switch rsV4.EachMode {
    92  		case "":
    93  			eachMode = states.NoEach
    94  		case "list":
    95  			eachMode = states.EachList
    96  		case "map":
    97  			eachMode = states.EachMap
    98  		default:
    99  			diags = diags.Append(tfdiags.Sourceless(
   100  				tfdiags.Error,
   101  				"Invalid resource metadata in state",
   102  				fmt.Sprintf("Resource %s has invalid \"each\" value %q in state.", rAddr.Absolute(moduleAddr), eachMode),
   103  			))
   104  			continue
   105  		}
   106  
   107  		ms := state.EnsureModule(moduleAddr)
   108  
   109  		// Ensure the resource container object is present in the state.
   110  		ms.SetResourceMeta(rAddr, eachMode, providerAddr)
   111  
   112  		for _, isV4 := range rsV4.Instances {
   113  			keyRaw := isV4.IndexKey
   114  			var key addrs.InstanceKey
   115  			switch tk := keyRaw.(type) {
   116  			case int:
   117  				key = addrs.IntKey(tk)
   118  			case float64:
   119  				// Since JSON only has one number type, reading from encoding/json
   120  				// gives us a float64 here even if the number is whole.
   121  				// float64 has a smaller integer range than int, but in practice
   122  				// we rarely have more than a few tens of instances and so
   123  				// it's unlikely that we'll exhaust the 52 bits in a float64.
   124  				key = addrs.IntKey(int(tk))
   125  			case string:
   126  				key = addrs.StringKey(tk)
   127  			default:
   128  				if keyRaw != nil {
   129  					diags = diags.Append(tfdiags.Sourceless(
   130  						tfdiags.Error,
   131  						"Invalid resource instance metadata in state",
   132  						fmt.Sprintf("Resource %s has an instance with the invalid instance key %#v.", rAddr.Absolute(moduleAddr), keyRaw),
   133  					))
   134  					continue
   135  				}
   136  				key = addrs.NoKey
   137  			}
   138  
   139  			instAddr := rAddr.Instance(key)
   140  
   141  			obj := &states.ResourceInstanceObjectSrc{
   142  				SchemaVersion: isV4.SchemaVersion,
   143  			}
   144  
   145  			{
   146  				// Instance attributes
   147  				switch {
   148  				case isV4.AttributesRaw != nil:
   149  					obj.AttrsJSON = isV4.AttributesRaw
   150  				case isV4.AttributesFlat != nil:
   151  					obj.AttrsFlat = isV4.AttributesFlat
   152  				default:
   153  					// This is odd, but we'll accept it and just treat the
   154  					// object has being empty. In practice this should arise
   155  					// only from the contrived sort of state objects we tend
   156  					// to hand-write inline in tests.
   157  					obj.AttrsJSON = []byte{'{', '}'}
   158  				}
   159  			}
   160  
   161  			{
   162  				// Status
   163  				raw := isV4.Status
   164  				switch raw {
   165  				case "":
   166  					obj.Status = states.ObjectReady
   167  				case "tainted":
   168  					obj.Status = states.ObjectTainted
   169  				default:
   170  					diags = diags.Append(tfdiags.Sourceless(
   171  						tfdiags.Error,
   172  						"Invalid resource instance metadata in state",
   173  						fmt.Sprintf("Instance %s has invalid status %q.", instAddr.Absolute(moduleAddr), raw),
   174  					))
   175  					continue
   176  				}
   177  			}
   178  
   179  			if raw := isV4.PrivateRaw; len(raw) > 0 {
   180  				obj.Private = raw
   181  			}
   182  
   183  			{
   184  				depsRaw := isV4.Dependencies
   185  				deps := make([]addrs.Referenceable, 0, len(depsRaw))
   186  				for _, depRaw := range depsRaw {
   187  					ref, refDiags := addrs.ParseRefStr(depRaw)
   188  					diags = diags.Append(refDiags)
   189  					if refDiags.HasErrors() {
   190  						continue
   191  					}
   192  					if len(ref.Remaining) != 0 {
   193  						diags = diags.Append(tfdiags.Sourceless(
   194  							tfdiags.Error,
   195  							"Invalid resource instance metadata in state",
   196  							fmt.Sprintf("Instance %s declares dependency on %q, which is not a reference to a dependable object.", instAddr.Absolute(moduleAddr), depRaw),
   197  						))
   198  					}
   199  					if ref.Subject == nil {
   200  						// Should never happen
   201  						panic(fmt.Sprintf("parsing dependency %q for instance %s returned a nil address", depRaw, instAddr.Absolute(moduleAddr)))
   202  					}
   203  					deps = append(deps, ref.Subject)
   204  				}
   205  				obj.Dependencies = deps
   206  			}
   207  
   208  			switch {
   209  			case isV4.Deposed != "":
   210  				dk := states.DeposedKey(isV4.Deposed)
   211  				if len(dk) != 8 {
   212  					diags = diags.Append(tfdiags.Sourceless(
   213  						tfdiags.Error,
   214  						"Invalid resource instance metadata in state",
   215  						fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed),
   216  					))
   217  					continue
   218  				}
   219  				is := ms.ResourceInstance(instAddr)
   220  				if is.HasDeposed(dk) {
   221  					diags = diags.Append(tfdiags.Sourceless(
   222  						tfdiags.Error,
   223  						"Duplicate resource instance in state",
   224  						fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk),
   225  					))
   226  					continue
   227  				}
   228  
   229  				ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr)
   230  			default:
   231  				is := ms.ResourceInstance(instAddr)
   232  				if is.HasCurrent() {
   233  					diags = diags.Append(tfdiags.Sourceless(
   234  						tfdiags.Error,
   235  						"Duplicate resource instance in state",
   236  						fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)),
   237  					))
   238  					continue
   239  				}
   240  
   241  				ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr)
   242  			}
   243  		}
   244  
   245  		// We repeat this after creating the instances because
   246  		// SetResourceInstanceCurrent automatically resets this metadata based
   247  		// on the incoming objects. That behavior is useful when we're making
   248  		// piecemeal updates to the state during an apply, but when we're
   249  		// reading the state file we want to reflect its contents exactly.
   250  		ms.SetResourceMeta(rAddr, eachMode, providerAddr)
   251  	}
   252  
   253  	// The root module is special in that we persist its attributes and thus
   254  	// need to reload them now. (For descendent modules we just re-calculate
   255  	// them based on the latest configuration on each run.)
   256  	{
   257  		rootModule := state.RootModule()
   258  		for name, fos := range sV4.RootOutputs {
   259  			os := &states.OutputValue{}
   260  			os.Sensitive = fos.Sensitive
   261  
   262  			ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw))
   263  			if err != nil {
   264  				diags = diags.Append(tfdiags.Sourceless(
   265  					tfdiags.Error,
   266  					"Invalid output value type in state",
   267  					fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err),
   268  				))
   269  				continue
   270  			}
   271  
   272  			val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty)
   273  			if err != nil {
   274  				diags = diags.Append(tfdiags.Sourceless(
   275  					tfdiags.Error,
   276  					"Invalid output value saved in state",
   277  					fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err),
   278  				))
   279  				continue
   280  			}
   281  
   282  			os.Value = val
   283  			rootModule.OutputValues[name] = os
   284  		}
   285  	}
   286  
   287  	file.State = state
   288  	return file, diags
   289  }
   290  
   291  func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics {
   292  	// Here we'll convert back from the "File" representation to our
   293  	// stateV4 struct representation and write that.
   294  	//
   295  	// While we support legacy state formats for reading, we only support the
   296  	// latest for writing and so if a V5 is added in future then this function
   297  	// should be deleted and replaced with a writeStateV5, even though the
   298  	// read/prepare V4 functions above would stick around.
   299  
   300  	var diags tfdiags.Diagnostics
   301  	if file == nil || file.State == nil {
   302  		panic("attempt to write nil state to file")
   303  	}
   304  
   305  	var terraformVersion string
   306  	if file.TerraformVersion != nil {
   307  		terraformVersion = file.TerraformVersion.String()
   308  	}
   309  
   310  	sV4 := &stateV4{
   311  		TerraformVersion: terraformVersion,
   312  		Serial:           file.Serial,
   313  		Lineage:          file.Lineage,
   314  		RootOutputs:      map[string]outputStateV4{},
   315  		Resources:        []resourceStateV4{},
   316  	}
   317  
   318  	for name, os := range file.State.RootModule().OutputValues {
   319  		src, err := ctyjson.Marshal(os.Value, os.Value.Type())
   320  		if err != nil {
   321  			diags = diags.Append(tfdiags.Sourceless(
   322  				tfdiags.Error,
   323  				"Failed to serialize output value in state",
   324  				fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err),
   325  			))
   326  			continue
   327  		}
   328  
   329  		typeSrc, err := ctyjson.MarshalType(os.Value.Type())
   330  		if err != nil {
   331  			diags = diags.Append(tfdiags.Sourceless(
   332  				tfdiags.Error,
   333  				"Failed to serialize output value in state",
   334  				fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err),
   335  			))
   336  			continue
   337  		}
   338  
   339  		sV4.RootOutputs[name] = outputStateV4{
   340  			Sensitive:    os.Sensitive,
   341  			ValueRaw:     json.RawMessage(src),
   342  			ValueTypeRaw: json.RawMessage(typeSrc),
   343  		}
   344  	}
   345  
   346  	for _, ms := range file.State.Modules {
   347  		moduleAddr := ms.Addr
   348  		for _, rs := range ms.Resources {
   349  			resourceAddr := rs.Addr
   350  
   351  			var mode string
   352  			switch resourceAddr.Mode {
   353  			case addrs.ManagedResourceMode:
   354  				mode = "managed"
   355  			case addrs.DataResourceMode:
   356  				mode = "data"
   357  			default:
   358  				diags = diags.Append(tfdiags.Sourceless(
   359  					tfdiags.Error,
   360  					"Failed to serialize resource in state",
   361  					fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode),
   362  				))
   363  				continue
   364  			}
   365  
   366  			var eachMode string
   367  			switch rs.EachMode {
   368  			case states.NoEach:
   369  				eachMode = ""
   370  			case states.EachList:
   371  				eachMode = "list"
   372  			case states.EachMap:
   373  				eachMode = "map"
   374  			default:
   375  				diags = diags.Append(tfdiags.Sourceless(
   376  					tfdiags.Error,
   377  					"Failed to serialize resource in state",
   378  					fmt.Sprintf("Resource %s has \"each\" mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), rs.EachMode),
   379  				))
   380  				continue
   381  			}
   382  
   383  			sV4.Resources = append(sV4.Resources, resourceStateV4{
   384  				Module:         moduleAddr.String(),
   385  				Mode:           mode,
   386  				Type:           resourceAddr.Type,
   387  				Name:           resourceAddr.Name,
   388  				EachMode:       eachMode,
   389  				ProviderConfig: rs.ProviderConfig.String(),
   390  				Instances:      []instanceObjectStateV4{},
   391  			})
   392  			rsV4 := &(sV4.Resources[len(sV4.Resources)-1])
   393  
   394  			for key, is := range rs.Instances {
   395  				if is.HasCurrent() {
   396  					var objDiags tfdiags.Diagnostics
   397  					rsV4.Instances, objDiags = appendInstanceObjectStateV4(
   398  						rs, is, key, is.Current, states.NotDeposed,
   399  						rsV4.Instances,
   400  					)
   401  					diags = diags.Append(objDiags)
   402  				}
   403  				for dk, obj := range is.Deposed {
   404  					var objDiags tfdiags.Diagnostics
   405  					rsV4.Instances, objDiags = appendInstanceObjectStateV4(
   406  						rs, is, key, obj, dk,
   407  						rsV4.Instances,
   408  					)
   409  					diags = diags.Append(objDiags)
   410  				}
   411  			}
   412  		}
   413  	}
   414  
   415  	sV4.normalize()
   416  
   417  	src, err := json.MarshalIndent(sV4, "", "  ")
   418  	if err != nil {
   419  		// Shouldn't happen if we do our conversion to *stateV4 correctly above.
   420  		diags = diags.Append(tfdiags.Sourceless(
   421  			tfdiags.Error,
   422  			"Failed to serialize state",
   423  			fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err),
   424  		))
   425  		return diags
   426  	}
   427  	src = append(src, '\n')
   428  
   429  	_, err = w.Write(src)
   430  	if err != nil {
   431  		diags = diags.Append(tfdiags.Sourceless(
   432  			tfdiags.Error,
   433  			"Failed to write state",
   434  			fmt.Sprintf("An error occured while writing the serialized state: %s.", err),
   435  		))
   436  		return diags
   437  	}
   438  
   439  	return diags
   440  }
   441  
   442  func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) {
   443  	var diags tfdiags.Diagnostics
   444  
   445  	var status string
   446  	switch obj.Status {
   447  	case states.ObjectReady:
   448  		status = ""
   449  	case states.ObjectTainted:
   450  		status = "tainted"
   451  	default:
   452  		diags = diags.Append(tfdiags.Sourceless(
   453  			tfdiags.Error,
   454  			"Failed to serialize resource instance in state",
   455  			fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status),
   456  		))
   457  	}
   458  
   459  	var privateRaw []byte
   460  	if len(obj.Private) > 0 {
   461  		privateRaw = obj.Private
   462  	}
   463  
   464  	deps := make([]string, len(obj.Dependencies))
   465  	for i, depAddr := range obj.Dependencies {
   466  		deps[i] = depAddr.String()
   467  	}
   468  
   469  	var rawKey interface{}
   470  	switch tk := key.(type) {
   471  	case addrs.IntKey:
   472  		rawKey = int(tk)
   473  	case addrs.StringKey:
   474  		rawKey = string(tk)
   475  	default:
   476  		if key != addrs.NoKey {
   477  			diags = diags.Append(tfdiags.Sourceless(
   478  				tfdiags.Error,
   479  				"Failed to serialize resource instance in state",
   480  				fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key),
   481  			))
   482  		}
   483  	}
   484  
   485  	return append(isV4s, instanceObjectStateV4{
   486  		IndexKey:       rawKey,
   487  		Deposed:        string(deposed),
   488  		Status:         status,
   489  		SchemaVersion:  obj.SchemaVersion,
   490  		AttributesFlat: obj.AttrsFlat,
   491  		AttributesRaw:  obj.AttrsJSON,
   492  		PrivateRaw:     privateRaw,
   493  		Dependencies:   deps,
   494  	}), diags
   495  }
   496  
   497  type stateV4 struct {
   498  	Version          stateVersionV4           `json:"version"`
   499  	TerraformVersion string                   `json:"terraform_version"`
   500  	Serial           uint64                   `json:"serial"`
   501  	Lineage          string                   `json:"lineage"`
   502  	RootOutputs      map[string]outputStateV4 `json:"outputs"`
   503  	Resources        []resourceStateV4        `json:"resources"`
   504  }
   505  
   506  // normalize makes some in-place changes to normalize the way items are
   507  // stored to ensure that two functionally-equivalent states will be stored
   508  // identically.
   509  func (s *stateV4) normalize() {
   510  	sort.Stable(sortResourcesV4(s.Resources))
   511  	for _, rs := range s.Resources {
   512  		sort.Stable(sortInstancesV4(rs.Instances))
   513  	}
   514  }
   515  
   516  type outputStateV4 struct {
   517  	ValueRaw     json.RawMessage `json:"value"`
   518  	ValueTypeRaw json.RawMessage `json:"type"`
   519  	Sensitive    bool            `json:"sensitive,omitempty"`
   520  }
   521  
   522  type resourceStateV4 struct {
   523  	Module         string                  `json:"module,omitempty"`
   524  	Mode           string                  `json:"mode"`
   525  	Type           string                  `json:"type"`
   526  	Name           string                  `json:"name"`
   527  	EachMode       string                  `json:"each,omitempty"`
   528  	ProviderConfig string                  `json:"provider"`
   529  	Instances      []instanceObjectStateV4 `json:"instances"`
   530  }
   531  
   532  type instanceObjectStateV4 struct {
   533  	IndexKey interface{} `json:"index_key,omitempty"`
   534  	Status   string      `json:"status,omitempty"`
   535  	Deposed  string      `json:"deposed,omitempty"`
   536  
   537  	SchemaVersion  uint64            `json:"schema_version"`
   538  	AttributesRaw  json.RawMessage   `json:"attributes,omitempty"`
   539  	AttributesFlat map[string]string `json:"attributes_flat,omitempty"`
   540  
   541  	PrivateRaw []byte `json:"private,omitempty"`
   542  
   543  	Dependencies []string `json:"depends_on,omitempty"`
   544  }
   545  
   546  // stateVersionV4 is a weird special type we use to produce our hard-coded
   547  // "version": 4 in the JSON serialization.
   548  type stateVersionV4 struct{}
   549  
   550  func (sv stateVersionV4) MarshalJSON() ([]byte, error) {
   551  	return []byte{'4'}, nil
   552  }
   553  
   554  func (sv stateVersionV4) UnmarshalJSON([]byte) error {
   555  	// Nothing to do: we already know we're version 4
   556  	return nil
   557  }
   558  
   559  type sortResourcesV4 []resourceStateV4
   560  
   561  func (sr sortResourcesV4) Len() int      { return len(sr) }
   562  func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] }
   563  func (sr sortResourcesV4) Less(i, j int) bool {
   564  	switch {
   565  	case sr[i].Mode != sr[j].Mode:
   566  		return sr[i].Mode < sr[j].Mode
   567  	case sr[i].Type != sr[j].Type:
   568  		return sr[i].Type < sr[j].Type
   569  	case sr[i].Name != sr[j].Name:
   570  		return sr[i].Name < sr[j].Name
   571  	default:
   572  		return false
   573  	}
   574  }
   575  
   576  type sortInstancesV4 []instanceObjectStateV4
   577  
   578  func (si sortInstancesV4) Len() int      { return len(si) }
   579  func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] }
   580  func (si sortInstancesV4) Less(i, j int) bool {
   581  	ki := si[i].IndexKey
   582  	kj := si[j].IndexKey
   583  	if ki != kj {
   584  		if (ki == nil) != (kj == nil) {
   585  			return ki == nil
   586  		}
   587  		if kii, isInt := ki.(int); isInt {
   588  			if kji, isInt := kj.(int); isInt {
   589  				return kii < kji
   590  			}
   591  			return true
   592  		}
   593  		if kis, isStr := ki.(string); isStr {
   594  			if kjs, isStr := kj.(string); isStr {
   595  				return kis < kjs
   596  			}
   597  			return true
   598  		}
   599  	}
   600  	if si[i].Deposed != si[j].Deposed {
   601  		return si[i].Deposed < si[j].Deposed
   602  	}
   603  	return false
   604  }