2018-05-22 02:39:26 +02:00
package funcs
import (
2019-04-30 19:12:00 +02:00
"errors"
2018-05-22 02:39:26 +02:00
"fmt"
2018-05-31 23:46:24 +02:00
"sort"
2018-05-22 02:39:26 +02:00
"github.com/zclconf/go-cty/cty"
2018-05-24 20:48:44 +02:00
"github.com/zclconf/go-cty/cty/convert"
2018-05-22 02:39:26 +02:00
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib"
"github.com/zclconf/go-cty/cty/gocty"
)
var ElementFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
Type : cty . DynamicPseudoType ,
} ,
{
Name : "index" ,
Type : cty . Number ,
} ,
} ,
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
list := args [ 0 ]
listTy := list . Type ( )
switch {
case listTy . IsListType ( ) :
return listTy . ElementType ( ) , nil
case listTy . IsTupleType ( ) :
2018-10-15 23:07:46 +02:00
if ! args [ 1 ] . IsKnown ( ) {
// If the index isn't known yet then we can't predict the
// result type since each tuple element can have its own type.
return cty . DynamicPseudoType , nil
}
2018-05-22 02:39:26 +02:00
etys := listTy . TupleElementTypes ( )
var index int
err := gocty . FromCtyValue ( args [ 1 ] , & index )
if err != nil {
// e.g. fractional number where whole number is required
return cty . DynamicPseudoType , fmt . Errorf ( "invalid index: %s" , err )
}
if len ( etys ) == 0 {
2019-05-01 23:55:44 +02:00
return cty . DynamicPseudoType , errors . New ( "cannot use element function with an empty list" )
2018-05-22 02:39:26 +02:00
}
index = index % len ( etys )
return etys [ index ] , nil
default :
return cty . DynamicPseudoType , fmt . Errorf ( "cannot read elements from %s" , listTy . FriendlyName ( ) )
}
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( cty . Value , error ) {
var index int
err := gocty . FromCtyValue ( args [ 1 ] , & index )
if err != nil {
// can't happen because we checked this in the Type function above
return cty . DynamicVal , fmt . Errorf ( "invalid index: %s" , err )
}
2018-10-19 01:19:59 +02:00
if ! args [ 0 ] . IsKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
2018-05-22 02:39:26 +02:00
l := args [ 0 ] . LengthInt ( )
if l == 0 {
2019-05-01 23:55:44 +02:00
return cty . DynamicVal , errors . New ( "cannot use element function with an empty list" )
2018-05-22 02:39:26 +02:00
}
index = index % l
// We did all the necessary type checks in the type function above,
// so this is guaranteed not to fail.
return args [ 0 ] . Index ( cty . NumberIntVal ( int64 ( index ) ) ) , nil
} ,
} )
var LengthFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "value" ,
Type : cty . DynamicPseudoType ,
AllowDynamicType : true ,
AllowUnknown : true ,
} ,
} ,
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
collTy := args [ 0 ] . Type ( )
switch {
2018-11-06 02:13:05 +01:00
case collTy == cty . String || collTy . IsTupleType ( ) || collTy . IsObjectType ( ) || collTy . IsListType ( ) || collTy . IsMapType ( ) || collTy . IsSetType ( ) || collTy == cty . DynamicPseudoType :
2018-05-22 02:39:26 +02:00
return cty . Number , nil
default :
2019-05-01 23:55:44 +02:00
return cty . Number , errors . New ( "argument must be a string, a collection type, or a structural type" )
2018-05-22 02:39:26 +02:00
}
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( cty . Value , error ) {
coll := args [ 0 ]
collTy := args [ 0 ] . Type ( )
switch {
case collTy == cty . DynamicPseudoType :
return cty . UnknownVal ( cty . Number ) , nil
case collTy . IsTupleType ( ) :
l := len ( collTy . TupleElementTypes ( ) )
return cty . NumberIntVal ( int64 ( l ) ) , nil
case collTy . IsObjectType ( ) :
l := len ( collTy . AttributeTypes ( ) )
return cty . NumberIntVal ( int64 ( l ) ) , nil
case collTy == cty . String :
// We'll delegate to the cty stdlib strlen function here, because
// it deals with all of the complexities of tokenizing unicode
// grapheme clusters.
return stdlib . Strlen ( coll )
case collTy . IsListType ( ) || collTy . IsSetType ( ) || collTy . IsMapType ( ) :
return coll . Length ( ) , nil
default :
// Should never happen, because of the checks in our Type func above
2019-05-01 23:55:44 +02:00
return cty . UnknownVal ( cty . Number ) , errors . New ( "impossible value type for length(...)" )
2018-05-22 02:39:26 +02:00
}
} ,
} )
2019-05-02 16:47:19 +02:00
// CoalesceFunc constructs a function that takes any number of arguments and
2019-04-12 19:57:52 +02:00
// returns the first one that isn't empty. This function was copied from go-cty
// stdlib and modified so that it returns the first *non-empty* non-null element
// from a sequence, instead of merely the first non-null.
var CoalesceFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter { } ,
VarParam : & function . Parameter {
Name : "vals" ,
Type : cty . DynamicPseudoType ,
AllowUnknown : true ,
AllowDynamicType : true ,
AllowNull : true ,
} ,
Type : func ( args [ ] cty . Value ) ( ret cty . Type , err error ) {
argTypes := make ( [ ] cty . Type , len ( args ) )
for i , val := range args {
argTypes [ i ] = val . Type ( )
}
retType , _ := convert . UnifyUnsafe ( argTypes )
if retType == cty . NilType {
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "all arguments must have the same type" )
2019-04-12 19:57:52 +02:00
}
return retType , nil
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
for _ , argVal := range args {
// We already know this will succeed because of the checks in our Type func above
argVal , _ = convert . Convert ( argVal , retType )
if ! argVal . IsKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
if argVal . IsNull ( ) {
continue
}
if retType == cty . String && argVal . RawEquals ( cty . StringVal ( "" ) ) {
continue
}
return argVal , nil
}
2019-05-01 23:55:44 +02:00
return cty . NilVal , errors . New ( "no non-null, non-empty-string arguments" )
2019-04-12 19:57:52 +02:00
} ,
} )
2019-05-02 16:47:19 +02:00
// CoalesceListFunc constructs a function that takes any number of list arguments
2018-05-24 20:48:44 +02:00
// and returns the first one that isn't empty.
var CoalesceListFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter { } ,
VarParam : & function . Parameter {
Name : "vals" ,
2019-05-01 23:55:44 +02:00
Type : cty . DynamicPseudoType ,
2018-05-24 20:48:44 +02:00
AllowUnknown : true ,
AllowDynamicType : true ,
AllowNull : true ,
} ,
Type : func ( args [ ] cty . Value ) ( ret cty . Type , err error ) {
if len ( args ) == 0 {
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "at least one argument is required" )
2018-05-24 20:48:44 +02:00
}
argTypes := make ( [ ] cty . Type , len ( args ) )
for i , arg := range args {
2019-05-01 23:55:44 +02:00
// if any argument is unknown, we can't be certain know which type we will return
if ! arg . IsKnown ( ) {
return cty . DynamicPseudoType , nil
}
ty := arg . Type ( )
if ! ty . IsListType ( ) && ! ty . IsTupleType ( ) {
return cty . NilType , errors . New ( "coalescelist arguments must be lists or tuples" )
}
2018-05-24 20:48:44 +02:00
argTypes [ i ] = arg . Type ( )
}
2019-05-01 23:55:44 +02:00
last := argTypes [ 0 ]
// If there are mixed types, we have to return a dynamic type.
for _ , next := range argTypes [ 1 : ] {
if ! next . Equals ( last ) {
return cty . DynamicPseudoType , nil
}
2018-05-24 23:46:03 +02:00
}
2018-05-24 20:48:44 +02:00
2019-05-01 23:55:44 +02:00
return last , nil
2018-05-24 20:48:44 +02:00
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
for _ , arg := range args {
2018-10-15 23:43:02 +02:00
if ! arg . IsKnown ( ) {
// If we run into an unknown list at some point, we can't
// predict the final result yet. (If there's a known, non-empty
// arg before this then we won't get here.)
return cty . UnknownVal ( retType ) , nil
}
2018-05-24 20:48:44 +02:00
2019-05-01 23:55:44 +02:00
if arg . LengthInt ( ) > 0 {
return arg , nil
2018-05-24 20:48:44 +02:00
}
}
2019-05-01 23:55:44 +02:00
return cty . NilVal , errors . New ( "no non-null arguments" )
2018-05-24 20:48:44 +02:00
} ,
} )
2019-05-02 16:47:19 +02:00
// CompactFunc constructs a function that takes a list of strings and returns a new list
2018-05-24 23:46:03 +02:00
// with any empty string elements removed.
var CompactFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
Type : cty . List ( cty . String ) ,
} ,
} ,
Type : function . StaticReturnType ( cty . List ( cty . String ) ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2018-06-06 19:43:58 +02:00
listVal := args [ 0 ]
if ! listVal . IsWhollyKnown ( ) {
// If some of the element values aren't known yet then we
// can't yet return a compacted list
return cty . UnknownVal ( retType ) , nil
}
2018-05-24 23:46:03 +02:00
var outputList [ ] cty . Value
2018-06-06 19:43:58 +02:00
for it := listVal . ElementIterator ( ) ; it . Next ( ) ; {
2018-05-24 23:46:03 +02:00
_ , v := it . Element ( )
2019-07-11 19:31:22 +02:00
if v . IsNull ( ) || v . AsString ( ) == "" {
2018-05-24 23:46:03 +02:00
continue
}
outputList = append ( outputList , v )
}
2018-05-26 01:30:50 +02:00
if len ( outputList ) == 0 {
return cty . ListValEmpty ( cty . String ) , nil
}
2018-05-24 23:46:03 +02:00
return cty . ListVal ( outputList ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// ContainsFunc constructs a function that determines whether a given list or
// set contains a given single value as one of its elements.
2018-05-25 21:57:26 +02:00
var ContainsFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
2019-05-02 00:13:06 +02:00
Type : cty . DynamicPseudoType ,
2018-05-25 21:57:26 +02:00
} ,
{
Name : "value" ,
Type : cty . DynamicPseudoType ,
} ,
} ,
Type : function . StaticReturnType ( cty . Bool ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2019-05-02 00:13:06 +02:00
arg := args [ 0 ]
ty := arg . Type ( )
if ! ty . IsListType ( ) && ! ty . IsTupleType ( ) && ! ty . IsSetType ( ) {
return cty . NilVal , errors . New ( "argument must be list, tuple, or set" )
}
2018-05-25 21:57:26 +02:00
2019-05-02 00:13:06 +02:00
_ , err = Index ( cty . TupleVal ( arg . AsValueSlice ( ) ) , args [ 1 ] )
2018-05-25 21:57:26 +02:00
if err != nil {
return cty . False , nil
}
return cty . True , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// IndexFunc constructs a function that finds the element index for a given value in a list.
2018-05-25 21:57:26 +02:00
var IndexFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
Type : cty . DynamicPseudoType ,
} ,
{
Name : "value" ,
Type : cty . DynamicPseudoType ,
} ,
} ,
Type : function . StaticReturnType ( cty . Number ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2018-05-26 01:30:50 +02:00
if ! ( args [ 0 ] . Type ( ) . IsListType ( ) || args [ 0 ] . Type ( ) . IsTupleType ( ) ) {
2019-05-01 23:55:44 +02:00
return cty . NilVal , errors . New ( "argument must be a list or tuple" )
2018-05-26 01:30:50 +02:00
}
2018-10-19 01:19:59 +02:00
if ! args [ 0 ] . IsKnown ( ) {
return cty . UnknownVal ( cty . Number ) , nil
}
2018-05-25 21:57:26 +02:00
if args [ 0 ] . LengthInt ( ) == 0 { // Easy path
2019-05-01 23:55:44 +02:00
return cty . NilVal , errors . New ( "cannot search an empty list" )
2018-05-25 21:57:26 +02:00
}
for it := args [ 0 ] . ElementIterator ( ) ; it . Next ( ) ; {
i , v := it . Element ( )
eq , err := stdlib . Equal ( v , args [ 1 ] )
if err != nil {
return cty . NilVal , err
}
if ! eq . IsKnown ( ) {
return cty . UnknownVal ( cty . Number ) , nil
}
if eq . True ( ) {
return i , nil
}
}
2019-05-01 23:55:44 +02:00
return cty . NilVal , errors . New ( "item not found" )
2018-05-25 21:57:26 +02:00
} ,
} )
2019-05-02 16:47:19 +02:00
// DistinctFunc constructs a function that takes a list and returns a new list
2018-05-26 01:15:50 +02:00
// with any duplicate elements removed.
var DistinctFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
Type : cty . List ( cty . DynamicPseudoType ) ,
} ,
} ,
2018-06-06 19:43:58 +02:00
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
return args [ 0 ] . Type ( ) , nil
} ,
2018-05-26 01:15:50 +02:00
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2018-06-06 19:43:58 +02:00
listVal := args [ 0 ]
if ! listVal . IsWhollyKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
2018-05-26 01:15:50 +02:00
var list [ ] cty . Value
2018-06-06 19:43:58 +02:00
for it := listVal . ElementIterator ( ) ; it . Next ( ) ; {
2018-05-26 01:15:50 +02:00
_ , v := it . Element ( )
list , err = appendIfMissing ( list , v )
if err != nil {
return cty . NilVal , err
}
}
2019-05-31 16:37:23 +02:00
if len ( list ) == 0 {
return cty . ListValEmpty ( retType . ElementType ( ) ) , nil
}
2018-05-26 01:15:50 +02:00
return cty . ListVal ( list ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// ChunklistFunc constructs a function that splits a single list into fixed-size chunks,
2018-05-26 01:15:50 +02:00
// returning a list of lists.
var ChunklistFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
Type : cty . List ( cty . DynamicPseudoType ) ,
} ,
{
Name : "size" ,
Type : cty . Number ,
} ,
} ,
2018-05-29 19:05:58 +02:00
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
return cty . List ( args [ 0 ] . Type ( ) ) , nil
} ,
2018-05-26 01:15:50 +02:00
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2018-06-06 19:43:58 +02:00
listVal := args [ 0 ]
2019-05-01 21:29:12 +02:00
if ! listVal . IsKnown ( ) {
2018-06-06 19:43:58 +02:00
return cty . UnknownVal ( retType ) , nil
}
2019-07-18 16:01:00 +02:00
if listVal . LengthInt ( ) == 0 {
return cty . ListValEmpty ( listVal . Type ( ) ) , nil
}
2018-05-26 01:15:50 +02:00
var size int
err = gocty . FromCtyValue ( args [ 1 ] , & size )
if err != nil {
return cty . NilVal , fmt . Errorf ( "invalid index: %s" , err )
}
if size < 0 {
2019-05-01 23:55:44 +02:00
return cty . NilVal , errors . New ( "the size argument must be positive" )
2018-05-26 01:15:50 +02:00
}
output := make ( [ ] cty . Value , 0 )
// if size is 0, returns a list made of the initial list
if size == 0 {
2018-06-06 19:43:58 +02:00
output = append ( output , listVal )
2018-05-26 01:15:50 +02:00
return cty . ListVal ( output ) , nil
}
chunk := make ( [ ] cty . Value , 0 )
l := args [ 0 ] . LengthInt ( )
i := 0
2018-06-06 19:43:58 +02:00
for it := listVal . ElementIterator ( ) ; it . Next ( ) ; {
2018-05-26 01:15:50 +02:00
_ , v := it . Element ( )
chunk = append ( chunk , v )
// Chunk when index isn't 0, or when reaching the values's length
if ( i + 1 ) % size == 0 || ( i + 1 ) == l {
output = append ( output , cty . ListVal ( chunk ) )
chunk = make ( [ ] cty . Value , 0 )
}
i ++
}
return cty . ListVal ( output ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// FlattenFunc constructs a function that takes a list and replaces any elements
2018-05-30 16:26:19 +02:00
// that are lists with a flattened sequence of the list contents.
var FlattenFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
2019-05-01 16:19:40 +02:00
Type : cty . DynamicPseudoType ,
2018-05-30 16:26:19 +02:00
} ,
} ,
2019-05-01 16:19:40 +02:00
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
if ! args [ 0 ] . IsWhollyKnown ( ) {
return cty . DynamicPseudoType , nil
}
argTy := args [ 0 ] . Type ( )
if ! argTy . IsListType ( ) && ! argTy . IsSetType ( ) && ! argTy . IsTupleType ( ) {
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "can only flatten lists, sets and tuples" )
2019-05-01 16:19:40 +02:00
}
2019-05-01 20:22:50 +02:00
retVal , known := flattener ( args [ 0 ] )
if ! known {
return cty . DynamicPseudoType , nil
}
2019-05-01 16:19:40 +02:00
tys := make ( [ ] cty . Type , len ( retVal ) )
for i , ty := range retVal {
tys [ i ] = ty . Type ( )
}
return cty . Tuple ( tys ) , nil
} ,
2018-05-30 16:26:19 +02:00
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
inputList := args [ 0 ]
if inputList . LengthInt ( ) == 0 {
2019-05-01 16:19:40 +02:00
return cty . EmptyTupleVal , nil
2018-05-30 16:26:19 +02:00
}
2019-05-01 20:22:50 +02:00
out , known := flattener ( inputList )
if ! known {
return cty . UnknownVal ( retType ) , nil
}
return cty . TupleVal ( out ) , nil
2018-05-30 16:26:19 +02:00
} ,
} )
2019-05-01 20:22:50 +02:00
// Flatten until it's not a cty.List, and return whether the value is known.
// We can flatten lists with unknown values, as long as they are not
// lists themselves.
func flattener ( flattenList cty . Value ) ( [ ] cty . Value , bool ) {
out := make ( [ ] cty . Value , 0 )
2018-05-30 16:26:19 +02:00
for it := flattenList . ElementIterator ( ) ; it . Next ( ) ; {
_ , val := it . Element ( )
2019-05-01 16:19:40 +02:00
if val . Type ( ) . IsListType ( ) || val . Type ( ) . IsSetType ( ) || val . Type ( ) . IsTupleType ( ) {
2019-05-01 20:22:50 +02:00
if ! val . IsKnown ( ) {
return out , false
}
res , known := flattener ( val )
if ! known {
return res , known
}
out = append ( out , res ... )
2018-05-30 16:26:19 +02:00
} else {
2019-05-01 20:22:50 +02:00
out = append ( out , val )
2018-05-30 16:26:19 +02:00
}
}
2019-05-01 20:22:50 +02:00
return out , true
2018-05-30 16:26:19 +02:00
}
2019-05-02 16:47:19 +02:00
// KeysFunc constructs a function that takes a map and returns a sorted list of the map keys.
2018-05-31 00:38:55 +02:00
var KeysFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
2018-11-06 03:03:40 +01:00
Name : "inputMap" ,
Type : cty . DynamicPseudoType ,
AllowUnknown : true ,
2018-05-31 00:38:55 +02:00
} ,
} ,
2018-11-06 03:03:40 +01:00
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
2018-10-16 22:40:02 +02:00
ty := args [ 0 ] . Type ( )
2018-11-06 03:03:40 +01:00
switch {
case ty . IsMapType ( ) :
return cty . List ( cty . String ) , nil
case ty . IsObjectType ( ) :
atys := ty . AttributeTypes ( )
if len ( atys ) == 0 {
return cty . EmptyTuple , nil
}
// All of our result elements will be strings, and atys just
// decides how many there are.
etys := make ( [ ] cty . Type , len ( atys ) )
for i := range etys {
etys [ i ] = cty . String
}
return cty . Tuple ( etys ) , nil
default :
return cty . DynamicPseudoType , function . NewArgErrorf ( 0 , "must have map or object type" )
2018-10-16 22:40:02 +02:00
}
2018-11-06 03:03:40 +01:00
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( cty . Value , error ) {
m := args [ 0 ]
var keys [ ] cty . Value
2018-05-31 00:38:55 +02:00
2018-11-06 03:03:40 +01:00
switch {
case m . Type ( ) . IsObjectType ( ) :
// In this case we allow unknown values so we must work only with
// the attribute _types_, not with the value itself.
var names [ ] string
for name := range m . Type ( ) . AttributeTypes ( ) {
names = append ( names , name )
}
sort . Strings ( names ) // same ordering guaranteed by cty's ElementIterator
if len ( names ) == 0 {
return cty . EmptyTupleVal , nil
}
keys = make ( [ ] cty . Value , len ( names ) )
for i , name := range names {
keys [ i ] = cty . StringVal ( name )
2018-05-31 00:38:55 +02:00
}
2018-11-06 03:03:40 +01:00
return cty . TupleVal ( keys ) , nil
default :
if ! m . IsKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
// cty guarantees that ElementIterator will iterate in lexicographical
// order by key.
for it := args [ 0 ] . ElementIterator ( ) ; it . Next ( ) ; {
k , _ := it . Element ( )
keys = append ( keys , k )
}
if len ( keys ) == 0 {
return cty . ListValEmpty ( cty . String ) , nil
}
return cty . ListVal ( keys ) , nil
2018-05-31 00:38:55 +02:00
}
} ,
} )
2019-05-02 16:47:19 +02:00
// ListFunc constructs a function that takes an arbitrary number of arguments
2018-05-30 17:41:31 +02:00
// and returns a list containing those values in the same order.
//
// This function is deprecated in Terraform v0.12
var ListFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter { } ,
VarParam : & function . Parameter {
Name : "vals" ,
Type : cty . DynamicPseudoType ,
AllowUnknown : true ,
AllowDynamicType : true ,
AllowNull : true ,
} ,
Type : func ( args [ ] cty . Value ) ( ret cty . Type , err error ) {
if len ( args ) == 0 {
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "at least one argument is required" )
2018-05-30 17:41:31 +02:00
}
argTypes := make ( [ ] cty . Type , len ( args ) )
for i , arg := range args {
argTypes [ i ] = arg . Type ( )
}
retType , _ := convert . UnifyUnsafe ( argTypes )
if retType == cty . NilType {
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "all arguments must have the same type" )
2018-05-30 17:41:31 +02:00
}
return cty . List ( retType ) , nil
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
newList := make ( [ ] cty . Value , 0 , len ( args ) )
for _ , arg := range args {
// We already know this will succeed because of the checks in our Type func above
arg , _ = convert . Convert ( arg , retType . ElementType ( ) )
newList = append ( newList , arg )
}
return cty . ListVal ( newList ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// LookupFunc constructs a function that performs dynamic lookups of map types.
2018-05-31 18:33:43 +02:00
var LookupFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "inputMap" ,
2018-10-16 20:04:19 +02:00
Type : cty . DynamicPseudoType ,
2018-05-31 18:33:43 +02:00
} ,
{
Name : "key" ,
Type : cty . String ,
} ,
} ,
VarParam : & function . Parameter {
Name : "default" ,
Type : cty . DynamicPseudoType ,
AllowUnknown : true ,
AllowDynamicType : true ,
AllowNull : true ,
} ,
2018-06-06 19:43:58 +02:00
Type : func ( args [ ] cty . Value ) ( ret cty . Type , err error ) {
2018-05-31 18:33:43 +02:00
if len ( args ) < 1 || len ( args ) > 3 {
2018-06-06 19:43:58 +02:00
return cty . NilType , fmt . Errorf ( "lookup() takes two or three arguments, got %d" , len ( args ) )
2018-05-31 18:33:43 +02:00
}
2018-10-16 21:09:04 +02:00
2018-10-16 20:04:19 +02:00
ty := args [ 0 ] . Type ( )
2018-10-22 12:58:47 +02:00
2018-10-16 20:04:19 +02:00
switch {
case ty . IsObjectType ( ) :
2018-10-22 12:58:47 +02:00
if ! args [ 1 ] . IsKnown ( ) {
2018-10-23 12:42:46 +02:00
return cty . DynamicPseudoType , nil
2018-10-22 12:58:47 +02:00
}
key := args [ 1 ] . AsString ( )
2018-10-16 21:09:04 +02:00
if ty . HasAttribute ( key ) {
return args [ 0 ] . GetAttr ( key ) . Type ( ) , nil
} else if len ( args ) == 3 {
// if the key isn't found but a default is provided,
// return the default type
return args [ 2 ] . Type ( ) , nil
}
2018-10-16 23:55:57 +02:00
return cty . DynamicPseudoType , function . NewArgErrorf ( 0 , "the given object has no attribute %q" , key )
2018-10-16 21:57:05 +02:00
case ty . IsMapType ( ) :
2019-08-01 21:52:52 +02:00
if len ( args ) == 3 {
_ , err = convert . Convert ( args [ 2 ] , ty . ElementType ( ) )
if err != nil {
return cty . NilType , function . NewArgErrorf ( 2 , "the default value must have the same type as the map elements" )
}
}
2018-10-16 21:09:04 +02:00
return ty . ElementType ( ) , nil
2018-10-16 21:57:05 +02:00
default :
2018-10-16 23:55:57 +02:00
return cty . NilType , function . NewArgErrorf ( 0 , "lookup() requires a map as the first argument" )
2018-10-16 20:04:19 +02:00
}
2018-06-06 19:43:58 +02:00
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2018-06-06 17:09:33 +02:00
var defaultVal cty . Value
2018-05-31 18:33:43 +02:00
defaultValueSet := false
if len ( args ) == 3 {
2018-06-06 17:09:33 +02:00
defaultVal = args [ 2 ]
2018-05-31 18:33:43 +02:00
defaultValueSet = true
}
mapVar := args [ 0 ]
lookupKey := args [ 1 ] . AsString ( )
2018-06-06 19:43:58 +02:00
if ! mapVar . IsWhollyKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
2018-10-16 21:09:04 +02:00
if mapVar . Type ( ) . IsObjectType ( ) {
if mapVar . Type ( ) . HasAttribute ( lookupKey ) {
return mapVar . GetAttr ( lookupKey ) , nil
2018-10-16 20:04:19 +02:00
}
2018-10-16 21:09:04 +02:00
} else if mapVar . HasIndex ( cty . StringVal ( lookupKey ) ) == cty . True {
2019-08-01 21:52:52 +02:00
return mapVar . Index ( cty . StringVal ( lookupKey ) ) , nil
2018-05-31 18:33:43 +02:00
}
if defaultValueSet {
2018-06-06 19:43:58 +02:00
defaultVal , err = convert . Convert ( defaultVal , retType )
if err != nil {
return cty . NilVal , err
2018-06-06 17:09:33 +02:00
}
2018-06-06 19:43:58 +02:00
return defaultVal , nil
2018-05-31 18:33:43 +02:00
}
2018-06-06 17:09:33 +02:00
return cty . UnknownVal ( cty . DynamicPseudoType ) , fmt . Errorf (
2018-05-31 18:33:43 +02:00
"lookup failed to find '%s'" , lookupKey )
} ,
} )
2019-05-02 16:47:19 +02:00
// MapFunc constructs a function that takes an even number of arguments and
2018-05-30 22:37:48 +02:00
// returns a map whose elements are constructed from consecutive pairs of arguments.
//
// This function is deprecated in Terraform v0.12
var MapFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter { } ,
VarParam : & function . Parameter {
Name : "vals" ,
Type : cty . DynamicPseudoType ,
AllowUnknown : true ,
AllowDynamicType : true ,
AllowNull : true ,
} ,
Type : func ( args [ ] cty . Value ) ( ret cty . Type , err error ) {
if len ( args ) < 2 || len ( args ) % 2 != 0 {
return cty . NilType , fmt . Errorf ( "map requires an even number of two or more arguments, got %d" , len ( args ) )
}
argTypes := make ( [ ] cty . Type , len ( args ) / 2 )
index := 0
for i := 0 ; i < len ( args ) ; i += 2 {
argTypes [ index ] = args [ i + 1 ] . Type ( )
index ++
}
valType , _ := convert . UnifyUnsafe ( argTypes )
if valType == cty . NilType {
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "all arguments must have the same type" )
2018-05-30 22:37:48 +02:00
}
return cty . Map ( valType ) , nil
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2018-06-06 19:43:58 +02:00
for _ , arg := range args {
if ! arg . IsWhollyKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
}
2018-05-30 22:37:48 +02:00
outputMap := make ( map [ string ] cty . Value )
for i := 0 ; i < len ( args ) ; i += 2 {
key := args [ i ] . AsString ( )
err := gocty . FromCtyValue ( args [ i ] , & key )
if err != nil {
return cty . NilVal , err
}
val := args [ i + 1 ]
var variable cty . Value
err = gocty . FromCtyValue ( val , & variable )
if err != nil {
return cty . NilVal , err
}
// We already know this will succeed because of the checks in our Type func above
variable , _ = convert . Convert ( variable , retType . ElementType ( ) )
// Check for duplicate keys
if _ , ok := outputMap [ key ] ; ok {
return cty . NilVal , fmt . Errorf ( "argument %d is a duplicate key: %q" , i + 1 , key )
}
outputMap [ key ] = variable
}
return cty . MapVal ( outputMap ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// MatchkeysFunc constructs a function that constructs a new list by taking a
2018-05-29 22:58:32 +02:00
// subset of elements from one list whose indexes match the corresponding
// indexes of values in another list.
var MatchkeysFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "values" ,
Type : cty . List ( cty . DynamicPseudoType ) ,
} ,
{
Name : "keys" ,
Type : cty . List ( cty . DynamicPseudoType ) ,
} ,
{
Name : "searchset" ,
Type : cty . List ( cty . DynamicPseudoType ) ,
} ,
} ,
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
2019-06-04 17:54:26 +02:00
ty , _ := convert . UnifyUnsafe ( [ ] cty . Type { args [ 1 ] . Type ( ) , args [ 2 ] . Type ( ) } )
2019-06-04 00:00:53 +02:00
if ty == cty . NilType {
2019-06-04 14:36:30 +02:00
return cty . NilType , errors . New ( "keys and searchset must be of the same type" )
2018-05-29 22:58:32 +02:00
}
2019-06-04 14:36:30 +02:00
// the return type is based on args[0] (values)
2018-05-29 22:58:32 +02:00
return args [ 0 ] . Type ( ) , nil
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2018-10-19 01:19:59 +02:00
if ! args [ 0 ] . IsKnown ( ) {
return cty . UnknownVal ( cty . List ( retType . ElementType ( ) ) ) , nil
}
2018-05-30 16:26:19 +02:00
if args [ 0 ] . LengthInt ( ) != args [ 1 ] . LengthInt ( ) {
2019-05-01 23:55:44 +02:00
return cty . ListValEmpty ( retType . ElementType ( ) ) , errors . New ( "length of keys and values should be equal" )
2018-05-30 16:26:19 +02:00
}
2018-05-29 22:58:32 +02:00
output := make ( [ ] cty . Value , 0 )
values := args [ 0 ]
2019-06-04 14:36:30 +02:00
// Keys and searchset must be the same type.
// We can skip error checking here because we've already verified that
// they can be unified in the Type function
2019-06-04 17:54:26 +02:00
ty , _ := convert . UnifyUnsafe ( [ ] cty . Type { args [ 1 ] . Type ( ) , args [ 2 ] . Type ( ) } )
2019-06-04 14:36:30 +02:00
keys , _ := convert . Convert ( args [ 1 ] , ty )
searchset , _ := convert . Convert ( args [ 2 ] , ty )
2018-05-29 22:58:32 +02:00
// if searchset is empty, return an empty list.
if searchset . LengthInt ( ) == 0 {
2018-05-30 16:26:19 +02:00
return cty . ListValEmpty ( retType . ElementType ( ) ) , nil
2018-05-29 22:58:32 +02:00
}
2018-06-07 19:51:57 +02:00
if ! values . IsWhollyKnown ( ) || ! keys . IsWhollyKnown ( ) {
2018-06-06 19:43:58 +02:00
return cty . UnknownVal ( retType ) , nil
}
2018-05-29 22:58:32 +02:00
i := 0
for it := keys . ElementIterator ( ) ; it . Next ( ) ; {
_ , key := it . Element ( )
for iter := searchset . ElementIterator ( ) ; iter . Next ( ) ; {
_ , search := iter . Element ( )
eq , err := stdlib . Equal ( key , search )
if err != nil {
return cty . NilVal , err
}
2018-05-30 16:26:19 +02:00
if ! eq . IsKnown ( ) {
return cty . ListValEmpty ( retType . ElementType ( ) ) , nil
}
2018-05-29 22:58:32 +02:00
if eq . True ( ) {
v := values . Index ( cty . NumberIntVal ( int64 ( i ) ) )
output = append ( output , v )
break
}
}
i ++
}
// if we haven't matched any key, then output is an empty list.
if len ( output ) == 0 {
2018-05-30 16:26:19 +02:00
return cty . ListValEmpty ( retType . ElementType ( ) ) , nil
2018-05-29 22:58:32 +02:00
}
return cty . ListVal ( output ) , nil
} ,
} )
2020-02-04 20:40:00 +01:00
// MergeFunc constructs a function that takes an arbitrary number of maps or objects, and
// returns a single value that contains a merged set of keys and values from
// all of the inputs.
2018-05-31 20:47:50 +02:00
//
2020-02-04 20:40:00 +01:00
// If more than one given map or object defines the same key then the one that
// is later in the argument sequence takes precedence.
2018-05-31 20:47:50 +02:00
var MergeFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter { } ,
VarParam : & function . Parameter {
Name : "maps" ,
Type : cty . DynamicPseudoType ,
AllowDynamicType : true ,
2020-02-04 20:40:00 +01:00
AllowNull : true ,
} ,
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
// empty args is accepted, so assume an empty object since we have no
// key-value types.
if len ( args ) == 0 {
return cty . EmptyObject , nil
}
// collect the possible object attrs
attrs := map [ string ] cty . Type { }
first := cty . NilType
matching := true
attrsKnown := true
for i , arg := range args {
ty := arg . Type ( )
// any dynamic args mean we can't compute a type
if ty . Equals ( cty . DynamicPseudoType ) {
return cty . DynamicPseudoType , nil
}
// check for invalid arguments
if ! ty . IsMapType ( ) && ! ty . IsObjectType ( ) {
return cty . NilType , fmt . Errorf ( "arguments must be maps or objects, got %#v" , ty . FriendlyName ( ) )
}
switch {
case ty . IsObjectType ( ) && ! arg . IsNull ( ) :
for attr , aty := range ty . AttributeTypes ( ) {
attrs [ attr ] = aty
}
case ty . IsMapType ( ) :
switch {
case arg . IsNull ( ) :
// pass, nothing to add
case arg . IsKnown ( ) :
ety := arg . Type ( ) . ElementType ( )
for it := arg . ElementIterator ( ) ; it . Next ( ) ; {
attr , _ := it . Element ( )
attrs [ attr . AsString ( ) ] = ety
}
default :
// any unknown maps means we don't know all possible attrs
// for the return type
attrsKnown = false
}
}
// record the first argument type for comparison
if i == 0 {
first = arg . Type ( )
continue
}
if ! ty . Equals ( first ) && matching {
matching = false
}
}
// the types all match, so use the first argument type
if matching {
return first , nil
}
// We had a mix of unknown maps and objects, so we can't predict the
// attributes
if ! attrsKnown {
return cty . DynamicPseudoType , nil
}
return cty . Object ( attrs ) , nil
2018-05-31 20:47:50 +02:00
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
outputMap := make ( map [ string ] cty . Value )
2020-02-04 20:40:00 +01:00
// if all inputs are null, return a null value rather than an object
// with null attributes
allNull := true
2018-05-31 20:47:50 +02:00
for _ , arg := range args {
2020-02-04 20:40:00 +01:00
if arg . IsNull ( ) {
continue
} else {
allNull = false
2018-05-31 20:47:50 +02:00
}
2020-02-04 20:40:00 +01:00
2018-05-31 20:47:50 +02:00
for it := arg . ElementIterator ( ) ; it . Next ( ) ; {
k , v := it . Element ( )
outputMap [ k . AsString ( ) ] = v
}
}
2020-02-04 20:40:00 +01:00
switch {
case allNull :
return cty . NullVal ( retType ) , nil
case retType . IsMapType ( ) :
return cty . MapVal ( outputMap ) , nil
case retType . IsObjectType ( ) , retType . Equals ( cty . DynamicPseudoType ) :
return cty . ObjectVal ( outputMap ) , nil
default :
panic ( fmt . Sprintf ( "unexpected return type: %#v" , retType ) )
}
2018-05-31 20:47:50 +02:00
} ,
} )
2019-03-20 01:09:01 +01:00
// ReverseFunc takes a sequence and produces a new sequence of the same length
// with all of the same elements as the given sequence but in reverse order.
var ReverseFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
Type : cty . DynamicPseudoType ,
} ,
} ,
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
argTy := args [ 0 ] . Type ( )
switch {
case argTy . IsTupleType ( ) :
argTys := argTy . TupleElementTypes ( )
retTys := make ( [ ] cty . Type , len ( argTys ) )
for i , ty := range argTys {
retTys [ len ( retTys ) - i - 1 ] = ty
}
return cty . Tuple ( retTys ) , nil
case argTy . IsListType ( ) , argTy . IsSetType ( ) : // We accept sets here to mimic the usual behavior of auto-converting to list
return cty . List ( argTy . ElementType ( ) ) , nil
default :
return cty . NilType , function . NewArgErrorf ( 0 , "can only reverse list or tuple values, not %s" , argTy . FriendlyName ( ) )
}
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
in := args [ 0 ] . AsValueSlice ( )
outVals := make ( [ ] cty . Value , len ( in ) )
for i , v := range in {
outVals [ len ( outVals ) - i - 1 ] = v
}
switch {
case retType . IsTupleType ( ) :
return cty . TupleVal ( outVals ) , nil
default :
if len ( outVals ) == 0 {
return cty . ListValEmpty ( retType . ElementType ( ) ) , nil
}
return cty . ListVal ( outVals ) , nil
}
} ,
} )
2019-09-05 19:08:34 +02:00
// SetProductFunc calculates the Cartesian product of two or more sets or
2019-01-15 18:47:14 +01:00
// sequences. If the arguments are all lists then the result is a list of tuples,
// preserving the ordering of all of the input lists. Otherwise the result is a
// set of tuples.
var SetProductFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter { } ,
VarParam : & function . Parameter {
Name : "sets" ,
Type : cty . DynamicPseudoType ,
} ,
Type : func ( args [ ] cty . Value ) ( retType cty . Type , err error ) {
if len ( args ) < 2 {
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "at least two arguments are required" )
2019-01-15 18:47:14 +01:00
}
listCount := 0
elemTys := make ( [ ] cty . Type , len ( args ) )
for i , arg := range args {
aty := arg . Type ( )
switch {
case aty . IsSetType ( ) :
elemTys [ i ] = aty . ElementType ( )
case aty . IsListType ( ) :
elemTys [ i ] = aty . ElementType ( )
listCount ++
case aty . IsTupleType ( ) :
// We can accept a tuple type only if there's some common type
// that all of its elements can be converted to.
allEtys := aty . TupleElementTypes ( )
if len ( allEtys ) == 0 {
elemTys [ i ] = cty . DynamicPseudoType
listCount ++
break
}
ety , _ := convert . UnifyUnsafe ( allEtys )
if ety == cty . NilType {
return cty . NilType , function . NewArgErrorf ( i , "all elements must be of the same type" )
}
elemTys [ i ] = ety
listCount ++
default :
return cty . NilType , function . NewArgErrorf ( i , "a set or a list is required" )
}
}
if listCount == len ( args ) {
return cty . List ( cty . Tuple ( elemTys ) ) , nil
}
return cty . Set ( cty . Tuple ( elemTys ) ) , nil
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
ety := retType . ElementType ( )
total := 1
for _ , arg := range args {
// Because of our type checking function, we are guaranteed that
// all of the arguments are known, non-null values of types that
// support LengthInt.
total *= arg . LengthInt ( )
}
if total == 0 {
// If any of the arguments was an empty collection then our result
// is also an empty collection, which we'll short-circuit here.
if retType . IsListType ( ) {
return cty . ListValEmpty ( ety ) , nil
}
return cty . SetValEmpty ( ety ) , nil
}
subEtys := ety . TupleElementTypes ( )
product := make ( [ ] [ ] cty . Value , total )
b := make ( [ ] cty . Value , total * len ( args ) )
n := make ( [ ] int , len ( args ) )
s := 0
argVals := make ( [ ] [ ] cty . Value , len ( args ) )
for i , arg := range args {
argVals [ i ] = arg . AsValueSlice ( )
}
for i := range product {
e := s + len ( args )
pi := b [ s : e ]
product [ i ] = pi
s = e
for j , n := range n {
val := argVals [ j ] [ n ]
ty := subEtys [ j ]
if ! val . Type ( ) . Equals ( ty ) {
var err error
val , err = convert . Convert ( val , ty )
if err != nil {
// Should never happen since we checked this in our
// type-checking function.
return cty . NilVal , fmt . Errorf ( "failed to convert argVals[%d][%d] to %s; this is a bug in Terraform" , j , n , ty . FriendlyName ( ) )
}
}
pi [ j ] = val
}
for j := len ( n ) - 1 ; j >= 0 ; j -- {
n [ j ] ++
if n [ j ] < len ( argVals [ j ] ) {
break
}
n [ j ] = 0
}
}
productVals := make ( [ ] cty . Value , total )
for i , vals := range product {
productVals [ i ] = cty . TupleVal ( vals )
}
if retType . IsListType ( ) {
return cty . ListVal ( productVals ) , nil
}
return cty . SetVal ( productVals ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// SliceFunc constructs a function that extracts some consecutive elements
2018-05-31 23:46:24 +02:00
// from within a list.
var SliceFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "list" ,
2019-04-30 19:12:00 +02:00
Type : cty . DynamicPseudoType ,
2018-05-31 23:46:24 +02:00
} ,
{
2019-05-17 02:16:24 +02:00
Name : "start_index" ,
2018-05-31 23:46:24 +02:00
Type : cty . Number ,
} ,
{
2019-05-17 02:16:24 +02:00
Name : "end_index" ,
2018-05-31 23:46:24 +02:00
Type : cty . Number ,
} ,
} ,
2018-06-01 00:49:15 +02:00
Type : func ( args [ ] cty . Value ) ( cty . Type , error ) {
2019-04-30 19:12:00 +02:00
arg := args [ 0 ]
argTy := arg . Type ( )
2018-05-31 23:46:24 +02:00
2019-05-17 02:16:24 +02:00
if argTy . IsSetType ( ) {
return cty . NilType , function . NewArgErrorf ( 0 , "cannot slice a set, because its elements do not have indices; use the tolist function to force conversion to list if the ordering of the result is not important" )
}
2019-04-30 19:12:00 +02:00
if ! argTy . IsListType ( ) && ! argTy . IsTupleType ( ) {
2019-05-17 02:16:24 +02:00
return cty . NilType , function . NewArgErrorf ( 0 , "must be a list or tuple value" )
}
startIndex , endIndex , idxsKnown , err := sliceIndexes ( args )
if err != nil {
return cty . NilType , err
2018-05-31 23:46:24 +02:00
}
2019-04-30 19:12:00 +02:00
if argTy . IsListType ( ) {
return argTy , nil
2018-05-31 23:46:24 +02:00
}
2019-04-30 19:12:00 +02:00
2019-05-17 02:16:24 +02:00
if ! idxsKnown {
// If we don't know our start/end indices then we can't predict
// the result type if we're planning to return a tuple.
return cty . DynamicPseudoType , nil
2018-05-31 23:46:24 +02:00
}
2019-04-30 19:12:00 +02:00
return cty . Tuple ( argTy . TupleElementTypes ( ) [ startIndex : endIndex ] ) , nil
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
inputList := args [ 0 ]
2018-05-31 23:46:24 +02:00
2019-05-17 02:16:24 +02:00
if retType == cty . DynamicPseudoType {
return cty . DynamicVal , nil
}
// we ignore idxsKnown return value here because the indices are always
// known here, or else the call would've short-circuited.
startIndex , endIndex , _ , err := sliceIndexes ( args )
2019-04-30 19:12:00 +02:00
if err != nil {
return cty . NilVal , err
2018-05-31 23:46:24 +02:00
}
2019-04-30 19:12:00 +02:00
if endIndex - startIndex == 0 {
if retType . IsTupleType ( ) {
return cty . EmptyTupleVal , nil
}
2018-06-01 00:49:15 +02:00
return cty . ListValEmpty ( retType . ElementType ( ) ) , nil
2018-05-31 23:46:24 +02:00
}
2019-04-30 19:12:00 +02:00
outputList := inputList . AsValueSlice ( ) [ startIndex : endIndex ]
if retType . IsTupleType ( ) {
return cty . TupleVal ( outputList ) , nil
}
2018-05-31 23:46:24 +02:00
return cty . ListVal ( outputList ) , nil
} ,
} )
2019-05-17 02:16:24 +02:00
func sliceIndexes ( args [ ] cty . Value ) ( int , int , bool , error ) {
var startIndex , endIndex , length int
var startKnown , endKnown , lengthKnown bool
2019-04-30 19:12:00 +02:00
2019-05-17 02:16:24 +02:00
if args [ 0 ] . Type ( ) . IsTupleType ( ) || args [ 0 ] . IsKnown ( ) { // if it's a tuple then we always know the length by the type, but lists must be known
length = args [ 0 ] . LengthInt ( )
lengthKnown = true
2019-04-30 19:12:00 +02:00
}
2019-05-17 02:16:24 +02:00
if args [ 1 ] . IsKnown ( ) {
if err := gocty . FromCtyValue ( args [ 1 ] , & startIndex ) ; err != nil {
return 0 , 0 , false , function . NewArgErrorf ( 1 , "invalid start index: %s" , err )
}
if startIndex < 0 {
return 0 , 0 , false , function . NewArgErrorf ( 1 , "start index must not be less than zero" )
}
if lengthKnown && startIndex > length {
return 0 , 0 , false , function . NewArgErrorf ( 1 , "start index must not be greater than the length of the list" )
}
startKnown = true
2019-04-30 19:12:00 +02:00
}
2019-05-17 02:16:24 +02:00
if args [ 2 ] . IsKnown ( ) {
if err := gocty . FromCtyValue ( args [ 2 ] , & endIndex ) ; err != nil {
return 0 , 0 , false , function . NewArgErrorf ( 2 , "invalid end index: %s" , err )
}
if endIndex < 0 {
return 0 , 0 , false , function . NewArgErrorf ( 2 , "end index must not be less than zero" )
}
if lengthKnown && endIndex > length {
return 0 , 0 , false , function . NewArgErrorf ( 2 , "end index must not be greater than the length of the list" )
}
endKnown = true
2019-04-30 19:12:00 +02:00
}
2019-05-17 02:16:24 +02:00
if startKnown && endKnown {
if startIndex > endIndex {
return 0 , 0 , false , function . NewArgErrorf ( 1 , "start index must not be greater than end index" )
}
2019-04-30 19:12:00 +02:00
}
2019-05-17 02:16:24 +02:00
return startIndex , endIndex , startKnown && endKnown , nil
2019-04-30 19:12:00 +02:00
}
2019-05-02 16:47:19 +02:00
// TransposeFunc constructs a function that takes a map of lists of strings and
2018-05-31 23:46:24 +02:00
// swaps the keys and values to produce a new map of lists of strings.
var TransposeFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "values" ,
Type : cty . Map ( cty . List ( cty . String ) ) ,
} ,
} ,
Type : function . StaticReturnType ( cty . Map ( cty . List ( cty . String ) ) ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
inputMap := args [ 0 ]
2018-06-01 00:49:15 +02:00
if ! inputMap . IsWhollyKnown ( ) {
return cty . UnknownVal ( retType ) , nil
}
2018-05-31 23:46:24 +02:00
outputMap := make ( map [ string ] cty . Value )
tmpMap := make ( map [ string ] [ ] string )
for it := inputMap . ElementIterator ( ) ; it . Next ( ) ; {
inKey , inVal := it . Element ( )
for iter := inVal . ElementIterator ( ) ; iter . Next ( ) ; {
_ , val := iter . Element ( )
if ! val . Type ( ) . Equals ( cty . String ) {
2019-05-01 23:55:44 +02:00
return cty . MapValEmpty ( cty . List ( cty . String ) ) , errors . New ( "input must be a map of lists of strings" )
2018-05-31 23:46:24 +02:00
}
outKey := val . AsString ( )
if _ , ok := tmpMap [ outKey ] ; ! ok {
tmpMap [ outKey ] = make ( [ ] string , 0 )
}
outVal := tmpMap [ outKey ]
outVal = append ( outVal , inKey . AsString ( ) )
sort . Strings ( outVal )
tmpMap [ outKey ] = outVal
}
}
for outKey , outVal := range tmpMap {
values := make ( [ ] cty . Value , 0 )
for _ , v := range outVal {
values = append ( values , cty . StringVal ( v ) )
}
outputMap [ outKey ] = cty . ListVal ( values )
}
2019-11-08 18:40:39 +01:00
if len ( outputMap ) == 0 {
return cty . MapValEmpty ( cty . List ( cty . String ) ) , nil
}
2018-05-31 23:46:24 +02:00
return cty . MapVal ( outputMap ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// ValuesFunc constructs a function that returns a list of the map values,
2018-06-06 17:31:22 +02:00
// in the order of the sorted keys.
2018-06-01 18:23:06 +02:00
var ValuesFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "values" ,
2018-10-16 22:40:02 +02:00
Type : cty . DynamicPseudoType ,
2018-06-01 18:23:06 +02:00
} ,
} ,
2018-06-06 17:31:22 +02:00
Type : func ( args [ ] cty . Value ) ( ret cty . Type , err error ) {
2018-10-16 22:40:02 +02:00
ty := args [ 0 ] . Type ( )
if ty . IsMapType ( ) {
return cty . List ( ty . ElementType ( ) ) , nil
} else if ty . IsObjectType ( ) {
2018-11-06 03:03:40 +01:00
// The result is a tuple type with all of the same types as our
// object type's attributes, sorted in lexicographical order by the
// keys. (This matches the sort order guaranteed by ElementIterator
// on a cty object value.)
atys := ty . AttributeTypes ( )
if len ( atys ) == 0 {
return cty . EmptyTuple , nil
}
attrNames := make ( [ ] string , 0 , len ( atys ) )
for name := range atys {
attrNames = append ( attrNames , name )
}
sort . Strings ( attrNames )
tys := make ( [ ] cty . Type , len ( attrNames ) )
for i , name := range attrNames {
tys [ i ] = atys [ name ]
2018-10-16 22:40:02 +02:00
}
2018-10-16 23:55:57 +02:00
return cty . Tuple ( tys ) , nil
2018-10-16 22:40:02 +02:00
}
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "values() requires a map as the first argument" )
2018-06-06 17:31:22 +02:00
} ,
2018-06-01 18:23:06 +02:00
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
mapVar := args [ 0 ]
2018-06-07 19:51:57 +02:00
2018-11-06 03:03:40 +01:00
// We can just iterate the map/object value here because cty guarantees
// that these types always iterate in key lexicographical order.
2018-06-01 18:23:06 +02:00
var values [ ] cty . Value
2018-11-06 03:03:40 +01:00
for it := mapVar . ElementIterator ( ) ; it . Next ( ) ; {
_ , val := it . Element ( )
values = append ( values , val )
2018-06-01 18:23:06 +02:00
}
2018-10-16 23:55:57 +02:00
if retType . IsTupleType ( ) {
return cty . TupleVal ( values ) , nil
}
2018-06-01 18:23:06 +02:00
if len ( values ) == 0 {
2018-06-07 19:51:57 +02:00
return cty . ListValEmpty ( retType . ElementType ( ) ) , nil
2018-06-01 18:23:06 +02:00
}
return cty . ListVal ( values ) , nil
} ,
} )
2019-05-02 16:47:19 +02:00
// ZipmapFunc constructs a function that constructs a map from a list of keys
2018-06-01 18:23:06 +02:00
// and a corresponding list of values.
var ZipmapFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "keys" ,
Type : cty . List ( cty . String ) ,
} ,
{
Name : "values" ,
2018-11-28 02:22:27 +01:00
Type : cty . DynamicPseudoType ,
2018-06-01 18:23:06 +02:00
} ,
} ,
Type : func ( args [ ] cty . Value ) ( ret cty . Type , err error ) {
keys := args [ 0 ]
values := args [ 1 ]
2018-11-28 02:22:27 +01:00
valuesTy := values . Type ( )
2018-06-01 18:23:06 +02:00
2018-11-28 02:22:27 +01:00
switch {
case valuesTy . IsListType ( ) :
return cty . Map ( values . Type ( ) . ElementType ( ) ) , nil
case valuesTy . IsTupleType ( ) :
if ! keys . IsWhollyKnown ( ) {
// Since zipmap with a tuple produces an object, we need to know
// all of the key names before we can predict our result type.
return cty . DynamicPseudoType , nil
}
2018-06-01 18:23:06 +02:00
2018-11-28 02:22:27 +01:00
keysRaw := keys . AsValueSlice ( )
valueTypesRaw := valuesTy . TupleElementTypes ( )
if len ( keysRaw ) != len ( valueTypesRaw ) {
return cty . NilType , fmt . Errorf ( "number of keys (%d) does not match number of values (%d)" , len ( keysRaw ) , len ( valueTypesRaw ) )
}
atys := make ( map [ string ] cty . Type , len ( valueTypesRaw ) )
for i , keyVal := range keysRaw {
if keyVal . IsNull ( ) {
return cty . NilType , fmt . Errorf ( "keys list has null value at index %d" , i )
}
key := keyVal . AsString ( )
atys [ key ] = valueTypesRaw [ i ]
}
return cty . Object ( atys ) , nil
default :
2019-05-01 23:55:44 +02:00
return cty . NilType , errors . New ( "values argument must be a list or tuple value" )
2018-06-01 18:23:06 +02:00
}
} ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
keys := args [ 0 ]
values := args [ 1 ]
2018-11-28 02:22:27 +01:00
if ! keys . IsWhollyKnown ( ) {
// Unknown map keys and object attributes are not supported, so
// our entire result must be unknown in this case.
return cty . UnknownVal ( retType ) , nil
}
// both keys and values are guaranteed to be shallowly-known here,
// because our declared params above don't allow unknown or null values.
if keys . LengthInt ( ) != values . LengthInt ( ) {
return cty . NilVal , fmt . Errorf ( "number of keys (%d) does not match number of values (%d)" , keys . LengthInt ( ) , values . LengthInt ( ) )
2018-06-01 18:23:06 +02:00
}
output := make ( map [ string ] cty . Value )
i := 0
for it := keys . ElementIterator ( ) ; it . Next ( ) ; {
_ , v := it . Element ( )
val := values . Index ( cty . NumberIntVal ( int64 ( i ) ) )
output [ v . AsString ( ) ] = val
i ++
}
2018-11-28 02:22:27 +01:00
switch {
case retType . IsMapType ( ) :
if len ( output ) == 0 {
return cty . MapValEmpty ( retType . ElementType ( ) ) , nil
}
return cty . MapVal ( output ) , nil
case retType . IsObjectType ( ) :
return cty . ObjectVal ( output ) , nil
default :
// Should never happen because the type-check function should've
// caught any other case.
return cty . NilVal , fmt . Errorf ( "internally selected incorrect result type %s (this is a bug)" , retType . FriendlyName ( ) )
}
2018-06-01 18:23:06 +02:00
} ,
} )
2018-05-31 00:38:55 +02:00
// helper function to add an element to a list, if it does not already exist
2018-05-26 01:15:50 +02:00
func appendIfMissing ( slice [ ] cty . Value , element cty . Value ) ( [ ] cty . Value , error ) {
for _ , ele := range slice {
eq , err := stdlib . Equal ( ele , element )
if err != nil {
return slice , err
}
if eq . True ( ) {
return slice , nil
}
}
return append ( slice , element ) , nil
}
2018-05-22 02:39:26 +02:00
// Element returns a single element from a given list at the given index. If
// index is greater than the length of the list then it is wrapped modulo
// the list length.
func Element ( list , index cty . Value ) ( cty . Value , error ) {
return ElementFunc . Call ( [ ] cty . Value { list , index } )
}
// Length returns the number of elements in the given collection or number of
// Unicode characters in the given string.
func Length ( collection cty . Value ) ( cty . Value , error ) {
return LengthFunc . Call ( [ ] cty . Value { collection } )
}
2018-05-24 20:48:44 +02:00
2019-04-12 19:57:52 +02:00
// Coalesce takes any number of arguments and returns the first one that isn't empty.
func Coalesce ( args ... cty . Value ) ( cty . Value , error ) {
return CoalesceFunc . Call ( args )
}
2018-05-24 20:48:44 +02:00
// CoalesceList takes any number of list arguments and returns the first one that isn't empty.
func CoalesceList ( args ... cty . Value ) ( cty . Value , error ) {
return CoalesceListFunc . Call ( args )
}
2018-05-24 23:46:03 +02:00
// Compact takes a list of strings and returns a new list
// with any empty string elements removed.
func Compact ( list cty . Value ) ( cty . Value , error ) {
2018-05-25 00:20:22 +02:00
return CompactFunc . Call ( [ ] cty . Value { list } )
2018-05-24 23:46:03 +02:00
}
2018-05-25 21:57:26 +02:00
// Contains determines whether a given list contains a given single value
// as one of its elements.
func Contains ( list , value cty . Value ) ( cty . Value , error ) {
return ContainsFunc . Call ( [ ] cty . Value { list , value } )
}
// Index finds the element index for a given value in a list.
func Index ( list , value cty . Value ) ( cty . Value , error ) {
return IndexFunc . Call ( [ ] cty . Value { list , value } )
}
2018-05-26 01:15:50 +02:00
// Distinct takes a list and returns a new list with any duplicate elements removed.
func Distinct ( list cty . Value ) ( cty . Value , error ) {
return DistinctFunc . Call ( [ ] cty . Value { list } )
}
// Chunklist splits a single list into fixed-size chunks, returning a list of lists.
func Chunklist ( list , size cty . Value ) ( cty . Value , error ) {
return ChunklistFunc . Call ( [ ] cty . Value { list , size } )
}
2018-05-29 22:58:32 +02:00
2018-05-30 16:26:19 +02:00
// Flatten takes a list and replaces any elements that are lists with a flattened
// sequence of the list contents.
func Flatten ( list cty . Value ) ( cty . Value , error ) {
return FlattenFunc . Call ( [ ] cty . Value { list } )
}
2018-05-31 00:38:55 +02:00
// Keys takes a map and returns a sorted list of the map keys.
func Keys ( inputMap cty . Value ) ( cty . Value , error ) {
return KeysFunc . Call ( [ ] cty . Value { inputMap } )
}
2018-05-30 17:41:31 +02:00
// List takes any number of list arguments and returns a list containing those
// values in the same order.
func List ( args ... cty . Value ) ( cty . Value , error ) {
return ListFunc . Call ( args )
}
2018-05-31 18:33:43 +02:00
// Lookup performs a dynamic lookup into a map.
// There are two required arguments, map and key, plus an optional default,
// which is a value to return if no key is found in map.
func Lookup ( args ... cty . Value ) ( cty . Value , error ) {
return LookupFunc . Call ( args )
}
2018-05-30 22:37:48 +02:00
// Map takes an even number of arguments and returns a map whose elements are constructed
// from consecutive pairs of arguments.
func Map ( args ... cty . Value ) ( cty . Value , error ) {
return MapFunc . Call ( args )
}
2018-05-29 22:58:32 +02:00
// Matchkeys constructs a new list by taking a subset of elements from one list
// whose indexes match the corresponding indexes of values in another list.
func Matchkeys ( values , keys , searchset cty . Value ) ( cty . Value , error ) {
return MatchkeysFunc . Call ( [ ] cty . Value { values , keys , searchset } )
}
2018-05-31 20:47:50 +02:00
// Merge takes an arbitrary number of maps and returns a single map that contains
// a merged set of elements from all of the maps.
//
// If more than one given map defines the same key then the one that is later in
// the argument sequence takes precedence.
func Merge ( maps ... cty . Value ) ( cty . Value , error ) {
return MergeFunc . Call ( maps )
}
2018-05-31 23:46:24 +02:00
2019-03-20 01:09:01 +01:00
// Reverse takes a sequence and produces a new sequence of the same length
// with all of the same elements as the given sequence but in reverse order.
func Reverse ( list cty . Value ) ( cty . Value , error ) {
return ReverseFunc . Call ( [ ] cty . Value { list } )
}
2019-09-05 19:08:34 +02:00
// SetProduct computes the Cartesian product of sets or sequences.
2019-01-15 18:47:14 +01:00
func SetProduct ( sets ... cty . Value ) ( cty . Value , error ) {
return SetProductFunc . Call ( sets )
}
2018-05-31 23:46:24 +02:00
// Slice extracts some consecutive elements from within a list.
func Slice ( list , start , end cty . Value ) ( cty . Value , error ) {
return SliceFunc . Call ( [ ] cty . Value { list , start , end } )
}
// Transpose takes a map of lists of strings and swaps the keys and values to
// produce a new map of lists of strings.
func Transpose ( values cty . Value ) ( cty . Value , error ) {
return TransposeFunc . Call ( [ ] cty . Value { values } )
}
2018-06-01 18:23:06 +02:00
// Values returns a list of the map values, in the order of the sorted keys.
// This function only works on flat maps.
func Values ( values cty . Value ) ( cty . Value , error ) {
return ValuesFunc . Call ( [ ] cty . Value { values } )
}
// Zipmap constructs a map from a list of keys and a corresponding list of values.
func Zipmap ( keys , values cty . Value ) ( cty . Value , error ) {
return ZipmapFunc . Call ( [ ] cty . Value { keys , values } )
}