Skip to content

Commit 3836aeb

Browse files
committed
bake: add semvercmp func to stdlib
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
1 parent 2bcb098 commit 3836aeb

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

bake/hclparser/stdlib.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/Masterminds/semver/v3"
1314
"github.com/docker/cli/cli/config"
1415
"github.com/hashicorp/go-cty-funcs/cidr"
1516
"github.com/hashicorp/go-cty-funcs/crypto"
@@ -103,6 +104,7 @@ var stdlibFunctions = []funcDef{
103104
{name: "reverselist", fn: stdlib.ReverseListFunc},
104105
{name: "rsadecrypt", fn: crypto.RsaDecryptFunc, descriptionAlt: `Decrypts an RSA-encrypted ciphertext.`},
105106
{name: "sanitize", factory: sanitizeFunc},
107+
{name: "semvercmp", factory: semvercmpFunc},
106108
{name: "sethaselement", fn: stdlib.SetHasElementFunc},
107109
{name: "setintersection", fn: stdlib.SetIntersectionFunc},
108110
{name: "setproduct", fn: stdlib.SetProductFunc},
@@ -246,6 +248,36 @@ func sanitizeFunc() function.Function {
246248
})
247249
}
248250

251+
// semvercmpFunc constructs a function that checks if a version satisfies a
252+
// constraint.
253+
func semvercmpFunc() function.Function {
254+
return function.New(&function.Spec{
255+
Description: `Returns true if version satisfies a constraint.`,
256+
Params: []function.Parameter{
257+
{
258+
Name: "version",
259+
Type: cty.String,
260+
},
261+
{
262+
Name: "contraint",
263+
Type: cty.String,
264+
},
265+
},
266+
Type: function.StaticReturnType(cty.Bool),
267+
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
268+
version, err := semver.NewVersion(args[0].AsString())
269+
if err != nil {
270+
return cty.UnknownVal(cty.Bool), err
271+
}
272+
constraint, err := semver.NewConstraint(args[1].AsString())
273+
if err != nil {
274+
return cty.UnknownVal(cty.Bool), err
275+
}
276+
return cty.BoolVal(constraint.Check(version)), nil
277+
},
278+
})
279+
}
280+
249281
// timestampFunc constructs a function that returns a string representation of the current date and time.
250282
//
251283
// This function was imported from terraform's datetime utilities.

bake/hclparser/stdlib_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,61 @@ func TestHomedir(t *testing.T) {
205205
require.NotEmpty(t, home.AsString())
206206
require.True(t, filepath.IsAbs(home.AsString()))
207207
}
208+
209+
func TestSemverCmp(t *testing.T) {
210+
type testCase struct {
211+
version cty.Value
212+
constraint cty.Value
213+
want cty.Value
214+
wantErr bool
215+
}
216+
tests := map[string]testCase{
217+
"valid constraint satisfied": {
218+
version: cty.StringVal("1.2.3"),
219+
constraint: cty.StringVal(">= 1.0.0"),
220+
want: cty.BoolVal(true),
221+
},
222+
"valid constraint not satisfied": {
223+
version: cty.StringVal("2.1.0"),
224+
constraint: cty.StringVal("< 2.0.0"),
225+
want: cty.BoolVal(false),
226+
},
227+
"valid constraint satisfied without patch": {
228+
version: cty.StringVal("3.22"),
229+
constraint: cty.StringVal(">= 3.20"),
230+
want: cty.BoolVal(true),
231+
},
232+
"invalid version": {
233+
version: cty.StringVal("not-a-version"),
234+
constraint: cty.StringVal(">= 1.0.0"),
235+
wantErr: true,
236+
},
237+
"invalid constraint": {
238+
version: cty.StringVal("1.2.3"),
239+
constraint: cty.StringVal("not-a-constraint"),
240+
wantErr: true,
241+
},
242+
"empty version": {
243+
version: cty.StringVal(""),
244+
constraint: cty.StringVal(">= 1.0.0"),
245+
wantErr: true,
246+
},
247+
"empty constraint": {
248+
version: cty.StringVal("1.2.3"),
249+
constraint: cty.StringVal(""),
250+
wantErr: true,
251+
},
252+
}
253+
for name, test := range tests {
254+
name, test := name, test
255+
t.Run(name, func(t *testing.T) {
256+
got, err := semvercmpFunc().Call([]cty.Value{test.version, test.constraint})
257+
if test.wantErr {
258+
require.Error(t, err)
259+
} else {
260+
require.NoError(t, err)
261+
require.Equal(t, test.want, got)
262+
}
263+
})
264+
}
265+
}

docs/bake-stdlib.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ title: Bake standard library functions
7979
| [`reverselist`](#reverselist) | Returns the given list with its elements in reverse order. |
8080
| [`rsadecrypt`](#rsadecrypt) | Decrypts an RSA-encrypted ciphertext. |
8181
| [`sanitize`](#sanitize) | Replaces all non-alphanumeric characters with a underscore, leaving only characters that are valid for a Bake target name. |
82+
| [`semvercmp`](#semvercmp) | Returns true if version satisfies a constraint. |
8283
| [`sethaselement`](#sethaselement) | Returns true if the given set contains the given element, or false otherwise. |
8384
| [`setintersection`](#setintersection) | Returns the intersection of all given sets. |
8485
| [`setproduct`](#setproduct) | Calculates the cartesian product of two or more sets. |
@@ -1065,6 +1066,31 @@ target "webapp-dev" {
10651066
}
10661067
```
10671068

1069+
## `semvercmp`
1070+
1071+
This function checks if a semantic version fits within a set of constraints.
1072+
See [Checking Version Constraints](https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints)
1073+
for details.
1074+
1075+
```hcl
1076+
# docker-bake.hcl
1077+
variable "ALPINE_VERSION" {
1078+
default = "3.23"
1079+
}
1080+
1081+
target "webapp-dev" {
1082+
dockerfile = "Dockerfile.webapp"
1083+
platforms = semvercmp(ALPINE_VERSION, ">= 3.20") ? [
1084+
"linux/amd64",
1085+
"linux/arm64",
1086+
"linux/riscv64"
1087+
] : [
1088+
"linux/amd64",
1089+
"linux/arm64"
1090+
]
1091+
}
1092+
```
1093+
10681094
## `sethaselement`
10691095

10701096
```hcl

0 commit comments

Comments
 (0)