github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/terraform/parser/funcs/defaults.go (about) 1 // Copied from github.com/hashicorp/terraform/internal/lang/funcs 2 package funcs 3 4 import ( 5 "fmt" 6 7 "github.com/zclconf/go-cty/cty" 8 "github.com/zclconf/go-cty/cty/convert" 9 "github.com/zclconf/go-cty/cty/function" 10 ) 11 12 // DefaultsFunc is a helper function for substituting default values in 13 // place of null values in a given data structure. 14 // 15 // See the documentation for function Defaults for more information. 16 var DefaultsFunc = function.New(&function.Spec{ 17 Params: []function.Parameter{ 18 { 19 Name: "input", 20 Type: cty.DynamicPseudoType, 21 AllowNull: true, 22 AllowMarked: true, 23 }, 24 { 25 Name: "defaults", 26 Type: cty.DynamicPseudoType, 27 AllowMarked: true, 28 }, 29 }, 30 Type: func(args []cty.Value) (cty.Type, error) { 31 // The result type is guaranteed to be the same as the input type, 32 // since all we're doing is replacing null values with non-null 33 // values of the same type. 34 retType := args[0].Type() 35 defaultsType := args[1].Type() 36 37 // This function is aimed at filling in object types or collections 38 // of object types where some of the attributes might be null, so 39 // it doesn't make sense to use a primitive type directly with it. 40 // (The "coalesce" function may be appropriate for such cases.) 41 if retType.IsPrimitiveType() { 42 // This error message is a bit of a fib because we can actually 43 // apply defaults to tuples too, but we expect that to be so 44 // unusual as to not be worth mentioning here, because mentioning 45 // it would require using some less-well-known Terraform language 46 // terminology in the message (tuple types, structural types). 47 return cty.DynamicPseudoType, function.NewArgErrorf(1, "only object types and collections of object types can have defaults applied") 48 } 49 50 defaultsPath := make(cty.Path, 0, 4) // some capacity so that most structures won't reallocate 51 if err := defaultsAssertSuitableFallback(retType, defaultsType, defaultsPath); err != nil { 52 errMsg := err.Error() 53 return cty.DynamicPseudoType, function.NewArgErrorf(1, "%s", errMsg) 54 } 55 56 return retType, nil 57 }, 58 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 59 if args[0].Type().HasDynamicTypes() { 60 // If the types our input object aren't known yet for some reason 61 // then we'll defer all of our work here, because our 62 // interpretation of the defaults depends on the types in 63 // the input. 64 return cty.UnknownVal(retType), nil 65 } 66 67 v := defaultsApply(args[0], args[1]) 68 return v, nil 69 }, 70 }) 71 72 func defaultsApply(input, fallback cty.Value) cty.Value { 73 wantTy := input.Type() 74 75 umInput, inputMarks := input.Unmark() 76 umFb, fallbackMarks := fallback.Unmark() 77 78 // If neither are known, we very conservatively return an unknown value 79 // with the union of marks on both input and default. 80 if !(umInput.IsKnown() && umFb.IsKnown()) { 81 return cty.UnknownVal(wantTy).WithMarks(inputMarks).WithMarks(fallbackMarks) 82 } 83 84 // For the rest of this function we're assuming that the given defaults 85 // will always be valid, because we expect to have caught any problems 86 // during the type checking phase. Any inconsistencies that reach here are 87 // therefore considered to be implementation bugs, and so will panic. 88 89 // Our strategy depends on the kind of type we're working with. 90 switch { 91 case wantTy.IsPrimitiveType(): 92 // For leaf primitive values the rule is relatively simple: use the 93 // input if it's non-null, or fallback if input is null. 94 if !umInput.IsNull() { 95 return input 96 } 97 v, err := convert.Convert(umFb, wantTy) 98 if err != nil { 99 // Should not happen because we checked in defaultsAssertSuitableFallback 100 panic(err.Error()) 101 } 102 return v.WithMarks(fallbackMarks) 103 104 case wantTy.IsObjectType(): 105 // For structural types, a null input value must be passed through. We 106 // do not apply default values for missing optional structural values, 107 // only their contents. 108 // 109 // We also pass through the input if the fallback value is null. This 110 // can happen if the given defaults do not include a value for this 111 // attribute. 112 if umInput.IsNull() || umFb.IsNull() { 113 return input 114 } 115 atys := wantTy.AttributeTypes() 116 ret := map[string]cty.Value{} 117 for attr, aty := range atys { 118 inputSub := umInput.GetAttr(attr) 119 fallbackSub := cty.NullVal(aty) 120 if umFb.Type().HasAttribute(attr) { 121 fallbackSub = umFb.GetAttr(attr) 122 } 123 ret[attr] = defaultsApply(inputSub.WithMarks(inputMarks), fallbackSub.WithMarks(fallbackMarks)) 124 } 125 return cty.ObjectVal(ret) 126 127 case wantTy.IsTupleType(): 128 // For structural types, a null input value must be passed through. We 129 // do not apply default values for missing optional structural values, 130 // only their contents. 131 // 132 // We also pass through the input if the fallback value is null. This 133 // can happen if the given defaults do not include a value for this 134 // attribute. 135 if umInput.IsNull() || umFb.IsNull() { 136 return input 137 } 138 139 l := wantTy.Length() 140 ret := make([]cty.Value, l) 141 for i := 0; i < l; i++ { 142 inputSub := umInput.Index(cty.NumberIntVal(int64(i))) 143 fallbackSub := umFb.Index(cty.NumberIntVal(int64(i))) 144 ret[i] = defaultsApply(inputSub.WithMarks(inputMarks), fallbackSub.WithMarks(fallbackMarks)) 145 } 146 return cty.TupleVal(ret) 147 148 case wantTy.IsCollectionType(): 149 // For collection types we apply a single fallback value to each 150 // element of the input collection, because in the situations this 151 // function is intended for we assume that the number of elements 152 // is the caller's decision, and so we'll just apply the same defaults 153 // to all of the elements. 154 ety := wantTy.ElementType() 155 switch { 156 case wantTy.IsMapType(): 157 newVals := map[string]cty.Value{} 158 159 if !umInput.IsNull() { 160 for it := umInput.ElementIterator(); it.Next(); { 161 k, v := it.Element() 162 newVals[k.AsString()] = defaultsApply(v.WithMarks(inputMarks), fallback.WithMarks(fallbackMarks)) 163 } 164 } 165 166 if len(newVals) == 0 { 167 return cty.MapValEmpty(ety) 168 } 169 return cty.MapVal(newVals) 170 case wantTy.IsListType(), wantTy.IsSetType(): 171 var newVals []cty.Value 172 173 if !umInput.IsNull() { 174 for it := umInput.ElementIterator(); it.Next(); { 175 _, v := it.Element() 176 newV := defaultsApply(v.WithMarks(inputMarks), fallback.WithMarks(fallbackMarks)) 177 newVals = append(newVals, newV) 178 } 179 } 180 181 if len(newVals) == 0 { 182 if wantTy.IsSetType() { 183 return cty.SetValEmpty(ety) 184 } 185 return cty.ListValEmpty(ety) 186 } 187 if wantTy.IsSetType() { 188 return cty.SetVal(newVals) 189 } 190 return cty.ListVal(newVals) 191 default: 192 // There are no other collection types, so this should not happen 193 panic(fmt.Sprintf("invalid collection type %#v", wantTy)) 194 } 195 default: 196 // We should've caught anything else in defaultsAssertSuitableFallback, 197 // so this should not happen. 198 panic(fmt.Sprintf("invalid target type %#v", wantTy)) 199 } 200 } 201 202 func defaultsAssertSuitableFallback(wantTy, fallbackTy cty.Type, fallbackPath cty.Path) error { 203 // If the type we want is a collection type then we need to keep peeling 204 // away collection type wrappers until we find the non-collection-type 205 // that's underneath, which is what the fallback will actually be applied 206 // to. 207 inCollection := false 208 for wantTy.IsCollectionType() { 209 wantTy = wantTy.ElementType() 210 inCollection = true 211 } 212 213 switch { 214 case wantTy.IsPrimitiveType(): 215 // The fallback is valid if it's equal to or convertible to what we want. 216 if fallbackTy.Equals(wantTy) { 217 return nil 218 } 219 conversion := convert.GetConversion(fallbackTy, wantTy) 220 if conversion == nil { 221 msg := convert.MismatchMessage(fallbackTy, wantTy) 222 return fallbackPath.NewErrorf("invalid default value for %s: %s", wantTy.FriendlyName(), msg) 223 } 224 return nil 225 case wantTy.IsObjectType(): 226 if !fallbackTy.IsObjectType() { 227 if inCollection { 228 return fallbackPath.NewErrorf("the default value for a collection of an object type must itself be an object type, not %s", fallbackTy.FriendlyName()) 229 } 230 return fallbackPath.NewErrorf("the default value for an object type must itself be an object type, not %s", fallbackTy.FriendlyName()) 231 } 232 for attr, wantAty := range wantTy.AttributeTypes() { 233 if !fallbackTy.HasAttribute(attr) { 234 continue // it's always okay to not have a default value 235 } 236 fallbackSubpath := fallbackPath.GetAttr(attr) 237 fallbackSubTy := fallbackTy.AttributeType(attr) 238 err := defaultsAssertSuitableFallback(wantAty, fallbackSubTy, fallbackSubpath) 239 if err != nil { 240 return err 241 } 242 } 243 for attr := range fallbackTy.AttributeTypes() { 244 if !wantTy.HasAttribute(attr) { 245 fallbackSubpath := fallbackPath.GetAttr(attr) 246 return fallbackSubpath.NewErrorf("target type does not expect an attribute named %q", attr) 247 } 248 } 249 return nil 250 case wantTy.IsTupleType(): 251 if !fallbackTy.IsTupleType() { 252 if inCollection { 253 return fallbackPath.NewErrorf("the default value for a collection of a tuple type must itself be a tuple type, not %s", fallbackTy.FriendlyName()) 254 } 255 return fallbackPath.NewErrorf("the default value for a tuple type must itself be a tuple type, not %s", fallbackTy.FriendlyName()) 256 } 257 wantEtys := wantTy.TupleElementTypes() 258 fallbackEtys := fallbackTy.TupleElementTypes() 259 if got, want := len(wantEtys), len(fallbackEtys); got != want { 260 return fallbackPath.NewErrorf("the default value for a tuple type of length %d must also have length %d, not %d", want, want, got) 261 } 262 for i := 0; i < len(wantEtys); i++ { 263 fallbackSubpath := fallbackPath.IndexInt(i) 264 wantSubTy := wantEtys[i] 265 fallbackSubTy := fallbackEtys[i] 266 err := defaultsAssertSuitableFallback(wantSubTy, fallbackSubTy, fallbackSubpath) 267 if err != nil { 268 return err 269 } 270 } 271 return nil 272 default: 273 // No other types are supported right now. 274 return fallbackPath.NewErrorf("cannot apply defaults to %s", wantTy.FriendlyName()) 275 } 276 } 277 278 // Defaults is a helper function for substituting default values in 279 // place of null values in a given data structure. 280 // 281 // This is primarily intended for use with a module input variable that 282 // has an object type constraint (or a collection thereof) that has optional 283 // attributes, so that the receiver of a value that omits those attributes 284 // can insert non-null default values in place of the null values caused by 285 // omitting the attributes. 286 func Defaults(input, defaults cty.Value) (cty.Value, error) { 287 return DefaultsFunc.Call([]cty.Value{input, defaults}) 288 }