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 }