bake: add semvercmp func to stdlib

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2026-01-06 09:34:10 +01:00
parent 2bcb098cbb
commit 3836aeb949
3 changed files with 116 additions and 0 deletions
+32
View File
@@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/Masterminds/semver/v3"
"github.com/docker/cli/cli/config"
"github.com/hashicorp/go-cty-funcs/cidr"
"github.com/hashicorp/go-cty-funcs/crypto"
@@ -103,6 +104,7 @@ var stdlibFunctions = []funcDef{
{name: "reverselist", fn: stdlib.ReverseListFunc},
{name: "rsadecrypt", fn: crypto.RsaDecryptFunc, descriptionAlt: `Decrypts an RSA-encrypted ciphertext.`},
{name: "sanitize", factory: sanitizeFunc},
{name: "semvercmp", factory: semvercmpFunc},
{name: "sethaselement", fn: stdlib.SetHasElementFunc},
{name: "setintersection", fn: stdlib.SetIntersectionFunc},
{name: "setproduct", fn: stdlib.SetProductFunc},
@@ -246,6 +248,36 @@ func sanitizeFunc() function.Function {
})
}
// semvercmpFunc constructs a function that checks if a version satisfies a
// constraint.
func semvercmpFunc() function.Function {
return function.New(&function.Spec{
Description: `Returns true if version satisfies a constraint.`,
Params: []function.Parameter{
{
Name: "version",
Type: cty.String,
},
{
Name: "contraint",
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
version, err := semver.NewVersion(args[0].AsString())
if err != nil {
return cty.UnknownVal(cty.Bool), err
}
constraint, err := semver.NewConstraint(args[1].AsString())
if err != nil {
return cty.UnknownVal(cty.Bool), err
}
return cty.BoolVal(constraint.Check(version)), nil
},
})
}
// timestampFunc constructs a function that returns a string representation of the current date and time.
//
// This function was imported from terraform's datetime utilities.
+58
View File
@@ -205,3 +205,61 @@ func TestHomedir(t *testing.T) {
require.NotEmpty(t, home.AsString())
require.True(t, filepath.IsAbs(home.AsString()))
}
func TestSemverCmp(t *testing.T) {
type testCase struct {
version cty.Value
constraint cty.Value
want cty.Value
wantErr bool
}
tests := map[string]testCase{
"valid constraint satisfied": {
version: cty.StringVal("1.2.3"),
constraint: cty.StringVal(">= 1.0.0"),
want: cty.BoolVal(true),
},
"valid constraint not satisfied": {
version: cty.StringVal("2.1.0"),
constraint: cty.StringVal("< 2.0.0"),
want: cty.BoolVal(false),
},
"valid constraint satisfied without patch": {
version: cty.StringVal("3.22"),
constraint: cty.StringVal(">= 3.20"),
want: cty.BoolVal(true),
},
"invalid version": {
version: cty.StringVal("not-a-version"),
constraint: cty.StringVal(">= 1.0.0"),
wantErr: true,
},
"invalid constraint": {
version: cty.StringVal("1.2.3"),
constraint: cty.StringVal("not-a-constraint"),
wantErr: true,
},
"empty version": {
version: cty.StringVal(""),
constraint: cty.StringVal(">= 1.0.0"),
wantErr: true,
},
"empty constraint": {
version: cty.StringVal("1.2.3"),
constraint: cty.StringVal(""),
wantErr: true,
},
}
for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
got, err := semvercmpFunc().Call([]cty.Value{test.version, test.constraint})
if test.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, test.want, got)
}
})
}
}