github.com/opentofu/opentofu@v1.7.1/internal/genconfig/generate_config.go (about)

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