github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/helper/plugin/unknown.go (about)

     1  package plugin
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
     7  	"github.com/zclconf/go-cty/cty"
     8  )
     9  
    10  // SetUnknowns takes a cty.Value, and compares it to the schema setting any null
    11  // values which are computed to unknown.
    12  func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
    13  	if !val.IsKnown() {
    14  		return val
    15  	}
    16  
    17  	// If the object was null, we still need to handle the top level attributes
    18  	// which might be computed, but we don't need to expand the blocks.
    19  	if val.IsNull() {
    20  		objMap := map[string]cty.Value{}
    21  		allNull := true
    22  		for name, attr := range schema.Attributes {
    23  			switch {
    24  			case attr.Computed:
    25  				objMap[name] = cty.UnknownVal(attr.Type)
    26  				allNull = false
    27  			default:
    28  				objMap[name] = cty.NullVal(attr.Type)
    29  			}
    30  		}
    31  
    32  		// If this object has no unknown attributes, then we can leave it null.
    33  		if allNull {
    34  			return val
    35  		}
    36  
    37  		return cty.ObjectVal(objMap)
    38  	}
    39  
    40  	valMap := val.AsValueMap()
    41  	newVals := make(map[string]cty.Value)
    42  
    43  	for name, attr := range schema.Attributes {
    44  		v := valMap[name]
    45  
    46  		if attr.Computed && v.IsNull() {
    47  			newVals[name] = cty.UnknownVal(attr.Type)
    48  			continue
    49  		}
    50  
    51  		newVals[name] = v
    52  	}
    53  
    54  	for name, blockS := range schema.BlockTypes {
    55  		blockVal := valMap[name]
    56  		if blockVal.IsNull() || !blockVal.IsKnown() {
    57  			newVals[name] = blockVal
    58  			continue
    59  		}
    60  
    61  		blockValType := blockVal.Type()
    62  		blockElementType := blockS.Block.ImpliedType()
    63  
    64  		// This switches on the value type here, so we can correctly switch
    65  		// between Tuples/Lists and Maps/Objects.
    66  		switch {
    67  		case blockS.Nesting == configschema.NestingSingle || blockS.Nesting == configschema.NestingGroup:
    68  			// NestingSingle is the only exception here, where we treat the
    69  			// block directly as an object
    70  			newVals[name] = SetUnknowns(blockVal, &blockS.Block)
    71  
    72  		case blockValType.IsSetType(), blockValType.IsListType(), blockValType.IsTupleType():
    73  			listVals := blockVal.AsValueSlice()
    74  			newListVals := make([]cty.Value, 0, len(listVals))
    75  
    76  			for _, v := range listVals {
    77  				newListVals = append(newListVals, SetUnknowns(v, &blockS.Block))
    78  			}
    79  
    80  			switch {
    81  			case blockValType.IsSetType():
    82  				switch len(newListVals) {
    83  				case 0:
    84  					newVals[name] = cty.SetValEmpty(blockElementType)
    85  				default:
    86  					newVals[name] = cty.SetVal(newListVals)
    87  				}
    88  			case blockValType.IsListType():
    89  				switch len(newListVals) {
    90  				case 0:
    91  					newVals[name] = cty.ListValEmpty(blockElementType)
    92  				default:
    93  					newVals[name] = cty.ListVal(newListVals)
    94  				}
    95  			case blockValType.IsTupleType():
    96  				newVals[name] = cty.TupleVal(newListVals)
    97  			}
    98  
    99  		case blockValType.IsMapType(), blockValType.IsObjectType():
   100  			mapVals := blockVal.AsValueMap()
   101  			newMapVals := make(map[string]cty.Value)
   102  
   103  			for k, v := range mapVals {
   104  				newMapVals[k] = SetUnknowns(v, &blockS.Block)
   105  			}
   106  
   107  			switch {
   108  			case blockValType.IsMapType():
   109  				switch len(newMapVals) {
   110  				case 0:
   111  					newVals[name] = cty.MapValEmpty(blockElementType)
   112  				default:
   113  					newVals[name] = cty.MapVal(newMapVals)
   114  				}
   115  			case blockValType.IsObjectType():
   116  				if len(newMapVals) == 0 {
   117  					// We need to populate empty values to make a valid object.
   118  					for attr, ty := range blockElementType.AttributeTypes() {
   119  						newMapVals[attr] = cty.NullVal(ty)
   120  					}
   121  				}
   122  				newVals[name] = cty.ObjectVal(newMapVals)
   123  			}
   124  
   125  		default:
   126  			panic(fmt.Sprintf("failed to set unknown values for nested block %q:%#v", name, blockValType))
   127  		}
   128  	}
   129  
   130  	return cty.ObjectVal(newVals)
   131  }