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