github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/command/jsonstate/state.go (about)

     1  package jsonstate
     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/hashicorp/terraform/internal/addrs"
    12  	"github.com/hashicorp/terraform/internal/command/jsonchecks"
    13  	"github.com/hashicorp/terraform/internal/lang/marks"
    14  	"github.com/hashicorp/terraform/internal/states"
    15  	"github.com/hashicorp/terraform/internal/states/statefile"
    16  	"github.com/hashicorp/terraform/internal/terraform"
    17  )
    18  
    19  // FormatVersion represents the version of the json format and will be
    20  // incremented for any change to this format that requires changes to a
    21  // consuming parser.
    22  const FormatVersion = "1.0"
    23  
    24  // state is the top-level representation of the json format of a terraform
    25  // state.
    26  type state struct {
    27  	FormatVersion    string          `json:"format_version,omitempty"`
    28  	TerraformVersion string          `json:"terraform_version,omitempty"`
    29  	Values           *stateValues    `json:"values,omitempty"`
    30  	Checks           json.RawMessage `json:"checks,omitempty"`
    31  }
    32  
    33  // stateValues is the common representation of resolved values for both the prior
    34  // state (which is always complete) and the planned new state.
    35  type stateValues struct {
    36  	Outputs    map[string]output `json:"outputs,omitempty"`
    37  	RootModule module            `json:"root_module,omitempty"`
    38  }
    39  
    40  type output struct {
    41  	Sensitive bool            `json:"sensitive"`
    42  	Value     json.RawMessage `json:"value,omitempty"`
    43  	Type      json.RawMessage `json:"type,omitempty"`
    44  }
    45  
    46  // module is the representation of a module in state. This can be the root module
    47  // or a child module
    48  type module struct {
    49  	// Resources are sorted in a user-friendly order that is undefined at this
    50  	// time, but consistent.
    51  	Resources []resource `json:"resources,omitempty"`
    52  
    53  	// Address is the absolute module address, omitted for the root module
    54  	Address string `json:"address,omitempty"`
    55  
    56  	// Each module object can optionally have its own nested "child_modules",
    57  	// recursively describing the full module tree.
    58  	ChildModules []module `json:"child_modules,omitempty"`
    59  }
    60  
    61  // Resource is the representation of a resource in the state.
    62  type resource struct {
    63  	// Address is the absolute resource address
    64  	Address string `json:"address,omitempty"`
    65  
    66  	// Mode can be "managed" or "data"
    67  	Mode string `json:"mode,omitempty"`
    68  
    69  	Type string `json:"type,omitempty"`
    70  	Name string `json:"name,omitempty"`
    71  
    72  	// Index is omitted for a resource not using `count` or `for_each`.
    73  	Index addrs.InstanceKey `json:"index,omitempty"`
    74  
    75  	// ProviderName allows the property "type" to be interpreted unambiguously
    76  	// in the unusual situation where a provider offers a resource type whose
    77  	// name does not start with its own name, such as the "googlebeta" provider
    78  	// offering "google_compute_instance".
    79  	ProviderName string `json:"provider_name"`
    80  
    81  	// SchemaVersion indicates which version of the resource type schema the
    82  	// "values" property conforms to.
    83  	SchemaVersion uint64 `json:"schema_version"`
    84  
    85  	// AttributeValues is the JSON representation of the attribute values of the
    86  	// resource, whose structure depends on the resource type schema. Any
    87  	// unknown values are omitted or set to null, making them indistinguishable
    88  	// from absent values.
    89  	AttributeValues attributeValues `json:"values,omitempty"`
    90  
    91  	// SensitiveValues is similar to AttributeValues, but with all sensitive
    92  	// values replaced with true, and all non-sensitive leaf values omitted.
    93  	SensitiveValues json.RawMessage `json:"sensitive_values,omitempty"`
    94  
    95  	// DependsOn contains a list of the resource's dependencies. The entries are
    96  	// addresses relative to the containing module.
    97  	DependsOn []string `json:"depends_on,omitempty"`
    98  
    99  	// Tainted is true if the resource is tainted in terraform state.
   100  	Tainted bool `json:"tainted,omitempty"`
   101  
   102  	// Deposed is set if the resource is deposed in terraform state.
   103  	DeposedKey string `json:"deposed_key,omitempty"`
   104  }
   105  
   106  // attributeValues is the JSON representation of the attribute values of the
   107  // resource, whose structure depends on the resource type schema.
   108  type attributeValues map[string]interface{}
   109  
   110  func marshalAttributeValues(value cty.Value) attributeValues {
   111  	// unmark our value to show all values
   112  	value, _ = value.UnmarkDeep()
   113  
   114  	if value == cty.NilVal || value.IsNull() {
   115  		return nil
   116  	}
   117  
   118  	ret := make(attributeValues)
   119  
   120  	it := value.ElementIterator()
   121  	for it.Next() {
   122  		k, v := it.Element()
   123  		vJSON, _ := ctyjson.Marshal(v, v.Type())
   124  		ret[k.AsString()] = json.RawMessage(vJSON)
   125  	}
   126  	return ret
   127  }
   128  
   129  // newState() returns a minimally-initialized state
   130  func newState() *state {
   131  	return &state{
   132  		FormatVersion: FormatVersion,
   133  	}
   134  }
   135  
   136  // Marshal returns the json encoding of a terraform state.
   137  func Marshal(sf *statefile.File, schemas *terraform.Schemas) ([]byte, error) {
   138  	output := newState()
   139  
   140  	if sf == nil || sf.State.Empty() {
   141  		ret, err := json.Marshal(output)
   142  		return ret, err
   143  	}
   144  
   145  	if sf.TerraformVersion != nil {
   146  		output.TerraformVersion = sf.TerraformVersion.String()
   147  	}
   148  
   149  	// output.StateValues
   150  	err := output.marshalStateValues(sf.State, schemas)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	// output.Checks
   156  	if sf.State.CheckResults != nil && sf.State.CheckResults.ConfigResults.Len() > 0 {
   157  		output.Checks = jsonchecks.MarshalCheckStates(sf.State.CheckResults)
   158  	}
   159  
   160  	ret, err := json.Marshal(output)
   161  	return ret, err
   162  }
   163  
   164  func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.Schemas) error {
   165  	var sv stateValues
   166  	var err error
   167  
   168  	// only marshal the root module outputs
   169  	sv.Outputs, err = MarshalOutputs(s.RootModule().OutputValues)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	// use the state and module map to build up the module structure
   175  	sv.RootModule, err = marshalRootModule(s, schemas)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	jsonstate.Values = &sv
   181  	return nil
   182  }
   183  
   184  // MarshalOutputs translates a map of states.OutputValue to a map of jsonstate.output,
   185  // which are defined for json encoding.
   186  func MarshalOutputs(outputs map[string]*states.OutputValue) (map[string]output, error) {
   187  	if outputs == nil {
   188  		return nil, nil
   189  	}
   190  
   191  	ret := make(map[string]output)
   192  	for k, v := range outputs {
   193  		ty := v.Value.Type()
   194  		ov, err := ctyjson.Marshal(v.Value, ty)
   195  		if err != nil {
   196  			return ret, err
   197  		}
   198  		ot, err := ctyjson.MarshalType(ty)
   199  		if err != nil {
   200  			return ret, err
   201  		}
   202  		ret[k] = output{
   203  			Value:     ov,
   204  			Type:      ot,
   205  			Sensitive: v.Sensitive,
   206  		}
   207  	}
   208  
   209  	return ret, nil
   210  }
   211  
   212  func marshalRootModule(s *states.State, schemas *terraform.Schemas) (module, error) {
   213  	var ret module
   214  	var err error
   215  
   216  	ret.Address = ""
   217  	rs, err := marshalResources(s.RootModule().Resources, addrs.RootModuleInstance, schemas)
   218  	if err != nil {
   219  		return ret, err
   220  	}
   221  	ret.Resources = rs
   222  
   223  	// build a map of module -> set[child module addresses]
   224  	moduleChildSet := make(map[string]map[string]struct{})
   225  	for _, mod := range s.Modules {
   226  		if mod.Addr.IsRoot() {
   227  			continue
   228  		} else {
   229  			for childAddr := mod.Addr; !childAddr.IsRoot(); childAddr = childAddr.Parent() {
   230  				if _, ok := moduleChildSet[childAddr.Parent().String()]; !ok {
   231  					moduleChildSet[childAddr.Parent().String()] = map[string]struct{}{}
   232  				}
   233  				moduleChildSet[childAddr.Parent().String()][childAddr.String()] = struct{}{}
   234  			}
   235  		}
   236  	}
   237  
   238  	// transform the previous map into map of module -> [child module addresses]
   239  	moduleMap := make(map[string][]addrs.ModuleInstance)
   240  	for parent, children := range moduleChildSet {
   241  		for child := range children {
   242  			childModuleInstance, diags := addrs.ParseModuleInstanceStr(child)
   243  			if diags.HasErrors() {
   244  				return ret, diags.Err()
   245  			}
   246  			moduleMap[parent] = append(moduleMap[parent], childModuleInstance)
   247  		}
   248  	}
   249  
   250  	// use the state and module map to build up the module structure
   251  	ret.ChildModules, err = marshalModules(s, schemas, moduleMap[""], moduleMap)
   252  	return ret, err
   253  }
   254  
   255  // marshalModules is an ungainly recursive function to build a module structure
   256  // out of terraform state.
   257  func marshalModules(
   258  	s *states.State,
   259  	schemas *terraform.Schemas,
   260  	modules []addrs.ModuleInstance,
   261  	moduleMap map[string][]addrs.ModuleInstance,
   262  ) ([]module, error) {
   263  	var ret []module
   264  	for _, child := range modules {
   265  		// cm for child module, naming things is hard.
   266  		cm := module{Address: child.String()}
   267  
   268  		// the module may be resourceless and contain only submodules, it will then be nil here
   269  		stateMod := s.Module(child)
   270  		if stateMod != nil {
   271  			rs, err := marshalResources(stateMod.Resources, stateMod.Addr, schemas)
   272  			if err != nil {
   273  				return nil, err
   274  			}
   275  			cm.Resources = rs
   276  		}
   277  
   278  		if moduleMap[child.String()] != nil {
   279  			moreChildModules, err := marshalModules(s, schemas, moduleMap[child.String()], moduleMap)
   280  			if err != nil {
   281  				return nil, err
   282  			}
   283  			cm.ChildModules = moreChildModules
   284  		}
   285  
   286  		ret = append(ret, cm)
   287  	}
   288  
   289  	// sort the child modules by address for consistency.
   290  	sort.Slice(ret, func(i, j int) bool {
   291  		return ret[i].Address < ret[j].Address
   292  	})
   293  
   294  	return ret, nil
   295  }
   296  
   297  func marshalResources(resources map[string]*states.Resource, module addrs.ModuleInstance, schemas *terraform.Schemas) ([]resource, error) {
   298  	var ret []resource
   299  
   300  	for _, r := range resources {
   301  		for k, ri := range r.Instances {
   302  
   303  			resAddr := r.Addr.Resource
   304  
   305  			current := resource{
   306  				Address:      r.Addr.Instance(k).String(),
   307  				Index:        k,
   308  				Type:         resAddr.Type,
   309  				Name:         resAddr.Name,
   310  				ProviderName: r.ProviderConfig.Provider.String(),
   311  			}
   312  
   313  			switch resAddr.Mode {
   314  			case addrs.ManagedResourceMode:
   315  				current.Mode = "managed"
   316  			case addrs.DataResourceMode:
   317  				current.Mode = "data"
   318  			default:
   319  				return ret, fmt.Errorf("resource %s has an unsupported mode %s",
   320  					resAddr.String(),
   321  					resAddr.Mode.String(),
   322  				)
   323  			}
   324  
   325  			schema, version := schemas.ResourceTypeConfig(
   326  				r.ProviderConfig.Provider,
   327  				resAddr.Mode,
   328  				resAddr.Type,
   329  			)
   330  
   331  			// It is possible that the only instance is deposed
   332  			if ri.Current != nil {
   333  				if version != ri.Current.SchemaVersion {
   334  					return nil, fmt.Errorf("schema version %d for %s in state does not match version %d from the provider", ri.Current.SchemaVersion, resAddr, version)
   335  				}
   336  
   337  				current.SchemaVersion = ri.Current.SchemaVersion
   338  
   339  				if schema == nil {
   340  					return nil, fmt.Errorf("no schema found for %s (in provider %s)", resAddr.String(), r.ProviderConfig.Provider)
   341  				}
   342  				riObj, err := ri.Current.Decode(schema.ImpliedType())
   343  				if err != nil {
   344  					return nil, err
   345  				}
   346  
   347  				current.AttributeValues = marshalAttributeValues(riObj.Value)
   348  
   349  				s := SensitiveAsBool(riObj.Value)
   350  				v, err := ctyjson.Marshal(s, s.Type())
   351  				if err != nil {
   352  					return nil, err
   353  				}
   354  				current.SensitiveValues = v
   355  
   356  				if len(riObj.Dependencies) > 0 {
   357  					dependencies := make([]string, len(riObj.Dependencies))
   358  					for i, v := range riObj.Dependencies {
   359  						dependencies[i] = v.String()
   360  					}
   361  					current.DependsOn = dependencies
   362  				}
   363  
   364  				if riObj.Status == states.ObjectTainted {
   365  					current.Tainted = true
   366  				}
   367  				ret = append(ret, current)
   368  			}
   369  
   370  			for deposedKey, rios := range ri.Deposed {
   371  				// copy the base fields from the current instance
   372  				deposed := resource{
   373  					Address:      current.Address,
   374  					Type:         current.Type,
   375  					Name:         current.Name,
   376  					ProviderName: current.ProviderName,
   377  					Mode:         current.Mode,
   378  					Index:        current.Index,
   379  				}
   380  
   381  				riObj, err := rios.Decode(schema.ImpliedType())
   382  				if err != nil {
   383  					return nil, err
   384  				}
   385  
   386  				deposed.AttributeValues = marshalAttributeValues(riObj.Value)
   387  
   388  				s := SensitiveAsBool(riObj.Value)
   389  				v, err := ctyjson.Marshal(s, s.Type())
   390  				if err != nil {
   391  					return nil, err
   392  				}
   393  				deposed.SensitiveValues = v
   394  
   395  				if len(riObj.Dependencies) > 0 {
   396  					dependencies := make([]string, len(riObj.Dependencies))
   397  					for i, v := range riObj.Dependencies {
   398  						dependencies[i] = v.String()
   399  					}
   400  					deposed.DependsOn = dependencies
   401  				}
   402  
   403  				if riObj.Status == states.ObjectTainted {
   404  					deposed.Tainted = true
   405  				}
   406  				deposed.DeposedKey = deposedKey.String()
   407  				ret = append(ret, deposed)
   408  			}
   409  		}
   410  	}
   411  
   412  	sort.Slice(ret, func(i, j int) bool {
   413  		return ret[i].Address < ret[j].Address
   414  	})
   415  
   416  	return ret, nil
   417  }
   418  
   419  func SensitiveAsBool(val cty.Value) cty.Value {
   420  	if val.HasMark(marks.Sensitive) {
   421  		return cty.True
   422  	}
   423  
   424  	ty := val.Type()
   425  	switch {
   426  	case val.IsNull(), ty.IsPrimitiveType(), ty.Equals(cty.DynamicPseudoType):
   427  		return cty.False
   428  	case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
   429  		if !val.IsKnown() {
   430  			// If the collection is unknown we can't say anything about the
   431  			// sensitivity of its contents
   432  			return cty.EmptyTupleVal
   433  		}
   434  		length := val.LengthInt()
   435  		if length == 0 {
   436  			// If there are no elements then we can't have sensitive values
   437  			return cty.EmptyTupleVal
   438  		}
   439  		vals := make([]cty.Value, 0, length)
   440  		it := val.ElementIterator()
   441  		for it.Next() {
   442  			_, v := it.Element()
   443  			vals = append(vals, SensitiveAsBool(v))
   444  		}
   445  		// The above transform may have changed the types of some of the
   446  		// elements, so we'll always use a tuple here in case we've now made
   447  		// different elements have different types. Our ultimate goal is to
   448  		// marshal to JSON anyway, and all of these sequence types are
   449  		// indistinguishable in JSON.
   450  		return cty.TupleVal(vals)
   451  	case ty.IsMapType() || ty.IsObjectType():
   452  		if !val.IsKnown() {
   453  			// If the map/object is unknown we can't say anything about the
   454  			// sensitivity of its attributes
   455  			return cty.EmptyObjectVal
   456  		}
   457  		var length int
   458  		switch {
   459  		case ty.IsMapType():
   460  			length = val.LengthInt()
   461  		default:
   462  			length = len(val.Type().AttributeTypes())
   463  		}
   464  		if length == 0 {
   465  			// If there are no elements then we can't have sensitive values
   466  			return cty.EmptyObjectVal
   467  		}
   468  		vals := make(map[string]cty.Value)
   469  		it := val.ElementIterator()
   470  		for it.Next() {
   471  			k, v := it.Element()
   472  			s := SensitiveAsBool(v)
   473  			// Omit all of the "false"s for non-sensitive values for more
   474  			// compact serialization
   475  			if !s.RawEquals(cty.False) {
   476  				vals[k.AsString()] = s
   477  			}
   478  		}
   479  		// The above transform may have changed the types of some of the
   480  		// elements, so we'll always use an object here in case we've now made
   481  		// different elements have different types. Our ultimate goal is to
   482  		// marshal to JSON anyway, and all of these mapping types are
   483  		// indistinguishable in JSON.
   484  		return cty.ObjectVal(vals)
   485  	default:
   486  		// Should never happen, since the above should cover all types
   487  		panic(fmt.Sprintf("sensitiveAsBool cannot handle %#v", val))
   488  	}
   489  }