bake: requires explicit allow for local output delete mode

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2026-06-10 16:41:09 +02:00
committed by CrazyMax
parent 1916210ddc
commit 473c6ef306
5 changed files with 165 additions and 19 deletions
+42 -18
View File
@@ -17,6 +17,7 @@ import (
"github.com/containerd/console"
"github.com/docker/buildx/build"
"github.com/docker/buildx/util/osutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/entitlements"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -26,27 +27,29 @@ import (
type EntitlementKey string
const (
EntitlementKeyNetworkHost EntitlementKey = "network.host"
EntitlementKeySecurityInsecure EntitlementKey = "security.insecure"
EntitlementKeyDevice EntitlementKey = "device"
EntitlementKeyFSRead EntitlementKey = "fs.read"
EntitlementKeyFSWrite EntitlementKey = "fs.write"
EntitlementKeyFS EntitlementKey = "fs"
EntitlementKeyImagePush EntitlementKey = "image.push"
EntitlementKeyImageLoad EntitlementKey = "image.load"
EntitlementKeyImage EntitlementKey = "image"
EntitlementKeySSH EntitlementKey = "ssh"
EntitlementKeyNetworkHost EntitlementKey = "network.host"
EntitlementKeySecurityInsecure EntitlementKey = "security.insecure"
EntitlementKeyDevice EntitlementKey = "device"
EntitlementKeyFSRead EntitlementKey = "fs.read"
EntitlementKeyFSWrite EntitlementKey = "fs.write"
EntitlementKeyFS EntitlementKey = "fs"
EntitlementKeyImagePush EntitlementKey = "image.push"
EntitlementKeyImageLoad EntitlementKey = "image.load"
EntitlementKeyImage EntitlementKey = "image"
EntitlementKeySSH EntitlementKey = "ssh"
EntitlementKeyLocalOutputDelete EntitlementKey = "local-output-delete"
)
type EntitlementConf struct {
NetworkHost bool
SecurityInsecure bool
Devices *EntitlementsDevicesConf
FSRead []string
FSWrite []string
ImagePush []string
ImageLoad []string
SSH bool
NetworkHost bool
SecurityInsecure bool
Devices *EntitlementsDevicesConf
FSRead []string
FSWrite []string
ImagePush []string
ImageLoad []string
SSH bool
LocalOutputDelete bool
}
type EntitlementsDevicesConf struct {
@@ -64,6 +67,8 @@ func ParseEntitlements(in []string) (EntitlementConf, error) {
conf.SecurityInsecure = true
case string(EntitlementKeySSH):
conf.SSH = true
case string(EntitlementKeyLocalOutputDelete):
conf.LocalOutputDelete = true
default:
k, v, _ := strings.Cut(e, "=")
switch k {
@@ -97,6 +102,8 @@ func ParseEntitlements(in []string) (EntitlementConf, error) {
case string(EntitlementKeyImage):
conf.ImagePush = append(conf.ImagePush, v)
conf.ImageLoad = append(conf.ImageLoad, v)
case string(EntitlementKeyLocalOutputDelete):
return conf, errors.Errorf("%s does not accept a value", EntitlementKeyLocalOutputDelete)
default:
return conf, errors.Errorf("unknown entitlement key %q", k)
}
@@ -164,6 +171,19 @@ func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) erro
rwPaths[p] = struct{}{}
}
for _, ex := range bo.Exports {
if ex.Type != client.ExporterLocal {
continue
}
mode, err := client.ParseLocalExporterMode(ex.Attrs["mode"])
if err != nil {
return err
}
if mode == client.LocalExporterModeDelete && !c.LocalOutputDelete {
expected.LocalOutputDelete = true
}
}
for _, ce := range bo.CacheTo {
if ce.Type == "local" {
if dest, ok := ce.Attrs["dest"]; ok {
@@ -249,6 +269,10 @@ func (c EntitlementConf) Prompt(ctx context.Context, isRemote bool, out io.Write
msgsFS = append(msgsFS, " - Forwarding default SSH agent socket")
flagsFS = append(flagsFS, string(EntitlementKeySSH))
}
if c.LocalOutputDelete {
msgs = append(msgs, " - Deleting stale files from local output destinations")
flags = append(flags, string(EntitlementKeyLocalOutputDelete))
}
roPaths, rwPaths, commonPaths := groupSamePaths(c.FSRead, c.FSWrite)
wd, err := os.Getwd()
+111
View File
@@ -1,6 +1,8 @@
package bake
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
@@ -10,6 +12,7 @@ import (
"github.com/docker/buildx/build"
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/osutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/util/entitlements"
"github.com/stretchr/testify/require"
@@ -405,6 +408,73 @@ func TestValidateEntitlements(t *testing.T) {
FSWrite: []string{dir1},
},
},
{
name: "LocalOutputDeleteMissing",
conf: EntitlementConf{
FSWrite: []string{"*"},
},
opt: build.Options{
Inputs: build.Inputs{
ContextState: &llb.State{},
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: dir1,
Attrs: map[string]string{
"mode": string(client.LocalExporterModeDelete),
},
},
},
ExportsLocalPathsTemporary: []string{dir1},
},
expected: EntitlementConf{
LocalOutputDelete: true,
},
},
{
name: "LocalOutputDeleteSet",
conf: EntitlementConf{
FSWrite: []string{"*"},
LocalOutputDelete: true,
},
opt: build.Options{
Inputs: build.Inputs{
ContextState: &llb.State{},
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: dir1,
Attrs: map[string]string{
"mode": string(client.LocalExporterModeDelete),
},
},
},
ExportsLocalPathsTemporary: []string{dir1},
},
},
{
name: "LocalOutputCopy",
conf: EntitlementConf{
FSWrite: []string{"*"},
},
opt: build.Options{
Inputs: build.Inputs{
ContextState: &llb.State{},
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: dir1,
Attrs: map[string]string{
"mode": string(client.LocalExporterModeCopy),
},
},
},
ExportsLocalPathsTemporary: []string{dir1},
},
},
}
for _, tc := range tcases {
@@ -416,6 +486,47 @@ func TestValidateEntitlements(t *testing.T) {
}
}
func TestValidateEntitlementsInvalidLocalOutputMode(t *testing.T) {
_, err := EntitlementConf{}.Validate(map[string]build.Options{
"test": {
Inputs: build.Inputs{
ContextState: &llb.State{},
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
Attrs: map[string]string{
"mode": "backup",
},
},
},
},
})
require.ErrorContains(t, err, `invalid local exporter mode "backup"`)
}
func TestParseEntitlementsLocalOutputDelete(t *testing.T) {
conf, err := ParseEntitlements([]string{string(EntitlementKeyLocalOutputDelete)})
require.NoError(t, err)
require.True(t, conf.LocalOutputDelete)
_, err = ParseEntitlements([]string{string(EntitlementKeyLocalOutputDelete) + "=true"})
require.ErrorContains(t, err, "local-output-delete does not accept a value")
}
func TestPromptLocalOutputDeleteCannotBeDisabledWithFSEntitlements(t *testing.T) {
t.Setenv("BUILDX_BAKE_ENTITLEMENTS_FS", "0")
ctx, cancel := context.WithCancelCause(context.Background())
cancel(nil)
var out bytes.Buffer
err := EntitlementConf{LocalOutputDelete: true}.Prompt(ctx, true, &out)
require.ErrorContains(t, err, "additional privileges requested")
require.Contains(t, out.String(), "Deleting stale files from local output destinations")
require.Contains(t, out.String(), "--allow=local-output-delete")
}
func TestGroupSamePaths(t *testing.T) {
tests := []struct {
name string