Skip to content

Commit

Permalink
hcl2template/functions: Add Non-null refinements for various functions
Browse files Browse the repository at this point in the history
cty's new "refinements" concept allows us to reduce the range of unknown
values from our functions. This initial changeset focuses only on
declaring which functions are guaranteed to return a non-null result,
which is a helpful baseline refinement because it allows "== null" and
"!= null" tests to produce known results even when the given value is
otherwise unknown.

This commit also includes some updates to test results that are now
refined based on cty's own built-in refinement behaviors, just as a
result of us having updated cty in the previous commit.
  • Loading branch information
nywilken committed Nov 29, 2023
1 parent 2aad170 commit 263821a
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 24 deletions.
35 changes: 20 additions & 15 deletions hcl2template/function/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ import (
"github.com/zclconf/go-cty/cty/function"
)

// InitTime is the UTC time when this package was initialized. It is
// initTime is the UTC time when this package was initialized. It is
// used as the timestamp for all configuration templates so that they
// match for a single build.
var InitTime time.Time
var initTime time.Time

func init() {
InitTime = time.Now().UTC()
initTime = time.Now().UTC()
}

// TimestampFunc constructs a function that returns a string representation of the current date and time.
var TimestampFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Type: function.StaticReturnType(cty.String),
Params: []function.Parameter{},
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil
},
Expand All @@ -40,42 +41,46 @@ func Timestamp() (cty.Value, error) {
}

// LegacyIsotimeFunc constructs a function that returns a string representation
// of the current date and time using golang's datetime formatting.
// of the current date and time using the Go language datetime formatting syntax.
var LegacyIsotimeFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "format",
Type: cty.String,
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
if len(args) > 1 {
return cty.StringVal(""), fmt.Errorf("too many values, 1 needed: %v", args)
} else if len(args) == 0 {
return cty.StringVal(InitTime.Format(time.RFC3339)), nil
}
if len(args) == 0 {
return cty.StringVal(initTime.Format(time.RFC3339)), nil
}
format := args[0].AsString()
return cty.StringVal(InitTime.Format(format)), nil
return cty.StringVal(initTime.Format(format)), nil
},
})

// LegacyStrftimeFunc constructs a function that returns a string representation
// of the current date and time using golang's strftime datetime formatting.
// of the current date and time using the Go language strftime datetime formatting syntax.
var LegacyStrftimeFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "format",
Type: cty.String,
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
if len(args) > 1 {
return cty.StringVal(""), fmt.Errorf("too many values, 1 needed: %v", args)
} else if len(args) == 0 {
return cty.StringVal(InitTime.Format(time.RFC3339)), nil
}
if len(args) == 0 {
return cty.StringVal(initTime.Format(time.RFC3339)), nil
}
format := args[0].AsString()
return cty.StringVal(strftime.Format(format, InitTime)), nil
return cty.StringVal(strftime.Format(format, initTime)), nil
},
})

Expand Down
3 changes: 2 additions & 1 deletion hcl2template/function/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ var EnvFunc = function.New(&function.Spec{
AllowUnknown: false,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
key := args[0].AsString()
value := os.Getenv(key)
Expand Down
3 changes: 2 additions & 1 deletion hcl2template/function/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ var IndexFunc = function.New(&function.Spec{
Type: cty.DynamicPseudoType,
},
},
Type: function.StaticReturnType(cty.Number),
Type: function.StaticReturnType(cty.Number),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) {
return cty.NilVal, errors.New("argument must be a list or tuple")
Expand Down
1 change: 1 addition & 0 deletions hcl2template/function/length.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var LengthFunc = function.New(&function.Spec{
return cty.Number, errors.New("argument must be a string, a collection type, or a structural type")
}
},
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
coll := args[0]
collTy := args[0].Type()
Expand Down
18 changes: 11 additions & 7 deletions hcl2template/function/length_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package function

import (
"fmt"
"math"
"testing"

"github.com/zclconf/go-cty/cty"
Expand Down Expand Up @@ -69,11 +70,15 @@ func TestLength(t *testing.T) {
},
{
cty.UnknownVal(cty.List(cty.Bool)),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).Refine().
NotNull().
NumberRangeLowerBound(cty.Zero, true).
NumberRangeUpperBound(cty.NumberIntVal(math.MaxInt), true).
NewValue(),
},
{
cty.DynamicVal,
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
},
{
cty.StringVal("hello"),
Expand Down Expand Up @@ -118,11 +123,10 @@ func TestLength(t *testing.T) {
},
{
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.Number),
},
{
cty.DynamicVal,
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).Refine().
NotNull().
NumberRangeLowerBound(cty.Zero, true).
NewValue(),
},
}

Expand Down
9 changes: 9 additions & 0 deletions hcl2template/function/refinements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package function

import (
"github.com/zclconf/go-cty/cty"
)

func refineNotNull(b *cty.RefinementBuilder) *cty.RefinementBuilder {
return b.NotNull()
}

0 comments on commit 263821a

Please sign in to comment.