From 64e2b2532ae8c9494080fa5db06595dee113beec Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:15:55 +0100 Subject: [PATCH] bake: ulimits support Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- bake/bake.go | 15 +++++++++++++++ bake/bake_test.go | 2 ++ bake/compose.go | 14 ++++++++++++++ bake/compose_test.go | 5 +++++ docs/bake-reference.md | 20 ++++++++++++++++++++ tests/bake.go | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 89 insertions(+) diff --git a/bake/bake.go b/bake/bake.go index f4d662ae..8facfb79 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -701,6 +701,7 @@ type Target struct { NetworkMode *string `json:"-" hcl:"-" cty:"-"` NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"` ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"` + Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"` // IMPORTANT: if you add more fields here, do not forget to update newOverrides and docs/bake-reference.md. // linked is a private field to mark a target used as a linked one @@ -723,6 +724,7 @@ func (t *Target) normalize() { t.CacheTo = removeDupes(t.CacheTo) t.Outputs = removeDupes(t.Outputs) t.NoCacheFilter = removeDupes(t.NoCacheFilter) + t.Ulimits = removeDupes(t.Ulimits) for k, v := range t.Contexts { if v == "" { @@ -814,6 +816,9 @@ func (t *Target) Merge(t2 *Target) { if t2.ShmSize != nil { // no merge t.ShmSize = t2.ShmSize } + if t2.Ulimits != nil { // merge + t.Ulimits = append(t.Ulimits, t2.Ulimits...) + } t.Inherits = append(t.Inherits, t2.Inherits...) } @@ -880,6 +885,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error { t.NoCacheFilter = o.ArrValue case "shm-size": t.ShmSize = &value + case "ulimits": + t.Ulimits = o.ArrValue case "pull": pull, err := strconv.ParseBool(value) if err != nil { @@ -1335,6 +1342,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { return nil, err } + ulimits := dockeropts.NewUlimitOpt(nil) + for _, field := range t.Ulimits { + if err := ulimits.Set(field); err != nil { + return nil, err + } + } + bo.Ulimits = ulimits + return bo, nil } diff --git a/bake/bake_test.go b/bake/bake_test.go index 8ecd5f2b..39f9ad05 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -23,6 +23,7 @@ target "webDEP" { } no-cache = true shm-size = "128m" + ulimits = ["nofile=1024:1024"] } target "webapp" { @@ -47,6 +48,7 @@ target "webapp" { require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"]) require.Equal(t, true, *m["webapp"].NoCache) require.Equal(t, "128m", *m["webapp"].ShmSize) + require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits) require.Nil(t, m["webapp"].Pull) require.Equal(t, 1, len(g)) diff --git a/bake/compose.go b/bake/compose.go index 2502f826..42554c7c 100644 --- a/bake/compose.go +++ b/bake/compose.go @@ -2,6 +2,7 @@ package bake import ( "context" + "fmt" "os" "path/filepath" "strings" @@ -10,6 +11,7 @@ import ( "github.com/compose-spec/compose-go/v2/loader" composetypes "github.com/compose-spec/compose-go/v2/types" dockeropts "github.com/docker/cli/opts" + "github.com/docker/go-units" "github.com/pkg/errors" "gopkg.in/yaml.v3" ) @@ -94,6 +96,17 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf shmSize = &shmSizeStr } + var ulimits []string + if s.Build.Ulimits != nil { + for n, u := range s.Build.Ulimits { + ulimit, err := units.ParseUlimit(fmt.Sprintf("%s=%d:%d", n, u.Soft, u.Hard)) + if err != nil { + return nil, err + } + ulimits = append(ulimits, ulimit.String()) + } + } + var secrets []string for _, bs := range s.Build.Secrets { secret, err := composeToBuildkitSecret(bs, cfg.Secrets[bs.Source]) @@ -131,6 +144,7 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf NetworkMode: &s.Build.Network, Secrets: secrets, ShmSize: shmSize, + Ulimits: ulimits, } if err = t.composeExtTarget(s.Build.Extensions); err != nil { return nil, err diff --git a/bake/compose_test.go b/bake/compose_test.go index cf1d097a..be458be1 100644 --- a/bake/compose_test.go +++ b/bake/compose_test.go @@ -304,6 +304,10 @@ services: CT_ECR: foo CT_TAG: bar shm_size: 128m + ulimits: + nofile: + soft: 1024 + hard: 1024 x-bake: secret: - id=mysecret,src=/local/secret @@ -334,6 +338,7 @@ services: require.Equal(t, []string{"type=docker"}, c.Targets[1].Outputs) require.Equal(t, newBool(true), c.Targets[1].NoCache) require.Equal(t, ptrstr("128MiB"), c.Targets[1].ShmSize) + require.Equal(t, []string{"nofile=1024:1024"}, c.Targets[1].Ulimits) } func TestComposeExtDedup(t *testing.T) { diff --git a/docs/bake-reference.md b/docs/bake-reference.md index 6d433724..712bb07f 100644 --- a/docs/bake-reference.md +++ b/docs/bake-reference.md @@ -237,6 +237,7 @@ The following table shows the complete list of attributes that you can assign to | [`ssh`](#targetssh) | List | SSH agent sockets or keys to expose to the build | | [`tags`](#targettags) | List | Image names and tags | | [`target`](#targettarget) | String | Target build stage | +| [`ulimits`](#targetulimits) | List | Ulimit options | ### `target.args` @@ -893,6 +894,25 @@ target "default" { } ``` +### `target.ulimits` + +Ulimits are specified with a soft and hard limit as such: +`=[:]`, for example: + +```hcl +target "app" { + ulimits = [ + "nofile=1024:1024" + ] +} +``` + +> **Note** +> +> If you do not provide a `hard limit`, the `soft limit` is used +> for both values. If no `ulimits` are set, they are inherited from +> the default `ulimits` set on the daemon. + ## Group Groups allow you to invoke multiple builds (targets) at once. diff --git a/tests/bake.go b/tests/bake.go index 3b617a12..03c611b2 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -33,6 +33,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){ testBakeRemoteLocalContextRemoteDockerfile, testBakeEmpty, testBakeShmSize, + testBakeUlimits, } func testBakeLocal(t *testing.T, sb integration.Sandbox) { @@ -553,3 +554,35 @@ target "default" { require.NoError(t, err) require.Contains(t, string(dt), `size=131072k`) } + +func testBakeUlimits(t *testing.T, sb integration.Sandbox) { + dockerfile := []byte(` +FROM busybox AS build +RUN ulimit -n > first > /ulimit +FROM scratch +COPY --from=build /ulimit / + `) + bakefile := []byte(` +target "default" { + ulimits = ["nofile=1024:1024"] +} +`) + dir := tmpdir( + t, + fstest.CreateFile("docker-bake.hcl", bakefile, 0600), + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + + dirDest := t.TempDir() + + out, err := bakeCmd( + sb, + withDir(dir), + withArgs("--set", "*.output=type=local,dest="+dirDest), + ) + require.NoError(t, err, out) + + dt, err := os.ReadFile(filepath.Join(dirDest, "ulimit")) + require.NoError(t, err) + require.Contains(t, string(dt), `1024`) +}