hcl2template/functions: Add Non-null refinements for various functions

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.
deps/bump_packer-plugin-sdk_with_gocty_fix
Wilken Rivera 3 years ago
parent a2b3487cf2
commit 401934871c

@ -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
},
@ -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
},
})

@ -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)

@ -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")

@ -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()

@ -5,6 +5,7 @@ package function
import (
"fmt"
"math"
"testing"
"github.com/zclconf/go-cty/cty"
@ -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"),
@ -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(),
},
}

@ -0,0 +1,9 @@
package function
import (
"github.com/zclconf/go-cty/cty"
)
func refineNotNull(b *cty.RefinementBuilder) *cty.RefinementBuilder {
return b.NotNull()
}
Loading…
Cancel
Save