github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/genconfig/generate_config.go (about)

     1  package genconfig
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/hclwrite"
    10  	"github.com/zclconf/go-cty/cty"
    11  
    12  	"github.com/terramate-io/tf/addrs"
    13  	"github.com/terramate-io/tf/configs/configschema"
    14  	"github.com/terramate-io/tf/tfdiags"
    15  )
    16  
    17  // GenerateResourceContents generates HCL configuration code for the provided
    18  // resource and state value.
    19  //
    20  // If you want to generate actual valid Terraform code you should follow this
    21  // call up with a call to WrapResourceContents, which will place a Terraform
    22  // resource header around the attributes and blocks returned by this function.
    23  func GenerateResourceContents(addr addrs.AbsResourceInstance,
    24  	schema *configschema.Block,
    25  	pc addrs.LocalProviderConfig,
    26  	stateVal cty.Value) (string, tfdiags.Diagnostics) {
    27  	var buf strings.Builder
    28  
    29  	var diags tfdiags.Diagnostics
    30  
    31  	if pc.LocalName != addr.Resource.Resource.ImpliedProvider() || pc.Alias != "" {
    32  		buf.WriteString(strings.Repeat(" ", 2))
    33  		buf.WriteString(fmt.Sprintf("provider = %s\n", pc.StringCompact()))
    34  	}
    35  
    36  	stateVal = omitUnknowns(stateVal)
    37  	if stateVal.RawEquals(cty.NilVal) {
    38  		diags = diags.Append(writeConfigAttributes(addr, &buf, schema.Attributes, 2))
    39  		diags = diags.Append(writeConfigBlocks(addr, &buf, schema.BlockTypes, 2))
    40  	} else {
    41  		diags = diags.Append(writeConfigAttributesFromExisting(addr, &buf, stateVal, schema.Attributes, 2))
    42  		diags = diags.Append(writeConfigBlocksFromExisting(addr, &buf, stateVal, schema.BlockTypes, 2))
    43  	}
    44  
    45  	// The output better be valid HCL which can be parsed and formatted.
    46  	formatted := hclwrite.Format([]byte(buf.String()))
    47  	return string(formatted), diags
    48  }
    49  
    50  func WrapResourceContents(addr addrs.AbsResourceInstance, config string) string {
    51  	var buf strings.Builder
    52  
    53  	buf.WriteString(fmt.Sprintf("resource %q %q {\n", addr.Resource.Resource.Type, addr.Resource.Resource.Name))
    54  	buf.WriteString(config)
    55  	buf.WriteString("}")
    56  
    57  	// The output better be valid HCL which can be parsed and formatted.
    58  	formatted := hclwrite.Format([]byte(buf.String()))
    59  	return string(formatted)
    60  }
    61  
    62  func writeConfigAttributes(addr addrs.AbsResourceInstance, buf *strings.Builder, attrs map[string]*configschema.Attribute, indent int) tfdiags.Diagnostics {
    63  	var diags tfdiags.Diagnostics
    64  
    65  	if len(attrs) == 0 {
    66  		return diags
    67  	}
    68  
    69  	// Get a list of sorted attribute names so the output will be consistent between runs.
    70  	keys := make([]string, 0, len(attrs))
    71  	for k := range attrs {
    72  		keys = append(keys, k)
    73  	}
    74  	sort.Strings(keys)
    75  
    76  	for i := range keys {
    77  		name := keys[i]
    78  		attrS := attrs[name]
    79  		if attrS.NestedType != nil {
    80  			diags = diags.Append(writeConfigNestedTypeAttribute(addr, buf, name, attrS, indent))
    81  			continue
    82  		}
    83  		if attrS.Required {
    84  			buf.WriteString(strings.Repeat(" ", indent))
    85  			buf.WriteString(fmt.Sprintf("%s = ", name))
    86  			tok := hclwrite.TokensForValue(attrS.EmptyValue())
    87  			if _, err := tok.WriteTo(buf); err != nil {
    88  				diags = diags.Append(&hcl.Diagnostic{
    89  					Severity: hcl.DiagWarning,
    90  					Summary:  "Skipped part of config generation",
    91  					Detail:   fmt.Sprintf("Could not create attribute %s in %s when generating import configuration. The plan will likely report the missing attribute as being deleted.", name, addr),
    92  					Extra:    err,
    93  				})
    94  				continue
    95  			}
    96  			writeAttrTypeConstraint(buf, attrS)
    97  		} else if attrS.Optional {
    98  			buf.WriteString(strings.Repeat(" ", indent))
    99  			buf.WriteString(fmt.Sprintf("%s = ", name))
   100  			tok := hclwrite.TokensForValue(attrS.EmptyValue())
   101  			if _, err := tok.WriteTo(buf); err != nil {
   102  				diags = diags.Append(&hcl.Diagnostic{
   103  					Severity: hcl.DiagWarning,
   104  					Summary:  "Skipped part of config generation",
   105  					Detail:   fmt.Sprintf("Could not create attribute %s in %s when generating import configuration. The plan will likely report the missing attribute as being deleted.", name, addr),
   106  					Extra:    err,
   107  				})
   108  				continue
   109  			}
   110  			writeAttrTypeConstraint(buf, attrS)
   111  		}
   112  	}
   113  	return diags
   114  }
   115  
   116  func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, stateVal cty.Value, attrs map[string]*configschema.Attribute, indent int) tfdiags.Diagnostics {
   117  	var diags tfdiags.Diagnostics
   118  	if len(attrs) == 0 {
   119  		return diags
   120  	}
   121  
   122  	// Get a list of sorted attribute names so the output will be consistent between runs.
   123  	keys := make([]string, 0, len(attrs))
   124  	for k := range attrs {
   125  		keys = append(keys, k)
   126  	}
   127  	sort.Strings(keys)
   128  
   129  	for i := range keys {
   130  		name := keys[i]
   131  		attrS := attrs[name]
   132  		if attrS.NestedType != nil {
   133  			writeConfigNestedTypeAttributeFromExisting(addr, buf, name, attrS, stateVal, indent)
   134  			continue
   135  		}
   136  
   137  		// Exclude computed-only attributes
   138  		if attrS.Required || attrS.Optional {
   139  			buf.WriteString(strings.Repeat(" ", indent))
   140  			buf.WriteString(fmt.Sprintf("%s = ", name))
   141  
   142  			var val cty.Value
   143  			if !stateVal.IsNull() && stateVal.Type().HasAttribute(name) {
   144  				val = stateVal.GetAttr(name)
   145  			} else {
   146  				val = attrS.EmptyValue()
   147  			}
   148  			if val.Type() == cty.String {
   149  				// SHAMELESS HACK: If we have "" for an optional value, assume
   150  				// it is actually null, due to the legacy SDK.
   151  				if !val.IsNull() && attrS.Optional && len(val.AsString()) == 0 {
   152  					val = attrS.EmptyValue()
   153  				}
   154  			}
   155  			if attrS.Sensitive || val.IsMarked() {
   156  				buf.WriteString("null # sensitive")
   157  			} else {
   158  				tok := hclwrite.TokensForValue(val)
   159  				if _, err := tok.WriteTo(buf); err != nil {
   160  					diags = diags.Append(&hcl.Diagnostic{
   161  						Severity: hcl.DiagWarning,
   162  						Summary:  "Skipped part of config generation",
   163  						Detail:   fmt.Sprintf("Could not create attribute %s in %s when generating import configuration. The plan will likely report the missing attribute as being deleted.", name, addr),
   164  						Extra:    err,
   165  					})
   166  					continue
   167  				}
   168  			}
   169  
   170  			buf.WriteString("\n")
   171  		}
   172  	}
   173  	return diags
   174  }
   175  
   176  func writeConfigBlocks(addr addrs.AbsResourceInstance, buf *strings.Builder, blocks map[string]*configschema.NestedBlock, indent int) tfdiags.Diagnostics {
   177  	var diags tfdiags.Diagnostics
   178  
   179  	if len(blocks) == 0 {
   180  		return diags
   181  	}
   182  
   183  	// Get a list of sorted block names so the output will be consistent between runs.
   184  	names := make([]string, 0, len(blocks))
   185  	for k := range blocks {
   186  		names = append(names, k)
   187  	}
   188  	sort.Strings(names)
   189  
   190  	for i := range names {
   191  		name := names[i]
   192  		blockS := blocks[name]
   193  		diags = diags.Append(writeConfigNestedBlock(addr, buf, name, blockS, indent))
   194  	}
   195  	return diags
   196  }
   197  
   198  func writeConfigNestedBlock(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.NestedBlock, indent int) tfdiags.Diagnostics {
   199  	var diags tfdiags.Diagnostics
   200  
   201  	switch schema.Nesting {
   202  	case configschema.NestingSingle, configschema.NestingGroup:
   203  		buf.WriteString(strings.Repeat(" ", indent))
   204  		buf.WriteString(fmt.Sprintf("%s {", name))
   205  		writeBlockTypeConstraint(buf, schema)
   206  		diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2))
   207  		diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2))
   208  		buf.WriteString("}\n")
   209  		return diags
   210  	case configschema.NestingList, configschema.NestingSet:
   211  		buf.WriteString(strings.Repeat(" ", indent))
   212  		buf.WriteString(fmt.Sprintf("%s {", name))
   213  		writeBlockTypeConstraint(buf, schema)
   214  		diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2))
   215  		diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2))
   216  		buf.WriteString("}\n")
   217  		return diags
   218  	case configschema.NestingMap:
   219  		buf.WriteString(strings.Repeat(" ", indent))
   220  		// we use an arbitrary placeholder key (block label) "key"
   221  		buf.WriteString(fmt.Sprintf("%s \"key\" {", name))
   222  		writeBlockTypeConstraint(buf, schema)
   223  		diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2))
   224  		diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2))
   225  		buf.WriteString(strings.Repeat(" ", indent))
   226  		buf.WriteString("}\n")
   227  		return diags
   228  	default:
   229  		// This should not happen, the above should be exhaustive.
   230  		panic(fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String()))
   231  	}
   232  }
   233  
   234  func writeConfigNestedTypeAttribute(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.Attribute, indent int) tfdiags.Diagnostics {
   235  	var diags tfdiags.Diagnostics
   236  
   237  	buf.WriteString(strings.Repeat(" ", indent))
   238  	buf.WriteString(fmt.Sprintf("%s = ", name))
   239  
   240  	switch schema.NestedType.Nesting {
   241  	case configschema.NestingSingle:
   242  		buf.WriteString("{")
   243  		writeAttrTypeConstraint(buf, schema)
   244  		diags = diags.Append(writeConfigAttributes(addr, buf, schema.NestedType.Attributes, indent+2))
   245  		buf.WriteString(strings.Repeat(" ", indent))
   246  		buf.WriteString("}\n")
   247  		return diags
   248  	case configschema.NestingList, configschema.NestingSet:
   249  		buf.WriteString("[{")
   250  		writeAttrTypeConstraint(buf, schema)
   251  		diags = diags.Append(writeConfigAttributes(addr, buf, schema.NestedType.Attributes, indent+2))
   252  		buf.WriteString(strings.Repeat(" ", indent))
   253  		buf.WriteString("}]\n")
   254  		return diags
   255  	case configschema.NestingMap:
   256  		buf.WriteString("{")
   257  		writeAttrTypeConstraint(buf, schema)
   258  		buf.WriteString(strings.Repeat(" ", indent+2))
   259  		// we use an arbitrary placeholder key "key"
   260  		buf.WriteString("key = {\n")
   261  		diags = diags.Append(writeConfigAttributes(addr, buf, schema.NestedType.Attributes, indent+4))
   262  		buf.WriteString(strings.Repeat(" ", indent+2))
   263  		buf.WriteString("}\n")
   264  		buf.WriteString(strings.Repeat(" ", indent))
   265  		buf.WriteString("}\n")
   266  		return diags
   267  	default:
   268  		// This should not happen, the above should be exhaustive.
   269  		panic(fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String()))
   270  	}
   271  }
   272  
   273  func writeConfigBlocksFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, stateVal cty.Value, blocks map[string]*configschema.NestedBlock, indent int) tfdiags.Diagnostics {
   274  	var diags tfdiags.Diagnostics
   275  
   276  	if len(blocks) == 0 {
   277  		return diags
   278  	}
   279  
   280  	// Get a list of sorted block names so the output will be consistent between runs.
   281  	names := make([]string, 0, len(blocks))
   282  	for k := range blocks {
   283  		names = append(names, k)
   284  	}
   285  	sort.Strings(names)
   286  
   287  	for _, name := range names {
   288  		blockS := blocks[name]
   289  		// This shouldn't happen in real usage; state always has all values (set
   290  		// to null as needed), but it protects against panics in tests (and any
   291  		// really weird and unlikely cases).
   292  		if !stateVal.Type().HasAttribute(name) {
   293  			continue
   294  		}
   295  		blockVal := stateVal.GetAttr(name)
   296  		diags = diags.Append(writeConfigNestedBlockFromExisting(addr, buf, name, blockS, blockVal, indent))
   297  	}
   298  
   299  	return diags
   300  }
   301  
   302  func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.Attribute, stateVal cty.Value, indent int) tfdiags.Diagnostics {
   303  	var diags tfdiags.Diagnostics
   304  
   305  	switch schema.NestedType.Nesting {
   306  	case configschema.NestingSingle:
   307  		if schema.Sensitive || stateVal.IsMarked() {
   308  			buf.WriteString(strings.Repeat(" ", indent))
   309  			buf.WriteString(fmt.Sprintf("%s = {} # sensitive\n", name))
   310  			return diags
   311  		}
   312  
   313  		// This shouldn't happen in real usage; state always has all values (set
   314  		// to null as needed), but it protects against panics in tests (and any
   315  		// really weird and unlikely cases).
   316  		if !stateVal.Type().HasAttribute(name) {
   317  			return diags
   318  		}
   319  		nestedVal := stateVal.GetAttr(name)
   320  
   321  		if nestedVal.IsNull() {
   322  			// There is a difference between a null object, and an object with
   323  			// no attributes.
   324  			buf.WriteString(strings.Repeat(" ", indent))
   325  			buf.WriteString(fmt.Sprintf("%s = null\n", name))
   326  			return diags
   327  		}
   328  
   329  		buf.WriteString(strings.Repeat(" ", indent))
   330  		buf.WriteString(fmt.Sprintf("%s = {\n", name))
   331  		diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, nestedVal, schema.NestedType.Attributes, indent+2))
   332  		buf.WriteString("}\n")
   333  		return diags
   334  
   335  	case configschema.NestingList, configschema.NestingSet:
   336  
   337  		if schema.Sensitive || stateVal.IsMarked() {
   338  			buf.WriteString(strings.Repeat(" ", indent))
   339  			buf.WriteString(fmt.Sprintf("%s = [] # sensitive\n", name))
   340  			return diags
   341  		}
   342  
   343  		listVals := ctyCollectionValues(stateVal.GetAttr(name))
   344  		if listVals == nil {
   345  			// There is a difference between an empty list and a null list
   346  			buf.WriteString(strings.Repeat(" ", indent))
   347  			buf.WriteString(fmt.Sprintf("%s = null\n", name))
   348  			return diags
   349  		}
   350  
   351  		buf.WriteString(strings.Repeat(" ", indent))
   352  		buf.WriteString(fmt.Sprintf("%s = [\n", name))
   353  		for i := range listVals {
   354  			buf.WriteString(strings.Repeat(" ", indent+2))
   355  
   356  			// The entire element is marked.
   357  			if listVals[i].IsMarked() {
   358  				buf.WriteString("{}, # sensitive\n")
   359  				continue
   360  			}
   361  
   362  			buf.WriteString("{\n")
   363  			diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, listVals[i], schema.NestedType.Attributes, indent+4))
   364  			buf.WriteString(strings.Repeat(" ", indent+2))
   365  			buf.WriteString("},\n")
   366  		}
   367  		buf.WriteString(strings.Repeat(" ", indent))
   368  		buf.WriteString("]\n")
   369  		return diags
   370  
   371  	case configschema.NestingMap:
   372  		if schema.Sensitive || stateVal.IsMarked() {
   373  			buf.WriteString(strings.Repeat(" ", indent))
   374  			buf.WriteString(fmt.Sprintf("%s = {} # sensitive\n", name))
   375  			return diags
   376  		}
   377  
   378  		attr := stateVal.GetAttr(name)
   379  		if attr.IsNull() {
   380  			// There is a difference between an empty map and a null map.
   381  			buf.WriteString(strings.Repeat(" ", indent))
   382  			buf.WriteString(fmt.Sprintf("%s = null\n", name))
   383  			return diags
   384  		}
   385  
   386  		vals := attr.AsValueMap()
   387  		keys := make([]string, 0, len(vals))
   388  		for key := range vals {
   389  			keys = append(keys, key)
   390  		}
   391  		sort.Strings(keys)
   392  
   393  		buf.WriteString(strings.Repeat(" ", indent))
   394  		buf.WriteString(fmt.Sprintf("%s = {\n", name))
   395  		for _, key := range keys {
   396  			buf.WriteString(strings.Repeat(" ", indent+2))
   397  			buf.WriteString(fmt.Sprintf("%s = {", key))
   398  
   399  			// This entire value is marked
   400  			if vals[key].IsMarked() {
   401  				buf.WriteString("} # sensitive\n")
   402  				continue
   403  			}
   404  
   405  			buf.WriteString("\n")
   406  			diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, vals[key], schema.NestedType.Attributes, indent+4))
   407  			buf.WriteString(strings.Repeat(" ", indent+2))
   408  			buf.WriteString("}\n")
   409  		}
   410  		buf.WriteString(strings.Repeat(" ", indent))
   411  		buf.WriteString("}\n")
   412  		return diags
   413  
   414  	default:
   415  		// This should not happen, the above should be exhaustive.
   416  		panic(fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String()))
   417  	}
   418  }
   419  
   420  func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.NestedBlock, stateVal cty.Value, indent int) tfdiags.Diagnostics {
   421  	var diags tfdiags.Diagnostics
   422  
   423  	switch schema.Nesting {
   424  	case configschema.NestingSingle, configschema.NestingGroup:
   425  		if stateVal.IsNull() {
   426  			return diags
   427  		}
   428  		buf.WriteString(strings.Repeat(" ", indent))
   429  		buf.WriteString(fmt.Sprintf("%s {", name))
   430  
   431  		// If the entire value is marked, don't print any nested attributes
   432  		if stateVal.IsMarked() {
   433  			buf.WriteString("} # sensitive\n")
   434  			return diags
   435  		}
   436  		buf.WriteString("\n")
   437  		diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, stateVal, schema.Attributes, indent+2))
   438  		diags = diags.Append(writeConfigBlocksFromExisting(addr, buf, stateVal, schema.BlockTypes, indent+2))
   439  		buf.WriteString("}\n")
   440  		return diags
   441  	case configschema.NestingList, configschema.NestingSet:
   442  		if stateVal.IsMarked() {
   443  			buf.WriteString(strings.Repeat(" ", indent))
   444  			buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name))
   445  			return diags
   446  		}
   447  		listVals := ctyCollectionValues(stateVal)
   448  		for i := range listVals {
   449  			buf.WriteString(strings.Repeat(" ", indent))
   450  			buf.WriteString(fmt.Sprintf("%s {\n", name))
   451  			diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, listVals[i], schema.Attributes, indent+2))
   452  			diags = diags.Append(writeConfigBlocksFromExisting(addr, buf, listVals[i], schema.BlockTypes, indent+2))
   453  			buf.WriteString("}\n")
   454  		}
   455  		return diags
   456  	case configschema.NestingMap:
   457  		// If the entire value is marked, don't print any nested attributes
   458  		if stateVal.IsMarked() {
   459  			buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name))
   460  			return diags
   461  		}
   462  
   463  		vals := stateVal.AsValueMap()
   464  		keys := make([]string, 0, len(vals))
   465  		for key := range vals {
   466  			keys = append(keys, key)
   467  		}
   468  		sort.Strings(keys)
   469  		for _, key := range keys {
   470  			buf.WriteString(strings.Repeat(" ", indent))
   471  			buf.WriteString(fmt.Sprintf("%s %q {", name, key))
   472  			// This entire map element is marked
   473  			if vals[key].IsMarked() {
   474  				buf.WriteString("} # sensitive\n")
   475  				return diags
   476  			}
   477  			buf.WriteString("\n")
   478  			diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, vals[key], schema.Attributes, indent+2))
   479  			diags = diags.Append(writeConfigBlocksFromExisting(addr, buf, vals[key], schema.BlockTypes, indent+2))
   480  			buf.WriteString(strings.Repeat(" ", indent))
   481  			buf.WriteString("}\n")
   482  		}
   483  		return diags
   484  	default:
   485  		// This should not happen, the above should be exhaustive.
   486  		panic(fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String()))
   487  	}
   488  }
   489  
   490  func writeAttrTypeConstraint(buf *strings.Builder, schema *configschema.Attribute) {
   491  	if schema.Required {
   492  		buf.WriteString(" # REQUIRED ")
   493  	} else {
   494  		buf.WriteString(" # OPTIONAL ")
   495  	}
   496  
   497  	if schema.NestedType != nil {
   498  		buf.WriteString(fmt.Sprintf("%s\n", schema.NestedType.ImpliedType().FriendlyName()))
   499  	} else {
   500  		buf.WriteString(fmt.Sprintf("%s\n", schema.Type.FriendlyName()))
   501  	}
   502  }
   503  
   504  func writeBlockTypeConstraint(buf *strings.Builder, schema *configschema.NestedBlock) {
   505  	if schema.MinItems > 0 {
   506  		buf.WriteString(" # REQUIRED block\n")
   507  	} else {
   508  		buf.WriteString(" # OPTIONAL block\n")
   509  	}
   510  }
   511  
   512  // copied from command/format/diff
   513  func ctyCollectionValues(val cty.Value) []cty.Value {
   514  	if !val.IsKnown() || val.IsNull() {
   515  		return nil
   516  	}
   517  
   518  	var len int
   519  	if val.IsMarked() {
   520  		val, _ = val.Unmark()
   521  		len = val.LengthInt()
   522  	} else {
   523  		len = val.LengthInt()
   524  	}
   525  
   526  	ret := make([]cty.Value, 0, len)
   527  	for it := val.ElementIterator(); it.Next(); {
   528  		_, value := it.Element()
   529  		ret = append(ret, value)
   530  	}
   531  
   532  	return ret
   533  }
   534  
   535  // omitUnknowns recursively walks the src cty.Value and returns a new cty.Value,
   536  // omitting any unknowns.
   537  //
   538  // The result also normalizes some types: all sequence types are turned into
   539  // tuple types and all mapping types are converted to object types, since we
   540  // assume the result of this is just going to be serialized as JSON (and thus
   541  // lose those distinctions) anyway.
   542  func omitUnknowns(val cty.Value) cty.Value {
   543  	ty := val.Type()
   544  	switch {
   545  	case val.IsNull():
   546  		return val
   547  	case !val.IsKnown():
   548  		return cty.NilVal
   549  	case ty.IsPrimitiveType():
   550  		return val
   551  	case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
   552  		var vals []cty.Value
   553  		it := val.ElementIterator()
   554  		for it.Next() {
   555  			_, v := it.Element()
   556  			newVal := omitUnknowns(v)
   557  			if newVal != cty.NilVal {
   558  				vals = append(vals, newVal)
   559  			} else if newVal == cty.NilVal {
   560  				// element order is how we correlate unknownness, so we must
   561  				// replace unknowns with nulls
   562  				vals = append(vals, cty.NullVal(v.Type()))
   563  			}
   564  		}
   565  		// We use tuple types always here, because the work we did above
   566  		// may have caused the individual elements to have different types,
   567  		// and we're doing this work to produce JSON anyway and JSON marshalling
   568  		// represents all of these sequence types as an array.
   569  		return cty.TupleVal(vals)
   570  	case ty.IsMapType() || ty.IsObjectType():
   571  		vals := make(map[string]cty.Value)
   572  		it := val.ElementIterator()
   573  		for it.Next() {
   574  			k, v := it.Element()
   575  			newVal := omitUnknowns(v)
   576  			if newVal != cty.NilVal {
   577  				vals[k.AsString()] = newVal
   578  			}
   579  		}
   580  		// We use object types always here, because the work we did above
   581  		// may have caused the individual elements to have different types,
   582  		// and we're doing this work to produce JSON anyway and JSON marshalling
   583  		// represents both of these mapping types as an object.
   584  		return cty.ObjectVal(vals)
   585  	default:
   586  		// Should never happen, since the above should cover all types
   587  		panic(fmt.Sprintf("omitUnknowns cannot handle %#v", val))
   588  	}
   589  }