Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions bake/hclparser/stdlib.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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.
Expand Down
58 changes: 58 additions & 0 deletions bake/hclparser/stdlib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
26 changes: 26 additions & 0 deletions docs/bake-stdlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ title: Bake standard library functions
| [`reverselist`](#reverselist) | Returns the given list with its elements in reverse order. |
| [`rsadecrypt`](#rsadecrypt) | Decrypts an RSA-encrypted ciphertext. |
| [`sanitize`](#sanitize) | Replaces all non-alphanumeric characters with a underscore, leaving only characters that are valid for a Bake target name. |
| [`semvercmp`](#semvercmp) | Returns true if version satisfies a constraint. |
| [`sethaselement`](#sethaselement) | Returns true if the given set contains the given element, or false otherwise. |
| [`setintersection`](#setintersection) | Returns the intersection of all given sets. |
| [`setproduct`](#setproduct) | Calculates the cartesian product of two or more sets. |
Expand Down Expand Up @@ -1065,6 +1066,31 @@ target "webapp-dev" {
}
```

## `semvercmp`

This function checks if a semantic version fits within a set of constraints.
See [Checking Version Constraints](https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints)
for details.

```hcl
# docker-bake.hcl
variable "ALPINE_VERSION" {
default = "3.23"
}

target "webapp-dev" {
dockerfile = "Dockerfile.webapp"
platforms = semvercmp(ALPINE_VERSION, ">= 3.20") ? [
"linux/amd64",
"linux/arm64",
"linux/riscv64"
] : [
"linux/amd64",
"linux/arm64"
]
}
```

## `sethaselement`

```hcl
Expand Down