github.com/pulumi/terraform@v1.4.0/pkg/command/jsonplan/values.go (about)

     1  package jsonplan
     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/jsonstate"
    13  	"github.com/pulumi/terraform/pkg/configs/configschema"
    14  	"github.com/pulumi/terraform/pkg/plans"
    15  	"github.com/pulumi/terraform/pkg/states"
    16  	"github.com/pulumi/terraform/pkg/terraform"
    17  )
    18  
    19  // stateValues is the common representation of resolved values for both the
    20  // prior state (which is always complete) and the planned new state.
    21  type stateValues struct {
    22  	Outputs    map[string]output `json:"outputs,omitempty"`
    23  	RootModule module            `json:"root_module,omitempty"`
    24  }
    25  
    26  // attributeValues is the JSON representation of the attribute values of the
    27  // resource, whose structure depends on the resource type schema.
    28  type attributeValues map[string]interface{}
    29  
    30  func marshalAttributeValues(value cty.Value, schema *configschema.Block) attributeValues {
    31  	if value == cty.NilVal || value.IsNull() {
    32  		return nil
    33  	}
    34  	ret := make(attributeValues)
    35  
    36  	it := value.ElementIterator()
    37  	for it.Next() {
    38  		k, v := it.Element()
    39  		vJSON, _ := ctyjson.Marshal(v, v.Type())
    40  		ret[k.AsString()] = json.RawMessage(vJSON)
    41  	}
    42  	return ret
    43  }
    44  
    45  // marshalPlannedOutputs takes a list of changes and returns a map of output
    46  // values
    47  func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
    48  	if changes.Outputs == nil {
    49  		// No changes - we're done here!
    50  		return nil, nil
    51  	}
    52  
    53  	ret := make(map[string]output)
    54  
    55  	for _, oc := range changes.Outputs {
    56  		if oc.ChangeSrc.Action == plans.Delete {
    57  			continue
    58  		}
    59  
    60  		var after, afterType []byte
    61  		changeV, err := oc.Decode()
    62  		if err != nil {
    63  			return ret, err
    64  		}
    65  		// The values may be marked, but we must rely on the Sensitive flag
    66  		// as the decoded value is only an intermediate step in transcoding
    67  		// this to a json format.
    68  		changeV.After, _ = changeV.After.UnmarkDeep()
    69  
    70  		if changeV.After != cty.NilVal && changeV.After.IsWhollyKnown() {
    71  			ty := changeV.After.Type()
    72  			after, err = ctyjson.Marshal(changeV.After, ty)
    73  			if err != nil {
    74  				return ret, err
    75  			}
    76  			afterType, err = ctyjson.MarshalType(ty)
    77  			if err != nil {
    78  				return ret, err
    79  			}
    80  		}
    81  
    82  		ret[oc.Addr.OutputValue.Name] = output{
    83  			Value:     json.RawMessage(after),
    84  			Type:      json.RawMessage(afterType),
    85  			Sensitive: oc.Sensitive,
    86  		}
    87  	}
    88  
    89  	return ret, nil
    90  
    91  }
    92  
    93  func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (module, error) {
    94  	var ret module
    95  
    96  	// build two maps:
    97  	// 		module name -> [resource addresses]
    98  	// 		module -> [children modules]
    99  	moduleResourceMap := make(map[string][]addrs.AbsResourceInstance)
   100  	moduleMap := make(map[string][]addrs.ModuleInstance)
   101  	seenModules := make(map[string]bool)
   102  
   103  	for _, resource := range changes.Resources {
   104  		// If the resource is being deleted, skip over it.
   105  		// Deposed instances are always conceptually a destroy, but if they
   106  		// were gone during refresh then the change becomes a noop.
   107  		if resource.Action != plans.Delete && resource.DeposedKey == states.NotDeposed {
   108  			containingModule := resource.Addr.Module.String()
   109  			moduleResourceMap[containingModule] = append(moduleResourceMap[containingModule], resource.Addr)
   110  
   111  			// the root module has no parents
   112  			if !resource.Addr.Module.IsRoot() {
   113  				parent := resource.Addr.Module.Parent().String()
   114  				// we expect to see multiple resources in one module, so we
   115  				// only need to report the "parent" module for each child module
   116  				// once.
   117  				if !seenModules[containingModule] {
   118  					moduleMap[parent] = append(moduleMap[parent], resource.Addr.Module)
   119  					seenModules[containingModule] = true
   120  				}
   121  
   122  				// If any given parent module has no resources, it needs to be
   123  				// added to the moduleMap. This walks through the current
   124  				// resources' modules' ancestors, taking advantage of the fact
   125  				// that Ancestors() returns an ordered slice, and verifies that
   126  				// each one is in the map.
   127  				ancestors := resource.Addr.Module.Ancestors()
   128  				for i, ancestor := range ancestors[:len(ancestors)-1] {
   129  					aStr := ancestor.String()
   130  
   131  					// childStr here is the immediate child of the current step
   132  					childStr := ancestors[i+1].String()
   133  					// we likely will see multiple resources in one module, so we
   134  					// only need to report the "parent" module for each child module
   135  					// once.
   136  					if !seenModules[childStr] {
   137  						moduleMap[aStr] = append(moduleMap[aStr], ancestors[i+1])
   138  						seenModules[childStr] = true
   139  					}
   140  				}
   141  			}
   142  		}
   143  	}
   144  
   145  	// start with the root module
   146  	resources, err := marshalPlanResources(changes, moduleResourceMap[""], schemas)
   147  	if err != nil {
   148  		return ret, err
   149  	}
   150  	ret.Resources = resources
   151  
   152  	childModules, err := marshalPlanModules(changes, schemas, moduleMap[""], moduleMap, moduleResourceMap)
   153  	if err != nil {
   154  		return ret, err
   155  	}
   156  	sort.Slice(childModules, func(i, j int) bool {
   157  		return childModules[i].Address < childModules[j].Address
   158  	})
   159  
   160  	ret.ChildModules = childModules
   161  
   162  	return ret, nil
   163  }
   164  
   165  // marshalPlanResources
   166  func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstance, schemas *terraform.Schemas) ([]resource, error) {
   167  	var ret []resource
   168  
   169  	for _, ri := range ris {
   170  		r := changes.ResourceInstance(ri)
   171  		if r.Action == plans.Delete {
   172  			continue
   173  		}
   174  
   175  		resource := resource{
   176  			Address:      r.Addr.String(),
   177  			Type:         r.Addr.Resource.Resource.Type,
   178  			Name:         r.Addr.Resource.Resource.Name,
   179  			ProviderName: r.ProviderAddr.Provider.String(),
   180  			Index:        r.Addr.Resource.Key,
   181  		}
   182  
   183  		switch r.Addr.Resource.Resource.Mode {
   184  		case addrs.ManagedResourceMode:
   185  			resource.Mode = "managed"
   186  		case addrs.DataResourceMode:
   187  			resource.Mode = "data"
   188  		default:
   189  			return nil, fmt.Errorf("resource %s has an unsupported mode %s",
   190  				r.Addr.String(),
   191  				r.Addr.Resource.Resource.Mode.String(),
   192  			)
   193  		}
   194  
   195  		schema, schemaVer := schemas.ResourceTypeConfig(
   196  			r.ProviderAddr.Provider,
   197  			r.Addr.Resource.Resource.Mode,
   198  			resource.Type,
   199  		)
   200  		if schema == nil {
   201  			return nil, fmt.Errorf("no schema found for %s", r.Addr.String())
   202  		}
   203  		resource.SchemaVersion = schemaVer
   204  		changeV, err := r.Decode(schema.ImpliedType())
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  
   209  		// copy the marked After values so we can use these in marshalSensitiveValues
   210  		markedAfter := changeV.After
   211  
   212  		// The values may be marked, but we must rely on the Sensitive flag
   213  		// as the decoded value is only an intermediate step in transcoding
   214  		// this to a json format.
   215  		changeV.Before, _ = changeV.Before.UnmarkDeep()
   216  		changeV.After, _ = changeV.After.UnmarkDeep()
   217  
   218  		if changeV.After != cty.NilVal {
   219  			if changeV.After.IsWhollyKnown() {
   220  				resource.AttributeValues = marshalAttributeValues(changeV.After, schema)
   221  			} else {
   222  				knowns := omitUnknowns(changeV.After)
   223  				resource.AttributeValues = marshalAttributeValues(knowns, schema)
   224  			}
   225  		}
   226  
   227  		s := jsonstate.SensitiveAsBool(markedAfter)
   228  		v, err := ctyjson.Marshal(s, s.Type())
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  		resource.SensitiveValues = v
   233  
   234  		ret = append(ret, resource)
   235  	}
   236  
   237  	sort.Slice(ret, func(i, j int) bool {
   238  		return ret[i].Address < ret[j].Address
   239  	})
   240  
   241  	return ret, nil
   242  }
   243  
   244  // marshalPlanModules iterates over a list of modules to recursively describe
   245  // the full module tree.
   246  func marshalPlanModules(
   247  	changes *plans.Changes,
   248  	schemas *terraform.Schemas,
   249  	childModules []addrs.ModuleInstance,
   250  	moduleMap map[string][]addrs.ModuleInstance,
   251  	moduleResourceMap map[string][]addrs.AbsResourceInstance,
   252  ) ([]module, error) {
   253  
   254  	var ret []module
   255  
   256  	for _, child := range childModules {
   257  		moduleResources := moduleResourceMap[child.String()]
   258  		// cm for child module, naming things is hard.
   259  		var cm module
   260  		// don't populate the address for the root module
   261  		if child.String() != "" {
   262  			cm.Address = child.String()
   263  		}
   264  		rs, err := marshalPlanResources(changes, moduleResources, schemas)
   265  		if err != nil {
   266  			return nil, err
   267  		}
   268  		cm.Resources = rs
   269  
   270  		if len(moduleMap[child.String()]) > 0 {
   271  			moreChildModules, err := marshalPlanModules(changes, schemas, moduleMap[child.String()], moduleMap, moduleResourceMap)
   272  			if err != nil {
   273  				return nil, err
   274  			}
   275  			cm.ChildModules = moreChildModules
   276  		}
   277  
   278  		ret = append(ret, cm)
   279  	}
   280  
   281  	return ret, nil
   282  }