diff --git a/lang/funcs/collection.go b/lang/funcs/collection.go index d83a79bd34..cca7423c09 100644 --- a/lang/funcs/collection.go +++ b/lang/funcs/collection.go @@ -57,40 +57,54 @@ var LengthFunc = function.New(&function.Spec{ }) // AllTrueFunc constructs a function that returns true if all elements of the -// collection are true or "true". If the collection is empty, return true. +// list are true. If the list is empty, return true. var AllTrueFunc = function.New(&function.Spec{ Params: []function.Parameter{ { - Name: "collection", - Type: cty.DynamicPseudoType, + Name: "list", + Type: cty.List(cty.Bool), }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { - ty := args[0].Type() - if !ty.IsListType() && !ty.IsTupleType() && !ty.IsSetType() { - return cty.NilVal, errors.New("argument must be list, tuple, or set") - } - - tobool := MakeToFunc(cty.Bool) + result := cty.True for it := args[0].ElementIterator(); it.Next(); { _, v := it.Element() - if !v.IsKnown() { - return cty.UnknownVal(cty.Bool), nil - } - got, err := tobool.Call([]cty.Value{v}) - if err != nil { + if v.IsNull() { return cty.False, nil } - eq, err := stdlib.Equal(got, cty.True) - if err != nil { - return cty.NilVal, err - } - if eq.False() { + result = result.And(v) + if result.False() { return cty.False, nil } } - return cty.True, nil + return result, nil + }, +}) + +// AnyTrueFunc constructs a function that returns true if any element of the +// list is true. If the list is empty, return false. +var AnyTrueFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.List(cty.Bool), + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + result := cty.False + for it := args[0].ElementIterator(); it.Next(); { + _, v := it.Element() + if v.IsNull() { + continue + } + result = result.Or(v) + if result.True() { + return cty.True, nil + } + } + return result, nil }, }) @@ -620,12 +634,18 @@ func Length(collection cty.Value) (cty.Value, error) { return LengthFunc.Call([]cty.Value{collection}) } -// AllTrue returns true if all elements of the collection are true or "true". -// If the collection is empty, return true. +// AllTrue returns true if all elements of the list are true. If the list is empty, +// return true. func AllTrue(collection cty.Value) (cty.Value, error) { return AllTrueFunc.Call([]cty.Value{collection}) } +// AnyTrue returns true if any element of the list is true. If the list is empty, +// return false. +func AnyTrue(collection cty.Value) (cty.Value, error) { + return AnyTrueFunc.Call([]cty.Value{collection}) +} + // 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) diff --git a/lang/funcs/collection_test.go b/lang/funcs/collection_test.go index d98555196e..f74b3aafc3 100644 --- a/lang/funcs/collection_test.go +++ b/lang/funcs/collection_test.go @@ -146,17 +146,7 @@ func TestAllTrue(t *testing.T) { Err bool }{ { - cty.ListValEmpty(cty.String), - cty.True, - false, - }, - { - cty.TupleVal([]cty.Value{}), - cty.True, - false, - }, - { - cty.SetValEmpty(cty.Bool), + cty.ListValEmpty(cty.Bool), cty.True, false, }, @@ -165,16 +155,6 @@ func TestAllTrue(t *testing.T) { cty.True, false, }, - { - cty.ListVal([]cty.Value{cty.StringVal("true")}), - cty.True, - false, - }, - { - cty.TupleVal([]cty.Value{cty.True, cty.StringVal("true")}), - cty.True, - false, - }, { cty.ListVal([]cty.Value{cty.False}), cty.False, @@ -191,24 +171,14 @@ func TestAllTrue(t *testing.T) { false, }, { - cty.ListVal([]cty.Value{cty.NumberIntVal(1)}), - cty.False, - false, - }, - { - cty.StringVal("true"), - cty.False, + cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}), + cty.UnknownVal(cty.Bool), true, }, { - cty.ListVal([]cty.Value{cty.ListValEmpty(cty.String)}), - cty.False, - false, - }, - { - cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}), - cty.UnknownVal(cty.Bool), - false, + cty.NullVal(cty.List(cty.Bool)), + cty.NilVal, + true, }, } @@ -232,6 +202,69 @@ func TestAllTrue(t *testing.T) { } } +func TestAnyTrue(t *testing.T) { + tests := []struct { + Collection cty.Value + Want cty.Value + Err bool + }{ + { + cty.ListValEmpty(cty.Bool), + cty.False, + false, + }, + { + cty.ListVal([]cty.Value{cty.True}), + cty.True, + false, + }, + { + cty.ListVal([]cty.Value{cty.False}), + cty.False, + false, + }, + { + cty.ListVal([]cty.Value{cty.True, cty.False}), + cty.True, + false, + }, + { + cty.ListVal([]cty.Value{cty.False, cty.True}), + cty.True, + false, + }, + { + cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}), + cty.UnknownVal(cty.Bool), + true, + }, + { + cty.NullVal(cty.List(cty.Bool)), + cty.NilVal, + true, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("anytrue(%#v)", test.Collection), func(t *testing.T) { + got, err := AnyTrue(test.Collection) + + if test.Err { + if err == nil { + t.Fatal("succeeded; want error") + } + 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 TestCoalesce(t *testing.T) { tests := []struct { Values []cty.Value diff --git a/lang/functions.go b/lang/functions.go index c5ff6ac675..d48cace9b8 100644 --- a/lang/functions.go +++ b/lang/functions.go @@ -34,6 +34,7 @@ func (s *Scope) Functions() map[string]function.Function { "abs": stdlib.AbsoluteFunc, "abspath": funcs.AbsPathFunc, "alltrue": funcs.AllTrueFunc, + "anytrue": funcs.AnyTrueFunc, "basename": funcs.BasenameFunc, "base64decode": funcs.Base64DecodeFunc, "base64encode": funcs.Base64EncodeFunc, diff --git a/lang/functions_test.go b/lang/functions_test.go index ce62a7c4c7..4690a4a050 100644 --- a/lang/functions_test.go +++ b/lang/functions_test.go @@ -69,11 +69,18 @@ func TestFunctions(t *testing.T) { "alltrue": { { - `alltrue([true])`, + `alltrue(["true", true])`, cty.True, }, }, + "anytrue": { + { + `anytrue([])`, + cty.False, + }, + }, + "base64decode": { { `base64decode("YWJjMTIzIT8kKiYoKSctPUB+")`, diff --git a/website/docs/configuration/functions/anytrue.html.md b/website/docs/configuration/functions/anytrue.html.md new file mode 100644 index 0000000000..0a6005a716 --- /dev/null +++ b/website/docs/configuration/functions/anytrue.html.md @@ -0,0 +1,34 @@ +--- +layout: functions +page_title: anytrue - Functions - Configuration Language +sidebar_current: docs-funcs-collection-anytrue +description: |- + The anytrue function determines whether any element of a collection + is true or "true". If the collection is empty, it returns false. +--- + +# `anytrue` Function + +-> **Note:** This page is about Terraform 0.12 and later. For Terraform 0.11 and +earlier, see +[0.11 Configuration Language: Interpolation Syntax](../../configuration-0-11/interpolation.html). + +`anytrue` returns `true` if any element in a given collection is `true` +or `"true"`. It also returns `false` if the collection is empty. + +```hcl +anytrue(list) +``` + +## Examples + +```command +> anytrue(["true"]) +true +> anytrue([true]) +true +> anytrue([true, false]) +true +> anytrue([]) +false +``` diff --git a/website/layouts/functions.erb b/website/layouts/functions.erb index 4a2a9656e9..181040f53e 100644 --- a/website/layouts/functions.erb +++ b/website/layouts/functions.erb @@ -142,6 +142,10 @@ alltrue +