github.com/opentofu/opentofu@v1.7.1/internal/lang/funcs/collection.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 funcs 7 8 import ( 9 "errors" 10 "fmt" 11 "math/big" 12 "sort" 13 14 "github.com/zclconf/go-cty/cty" 15 "github.com/zclconf/go-cty/cty/convert" 16 "github.com/zclconf/go-cty/cty/function" 17 "github.com/zclconf/go-cty/cty/function/stdlib" 18 "github.com/zclconf/go-cty/cty/gocty" 19 ) 20 21 var LengthFunc = function.New(&function.Spec{ 22 Params: []function.Parameter{ 23 { 24 Name: "value", 25 Type: cty.DynamicPseudoType, 26 AllowDynamicType: true, 27 AllowUnknown: true, 28 AllowMarked: true, 29 }, 30 }, 31 Type: func(args []cty.Value) (cty.Type, error) { 32 collTy := args[0].Type() 33 switch { 34 case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType: 35 return cty.Number, nil 36 default: 37 return cty.Number, errors.New("argument must be a string, a collection type, or a structural type") 38 } 39 }, 40 RefineResult: refineNotNull, 41 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 42 coll := args[0] 43 collTy := args[0].Type() 44 marks := coll.Marks() 45 switch { 46 case collTy == cty.DynamicPseudoType: 47 return cty.UnknownVal(cty.Number).WithMarks(marks), nil 48 case collTy.IsTupleType(): 49 l := len(collTy.TupleElementTypes()) 50 return cty.NumberIntVal(int64(l)).WithMarks(marks), nil 51 case collTy.IsObjectType(): 52 l := len(collTy.AttributeTypes()) 53 return cty.NumberIntVal(int64(l)).WithMarks(marks), nil 54 case collTy == cty.String: 55 // We'll delegate to the cty stdlib strlen function here, because 56 // it deals with all of the complexities of tokenizing unicode 57 // grapheme clusters. 58 return stdlib.Strlen(coll) 59 case collTy.IsListType() || collTy.IsSetType() || collTy.IsMapType(): 60 return coll.Length(), nil 61 default: 62 // Should never happen, because of the checks in our Type func above 63 return cty.UnknownVal(cty.Number), errors.New("impossible value type for length(...)") 64 } 65 }, 66 }) 67 68 // AllTrueFunc constructs a function that returns true if all elements of the 69 // list are true. If the list is empty, return true. 70 var AllTrueFunc = function.New(&function.Spec{ 71 Params: []function.Parameter{ 72 { 73 Name: "list", 74 Type: cty.List(cty.Bool), 75 }, 76 }, 77 Type: function.StaticReturnType(cty.Bool), 78 RefineResult: refineNotNull, 79 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 80 result := cty.True 81 for it := args[0].ElementIterator(); it.Next(); { 82 _, v := it.Element() 83 if !v.IsKnown() { 84 return cty.UnknownVal(cty.Bool), nil 85 } 86 if v.IsNull() { 87 return cty.False, nil 88 } 89 result = result.And(v) 90 if result.False() { 91 return cty.False, nil 92 } 93 } 94 return result, nil 95 }, 96 }) 97 98 // AnyTrueFunc constructs a function that returns true if any element of the 99 // list is true. If the list is empty, return false. 100 var AnyTrueFunc = function.New(&function.Spec{ 101 Params: []function.Parameter{ 102 { 103 Name: "list", 104 Type: cty.List(cty.Bool), 105 }, 106 }, 107 Type: function.StaticReturnType(cty.Bool), 108 RefineResult: refineNotNull, 109 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 110 result := cty.False 111 var hasUnknown bool 112 for it := args[0].ElementIterator(); it.Next(); { 113 _, v := it.Element() 114 if !v.IsKnown() { 115 hasUnknown = true 116 continue 117 } 118 if v.IsNull() { 119 continue 120 } 121 result = result.Or(v) 122 if result.True() { 123 return cty.True, nil 124 } 125 } 126 if hasUnknown { 127 return cty.UnknownVal(cty.Bool), nil 128 } 129 return result, nil 130 }, 131 }) 132 133 // CoalesceFunc constructs a function that takes any number of arguments and 134 // returns the first one that isn't empty. This function was copied from go-cty 135 // stdlib and modified so that it returns the first *non-empty* non-null element 136 // from a sequence, instead of merely the first non-null. 137 var CoalesceFunc = function.New(&function.Spec{ 138 Params: []function.Parameter{}, 139 VarParam: &function.Parameter{ 140 Name: "vals", 141 Type: cty.DynamicPseudoType, 142 AllowUnknown: true, 143 AllowDynamicType: true, 144 AllowNull: true, 145 }, 146 Type: func(args []cty.Value) (ret cty.Type, err error) { 147 argTypes := make([]cty.Type, len(args)) 148 for i, val := range args { 149 argTypes[i] = val.Type() 150 } 151 retType, _ := convert.UnifyUnsafe(argTypes) 152 if retType == cty.NilType { 153 return cty.NilType, errors.New("all arguments must have the same type") 154 } 155 return retType, nil 156 }, 157 RefineResult: refineNotNull, 158 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 159 for _, argVal := range args { 160 // We already know this will succeed because of the checks in our Type func above 161 argVal, _ = convert.Convert(argVal, retType) 162 if !argVal.IsKnown() { 163 return cty.UnknownVal(retType), nil 164 } 165 if argVal.IsNull() { 166 continue 167 } 168 if retType == cty.String && argVal.RawEquals(cty.StringVal("")) { 169 continue 170 } 171 172 return argVal, nil 173 } 174 return cty.NilVal, errors.New("no non-null, non-empty-string arguments") 175 }, 176 }) 177 178 // IndexFunc constructs a function that finds the element index for a given value in a list. 179 var IndexFunc = function.New(&function.Spec{ 180 Params: []function.Parameter{ 181 { 182 Name: "list", 183 Type: cty.DynamicPseudoType, 184 }, 185 { 186 Name: "value", 187 Type: cty.DynamicPseudoType, 188 }, 189 }, 190 Type: function.StaticReturnType(cty.Number), 191 RefineResult: refineNotNull, 192 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 193 if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) { 194 return cty.NilVal, errors.New("argument must be a list or tuple") 195 } 196 197 if !args[0].IsKnown() { 198 return cty.UnknownVal(cty.Number), nil 199 } 200 201 if args[0].LengthInt() == 0 { // Easy path 202 return cty.NilVal, errors.New("cannot search an empty list") 203 } 204 205 for it := args[0].ElementIterator(); it.Next(); { 206 i, v := it.Element() 207 eq, err := stdlib.Equal(v, args[1]) 208 if err != nil { 209 return cty.NilVal, err 210 } 211 if !eq.IsKnown() { 212 return cty.UnknownVal(cty.Number), nil 213 } 214 if eq.True() { 215 return i, nil 216 } 217 } 218 return cty.NilVal, errors.New("item not found") 219 220 }, 221 }) 222 223 // LookupFunc constructs a function that performs dynamic lookups of map types. 224 var LookupFunc = function.New(&function.Spec{ 225 Params: []function.Parameter{ 226 { 227 Name: "inputMap", 228 Type: cty.DynamicPseudoType, 229 AllowMarked: true, 230 }, 231 { 232 Name: "key", 233 Type: cty.String, 234 AllowMarked: true, 235 }, 236 }, 237 VarParam: &function.Parameter{ 238 Name: "default", 239 Type: cty.DynamicPseudoType, 240 AllowUnknown: true, 241 AllowDynamicType: true, 242 AllowNull: true, 243 AllowMarked: true, 244 }, 245 Type: func(args []cty.Value) (ret cty.Type, err error) { 246 if len(args) < 1 || len(args) > 3 { 247 return cty.NilType, fmt.Errorf("lookup() takes two or three arguments, got %d", len(args)) 248 } 249 250 ty := args[0].Type() 251 252 switch { 253 case ty.IsObjectType(): 254 if !args[1].IsKnown() { 255 return cty.DynamicPseudoType, nil 256 } 257 258 keyVal, _ := args[1].Unmark() 259 key := keyVal.AsString() 260 if ty.HasAttribute(key) { 261 return args[0].GetAttr(key).Type(), nil 262 } else if len(args) == 3 { 263 // if the key isn't found but a default is provided, 264 // return the default type 265 return args[2].Type(), nil 266 } 267 return cty.DynamicPseudoType, function.NewArgErrorf(0, "the given object has no attribute %q", key) 268 case ty.IsMapType(): 269 if len(args) == 3 { 270 _, err = convert.Convert(args[2], ty.ElementType()) 271 if err != nil { 272 return cty.NilType, function.NewArgErrorf(2, "the default value must have the same type as the map elements") 273 } 274 } 275 return ty.ElementType(), nil 276 default: 277 return cty.NilType, function.NewArgErrorf(0, "lookup() requires a map as the first argument") 278 } 279 }, 280 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 281 var defaultVal cty.Value 282 defaultValueSet := false 283 284 if len(args) == 3 { 285 // intentionally leave default value marked 286 defaultVal = args[2] 287 defaultValueSet = true 288 } 289 290 // keep track of marks from the collection and key 291 var markses []cty.ValueMarks 292 293 // unmark collection, retain marks to reapply later 294 mapVar, mapMarks := args[0].Unmark() 295 markses = append(markses, mapMarks) 296 297 // include marks on the key in the result 298 keyVal, keyMarks := args[1].Unmark() 299 if len(keyMarks) > 0 { 300 markses = append(markses, keyMarks) 301 } 302 lookupKey := keyVal.AsString() 303 304 if !mapVar.IsKnown() { 305 return cty.UnknownVal(retType).WithMarks(markses...), nil 306 } 307 308 if mapVar.Type().IsObjectType() { 309 if mapVar.Type().HasAttribute(lookupKey) { 310 return mapVar.GetAttr(lookupKey).WithMarks(markses...), nil 311 } 312 } else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True { 313 return mapVar.Index(cty.StringVal(lookupKey)).WithMarks(markses...), nil 314 } 315 316 if defaultValueSet { 317 defaultVal, err = convert.Convert(defaultVal, retType) 318 if err != nil { 319 return cty.NilVal, err 320 } 321 return defaultVal.WithMarks(markses...), nil 322 } 323 324 return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf( 325 "lookup failed to find key %s", redactIfSensitive(lookupKey, keyMarks)) 326 }, 327 }) 328 329 // MatchkeysFunc constructs a function that constructs a new list by taking a 330 // subset of elements from one list whose indexes match the corresponding 331 // indexes of values in another list. 332 var MatchkeysFunc = function.New(&function.Spec{ 333 Params: []function.Parameter{ 334 { 335 Name: "values", 336 Type: cty.List(cty.DynamicPseudoType), 337 }, 338 { 339 Name: "keys", 340 Type: cty.List(cty.DynamicPseudoType), 341 }, 342 { 343 Name: "searchset", 344 Type: cty.List(cty.DynamicPseudoType), 345 }, 346 }, 347 Type: func(args []cty.Value) (cty.Type, error) { 348 ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()}) 349 if ty == cty.NilType { 350 return cty.NilType, errors.New("keys and searchset must be of the same type") 351 } 352 353 // the return type is based on args[0] (values) 354 return args[0].Type(), nil 355 }, 356 RefineResult: refineNotNull, 357 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 358 if !args[0].IsKnown() { 359 return cty.UnknownVal(cty.List(retType.ElementType())), nil 360 } 361 362 if args[0].LengthInt() != args[1].LengthInt() { 363 return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal") 364 } 365 366 output := make([]cty.Value, 0) 367 values := args[0] 368 369 // Keys and searchset must be the same type. 370 // We can skip error checking here because we've already verified that 371 // they can be unified in the Type function 372 ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()}) 373 keys, _ := convert.Convert(args[1], ty) 374 searchset, _ := convert.Convert(args[2], ty) 375 376 // if searchset is empty, return an empty list. 377 if searchset.LengthInt() == 0 { 378 return cty.ListValEmpty(retType.ElementType()), nil 379 } 380 381 if !values.IsWhollyKnown() || !keys.IsWhollyKnown() { 382 return cty.UnknownVal(retType), nil 383 } 384 385 i := 0 386 for it := keys.ElementIterator(); it.Next(); { 387 _, key := it.Element() 388 for iter := searchset.ElementIterator(); iter.Next(); { 389 _, search := iter.Element() 390 eq, err := stdlib.Equal(key, search) 391 if err != nil { 392 return cty.NilVal, err 393 } 394 if !eq.IsKnown() { 395 return cty.ListValEmpty(retType.ElementType()), nil 396 } 397 if eq.True() { 398 v := values.Index(cty.NumberIntVal(int64(i))) 399 output = append(output, v) 400 break 401 } 402 } 403 i++ 404 } 405 406 // if we haven't matched any key, then output is an empty list. 407 if len(output) == 0 { 408 return cty.ListValEmpty(retType.ElementType()), nil 409 } 410 return cty.ListVal(output), nil 411 }, 412 }) 413 414 // OneFunc returns either the first element of a one-element list, or null 415 // if given a zero-element list. 416 var OneFunc = function.New(&function.Spec{ 417 Params: []function.Parameter{ 418 { 419 Name: "list", 420 Type: cty.DynamicPseudoType, 421 }, 422 }, 423 Type: func(args []cty.Value) (cty.Type, error) { 424 ty := args[0].Type() 425 switch { 426 case ty.IsListType() || ty.IsSetType(): 427 return ty.ElementType(), nil 428 case ty.IsTupleType(): 429 etys := ty.TupleElementTypes() 430 switch len(etys) { 431 case 0: 432 // No specific type information, so we'll ultimately return 433 // a null value of unknown type. 434 return cty.DynamicPseudoType, nil 435 case 1: 436 return etys[0], nil 437 } 438 } 439 return cty.NilType, function.NewArgErrorf(0, "must be a list, set, or tuple value with either zero or one elements") 440 }, 441 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 442 val := args[0] 443 ty := val.Type() 444 445 // Our parameter spec above doesn't set AllowUnknown or AllowNull, 446 // so we can assume our top-level collection is both known and non-null 447 // in here. 448 449 switch { 450 case ty.IsListType() || ty.IsSetType(): 451 lenVal := val.Length() 452 if !lenVal.IsKnown() { 453 return cty.UnknownVal(retType), nil 454 } 455 var l int 456 err := gocty.FromCtyValue(lenVal, &l) 457 if err != nil { 458 // It would be very strange to get here, because that would 459 // suggest that the length is either not a number or isn't 460 // an integer, which would suggest a bug in cty. 461 return cty.NilVal, fmt.Errorf("invalid collection length: %w", err) 462 } 463 switch l { 464 case 0: 465 return cty.NullVal(retType), nil 466 case 1: 467 var ret cty.Value 468 // We'll use an iterator here because that works for both lists 469 // and sets, whereas indexing directly would only work for lists. 470 // Since we've just checked the length, we should only actually 471 // run this loop body once. 472 for it := val.ElementIterator(); it.Next(); { 473 _, ret = it.Element() 474 } 475 return ret, nil 476 } 477 case ty.IsTupleType(): 478 etys := ty.TupleElementTypes() 479 switch len(etys) { 480 case 0: 481 return cty.NullVal(retType), nil 482 case 1: 483 ret := val.Index(cty.NumberIntVal(0)) 484 return ret, nil 485 } 486 } 487 return cty.NilVal, function.NewArgErrorf(0, "must be a list, set, or tuple value with either zero or one elements") 488 }, 489 }) 490 491 // SumFunc constructs a function that returns the sum of all 492 // numbers provided in a list 493 var SumFunc = function.New(&function.Spec{ 494 Params: []function.Parameter{ 495 { 496 Name: "list", 497 Type: cty.DynamicPseudoType, 498 }, 499 }, 500 Type: function.StaticReturnType(cty.Number), 501 RefineResult: refineNotNull, 502 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 503 504 if !args[0].CanIterateElements() { 505 return cty.NilVal, function.NewArgErrorf(0, "cannot sum noniterable") 506 } 507 508 if args[0].LengthInt() == 0 { // Easy path 509 return cty.NilVal, function.NewArgErrorf(0, "cannot sum an empty list") 510 } 511 512 arg := args[0].AsValueSlice() 513 ty := args[0].Type() 514 515 if !ty.IsListType() && !ty.IsSetType() && !ty.IsTupleType() { 516 return cty.NilVal, function.NewArgErrorf(0, fmt.Sprintf("argument must be list, set, or tuple. Received %s", ty.FriendlyName())) 517 } 518 519 if !args[0].IsWhollyKnown() { 520 return cty.UnknownVal(cty.Number), nil 521 } 522 523 // big.Float.Add can panic if the input values are opposing infinities, 524 // so we must catch that here in order to remain within 525 // the cty Function abstraction. 526 defer func() { 527 if r := recover(); r != nil { 528 if _, ok := r.(big.ErrNaN); ok { 529 ret = cty.NilVal 530 err = fmt.Errorf("can't compute sum of opposing infinities") 531 } else { 532 // not a panic we recognize 533 panic(r) 534 } 535 } 536 }() 537 538 s := arg[0] 539 if s.IsNull() { 540 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 541 } 542 s, err = convert.Convert(s, cty.Number) 543 if err != nil { 544 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 545 } 546 for _, v := range arg[1:] { 547 if v.IsNull() { 548 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 549 } 550 v, err = convert.Convert(v, cty.Number) 551 if err != nil { 552 return cty.NilVal, function.NewArgErrorf(0, "argument must be list, set, or tuple of number values") 553 } 554 s = s.Add(v) 555 } 556 557 return s, nil 558 }, 559 }) 560 561 // TransposeFunc constructs a function that takes a map of lists of strings and 562 // swaps the keys and values to produce a new map of lists of strings. 563 var TransposeFunc = function.New(&function.Spec{ 564 Params: []function.Parameter{ 565 { 566 Name: "values", 567 Type: cty.Map(cty.List(cty.String)), 568 }, 569 }, 570 Type: function.StaticReturnType(cty.Map(cty.List(cty.String))), 571 RefineResult: refineNotNull, 572 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 573 inputMap := args[0] 574 if !inputMap.IsWhollyKnown() { 575 return cty.UnknownVal(retType), nil 576 } 577 578 outputMap := make(map[string]cty.Value) 579 tmpMap := make(map[string][]string) 580 581 for it := inputMap.ElementIterator(); it.Next(); { 582 inKey, inVal := it.Element() 583 for iter := inVal.ElementIterator(); iter.Next(); { 584 _, val := iter.Element() 585 if !val.Type().Equals(cty.String) { 586 return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings") 587 } 588 589 outKey := val.AsString() 590 if _, ok := tmpMap[outKey]; !ok { 591 tmpMap[outKey] = make([]string, 0) 592 } 593 outVal := tmpMap[outKey] 594 outVal = append(outVal, inKey.AsString()) 595 sort.Strings(outVal) 596 tmpMap[outKey] = outVal 597 } 598 } 599 600 for outKey, outVal := range tmpMap { 601 values := make([]cty.Value, 0) 602 for _, v := range outVal { 603 values = append(values, cty.StringVal(v)) 604 } 605 outputMap[outKey] = cty.ListVal(values) 606 } 607 608 if len(outputMap) == 0 { 609 return cty.MapValEmpty(cty.List(cty.String)), nil 610 } 611 612 return cty.MapVal(outputMap), nil 613 }, 614 }) 615 616 // ListFunc constructs a function that takes an arbitrary number of arguments 617 // and returns a list containing those values in the same order. 618 // 619 // This function is deprecated in Terraform v0.12 620 var ListFunc = function.New(&function.Spec{ 621 Params: []function.Parameter{}, 622 VarParam: &function.Parameter{ 623 Name: "vals", 624 Type: cty.DynamicPseudoType, 625 AllowUnknown: true, 626 AllowDynamicType: true, 627 AllowNull: true, 628 }, 629 Type: func(args []cty.Value) (ret cty.Type, err error) { 630 return cty.DynamicPseudoType, fmt.Errorf("the \"list\" function was deprecated in Terraform v0.12 and is not available in OpenTofu; use tolist([ ... ]) syntax to write a literal list") 631 }, 632 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 633 return cty.DynamicVal, fmt.Errorf("the \"list\" function was deprecated in Terraform v0.12 and is not available in OpenTofu; use tolist([ ... ]) syntax to write a literal list") 634 }, 635 }) 636 637 // MapFunc constructs a function that takes an even number of arguments and 638 // returns a map whose elements are constructed from consecutive pairs of arguments. 639 // 640 // This function is deprecated in Terraform v0.12 641 var MapFunc = function.New(&function.Spec{ 642 Params: []function.Parameter{}, 643 VarParam: &function.Parameter{ 644 Name: "vals", 645 Type: cty.DynamicPseudoType, 646 AllowUnknown: true, 647 AllowDynamicType: true, 648 AllowNull: true, 649 }, 650 Type: func(args []cty.Value) (ret cty.Type, err error) { 651 return cty.DynamicPseudoType, fmt.Errorf("the \"map\" function was deprecated in Terraform v0.12 and is not available in OpenTofu; use tomap({ ... }) syntax to write a literal map") 652 }, 653 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 654 return cty.DynamicVal, fmt.Errorf("the \"map\" function was deprecated in Terraform v0.12 and is not available in OpenTofu; use tomap({ ... }) syntax to write a literal map") 655 }, 656 }) 657 658 // Length returns the number of elements in the given collection or number of 659 // Unicode characters in the given string. 660 func Length(collection cty.Value) (cty.Value, error) { 661 return LengthFunc.Call([]cty.Value{collection}) 662 } 663 664 // AllTrue returns true if all elements of the list are true. If the list is empty, 665 // return true. 666 func AllTrue(collection cty.Value) (cty.Value, error) { 667 return AllTrueFunc.Call([]cty.Value{collection}) 668 } 669 670 // AnyTrue returns true if any element of the list is true. If the list is empty, 671 // return false. 672 func AnyTrue(collection cty.Value) (cty.Value, error) { 673 return AnyTrueFunc.Call([]cty.Value{collection}) 674 } 675 676 // Coalesce takes any number of arguments and returns the first one that isn't empty. 677 func Coalesce(args ...cty.Value) (cty.Value, error) { 678 return CoalesceFunc.Call(args) 679 } 680 681 // Index finds the element index for a given value in a list. 682 func Index(list, value cty.Value) (cty.Value, error) { 683 return IndexFunc.Call([]cty.Value{list, value}) 684 } 685 686 // List takes any number of arguments of types that can unify into a single 687 // type and returns a list containing those values in the same order, or 688 // returns an error if there is no single element type that all values can 689 // convert to. 690 func List(args ...cty.Value) (cty.Value, error) { 691 return ListFunc.Call(args) 692 } 693 694 // Lookup performs a dynamic lookup into a map. 695 // There are two required arguments, map and key, plus an optional default, 696 // which is a value to return if no key is found in map. 697 func Lookup(args ...cty.Value) (cty.Value, error) { 698 return LookupFunc.Call(args) 699 } 700 701 // Map takes an even number of arguments and returns a map whose elements are constructed 702 // from consecutive pairs of arguments. 703 func Map(args ...cty.Value) (cty.Value, error) { 704 return MapFunc.Call(args) 705 } 706 707 // Matchkeys constructs a new list by taking a subset of elements from one list 708 // whose indexes match the corresponding indexes of values in another list. 709 func Matchkeys(values, keys, searchset cty.Value) (cty.Value, error) { 710 return MatchkeysFunc.Call([]cty.Value{values, keys, searchset}) 711 } 712 713 // One returns either the first element of a one-element list, or null 714 // if given a zero-element list.. 715 func One(list cty.Value) (cty.Value, error) { 716 return OneFunc.Call([]cty.Value{list}) 717 } 718 719 // Sum adds numbers in a list, set, or tuple 720 func Sum(list cty.Value) (cty.Value, error) { 721 return SumFunc.Call([]cty.Value{list}) 722 } 723 724 // Transpose takes a map of lists of strings and swaps the keys and values to 725 // produce a new map of lists of strings. 726 func Transpose(values cty.Value) (cty.Value, error) { 727 return TransposeFunc.Call([]cty.Value{values}) 728 }