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