lang/funcs: Convert the "setproduct" function to the new approach
In our new world it produces either a set of a tuple type or a list of a tuple type, depending on the given argument types. The resulting collection's element tuple type is decided by the element types of the given collections, allowing type information to propagate even if unknown values are present.
This commit is contained in:
parent
9d11d427a8
commit
edb5f82de1
|
@ -807,6 +807,129 @@ var MergeFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// SetProductFunc calculates the cartesian product of two or more sets or
|
||||||
|
// 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 {
|
||||||
|
return cty.NilType, fmt.Errorf("at least two arguments are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// SliceFunc contructs a function that extracts some consecutive elements
|
// SliceFunc contructs a function that extracts some consecutive elements
|
||||||
// from within a list.
|
// from within a list.
|
||||||
var SliceFunc = function.New(&function.Spec{
|
var SliceFunc = function.New(&function.Spec{
|
||||||
|
@ -1169,6 +1292,11 @@ func Merge(maps ...cty.Value) (cty.Value, error) {
|
||||||
return MergeFunc.Call(maps)
|
return MergeFunc.Call(maps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetProduct computes the cartesian product of sets or sequences.
|
||||||
|
func SetProduct(sets ...cty.Value) (cty.Value, error) {
|
||||||
|
return SetProductFunc.Call(sets)
|
||||||
|
}
|
||||||
|
|
||||||
// Slice extracts some consecutive elements from within a list.
|
// Slice extracts some consecutive elements from within a list.
|
||||||
func Slice(list, start, end cty.Value) (cty.Value, error) {
|
func Slice(list, start, end cty.Value) (cty.Value, error) {
|
||||||
return SliceFunc.Call([]cty.Value{list, start, end})
|
return SliceFunc.Call([]cty.Value{list, start, end})
|
||||||
|
|
|
@ -1854,6 +1854,245 @@ func TestMerge(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetProduct(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Sets []cty.Value
|
||||||
|
Want cty.Value
|
||||||
|
Err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
cty.DynamicVal,
|
||||||
|
"at least two arguments are required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
},
|
||||||
|
cty.DynamicVal,
|
||||||
|
"at least two arguments are required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
cty.StringVal("hello"),
|
||||||
|
},
|
||||||
|
cty.DynamicVal,
|
||||||
|
"a set or a list is required", // this is an ArgError, so is presented against the second argument in particular
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
},
|
||||||
|
cty.SetValEmpty(cty.Tuple([]cty.Type{cty.String, cty.String})),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
},
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
},
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
},
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
},
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
},
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.True}),
|
||||||
|
},
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("true")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("true")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("true")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.EmptyTupleVal,
|
||||||
|
},
|
||||||
|
cty.ListValEmpty(cty.Tuple([]cty.Type{cty.String, cty.DynamicPseudoType})),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.ListVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.EmptyObjectVal}),
|
||||||
|
},
|
||||||
|
cty.DynamicVal,
|
||||||
|
"all elements must be of the same type", // this is an ArgError for the second argument
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("baz")}),
|
||||||
|
},
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("foo"), cty.StringVal("baz")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("foo"), cty.StringVal("baz")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("foo"), cty.StringVal("baz")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("bar"), cty.StringVal("baz")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("stg"), cty.StringVal("bar"), cty.StringVal("baz")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("prd"), cty.StringVal("bar"), cty.StringVal("baz")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("dev"), cty.StringVal("stg"), cty.StringVal("prd")}),
|
||||||
|
cty.SetValEmpty(cty.String),
|
||||||
|
},
|
||||||
|
cty.SetValEmpty(cty.Tuple([]cty.Type{cty.String, cty.String})),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("foo")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("bar")}),
|
||||||
|
},
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo")}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("bar")}),
|
||||||
|
},
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("foo")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.DynamicVal}),
|
||||||
|
},
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.DynamicVal}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.SetVal([]cty.Value{cty.StringVal("foo")}),
|
||||||
|
cty.SetVal([]cty.Value{cty.True, cty.DynamicVal}),
|
||||||
|
},
|
||||||
|
cty.SetVal([]cty.Value{
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.True}),
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.UnknownVal(cty.Bool)}),
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]cty.Value{
|
||||||
|
cty.UnknownVal(cty.Set(cty.String)),
|
||||||
|
cty.SetVal([]cty.Value{cty.True, cty.False}),
|
||||||
|
},
|
||||||
|
cty.UnknownVal(cty.Set(cty.Tuple([]cty.Type{cty.String, cty.Bool}))),
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("setproduct(%#v)", test.Sets), func(t *testing.T) {
|
||||||
|
got, err := SetProduct(test.Sets...)
|
||||||
|
|
||||||
|
if test.Err != "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("succeeded; want error")
|
||||||
|
}
|
||||||
|
if got, want := err.Error(), test.Err; got != want {
|
||||||
|
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !got.RawEquals(test.Want) {
|
||||||
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestSlice(t *testing.T) {
|
func TestSlice(t *testing.T) {
|
||||||
listOfStrings := cty.ListVal([]cty.Value{
|
listOfStrings := cty.ListVal([]cty.Value{
|
||||||
cty.StringVal("a"),
|
cty.StringVal("a"),
|
||||||
|
|
|
@ -81,6 +81,7 @@ func (s *Scope) Functions() map[string]function.Function {
|
||||||
"pow": funcs.PowFunc,
|
"pow": funcs.PowFunc,
|
||||||
"replace": funcs.ReplaceFunc,
|
"replace": funcs.ReplaceFunc,
|
||||||
"rsadecrypt": funcs.RsaDecryptFunc,
|
"rsadecrypt": funcs.RsaDecryptFunc,
|
||||||
|
"setproduct": funcs.SetProductFunc,
|
||||||
"sha1": funcs.Sha1Func,
|
"sha1": funcs.Sha1Func,
|
||||||
"sha256": funcs.Sha256Func,
|
"sha256": funcs.Sha256Func,
|
||||||
"sha512": funcs.Sha512Func,
|
"sha512": funcs.Sha512Func,
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
---
|
||||||
|
layout: "functions"
|
||||||
|
page_title: "setproduct - Functions - Configuration Language"
|
||||||
|
sidebar_current: "docs-funcs-collection-setproduct"
|
||||||
|
description: |-
|
||||||
|
The setproduct function finds all of the possible combinations of elements
|
||||||
|
from all of the given sets by computing the cartesian product.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `setproduct` Function
|
||||||
|
|
||||||
|
The `setproduct` function finds all of the possible combinations of elements
|
||||||
|
from all of the given sets by computing the
|
||||||
|
[cartesian product](https://en.wikipedia.org/wiki/Cartesian_product).
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
setproduct(sets...)
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is particularly useful for finding the exhaustive set of all
|
||||||
|
combinations of members of multiple sets, such as per-application-per-environment
|
||||||
|
resources.
|
||||||
|
|
||||||
|
```
|
||||||
|
> setproduct(["development", "staging", "production"], ["app1", "app2"])
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"development",
|
||||||
|
"app1",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"development",
|
||||||
|
"app2",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"staging",
|
||||||
|
"app1",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"staging",
|
||||||
|
"app2",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"production",
|
||||||
|
"app1",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"production",
|
||||||
|
"app2",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
You must past at least two arguments to this function.
|
||||||
|
|
||||||
|
Although defined primarily for sets, this function can also work with lists.
|
||||||
|
If all of the given arguments are lists then the result is a list, preserving
|
||||||
|
the ordering of the given lists. Otherwise the result is a set. In either case,
|
||||||
|
the result's element type is a list of values corresponding to each given
|
||||||
|
argument in turn.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
There is an example of the common usage of this function above. There are some
|
||||||
|
other situations that are less common when hand-writing but may arise in
|
||||||
|
reusable module situations.
|
||||||
|
|
||||||
|
If any of the arguments is empty then the result is always empty itself,
|
||||||
|
similar to how multiplying any number by zero gives zero:
|
||||||
|
|
||||||
|
```
|
||||||
|
> setproduct(["development", "staging", "production"], [])
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, if all of the arguments have only one element then the result has
|
||||||
|
only one element, which is the first element of each argument:
|
||||||
|
|
||||||
|
```
|
||||||
|
> setproduct(["a"], ["b"])
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Each argument must have a consistent type for all of its elements. If not,
|
||||||
|
Terraform will attempt to convert to the most general type, or produce an
|
||||||
|
error if such a conversion is impossible. For example, mixing both strings and
|
||||||
|
numbers results in the numbers being converted to strings so that the result
|
||||||
|
elements all have a consistent type:
|
||||||
|
|
||||||
|
```
|
||||||
|
> setproduct(["staging", "production"], ["a", 2])
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"staging",
|
||||||
|
"a",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"staging",
|
||||||
|
"2",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"production",
|
||||||
|
"a",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"production",
|
||||||
|
"2",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
```
|
|
@ -171,6 +171,10 @@
|
||||||
<a href="/docs/configuration/functions/merge.html">merge</a>
|
<a href="/docs/configuration/functions/merge.html">merge</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-funcs-collection-setproduct") %>>
|
||||||
|
<a href="/docs/configuration/functions/setproduct.html">setproduct</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-funcs-collection-slice") %>>
|
<li<%= sidebar_current("docs-funcs-collection-slice") %>>
|
||||||
<a href="/docs/configuration/functions/slice.html">slice</a>
|
<a href="/docs/configuration/functions/slice.html">slice</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue