Browse code

vendor: github.com/moby/buildkit v0.20.0-rc3

full diff: https://github.com/moby/buildkit/compare/v0.20.0-rc2...v0.20.0-rc3

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2025/02/19 05:47:35
Showing 12 changed files
... ...
@@ -63,7 +63,7 @@ require (
63 63
 	github.com/miekg/dns v1.1.61
64 64
 	github.com/mistifyio/go-zfs/v3 v3.0.1
65 65
 	github.com/mitchellh/copystructure v1.2.0
66
-	github.com/moby/buildkit v0.20.0-rc2
66
+	github.com/moby/buildkit v0.20.0-rc3
67 67
 	github.com/moby/docker-image-spec v1.3.1
68 68
 	github.com/moby/ipvs v1.1.0
69 69
 	github.com/moby/locker v1.0.1
... ...
@@ -383,8 +383,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
383 383
 github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
384 384
 github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
385 385
 github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs=
386
-github.com/moby/buildkit v0.20.0-rc2 h1:QjACghvG0pSAp7dk9aQMYWioDEOljDWyyoUjyg35qfg=
387
-github.com/moby/buildkit v0.20.0-rc2/go.mod h1:kMXf90l/f3zygRK8bYbyetfyzoJYntb6Bpi2VsLfXgQ=
386
+github.com/moby/buildkit v0.20.0-rc3 h1:iExrfuZZuFgFudeNJhXfp/5vzJWTNrlqZ/LYJk4dG2Q=
387
+github.com/moby/buildkit v0.20.0-rc3/go.mod h1:kMXf90l/f3zygRK8bYbyetfyzoJYntb6Bpi2VsLfXgQ=
388 388
 github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
389 389
 github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
390 390
 github.com/moby/ipvs v1.1.0 h1:ONN4pGaZQgAx+1Scz5RvWV4Q7Gb+mvfRh3NsPS+1XQQ=
... ...
@@ -63,10 +63,6 @@ func getConfig(attrs map[string]string) (*Config, error) {
63 63
 	if !ok {
64 64
 		scope = "buildkit"
65 65
 	}
66
-	url, ok := attrs[attrURL]
67
-	if !ok {
68
-		return nil, errors.Errorf("url not set for github actions cache")
69
-	}
70 66
 	token, ok := attrs[attrToken]
71 67
 	if !ok {
72 68
 		return nil, errors.Errorf("token not set for github actions cache")
... ...
@@ -80,12 +76,19 @@ func getConfig(attrs map[string]string) (*Config, error) {
80 80
 		}
81 81
 		apiVersionInt = int(i)
82 82
 	}
83
+	var url string
83 84
 	if apiVersionInt != 1 {
84 85
 		if v, ok := attrs[attrURLV2]; ok {
85 86
 			url = v
86 87
 			apiVersionInt = 2
87 88
 		}
88 89
 	}
90
+	if v, ok := attrs[attrURL]; ok && url == "" {
91
+		url = v
92
+	}
93
+	if url == "" {
94
+		return nil, errors.Errorf("url not set for github actions cache")
95
+	}
89 96
 	// best effort on old clients
90 97
 	if apiVersionInt == 0 {
91 98
 		if strings.Contains(url, "results-receiver.actions.githubusercontent.com") {
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"io"
8 8
 	"maps"
9 9
 	"os"
10
+	"slices"
10 11
 	"strings"
11 12
 	"time"
12 13
 
... ...
@@ -24,7 +25,6 @@ import (
24 24
 	"github.com/moby/buildkit/solver/pb"
25 25
 	spb "github.com/moby/buildkit/sourcepolicy/pb"
26 26
 	"github.com/moby/buildkit/util/bklog"
27
-	"github.com/moby/buildkit/util/entitlements"
28 27
 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
29 28
 	"github.com/pkg/errors"
30 29
 	"github.com/tonistiigi/fsutil"
... ...
@@ -45,7 +45,7 @@ type SolveOpt struct {
45 45
 	CacheExports          []CacheOptionsEntry
46 46
 	CacheImports          []CacheOptionsEntry
47 47
 	Session               []session.Attachable
48
-	AllowedEntitlements   []entitlements.Entitlement
48
+	AllowedEntitlements   []string
49 49
 	SharedSession         *session.Session // TODO: refactor to better session syncing
50 50
 	SessionPreInitialized bool             // TODO: refactor to better session syncing
51 51
 	Internal              bool
... ...
@@ -277,7 +277,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
277 277
 			FrontendAttrs:           frontendAttrs,
278 278
 			FrontendInputs:          frontendInputs,
279 279
 			Cache:                   &cacheOpt.options,
280
-			Entitlements:            entitlementsToPB(opt.AllowedEntitlements),
280
+			Entitlements:            slices.Clone(opt.AllowedEntitlements),
281 281
 			Internal:                opt.Internal,
282 282
 			SourcePolicy:            opt.SourcePolicy,
283 283
 		})
... ...
@@ -553,11 +553,3 @@ func prepareMounts(opt *SolveOpt) (map[string]fsutil.FS, error) {
553 553
 	}
554 554
 	return mounts, nil
555 555
 }
556
-
557
-func entitlementsToPB(entitlements []entitlements.Entitlement) []string {
558
-	clone := make([]string, len(entitlements))
559
-	for i, e := range entitlements {
560
-		clone[i] = string(e)
561
-	}
562
-	return clone
563
-}
... ...
@@ -77,8 +77,9 @@ type OTELConfig struct {
77 77
 }
78 78
 
79 79
 type CDIConfig struct {
80
-	Disabled *bool    `toml:"disabled"`
81
-	SpecDirs []string `toml:"specDirs"`
80
+	Disabled    *bool    `toml:"disabled"`
81
+	SpecDirs    []string `toml:"specDirs"`
82
+	AutoAllowed []string `toml:"autoAllowed"`
82 83
 }
83 84
 
84 85
 type GCConfig struct {
... ...
@@ -695,7 +695,7 @@ func toPBCDIDevices(manager *cdidevices.Manager) []*apitypes.CDIDevice {
695 695
 	for _, dev := range devs {
696 696
 		out = append(out, &apitypes.CDIDevice{
697 697
 			Name:        dev.Name,
698
-			AutoAllow:   true, // TODO
698
+			AutoAllow:   dev.AutoAllow,
699 699
 			Annotations: dev.Annotations,
700 700
 			OnDemand:    dev.OnDemand,
701 701
 		})
... ...
@@ -138,7 +138,7 @@ func (b *llbBridge) loadResult(ctx context.Context, def *pb.Definition, cacheImp
138 138
 	}
139 139
 	dpc := &detectPrunedCacheID{}
140 140
 
141
-	edge, err := Load(ctx, def, polEngine, dpc.Load, ValidateEntitlements(ent), WithCacheSources(cms), NormalizeRuntimePlatforms(), WithValidateCaps())
141
+	edge, err := Load(ctx, def, polEngine, dpc.Load, ValidateEntitlements(ent, w.CDIManager()), WithCacheSources(cms), NormalizeRuntimePlatforms(), WithValidateCaps())
142 142
 	if err != nil {
143 143
 		return nil, errors.Wrap(err, "failed to load LLB")
144 144
 	}
... ...
@@ -2,6 +2,7 @@ package cdidevices
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"strconv"
5 6
 	"strings"
6 7
 
7 8
 	"github.com/moby/buildkit/solver/pb"
... ...
@@ -13,7 +14,10 @@ import (
13 13
 	"tags.cncf.io/container-device-interface/pkg/parser"
14 14
 )
15 15
 
16
-const deviceAnnotationClass = "org.mobyproject.buildkit.device.class"
16
+const (
17
+	deviceAnnotationClass     = "org.mobyproject.buildkit.device.class"
18
+	deviceAnnotationAutoAllow = "org.mobyproject.buildkit.device.autoallow"
19
+)
17 20
 
18 21
 var installers = map[string]Setup{}
19 22
 
... ...
@@ -35,17 +39,38 @@ type Device struct {
35 35
 }
36 36
 
37 37
 type Manager struct {
38
-	cache  *cdi.Cache
39
-	locker *locker.Locker
38
+	cache       *cdi.Cache
39
+	locker      *locker.Locker
40
+	autoAllowed map[string]struct{}
40 41
 }
41 42
 
42
-func NewManager(cache *cdi.Cache) *Manager {
43
+func NewManager(cache *cdi.Cache, autoAllowed []string) *Manager {
44
+	m := make(map[string]struct{})
45
+	for _, d := range autoAllowed {
46
+		m[d] = struct{}{}
47
+	}
43 48
 	return &Manager{
44
-		cache:  cache,
45
-		locker: locker.New(),
49
+		cache:       cache,
50
+		locker:      locker.New(),
51
+		autoAllowed: m,
46 52
 	}
47 53
 }
48 54
 
55
+func (m *Manager) isAutoAllowed(kind, name string, annotations map[string]string) bool {
56
+	if _, ok := m.autoAllowed[name]; ok {
57
+		return true
58
+	}
59
+	if _, ok := m.autoAllowed[kind]; ok {
60
+		return true
61
+	}
62
+	if v, ok := annotations[deviceAnnotationAutoAllow]; ok {
63
+		if b, err := strconv.ParseBool(v); err == nil && b {
64
+			return true
65
+		}
66
+	}
67
+	return false
68
+}
69
+
49 70
 func (m *Manager) ListDevices() []Device {
50 71
 	devs := m.cache.ListDevices()
51 72
 	out := make([]Device, 0, len(devs))
... ...
@@ -53,10 +78,11 @@ func (m *Manager) ListDevices() []Device {
53 53
 	for _, dev := range devs {
54 54
 		kind, _, _ := strings.Cut(dev, "=")
55 55
 		dd := m.cache.GetDevice(dev)
56
+		annotations := deviceAnnotations(dd)
56 57
 		out = append(out, Device{
57 58
 			Name:        dev,
58
-			AutoAllow:   true, // TODO
59
-			Annotations: deviceAnnotations(dd),
59
+			AutoAllow:   m.isAutoAllowed(kind, dev, annotations),
60
+			Annotations: annotations,
60 61
 		})
61 62
 		kinds[kind] = struct{}{}
62 63
 	}
... ...
@@ -69,20 +95,31 @@ func (m *Manager) ListDevices() []Device {
69 69
 			continue
70 70
 		}
71 71
 		out = append(out, Device{
72
-			Name:     k,
73
-			OnDemand: true,
72
+			Name:      k,
73
+			OnDemand:  true,
74
+			AutoAllow: true,
74 75
 		})
75 76
 	}
76 77
 
77 78
 	return out
78 79
 }
79 80
 
81
+func (m *Manager) GetDevice(name string) Device {
82
+	kind, _, _ := strings.Cut(name, "=")
83
+	annotations := deviceAnnotations(m.cache.GetDevice(name))
84
+	return Device{
85
+		Name:        name,
86
+		AutoAllow:   m.isAutoAllowed(kind, name, annotations),
87
+		Annotations: annotations,
88
+	}
89
+}
90
+
80 91
 func (m *Manager) Refresh() error {
81 92
 	return m.cache.Refresh()
82 93
 }
83 94
 
84 95
 func (m *Manager) InjectDevices(spec *specs.Spec, devs ...*pb.CDIDevice) error {
85
-	pdevs, err := m.parseDevices(devs...)
96
+	pdevs, err := m.FindDevices(devs...)
86 97
 	if err != nil {
87 98
 		return err
88 99
 	} else if len(pdevs) == 0 {
... ...
@@ -93,13 +130,17 @@ func (m *Manager) InjectDevices(spec *specs.Spec, devs ...*pb.CDIDevice) error {
93 93
 	return err
94 94
 }
95 95
 
96
-func (m *Manager) parseDevices(devs ...*pb.CDIDevice) ([]string, error) {
96
+func (m *Manager) FindDevices(devs ...*pb.CDIDevice) ([]string, error) {
97 97
 	var out []string
98
+	if len(devs) == 0 {
99
+		return out, nil
100
+	}
101
+	list := m.cache.ListDevices()
98 102
 	for _, dev := range devs {
99 103
 		if dev == nil {
100 104
 			continue
101 105
 		}
102
-		pdev, err := m.parseDevice(dev)
106
+		pdev, err := m.parseDevice(dev, list)
103 107
 		if err != nil {
104 108
 			return nil, err
105 109
 		} else if len(pdev) == 0 {
... ...
@@ -110,7 +151,7 @@ func (m *Manager) parseDevices(devs ...*pb.CDIDevice) ([]string, error) {
110 110
 	return dedupSlice(out), nil
111 111
 }
112 112
 
113
-func (m *Manager) parseDevice(dev *pb.CDIDevice) ([]string, error) {
113
+func (m *Manager) parseDevice(dev *pb.CDIDevice, all []string) ([]string, error) {
114 114
 	var out []string
115 115
 
116 116
 	kind, name, _ := strings.Cut(dev.Name, "=")
... ...
@@ -127,7 +168,7 @@ func (m *Manager) parseDevice(dev *pb.CDIDevice) ([]string, error) {
127 127
 	switch name {
128 128
 	case "":
129 129
 		// first device of kind if no name is specified
130
-		for _, d := range m.cache.ListDevices() {
130
+		for _, d := range all {
131 131
 			if strings.HasPrefix(d, kind+"=") {
132 132
 				out = append(out, d)
133 133
 				break
... ...
@@ -135,14 +176,14 @@ func (m *Manager) parseDevice(dev *pb.CDIDevice) ([]string, error) {
135 135
 		}
136 136
 	case "*":
137 137
 		// all devices of kind if the name is a wildcard
138
-		for _, d := range m.cache.ListDevices() {
138
+		for _, d := range all {
139 139
 			if strings.HasPrefix(d, kind+"=") {
140 140
 				out = append(out, d)
141 141
 			}
142 142
 		}
143 143
 	default:
144 144
 		// the specified device
145
-		for _, d := range m.cache.ListDevices() {
145
+		for _, d := range all {
146 146
 			if d == dev.Name {
147 147
 				out = append(out, d)
148 148
 				break
... ...
@@ -150,7 +191,7 @@ func (m *Manager) parseDevice(dev *pb.CDIDevice) ([]string, error) {
150 150
 		}
151 151
 		if len(out) == 0 {
152 152
 			// check class annotation if name unknown
153
-			for _, d := range m.cache.ListDevices() {
153
+			for _, d := range all {
154 154
 				if !strings.HasPrefix(d, kind+"=") {
155 155
 					continue
156 156
 				}
... ...
@@ -214,6 +255,9 @@ func (m *Manager) OnDemandInstaller(name string) (func(context.Context) error, b
214 214
 			return errors.Wrapf(err, "failed to refresh CDI cache")
215 215
 		}
216 216
 
217
+		// TODO: this needs to be set as annotation to survive reboot
218
+		m.autoAllowed[name] = struct{}{}
219
+
217 220
 		return nil
218 221
 	}, true
219 222
 }
... ...
@@ -1110,19 +1110,26 @@ func supportedEntitlements(ents []string) []entitlements.Entitlement {
1110 1110
 		if e == string(entitlements.EntitlementSecurityInsecure) {
1111 1111
 			out = append(out, entitlements.EntitlementSecurityInsecure)
1112 1112
 		}
1113
+		if e == string(entitlements.EntitlementDevice) {
1114
+			out = append(out, entitlements.EntitlementDevice)
1115
+		}
1113 1116
 	}
1114 1117
 	return out
1115 1118
 }
1116 1119
 
1117 1120
 func loadEntitlements(b solver.Builder) (entitlements.Set, error) {
1118
-	var ent entitlements.Set = map[entitlements.Entitlement]struct{}{}
1121
+	var ent entitlements.Set = map[entitlements.Entitlement]entitlements.EntitlementsConfig{}
1119 1122
 	err := b.EachValue(context.TODO(), keyEntitlements, func(v interface{}) error {
1120 1123
 		set, ok := v.(entitlements.Set)
1121 1124
 		if !ok {
1122 1125
 			return errors.Errorf("invalid entitlements %T", v)
1123 1126
 		}
1124
-		for k := range set {
1125
-			ent[k] = struct{}{}
1127
+		for k, v := range set {
1128
+			if prev, ok := ent[k]; ok && prev != nil {
1129
+				prev.Merge(v)
1130
+			} else {
1131
+				ent[k] = v
1132
+			}
1126 1133
 		}
1127 1134
 		return nil
1128 1135
 	})
... ...
@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/containerd/platforms"
9 9
 	"github.com/moby/buildkit/solver"
10
+	"github.com/moby/buildkit/solver/llbsolver/cdidevices"
10 11
 	"github.com/moby/buildkit/solver/llbsolver/ops/opsutils"
11 12
 	"github.com/moby/buildkit/solver/pb"
12 13
 	"github.com/moby/buildkit/util/apicaps"
... ...
@@ -109,7 +110,7 @@ func NormalizeRuntimePlatforms() LoadOpt {
109 109
 	}
110 110
 }
111 111
 
112
-func ValidateEntitlements(ent entitlements.Set) LoadOpt {
112
+func ValidateEntitlements(ent entitlements.Set, cdiManager *cdidevices.Manager) LoadOpt {
113 113
 	return func(op *pb.Op, _ *pb.OpMetadata, opt *solver.VertexOptions) error {
114 114
 		switch op := op.Op.(type) {
115 115
 		case *pb.Op_Exec:
... ...
@@ -120,6 +121,75 @@ func ValidateEntitlements(ent entitlements.Set) LoadOpt {
120 120
 			if err := ent.Check(v); err != nil {
121 121
 				return err
122 122
 			}
123
+			if device := op.Exec.CdiDevices; len(device) > 0 {
124
+				var cfg *entitlements.DevicesConfig
125
+				if ent, ok := ent[entitlements.EntitlementDevice]; ok {
126
+					cfg, ok = ent.(*entitlements.DevicesConfig)
127
+					if !ok {
128
+						return errors.Errorf("invalid device entitlement config %T", ent)
129
+					}
130
+				}
131
+				if cfg != nil && cfg.All {
132
+					return nil
133
+				}
134
+
135
+				var allowedDevices []*pb.CDIDevice
136
+				var nonAliasedDevices []*pb.CDIDevice
137
+				if cfg != nil {
138
+					for _, d := range op.Exec.CdiDevices {
139
+						if newName, ok := cfg.Devices[d.Name]; ok && newName != "" {
140
+							d.Name = newName
141
+							allowedDevices = append(allowedDevices, d)
142
+						} else {
143
+							nonAliasedDevices = append(nonAliasedDevices, d)
144
+						}
145
+					}
146
+				} else {
147
+					nonAliasedDevices = op.Exec.CdiDevices
148
+				}
149
+
150
+				mountedDevices, err := cdiManager.FindDevices(nonAliasedDevices...)
151
+				if err != nil {
152
+					return err
153
+				}
154
+				if len(mountedDevices) == 0 {
155
+					op.Exec.CdiDevices = allowedDevices
156
+					return nil
157
+				}
158
+
159
+				grantedDevices := make(map[string]struct{})
160
+				if cfg != nil {
161
+					for d := range cfg.Devices {
162
+						resolved, err := cdiManager.FindDevices(&pb.CDIDevice{Name: d})
163
+						if err != nil {
164
+							return err
165
+						}
166
+						for _, r := range resolved {
167
+							grantedDevices[r] = struct{}{}
168
+						}
169
+					}
170
+				}
171
+
172
+				var forbidden []string
173
+				for _, d := range mountedDevices {
174
+					if _, ok := grantedDevices[d]; !ok {
175
+						if dev := cdiManager.GetDevice(d); !dev.AutoAllow {
176
+							forbidden = append(forbidden, d)
177
+							continue
178
+						}
179
+					}
180
+					allowedDevices = append(allowedDevices, &pb.CDIDevice{Name: d})
181
+				}
182
+
183
+				if len(forbidden) > 0 {
184
+					if len(forbidden) == 1 {
185
+						return errors.Errorf("device %s is requested by the build but not allowed", forbidden[0])
186
+					}
187
+					return errors.Errorf("devices %s are requested by the build but not allowed", strings.Join(forbidden, ", "))
188
+				}
189
+
190
+				op.Exec.CdiDevices = allowedDevices
191
+			}
123 192
 		}
124 193
 		return nil
125 194
 	}
... ...
@@ -1,31 +1,119 @@
1 1
 package entitlements
2 2
 
3 3
 import (
4
+	"strings"
5
+
4 6
 	"github.com/pkg/errors"
7
+	"github.com/tonistiigi/go-csvvalue"
5 8
 )
6 9
 
7 10
 type Entitlement string
8 11
 
12
+func (e Entitlement) String() string {
13
+	return string(e)
14
+}
15
+
9 16
 const (
10 17
 	EntitlementSecurityInsecure Entitlement = "security.insecure"
11 18
 	EntitlementNetworkHost      Entitlement = "network.host"
19
+	EntitlementDevice           Entitlement = "device"
12 20
 )
13 21
 
14 22
 var all = map[Entitlement]struct{}{
15 23
 	EntitlementSecurityInsecure: {},
16 24
 	EntitlementNetworkHost:      {},
25
+	EntitlementDevice:           {},
26
+}
27
+
28
+type EntitlementsConfig interface {
29
+	Merge(EntitlementsConfig) error
17 30
 }
18 31
 
19
-func Parse(s string) (Entitlement, error) {
32
+type DevicesConfig struct {
33
+	Devices map[string]string
34
+	All     bool
35
+}
36
+
37
+var _ EntitlementsConfig = &DevicesConfig{}
38
+
39
+func ParseDevicesConfig(s string) (*DevicesConfig, error) {
40
+	if s == "" {
41
+		return &DevicesConfig{All: true}, nil
42
+	}
43
+
44
+	fields, err := csvvalue.Fields(s, nil)
45
+	if err != nil {
46
+		return nil, err
47
+	}
48
+	deviceName := fields[0]
49
+	var deviceAlias string
50
+
51
+	for _, field := range fields[1:] {
52
+		k, v, ok := strings.Cut(field, "=")
53
+		if !ok {
54
+			return nil, errors.Errorf("invalid device config %q", field)
55
+		}
56
+		switch k {
57
+		case "alias":
58
+			deviceAlias = v
59
+		default:
60
+			return nil, errors.Errorf("unknown device config key %q", k)
61
+		}
62
+	}
63
+
64
+	cfg := &DevicesConfig{Devices: map[string]string{}}
65
+
66
+	if deviceAlias != "" {
67
+		cfg.Devices[deviceAlias] = deviceName
68
+	} else {
69
+		cfg.Devices[deviceName] = ""
70
+	}
71
+	return cfg, nil
72
+}
73
+
74
+func (c *DevicesConfig) Merge(in EntitlementsConfig) error {
75
+	c2, ok := in.(*DevicesConfig)
76
+	if !ok {
77
+		return errors.Errorf("cannot merge %T into %T", in, c)
78
+	}
79
+
80
+	if c2.All {
81
+		c.All = true
82
+		return nil
83
+	}
84
+
85
+	for k, v := range c2.Devices {
86
+		if c.Devices == nil {
87
+			c.Devices = map[string]string{}
88
+		}
89
+		c.Devices[k] = v
90
+	}
91
+	return nil
92
+}
93
+
94
+func Parse(s string) (Entitlement, EntitlementsConfig, error) {
95
+	var cfg EntitlementsConfig
96
+	key, rest, _ := strings.Cut(s, "=")
97
+	switch Entitlement(key) {
98
+	case EntitlementDevice:
99
+		s = key
100
+		var err error
101
+		cfg, err = ParseDevicesConfig(rest)
102
+		if err != nil {
103
+			return "", nil, err
104
+		}
105
+	default:
106
+	}
107
+
20 108
 	_, ok := all[Entitlement(s)]
21 109
 	if !ok {
22
-		return "", errors.Errorf("unknown entitlement %s", s)
110
+		return "", nil, errors.Errorf("unknown entitlement %s", s)
23 111
 	}
24
-	return Entitlement(s), nil
112
+	return Entitlement(s), cfg, nil
25 113
 }
26 114
 
27 115
 func WhiteList(allowed, supported []Entitlement) (Set, error) {
28
-	m := map[Entitlement]struct{}{}
116
+	m := map[Entitlement]EntitlementsConfig{}
29 117
 
30 118
 	var supm Set
31 119
 	if supported != nil {
... ...
@@ -37,7 +125,7 @@ func WhiteList(allowed, supported []Entitlement) (Set, error) {
37 37
 	}
38 38
 
39 39
 	for _, e := range allowed {
40
-		e, err := Parse(string(e))
40
+		e, cfg, err := Parse(string(e))
41 41
 		if err != nil {
42 42
 			return nil, err
43 43
 		}
... ...
@@ -46,13 +134,19 @@ func WhiteList(allowed, supported []Entitlement) (Set, error) {
46 46
 				return nil, errors.Errorf("granting entitlement %s is not allowed by build daemon configuration", e)
47 47
 			}
48 48
 		}
49
-		m[e] = struct{}{}
49
+		if prev, ok := m[e]; ok && prev != nil {
50
+			if err := prev.Merge(cfg); err != nil {
51
+				return nil, err
52
+			}
53
+		} else {
54
+			m[e] = cfg
55
+		}
50 56
 	}
51 57
 
52 58
 	return Set(m), nil
53 59
 }
54 60
 
55
-type Set map[Entitlement]struct{}
61
+type Set map[Entitlement]EntitlementsConfig
56 62
 
57 63
 func (s Set) Allowed(e Entitlement) bool {
58 64
 	_, ok := s[e]
... ...
@@ -77,4 +171,5 @@ func (s Set) Check(v Values) error {
77 77
 type Values struct {
78 78
 	NetworkHost      bool
79 79
 	SecurityInsecure bool
80
+	Devices          map[string]struct{}
80 81
 }
... ...
@@ -755,7 +755,7 @@ github.com/mitchellh/hashstructure/v2
755 755
 # github.com/mitchellh/reflectwalk v1.0.2
756 756
 ## explicit
757 757
 github.com/mitchellh/reflectwalk
758
-# github.com/moby/buildkit v0.20.0-rc2
758
+# github.com/moby/buildkit v0.20.0-rc3
759 759
 ## explicit; go 1.22.0
760 760
 github.com/moby/buildkit/api/services/control
761 761
 github.com/moby/buildkit/api/types