From d4716a69e1b345b274c7f9669897ec8e693d9af0 Mon Sep 17 00:00:00 2001 From: Arthur Burkart Date: Fri, 23 Oct 2020 16:52:48 -0400 Subject: [PATCH] lang/funcs: "anytrue" function This is an analog to the "alltrue" function, using OR as the reduce operator rather than AND. This also includes some simplification of the "alltrue" implementation to implement it similarly as a sort of reduce operation with AND as the reduce operator, but with the same effective behavior. --- lang/funcs/collection.go | 64 +++++++---- lang/funcs/collection_test.go | 105 ++++++++++++------ lang/functions.go | 1 + lang/functions_test.go | 9 +- .../configuration/functions/anytrue.html.md | 34 ++++++ website/layouts/functions.erb | 4 + 6 files changed, 158 insertions(+), 59 deletions(-) create mode 100644 website/docs/configuration/functions/anytrue.html.md diff --git a/lang/funcs/collection.go b/lang/funcs/collection.go index d83a79bd3..cca7423c0 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 d98555196..f74b3aafc 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 c5ff6ac67..d48cace9b 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 ce62a7c4c..4690a4a05 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 000000000..0a6005a71 --- /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 4a2a9656e..181040f53 100644 --- a/website/layouts/functions.erb +++ b/website/layouts/functions.erb @@ -142,6 +142,10 @@ alltrue +
  • + anytrue +
  • +
  • chunklist