solver: add per-step CPU and memory resource limits

Add support for setting cgroup resource limits (memory, memory-swap,
cpu-shares, cpu-period, cpu-quota, cpuset-cpus, cpuset-mems) on
individual build steps.

Signed-off-by: Jiří Moravčík <jiri.moravcik@gmail.com>
This commit is contained in:
Jiří Moravčík committed 2026-05-27 11:34:13 +02:00
1 parent 550cf8b8b7
commit f85c740c2b
30 files changed
+1863 -175

No files matched your search

+57
View File
@@ -157,6 +157,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
testShmSize,
testUlimit,
testCgroupParent,
testLinuxResources,
testNetworkMode,
testFrontendMetadataReturn,
testFrontendUseSolveResults,
@@ -1336,6 +1337,62 @@ func testCgroupParent(t *testing.T, sb integration.Sandbox) {
require.Equal(t, "", strings.TrimSpace(string(dt)))
}
func testLinuxResources(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
if sb.Rootless() {
t.SkipNow()
}
if _, err := os.Lstat("/sys/fs/cgroup/cgroup.subtree_control"); os.IsNotExist(err) {
t.Skipf("test requires cgroup v2")
}
c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()
img := llb.Image("alpine:latest")
st := llb.Scratch()
run := func(cmd string, ro ...llb.RunOption) {
st = img.Run(append(ro, llb.Shlex(cmd), llb.Dir("/wd"))...).AddMount("/wd", st)
}
// Test memory limit: set 64MiB and verify via cgroup
run(`sh -c "cat /sys/fs/cgroup/memory.max > mem_limited"`, llb.MemoryLimit(64*1024*1024))
run(`sh -c "cat /sys/fs/cgroup/memory.max > mem_default"`)
// Test CPU quota: set quota=50000 period=100000 (50% CPU) and verify
run(`sh -c "cat /sys/fs/cgroup/cpu.max > cpu_limited"`, llb.CPUQuota(50000), llb.CPUPeriod(100000))
def, err := st.Marshal(sb.Context())
require.NoError(t, err)
destDir := t.TempDir()
_, err = c.Solve(sb.Context(), def, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterLocal,
OutputDir: destDir,
},
},
}, nil)
require.NoError(t, err)
dt, err := os.ReadFile(filepath.Join(destDir, "mem_limited"))
require.NoError(t, err)
require.Equal(t, "67108864", strings.TrimSpace(string(dt)))
dt2, err := os.ReadFile(filepath.Join(destDir, "mem_default"))
require.NoError(t, err)
require.Equal(t, "max", strings.TrimSpace(string(dt2)))
dt3, err := os.ReadFile(filepath.Join(destDir, "cpu_limited"))
require.NoError(t, err)
require.Equal(t, "50000 100000", strings.TrimSpace(string(dt3)))
}
func testNetworkMode(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
c, err := New(sb.Context(), sb.Address())
+4
View File
@@ -255,6 +255,10 @@ func (e *ExecOp) Marshal(ctx context.Context, c *Constraints) (digest.Digest, []
meta.Ulimit = ul
}
if e.constraints.Metadata.LinuxResources != nil {
addCap(&e.constraints, pb.CapExecMetaLinuxResources)
}
network, err := getNetwork(e.base)(ctx, c)
if err != nil {
return "", nil, nil, nil, err
+91
View File
@@ -51,6 +51,97 @@ func TestValidGetMountIndex(t *testing.T) {
require.Equal(t, pb.OutputIndex(1), mountIndex, "unexpected mount index")
}
func TestLinuxResourcesMarshal(t *testing.T) {
t.Parallel()
st := Image("busybox:latest").
Run(
Shlex("true"),
MemoryLimit(64*1024*1024),
CPUShares(512),
CPUQuota(50000),
CPUPeriod(100000),
CpusetCpus("0-3"),
CpusetMems("0"),
).Root()
def, err := st.Marshal(context.TODO())
require.NoError(t, err)
// Resources should be in OpMetadata (not in the Op bytes / cache key)
var found bool
for _, md := range def.Metadata {
if md.LinuxResources == nil {
continue
}
found = true
res := md.LinuxResources
require.Equal(t, int64(64*1024*1024), res.Memory)
require.Equal(t, uint64(512), res.CpuShares)
require.Equal(t, int64(50000), res.CpuQuota)
require.Equal(t, uint64(100000), res.CpuPeriod)
require.Equal(t, "0-3", res.CpusetCpus)
require.Equal(t, "0", res.CpusetMems)
}
require.True(t, found, "LinuxResources not found in OpMetadata")
}
func TestLinuxResourcesNotInCacheKey(t *testing.T) {
t.Parallel()
// Two ops with same command but different resource limits must produce the same digest
st1 := Image("busybox:latest").
Run(Shlex("echo hello"), MemoryLimit(64*1024*1024)).Root()
st2 := Image("busybox:latest").
Run(Shlex("echo hello"), MemoryLimit(128*1024*1024)).Root()
st3 := Image("busybox:latest").
Run(Shlex("echo hello")).Root()
def1, err := st1.Marshal(context.TODO())
require.NoError(t, err)
def2, err := st2.Marshal(context.TODO())
require.NoError(t, err)
def3, err := st3.Marshal(context.TODO())
require.NoError(t, err)
// All three should produce the same definition bytes (same cache key)
require.Equal(t, def1.Def, def2.Def, "different resource limits should produce same digest")
require.Equal(t, def1.Def, def3.Def, "resource limits vs no limits should produce same digest")
}
func TestLinuxResourcesMerge(t *testing.T) {
t.Parallel()
// Test that individual resource limit functions merge correctly
st := Image("busybox:latest").
Run(
Shlex("true"),
MemoryLimit(64*1024*1024),
CPUShares(512),
).Root()
def, err := st.Marshal(context.TODO())
require.NoError(t, err)
for _, md := range def.Metadata {
if md.LinuxResources == nil {
continue
}
res := md.LinuxResources
require.Equal(t, int64(64*1024*1024), res.Memory)
require.Equal(t, uint64(512), res.CpuShares)
// Unset fields should be zero
require.Equal(t, int64(0), res.CpuQuota)
require.Equal(t, uint64(0), res.CpuPeriod)
return
}
t.Fatal("LinuxResources not found in OpMetadata")
}
func TestExecOpMarshalConsistency(t *testing.T) {
var prevDef [][]byte
st := Image("busybox:latest").
+11
View File
@@ -324,6 +324,17 @@ func getCgroupParent(s State) func(context.Context, *Constraints) (string, error
}
}
// LinuxResources holds CPU and memory resource limits for containers.
type LinuxResources struct {
Memory int64
MemorySwap int64
CPUShares uint64
CPUPeriod uint64
CPUQuota int64
CpusetCpus string
CpusetMems string
}
// Network returns a [StateOption] which sets the network mode used for containers created by [State.Run].
// This is the equivalent of [State.Network]
// See [State.With] for where to use this.
+82 -10
View File
@@ -591,6 +591,10 @@ func mergeMetadata(m1, m2 OpMetadata) OpMetadata {
m1.ProgressGroup = m2.ProgressGroup
}
if m2.LinuxResources != nil {
m1.LinuxResources = m2.LinuxResources
}
return m1
}
@@ -668,11 +672,12 @@ type Constraints struct {
// OpMetadata has a more friendly interface for pb.OpMetadata.
type OpMetadata struct {
IgnoreCache bool `json:"ignore_cache,omitempty"`
Description map[string]string `json:"description,omitempty"`
ExportCache *pb.ExportCache `json:"export_cache,omitempty"`
Caps map[apicaps.CapID]bool `json:"caps,omitempty"`
ProgressGroup *pb.ProgressGroup `json:"progress_group,omitempty"`
IgnoreCache bool `json:"ignore_cache,omitempty"`
Description map[string]string `json:"description,omitempty"`
ExportCache *pb.ExportCache `json:"export_cache,omitempty"`
Caps map[apicaps.CapID]bool `json:"caps,omitempty"`
ProgressGroup *pb.ProgressGroup `json:"progress_group,omitempty"`
LinuxResources *pb.LinuxResources `json:"linux_resources,omitempty"`
}
func NewOpMetadata(mpb *pb.OpMetadata) OpMetadata {
@@ -687,11 +692,12 @@ func (m OpMetadata) ToPB() *pb.OpMetadata {
caps[string(k)] = v
}
return &pb.OpMetadata{
IgnoreCache: m.IgnoreCache,
Description: m.Description,
ExportCache: m.ExportCache,
Caps: caps,
ProgressGroup: m.ProgressGroup,
IgnoreCache: m.IgnoreCache,
Description: m.Description,
ExportCache: m.ExportCache,
Caps: caps,
ProgressGroup: m.ProgressGroup,
LinuxResources: m.LinuxResources,
}
}
@@ -712,6 +718,7 @@ func (m *OpMetadata) FromPB(mpb *pb.OpMetadata) {
m.Caps = nil
}
m.ProgressGroup = mpb.ProgressGroup
m.LinuxResources = mpb.LinuxResources
}
func Platform(p ocispecs.Platform) ConstraintsOpt {
@@ -732,6 +739,71 @@ func ProgressGroup(id, name string, weak bool) ConstraintsOpt {
})
}
// WithLinuxResources sets all CPU/memory resource limits at once.
// Resource limits are applied via OpMetadata and do not affect the cache key.
func WithLinuxResources(res LinuxResources) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
c.Metadata.LinuxResources = &pb.LinuxResources{
Memory: res.Memory,
MemorySwap: res.MemorySwap,
CpuShares: res.CPUShares,
CpuPeriod: res.CPUPeriod,
CpuQuota: res.CPUQuota,
CpusetCpus: res.CpusetCpus,
CpusetMems: res.CpusetMems,
}
})
}
func ensureLinuxResources(c *Constraints) *pb.LinuxResources {
if c.Metadata.LinuxResources == nil {
c.Metadata.LinuxResources = &pb.LinuxResources{}
}
return c.Metadata.LinuxResources
}
func MemoryLimit(limit int64) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
ensureLinuxResources(c).Memory = limit
})
}
func MemorySwapLimit(limit int64) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
ensureLinuxResources(c).MemorySwap = limit
})
}
func CPUShares(shares uint64) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
ensureLinuxResources(c).CpuShares = shares
})
}
func CPUPeriod(period uint64) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
ensureLinuxResources(c).CpuPeriod = period
})
}
func CPUQuota(quota int64) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
ensureLinuxResources(c).CpuQuota = quota
})
}
func CpusetCpus(cpus string) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
ensureLinuxResources(c).CpusetCpus = cpus
})
}
func CpusetMems(mems string) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
ensureLinuxResources(c).CpusetMems = mems
})
}
var (
LinuxAmd64 = Platform(ocispecs.Platform{OS: "linux", Architecture: "amd64"})
LinuxArmhf = Platform(ocispecs.Platform{OS: "linux", Architecture: "arm", Variant: "v7"})
+37 -5
View File
@@ -85,9 +85,10 @@ should never have the same digest.
Options contain extra information that can be associated with the vertex but
what doesn't change the definition(or equality check) of it. Normally this is
either a hint to the solver, for example, to ignore cache when executing. It
can also be used for associating messages with the vertex that can be helpful
for tracing purposes.
either a hint to the solver, for example, to ignore cache when executing, or
runtime parameters like Linux resource limits (memory, CPU) that affect
execution but not the cache key. It can also be used for associating messages
with the vertex that can be helpful for tracing purposes.
## Operation interface
@@ -150,13 +151,13 @@ for its inputs.
- A content-based cache function allows computing a new cache key for an input
after it has completed. In LLB this is used for calculating a cache key based
on the checksum of file contents of the input snapshots.
> [!NOTE]
> For example, in the case of LLB, if a vertex is a FileOp that copies a file
> from one snapshot to another, the selector can be set to the path of the
> source file in the input snapshot, while the content-based cache function can
> be used to calculate the checksum of the file contents.
>
>
> If the source path changes, we need to invalidate the cache (which we do by
> changing the selector). However, if we do a content-based cache lookup for
> the input, then the file content may not have changed (which we can detect by
@@ -183,6 +184,37 @@ released if no more references remain.
Loading a vertex also creates a progress writer associated with it and sets up
the cache sources associated with the specific vertex.
### Linux resource limits
Linux resource limits (memory, CPU, cpuset) attached to a vertex via
`VertexOptions` get merged into the shared state using a "most relaxed wins"
policy. The merge is per resource. Scalar limits like `memory` and `cpu-shares`
take the larger value (with `0` treated as unlimited where the field's
semantics call for it). `cpu-period` and `cpu-quota` are picked as a pair
representing the higher effective CPU cap. `cpuset-cpus` and `cpuset-mems` are
unioned. Concurrent builds that share an op but specify different limits can
dedup safely this way. The merged value is snapshotted when the shared op is
first resolved (via `ResolveOp`) and passed straight to the op constructor.
Jobs that join after that reuse the already-resolved op and don't change its
effective limits.
#### Caveats
- **Merging is best-effort.** When two jobs share a vertex with different
limits, what actually applies depends on whether both loads finish before
the scheduler resolves the shared op. If they do, the merged value wins.
If one job races ahead and triggers `ResolveOp` first, only that job's
metadata is used; later joiners still merge into shared state, but nothing
reads it after `ResolveOp` has run. Because of that, every `Merge`
implementation must return a value that is safe for any participating job
on its own. "Most relaxed" satisfies this since every job ends up with at
least the limits it asked for.
- **No effect on the cache key.** `LinuxResources` sits in `OpMetadata`, not
in the op protobuf, so it doesn't enter the vertex digest. Two jobs that
differ only in resource limits dedup to the same vertex and share cache
entries.
- **Needs write access to cgroups.** When buildkitd doesn't have write access to cgroups, it cannot set the limits. This means the limits won't work in rootless mode.
After vertexes have been loaded to the job, it is safe to request a result from
an edge pointing to a previously loaded vertex. To do this `build(ctx, Edge)
(CachedResult, error)` method is called on the static scheduler instance
+1
View File
@@ -24,6 +24,7 @@ type Meta struct {
Ulimit []*pb.Ulimit
CDIDevices []*pb.CDIDevice
CgroupParent string
LinuxResources *pb.LinuxResources
NetMode pb.NetMode
SecurityMode pb.SecurityMode
ValidExitCodes []int
+6
View File
@@ -112,6 +112,12 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou
return nil, nil, err
}
if linuxResOpts, err := generateLinuxResourceOpts(meta.LinuxResources); err == nil {
opts = append(opts, linuxResOpts...)
} else {
return nil, nil, err
}
hostname := defaultHostname
if meta.Hostname != "" {
hostname = meta.Hostname
+7
View File
@@ -64,6 +64,13 @@ func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) {
return m, func() error { return nil }, nil
}
func generateLinuxResourceOpts(res *pb.LinuxResources) ([]oci.SpecOpts, error) {
if res == nil {
return nil, nil
}
return nil, errors.New("no support for Linux resource limits on Darwin")
}
func generateCDIOpts(_ *cdidevices.Manager, devices []*pb.CDIDevice) ([]oci.SpecOpts, error) {
if len(devices) == 0 {
return nil, nil
+7
View File
@@ -72,6 +72,13 @@ func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) {
return m, func() error { return nil }, nil
}
func generateLinuxResourceOpts(res *pb.LinuxResources) ([]oci.SpecOpts, error) {
if res == nil {
return nil, nil
}
return nil, errors.New("no support for Linux resource limits on FreeBSD")
}
func generateCDIOpts(_ *cdidevices.Manager, devices []*pb.CDIDevice) ([]oci.SpecOpts, error) {
if len(devices) == 0 {
return nil, nil
+39
View File
@@ -150,6 +150,45 @@ func generateRlimitOpts(ulimits []*pb.Ulimit) ([]oci.SpecOpts, error) {
}, nil
}
func generateLinuxResourceOpts(res *pb.LinuxResources) ([]oci.SpecOpts, error) {
if res == nil {
return nil, nil
}
var opts []oci.SpecOpts
if res.Memory != 0 {
opts = append(opts, oci.WithMemoryLimit(uint64(res.Memory)))
}
if res.MemorySwap != 0 {
swap := res.MemorySwap
opts = append(opts, func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
if s.Linux == nil {
return nil
}
if s.Linux.Resources == nil {
s.Linux.Resources = &specs.LinuxResources{}
}
if s.Linux.Resources.Memory == nil {
s.Linux.Resources.Memory = &specs.LinuxMemory{}
}
s.Linux.Resources.Memory.Swap = &swap
return nil
})
}
if res.CpuShares != 0 {
opts = append(opts, oci.WithCPUShares(res.CpuShares))
}
if res.CpuQuota != 0 || res.CpuPeriod != 0 {
opts = append(opts, oci.WithCPUCFS(res.CpuQuota, res.CpuPeriod))
}
if res.CpusetCpus != "" {
opts = append(opts, oci.WithCPUs(res.CpusetCpus))
}
if res.CpusetMems != "" {
opts = append(opts, oci.WithCPUsMems(res.CpusetMems))
}
return opts, nil
}
// genereateCDIOptions creates the OCI runtime spec options for injecting CDI
// devices.
func generateCDIOpts(manager *cdidevices.Manager, devs []*pb.CDIDevice) ([]oci.SpecOpts, error) {
+7
View File
@@ -112,6 +112,13 @@ func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) {
return m, func() error { return nil }, nil
}
func generateLinuxResourceOpts(res *pb.LinuxResources) ([]oci.SpecOpts, error) {
if res == nil {
return nil, nil
}
return nil, errors.New("no support for Linux resource limits on Windows")
}
func generateCDIOpts(_ *cdidevices.Manager, devices []*pb.CDIDevice) ([]oci.SpecOpts, error) {
if len(devices) == 0 {
return nil, nil
@@ -837,6 +837,7 @@ func (dctx *dispatchContext) dispatchStages(ctx context.Context, allReachable ma
ulimit: dctx.opt.Ulimits,
devices: dctx.opt.Devices,
cgroupParent: dctx.opt.CgroupParent,
linuxResources: dctx.opt.LinuxResources,
llbCaps: dctx.opt.LLBCaps,
sourceMap: dctx.opt.SourceMap,
lint: dctx.lint,
@@ -992,6 +993,7 @@ type dispatchOpt struct {
ulimit []*pb.Ulimit
devices []*pb.CDIDevice
cgroupParent string
linuxResources *pb.LinuxResources
llbCaps *apicaps.CapSet
sourceMap *llb.SourceMap
lint *linter.Linter
@@ -1485,6 +1487,20 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
}
}
if dopt.llbCaps != nil && dopt.llbCaps.Supports(pb.CapExecMetaLinuxResources) == nil {
if dopt.linuxResources != nil {
opt = append(opt, llb.WithLinuxResources(llb.LinuxResources{
Memory: dopt.linuxResources.Memory,
MemorySwap: dopt.linuxResources.MemorySwap,
CPUShares: dopt.linuxResources.CpuShares,
CPUPeriod: dopt.linuxResources.CpuPeriod,
CPUQuota: dopt.linuxResources.CpuQuota,
CpusetCpus: dopt.linuxResources.CpusetCpus,
CpusetMems: dopt.linuxResources.CpusetMems,
}))
}
}
d.state = d.state.Run(opt...).Root()
return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs, env), true, &d.state, d.epoch)
}
+52
View File
@@ -153,6 +153,7 @@ var allTests = integration.TestFuncs(
testShmSize,
testUlimit,
testCgroupParent,
testLinuxResources,
testNamedImageContext,
testNamedImageContextPlatform,
testNamedImageContextTimestamps,
@@ -7721,6 +7722,57 @@ COPY --from=base /out /
require.Contains(t, strings.TrimSpace(string(dt)), `Resource temporarily unavailable`)
}
func testLinuxResources(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
if sb.Rootless() {
t.SkipNow()
}
if _, err := os.Lstat("/sys/fs/cgroup/cgroup.subtree_control"); os.IsNotExist(err) {
t.Skipf("test requires cgroup v2")
}
f := getFrontend(t, sb)
dockerfile := []byte(`
FROM alpine AS base
RUN mkdir /out && cat /sys/fs/cgroup/memory.max > /out/memory
FROM scratch
COPY --from=base /out /
`)
dir := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)
c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()
destDir := t.TempDir()
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
FrontendAttrs: map[string]string{
"memory": "67108864",
},
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir,
},
},
}, nil)
require.NoError(t, err)
dt, err := os.ReadFile(filepath.Join(destDir, "memory"))
require.NoError(t, err)
require.Equal(t, "67108864", strings.TrimSpace(string(dt)))
}
func testStepNames(t *testing.T, sb integration.Sandbox) {
ctx := sb.Context()
+74
View File
@@ -9,6 +9,7 @@ import (
"github.com/docker/go-units"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/cpuset"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
@@ -96,6 +97,79 @@ func parseUlimits(v string) ([]*pb.Ulimit, error) {
return out, nil
}
func parseLinuxResources(opts map[string]string) (*pb.LinuxResources, error) {
var res pb.LinuxResources
if v, ok := opts[keyMemory]; ok && v != "" {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "invalid %s value: %s", keyMemory, v)
}
if n <= 0 {
return nil, errors.Errorf("invalid %s value: %s: must be > 0", keyMemory, v)
}
res.Memory = n
}
if v, ok := opts[keyMemorySwap]; ok && v != "" {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "invalid %s value: %s", keyMemorySwap, v)
}
if n < -1 || n == 0 {
return nil, errors.Errorf("invalid %s value: %s: must be -1 (unlimited) or > 0", keyMemorySwap, v)
}
res.MemorySwap = n
}
if v, ok := opts[keyCPUShares]; ok && v != "" {
n, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "invalid %s value: %s", keyCPUShares, v)
}
if n == 0 {
return nil, errors.Errorf("invalid %s value: %s: must be > 0", keyCPUShares, v)
}
res.CpuShares = n
}
if v, ok := opts[keyCPUPeriod]; ok && v != "" {
n, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "invalid %s value: %s", keyCPUPeriod, v)
}
if n == 0 {
return nil, errors.Errorf("invalid %s value: %s: must be > 0", keyCPUPeriod, v)
}
res.CpuPeriod = n
}
if v, ok := opts[keyCPUQuota]; ok && v != "" {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "invalid %s value: %s", keyCPUQuota, v)
}
if n <= 0 {
return nil, errors.Errorf("invalid %s value: %s: must be > 0", keyCPUQuota, v)
}
res.CpuQuota = n
}
if v, ok := opts[keyCpusetCpus]; ok && v != "" {
if err := cpuset.Validate(v); err != nil {
return nil, errors.Wrapf(err, "invalid %s value: %s", keyCpusetCpus, v)
}
res.CpusetCpus = v
}
if v, ok := opts[keyCpusetMems]; ok && v != "" {
if err := cpuset.Validate(v); err != nil {
return nil, errors.Wrapf(err, "invalid %s value: %s", keyCpusetMems, v)
}
res.CpusetMems = v
}
if res.Memory == 0 && res.MemorySwap == 0 && res.CpuShares == 0 &&
res.CpuPeriod == 0 && res.CpuQuota == 0 && res.CpusetCpus == "" && res.CpusetMems == "" {
return nil, nil
}
return &res, nil
}
func parseNetMode(v string) (pb.NetMode, error) {
if v == "" {
return llb.NetModeSandbox, nil
+14
View File
@@ -40,6 +40,13 @@ const (
keyShmSize = "shm-size"
keyTargetPlatform = "platform"
keyUlimit = "ulimit"
keyMemory = "memory"
keyMemorySwap = "memswap"
keyCPUShares = "cpushares"
keyCPUPeriod = "cpuperiod"
keyCPUQuota = "cpuquota"
keyCpusetCpus = "cpusetcpus"
keyCpusetMems = "cpusetmems"
keyCacheFrom = "cache-from" // for registry only. deprecated in favor of keyCacheImports
keyCacheImports = "cache-imports" // JSON representation of []CacheOptionsEntry
@@ -64,6 +71,7 @@ type Config struct {
ShmSize int64
Target string
Ulimits []*pb.Ulimit
LinuxResources *pb.LinuxResources
Devices []*pb.CDIDevice
LinterConfig *linter.Config
@@ -193,6 +201,12 @@ func (bc *Client) init() error {
}
bc.Ulimits = ulimits
linuxRes, err := parseLinuxResources(opts)
if err != nil {
return errors.Wrap(err, "failed to parse resource limits")
}
bc.LinuxResources = linuxRes
defaultNetMode, err := parseNetMode(opts[keyForceNetwork])
if err != nil {
return err
+27 -1
View File
@@ -73,6 +73,7 @@ type state struct {
cache map[string]CacheManager
mainCache CacheManager
metadata VertexMetadata
solver *Solver
}
@@ -622,6 +623,13 @@ func (jl *Solver) loadUnlocked(ctx context.Context, v, parent Vertex, j *Job, ca
}
}
}
if md := v.Options().Metadata; md != nil {
if st.metadata == nil {
st.metadata = md
} else {
st.metadata = st.metadata.Merge(md)
}
}
if j != nil {
if _, ok := st.jobs[j]; !ok {
@@ -1272,7 +1280,14 @@ func (s *sharedOp) Exec(ctx context.Context, inputs []Result) (outputs []Result,
func (s *sharedOp) getOp() (Op, error) {
s.opOnce.Do(func() {
s.subBuilder = s.st.builder()
s.op, s.err = s.resolver(s.st.vtx, s.subBuilder)
s.st.mu.Lock()
metadata := s.st.metadata
s.st.mu.Unlock()
v := s.st.vtx
if metadata != nil {
v = &vertexWithMetadata{Vertex: v, metadata: metadata}
}
s.op, s.err = s.resolver(v, s.subBuilder)
})
if s.err != nil {
return nil, s.err
@@ -1280,6 +1295,17 @@ func (s *sharedOp) getOp() (Op, error) {
return s.op, nil
}
type vertexWithMetadata struct {
Vertex
metadata VertexMetadata
}
func (v *vertexWithMetadata) Options() VertexOptions {
opts := v.Vertex.Options()
opts.Metadata = v.metadata
return opts
}
func (s *sharedOp) release() {
if s.execRes != nil {
for _, r := range s.execRes.execRes {
+1 -1
View File
@@ -140,7 +140,7 @@ func (b *llbBridge) loadResult(ctx context.Context, def *pb.Definition, cacheImp
}
dpc := &detectPrunedCacheID{}
edge, err := Load(ctx, def, b.policy(polEngine), dpc.Load, ValidateEntitlements(ent, w.CDIManager()), WithCacheSources(cms), NormalizeRuntimePlatforms(), WithValidateCaps())
edge, err := Load(ctx, def, b.policy(polEngine), dpc.Load, ValidateEntitlements(ent, w.CDIManager()), WithCacheSources(cms), NormalizeRuntimePlatforms(), WithValidateCaps(), WithLinuxResourcesMetadata())
if err != nil {
return nil, errors.Wrap(err, "failed to load LLB")
}
+147
View File
@@ -0,0 +1,147 @@
package linuxresources
import (
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/cpuset"
)
type Metadata struct {
LinuxResources *pb.LinuxResources
}
func (m *Metadata) Merge(other solver.VertexMetadata) solver.VertexMetadata {
if other == nil {
return m
}
o, ok := other.(*Metadata)
if !ok {
return m
}
return &Metadata{
LinuxResources: mergeRelaxed(m.LinuxResources, o.LinuxResources),
}
}
// mergeRelaxed returns the most relaxed of a and b, so a shared
// vertex is never throttled by a stricter sibling job.
func mergeRelaxed(a, b *pb.LinuxResources) *pb.LinuxResources {
if a == nil && b == nil {
return nil
}
if a == nil {
return b.CloneVT()
}
if b == nil {
return a.CloneVT()
}
period, quota := relaxedCPUBandwidth(a.CpuPeriod, a.CpuQuota, b.CpuPeriod, b.CpuQuota)
return &pb.LinuxResources{
Memory: relaxedMemory(a.Memory, b.Memory),
MemorySwap: relaxedMemorySwap(a.MemorySwap, b.MemorySwap),
CpuShares: relaxedCPUShares(a.CpuShares, b.CpuShares),
CpuPeriod: period,
CpuQuota: quota,
CpusetCpus: relaxedCpuset(a.CpusetCpus, b.CpusetCpus),
CpusetMems: relaxedCpuset(a.CpusetMems, b.CpusetMems),
}
}
// 0 means unlimited (most relaxed); otherwise higher = more relaxed.
func relaxedMemory(a, b int64) int64 {
if a == 0 || b == 0 {
return 0
}
return max(a, b)
}
// Docker memory-swap semantics: 0 = unset, -1 = unlimited (most relaxed),
// positive values are limits where higher = more relaxed.
func relaxedMemorySwap(a, b int64) int64 {
if a == 0 {
return b
}
if b == 0 {
return a
}
if a == -1 || b == -1 {
return -1
}
return max(a, b)
}
// 0 = unset (kernel default), not unlimited; otherwise higher = more relaxed.
func relaxedCPUShares(a, b uint64) uint64 {
if a == 0 {
return b
}
if b == 0 {
return a
}
return max(a, b)
}
// relaxedCPUBandwidth picks the (period, quota) pair with the higher cap
// (quota/period) without mixing them. Quota 0 = unlimited. On a tie, shorter
// period wins (same cap executes more frequently).
func relaxedCPUBandwidth(periodA uint64, quotaA int64, periodB uint64, quotaB int64) (uint64, int64) {
if quotaA == 0 && quotaB == 0 {
return 0, 0
}
if quotaA == 0 {
return periodA, 0
}
if quotaB == 0 {
return periodB, 0
}
// Compare quota/period via cross-multiplication when both periods are set;
// otherwise fall back to comparing raw quotas.
if periodA != 0 && periodB != 0 {
capA := quotaA * int64(periodB)
capB := quotaB * int64(periodA)
if capA > capB {
return periodA, quotaA
}
if capB > capA {
return periodB, quotaB
}
// equal caps: shorter period is more relaxed
if periodA <= periodB {
return periodA, quotaA
}
return periodB, quotaB
}
// At least one period is 0: compare quotas; on a tie, prefer the non-zero period.
if quotaA > quotaB {
return periodA, quotaA
}
if quotaB > quotaA {
return periodB, quotaB
}
if periodA == 0 {
return periodB, quotaB
}
return periodA, quotaA
}
// Empty = unset (most relaxed); otherwise the union of both sides, re-emitted
// in canonical form. On parse error, falls back to the side that parsed cleanly.
func relaxedCpuset(a, b string) string {
if a == "" || b == "" {
return ""
}
setA, errA := cpuset.Parse(a)
setB, errB := cpuset.Parse(b)
switch {
case errA != nil && errB != nil:
return a
case errA != nil:
return b
case errB != nil:
return a
}
for v := range setB {
setA[v] = struct{}{}
}
return cpuset.Format(setA)
}
@@ -0,0 +1,220 @@
package linuxresources
import (
"testing"
"github.com/moby/buildkit/solver/pb"
"github.com/stretchr/testify/require"
)
func TestMergeRelaxed(t *testing.T) {
t.Parallel()
t.Run("both nil", func(t *testing.T) {
result := mergeRelaxed(nil, nil)
require.Nil(t, result)
})
t.Run("first nil", func(t *testing.T) {
b := &pb.LinuxResources{Memory: 64 * 1024 * 1024}
result := mergeRelaxed(nil, b)
require.Equal(t, b, result)
})
t.Run("second nil", func(t *testing.T) {
a := &pb.LinuxResources{Memory: 64 * 1024 * 1024}
result := mergeRelaxed(a, nil)
require.Equal(t, a, result)
})
t.Run("higher memory wins", func(t *testing.T) {
a := &pb.LinuxResources{Memory: 64 * 1024 * 1024}
b := &pb.LinuxResources{Memory: 128 * 1024 * 1024}
result := mergeRelaxed(a, b)
require.Equal(t, int64(128*1024*1024), result.Memory)
})
t.Run("zero memory means unlimited and wins", func(t *testing.T) {
a := &pb.LinuxResources{Memory: 64 * 1024 * 1024}
b := &pb.LinuxResources{Memory: 0}
result := mergeRelaxed(a, b)
require.Equal(t, int64(0), result.Memory)
// Reverse order should also work
result = mergeRelaxed(b, a)
require.Equal(t, int64(0), result.Memory)
})
t.Run("zero memory swap means unset, preserves non-zero", func(t *testing.T) {
a := &pb.LinuxResources{MemorySwap: 256 * 1024 * 1024}
b := &pb.LinuxResources{MemorySwap: 0}
result := mergeRelaxed(a, b)
require.Equal(t, int64(256*1024*1024), result.MemorySwap)
// Reverse order
result = mergeRelaxed(b, a)
require.Equal(t, int64(256*1024*1024), result.MemorySwap)
})
t.Run("memory swap -1 means unlimited and wins", func(t *testing.T) {
a := &pb.LinuxResources{MemorySwap: 256 * 1024 * 1024}
b := &pb.LinuxResources{MemorySwap: -1}
result := mergeRelaxed(a, b)
require.Equal(t, int64(-1), result.MemorySwap)
// Reverse order
result = mergeRelaxed(b, a)
require.Equal(t, int64(-1), result.MemorySwap)
})
t.Run("higher memory swap wins", func(t *testing.T) {
a := &pb.LinuxResources{MemorySwap: 128 * 1024 * 1024}
b := &pb.LinuxResources{MemorySwap: 256 * 1024 * 1024}
result := mergeRelaxed(a, b)
require.Equal(t, int64(256*1024*1024), result.MemorySwap)
})
t.Run("higher cpu shares wins", func(t *testing.T) {
a := &pb.LinuxResources{CpuShares: 256}
b := &pb.LinuxResources{CpuShares: 512}
result := mergeRelaxed(a, b)
require.Equal(t, uint64(512), result.CpuShares)
})
t.Run("zero cpu shares means unset, preserves non-zero", func(t *testing.T) {
a := &pb.LinuxResources{CpuShares: 512}
b := &pb.LinuxResources{CpuShares: 0}
result := mergeRelaxed(a, b)
require.Equal(t, uint64(512), result.CpuShares)
// Reverse order
result = mergeRelaxed(b, a)
require.Equal(t, uint64(512), result.CpuShares)
})
t.Run("higher cpu quota wins", func(t *testing.T) {
a := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 100000}
b := &pb.LinuxResources{CpuQuota: 80000, CpuPeriod: 100000}
result := mergeRelaxed(a, b)
require.Equal(t, int64(80000), result.CpuQuota)
require.Equal(t, uint64(100000), result.CpuPeriod)
})
t.Run("zero cpu quota means unlimited and wins", func(t *testing.T) {
a := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 100000}
b := &pb.LinuxResources{CpuQuota: 0, CpuPeriod: 100000}
result := mergeRelaxed(a, b)
require.Equal(t, int64(0), result.CpuQuota)
})
t.Run("cpu bandwidth picks pair with higher cap, not mixed", func(t *testing.T) {
// cap A = 50000/100000 = 0.5; cap B = 80000/200000 = 0.4 — A wins as a pair.
a := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 100000}
b := &pb.LinuxResources{CpuQuota: 80000, CpuPeriod: 200000}
result := mergeRelaxed(a, b)
require.Equal(t, int64(50000), result.CpuQuota)
require.Equal(t, uint64(100000), result.CpuPeriod)
})
t.Run("cpu bandwidth: higher cap wins as a pair", func(t *testing.T) {
// cap A = 80000/100000 = 0.8; cap B = 50000/100000 = 0.5 — A wins.
a := &pb.LinuxResources{CpuQuota: 80000, CpuPeriod: 100000}
b := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 100000}
result := mergeRelaxed(a, b)
require.Equal(t, int64(80000), result.CpuQuota)
require.Equal(t, uint64(100000), result.CpuPeriod)
})
t.Run("equal quotas picks shorter period (more relaxed)", func(t *testing.T) {
a := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 100000}
b := &pb.LinuxResources{CpuQuota: 50000, CpuPeriod: 200000}
result := mergeRelaxed(a, b)
require.Equal(t, int64(50000), result.CpuQuota)
require.Equal(t, uint64(100000), result.CpuPeriod) // shorter period = more CPU
})
t.Run("empty cpuset string means unset and wins", func(t *testing.T) {
a := &pb.LinuxResources{CpusetCpus: "0-3"}
b := &pb.LinuxResources{CpusetCpus: ""}
result := mergeRelaxed(a, b)
require.Equal(t, "", result.CpusetCpus)
})
t.Run("cpuset subset is absorbed by superset", func(t *testing.T) {
a := &pb.LinuxResources{CpusetCpus: "0-3"}
b := &pb.LinuxResources{CpusetCpus: "0-7"}
result := mergeRelaxed(a, b)
require.Equal(t, "0-7", result.CpusetCpus)
})
t.Run("cpuset range and list merge into union", func(t *testing.T) {
a := &pb.LinuxResources{CpusetCpus: "0-7"}
b := &pb.LinuxResources{CpusetCpus: "0,1,2"}
result := mergeRelaxed(a, b)
require.Equal(t, "0-7", result.CpusetCpus)
})
t.Run("disjoint cpusets produce true union", func(t *testing.T) {
a := &pb.LinuxResources{CpusetCpus: "0-1"}
b := &pb.LinuxResources{CpusetCpus: "2-3"}
result := mergeRelaxed(a, b)
require.Equal(t, "0-3", result.CpusetCpus)
})
t.Run("cpuset union collapses adjacent ranges", func(t *testing.T) {
a := &pb.LinuxResources{CpusetCpus: "0,2,4"}
b := &pb.LinuxResources{CpusetCpus: "1,3,5"}
result := mergeRelaxed(a, b)
require.Equal(t, "0-5", result.CpusetCpus)
})
t.Run("cpuset union keeps gaps as separate ranges", func(t *testing.T) {
a := &pb.LinuxResources{CpusetCpus: "0-2,7"}
b := &pb.LinuxResources{CpusetCpus: "5,9"}
result := mergeRelaxed(a, b)
require.Equal(t, "0-2,5,7,9", result.CpusetCpus)
})
t.Run("all fields merged independently", func(t *testing.T) {
a := &pb.LinuxResources{
Memory: 64 * 1024 * 1024,
MemorySwap: 128 * 1024 * 1024,
CpuShares: 512,
CpuPeriod: 100000,
CpuQuota: 50000,
CpusetCpus: "0-3",
CpusetMems: "0",
}
b := &pb.LinuxResources{
Memory: 128 * 1024 * 1024,
MemorySwap: 64 * 1024 * 1024,
CpuShares: 256,
CpuPeriod: 200000,
CpuQuota: 80000,
CpusetCpus: "0-1",
CpusetMems: "0,1",
}
result := mergeRelaxed(a, b)
require.Equal(t, int64(128*1024*1024), result.Memory)
require.Equal(t, int64(128*1024*1024), result.MemorySwap)
require.Equal(t, uint64(512), result.CpuShares)
// CPU bandwidth is picked as a pair (not mixed). cap A = 50000/100000 =
// 0.5; cap B = 80000/200000 = 0.4 — pair A wins.
require.Equal(t, int64(50000), result.CpuQuota)
require.Equal(t, uint64(100000), result.CpuPeriod)
require.Equal(t, "0-3", result.CpusetCpus) // union of "0-3" and "0-1"
require.Equal(t, "0-1", result.CpusetMems) // union of "0" and "0,1"
})
t.Run("no pointer aliasing when one side is nil", func(t *testing.T) {
b := &pb.LinuxResources{Memory: 64 * 1024 * 1024}
result := mergeRelaxed(nil, b)
require.NotSame(t, b, result, "result should be a clone, not the same pointer")
require.Equal(t, b.Memory, result.Memory)
a := &pb.LinuxResources{Memory: 128 * 1024 * 1024}
result = mergeRelaxed(a, nil)
require.NotSame(t, a, result, "result should be a clone, not the same pointer")
require.Equal(t, a.Memory, result.Memory)
})
}
+25 -22
View File
@@ -37,37 +37,39 @@ import (
const execCacheType = "buildkit.exec.v0"
type ExecOp struct {
op *pb.ExecOp
cm cache.Manager
mm *mounts.MountManager
sm *session.Manager
exec executor.Executor
w worker.Worker
platform *pb.Platform
numInputs int
parallelism *semaphore.Weighted
rec resourcestypes.Recorder
digest digest.Digest
op *pb.ExecOp
cm cache.Manager
mm *mounts.MountManager
sm *session.Manager
exec executor.Executor
w worker.Worker
platform *pb.Platform
numInputs int
parallelism *semaphore.Weighted
rec resourcestypes.Recorder
digest digest.Digest
linuxResources *pb.LinuxResources
}
var _ solver.Op = &ExecOp{}
func NewExecOp(v solver.Vertex, op *pb.Op_Exec, platform *pb.Platform, cm cache.Manager, parallelism *semaphore.Weighted, sm *session.Manager, exec executor.Executor, w worker.Worker) (*ExecOp, error) {
func NewExecOp(v solver.Vertex, op *pb.Op_Exec, platform *pb.Platform, cm cache.Manager, parallelism *semaphore.Weighted, sm *session.Manager, exec executor.Executor, w worker.Worker, linuxResources *pb.LinuxResources) (*ExecOp, error) {
if err := opsutils.Validate(&pb.Op{Op: op}); err != nil {
return nil, err
}
name := fmt.Sprintf("exec %s", strings.Join(op.Exec.Meta.Args, " "))
return &ExecOp{
op: op.Exec,
mm: mounts.NewMountManager(name, cm, sm),
cm: cm,
sm: sm,
exec: exec,
numInputs: len(v.Inputs()),
w: w,
platform: platform,
parallelism: parallelism,
digest: v.Digest(),
op: op.Exec,
mm: mounts.NewMountManager(name, cm, sm),
cm: cm,
sm: sm,
exec: exec,
numInputs: len(v.Inputs()),
w: w,
platform: platform,
parallelism: parallelism,
digest: v.Digest(),
linuxResources: linuxResources,
}, nil
}
@@ -456,6 +458,7 @@ func (e *ExecOp) Exec(ctx context.Context, jobCtx solver.JobContext, inputs []so
Ulimit: e.op.Meta.Ulimit,
CDIDevices: e.op.CdiDevices,
CgroupParent: e.op.Meta.CgroupParent,
LinuxResources: e.linuxResources,
NetMode: e.op.Network,
SecurityMode: e.op.Security,
RemoveMountStubsRecursive: e.op.Meta.RemoveMountStubsRecursive,
+14
View File
@@ -9,6 +9,7 @@ import (
"github.com/containerd/platforms"
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/llbsolver/cdidevices"
"github.com/moby/buildkit/solver/llbsolver/linuxresources"
"github.com/moby/buildkit/solver/llbsolver/ops/opsutils"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
@@ -73,6 +74,19 @@ func WithCacheSources(cms []solver.CacheManager) LoadOpt {
}
}
func WithLinuxResourcesMetadata() LoadOpt {
return func(op *pb.Op, md *pb.OpMetadata, opt *solver.VertexOptions) error {
if _, ok := op.Op.(*pb.Op_Exec); !ok {
return nil
}
if md == nil || md.LinuxResources == nil {
return nil
}
opt.Metadata = &linuxresources.Metadata{LinuxResources: md.LinuxResources}
return nil
}
}
func NormalizeRuntimePlatforms() LoadOpt {
var defaultPlatform *pb.Platform
return func(op *pb.Op, _ *pb.OpMetadata, opt *solver.VertexOptions) error {
+7
View File
@@ -64,6 +64,7 @@ const (
CapExecMetaUlimit apicaps.CapID = "exec.meta.ulimit"
CapExecMetaCDI apicaps.CapID = "exec.meta.cdi"
CapExecMetaRemoveMountStubsRecursive apicaps.CapID = "exec.meta.removemountstubs.recursive"
CapExecMetaLinuxResources apicaps.CapID = "exec.meta.linux.resources"
CapExecMountBind apicaps.CapID = "exec.mount.bind"
CapExecMountBindReadWriteNoOutput apicaps.CapID = "exec.mount.bind.readwrite-nooutput"
CapExecMountCache apicaps.CapID = "exec.mount.cache"
@@ -397,6 +398,12 @@ func init() {
Status: apicaps.CapStatusExperimental,
})
Caps.Init(apicaps.Cap{
ID: CapExecMetaLinuxResources,
Enabled: true,
Status: apicaps.CapStatusExperimental,
})
Caps.Init(apicaps.Cap{
ID: CapExecMountBind,
Enabled: true,
+255 -135
View File
@@ -1604,11 +1604,12 @@ type OpMetadata struct {
Description map[string]string `protobuf:"bytes,2,rep,name=description,proto3" json:"description,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
// index 3 reserved for WorkerConstraint in previous versions
// WorkerConstraint worker_constraint = 3;
ExportCache *ExportCache `protobuf:"bytes,4,opt,name=export_cache,json=exportCache,proto3" json:"export_cache,omitempty"`
Caps map[string]bool `protobuf:"bytes,5,rep,name=caps,proto3" json:"caps,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
ProgressGroup *ProgressGroup `protobuf:"bytes,6,opt,name=progress_group,json=progressGroup,proto3" json:"progress_group,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
ExportCache *ExportCache `protobuf:"bytes,4,opt,name=export_cache,json=exportCache,proto3" json:"export_cache,omitempty"`
Caps map[string]bool `protobuf:"bytes,5,rep,name=caps,proto3" json:"caps,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
ProgressGroup *ProgressGroup `protobuf:"bytes,6,opt,name=progress_group,json=progressGroup,proto3" json:"progress_group,omitempty"`
LinuxResources *LinuxResources `protobuf:"bytes,7,opt,name=linux_resources,json=linuxResources,proto3" json:"linux_resources,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *OpMetadata) Reset() {
@@ -1676,6 +1677,13 @@ func (x *OpMetadata) GetProgressGroup() *ProgressGroup {
return nil
}
func (x *OpMetadata) GetLinuxResources() *LinuxResources {
if x != nil {
return x.LinuxResources
}
return nil
}
// Source is a source mapping description for a file
type Source struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -2106,6 +2114,101 @@ func (x *ProgressGroup) GetWeak() bool {
return false
}
// LinuxResources specifies cgroup resource limits for containers.
// Used in OpMetadata to set per-step resource constraints without
// affecting the cache key.
type LinuxResources struct {
state protoimpl.MessageState `protogen:"open.v1"`
Memory int64 `protobuf:"varint,1,opt,name=memory,proto3" json:"memory,omitempty"` // memory limit in bytes
MemorySwap int64 `protobuf:"varint,2,opt,name=memorySwap,proto3" json:"memorySwap,omitempty"` // memory+swap limit in bytes
CpuShares uint64 `protobuf:"varint,3,opt,name=cpuShares,proto3" json:"cpuShares,omitempty"` // relative CPU weight
CpuPeriod uint64 `protobuf:"varint,4,opt,name=cpuPeriod,proto3" json:"cpuPeriod,omitempty"` // CFS period in microseconds
CpuQuota int64 `protobuf:"varint,5,opt,name=cpuQuota,proto3" json:"cpuQuota,omitempty"` // CFS quota in microseconds
CpusetCpus string `protobuf:"bytes,6,opt,name=cpusetCpus,proto3" json:"cpusetCpus,omitempty"` // CPU affinity (e.g., "0-3")
CpusetMems string `protobuf:"bytes,7,opt,name=cpusetMems,proto3" json:"cpusetMems,omitempty"` // memory node affinity (e.g., "0,1")
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *LinuxResources) Reset() {
*x = LinuxResources{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *LinuxResources) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LinuxResources) ProtoMessage() {}
func (x *LinuxResources) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[26]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LinuxResources.ProtoReflect.Descriptor instead.
func (*LinuxResources) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{26}
}
func (x *LinuxResources) GetMemory() int64 {
if x != nil {
return x.Memory
}
return 0
}
func (x *LinuxResources) GetMemorySwap() int64 {
if x != nil {
return x.MemorySwap
}
return 0
}
func (x *LinuxResources) GetCpuShares() uint64 {
if x != nil {
return x.CpuShares
}
return 0
}
func (x *LinuxResources) GetCpuPeriod() uint64 {
if x != nil {
return x.CpuPeriod
}
return 0
}
func (x *LinuxResources) GetCpuQuota() int64 {
if x != nil {
return x.CpuQuota
}
return 0
}
func (x *LinuxResources) GetCpusetCpus() string {
if x != nil {
return x.CpusetCpus
}
return ""
}
func (x *LinuxResources) GetCpusetMems() string {
if x != nil {
return x.CpusetMems
}
return ""
}
type ProxyEnv struct {
state protoimpl.MessageState `protogen:"open.v1"`
HttpProxy string `protobuf:"bytes,1,opt,name=http_proxy,json=httpProxy,proto3" json:"http_proxy,omitempty"`
@@ -2119,7 +2222,7 @@ type ProxyEnv struct {
func (x *ProxyEnv) Reset() {
*x = ProxyEnv{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[26]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2131,7 +2234,7 @@ func (x *ProxyEnv) String() string {
func (*ProxyEnv) ProtoMessage() {}
func (x *ProxyEnv) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[26]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[27]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2144,7 +2247,7 @@ func (x *ProxyEnv) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProxyEnv.ProtoReflect.Descriptor instead.
func (*ProxyEnv) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{26}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{27}
}
func (x *ProxyEnv) GetHttpProxy() string {
@@ -2192,7 +2295,7 @@ type WorkerConstraints struct {
func (x *WorkerConstraints) Reset() {
*x = WorkerConstraints{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[27]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2204,7 +2307,7 @@ func (x *WorkerConstraints) String() string {
func (*WorkerConstraints) ProtoMessage() {}
func (x *WorkerConstraints) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[27]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[28]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2217,7 +2320,7 @@ func (x *WorkerConstraints) ProtoReflect() protoreflect.Message {
// Deprecated: Use WorkerConstraints.ProtoReflect.Descriptor instead.
func (*WorkerConstraints) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{27}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{28}
}
func (x *WorkerConstraints) GetFilter() []string {
@@ -2243,7 +2346,7 @@ type Definition struct {
func (x *Definition) Reset() {
*x = Definition{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[28]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2255,7 +2358,7 @@ func (x *Definition) String() string {
func (*Definition) ProtoMessage() {}
func (x *Definition) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[28]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[29]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2268,7 +2371,7 @@ func (x *Definition) ProtoReflect() protoreflect.Message {
// Deprecated: Use Definition.ProtoReflect.Descriptor instead.
func (*Definition) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{28}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{29}
}
func (x *Definition) GetDef() [][]byte {
@@ -2301,7 +2404,7 @@ type FileOp struct {
func (x *FileOp) Reset() {
*x = FileOp{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[29]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2313,7 +2416,7 @@ func (x *FileOp) String() string {
func (*FileOp) ProtoMessage() {}
func (x *FileOp) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[29]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[30]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2326,7 +2429,7 @@ func (x *FileOp) ProtoReflect() protoreflect.Message {
// Deprecated: Use FileOp.ProtoReflect.Descriptor instead.
func (*FileOp) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{29}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{30}
}
func (x *FileOp) GetActions() []*FileAction {
@@ -2356,7 +2459,7 @@ type FileAction struct {
func (x *FileAction) Reset() {
*x = FileAction{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[30]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2368,7 +2471,7 @@ func (x *FileAction) String() string {
func (*FileAction) ProtoMessage() {}
func (x *FileAction) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[30]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[31]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2381,7 +2484,7 @@ func (x *FileAction) ProtoReflect() protoreflect.Message {
// Deprecated: Use FileAction.ProtoReflect.Descriptor instead.
func (*FileAction) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{30}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{31}
}
func (x *FileAction) GetInput() int64 {
@@ -2537,7 +2640,7 @@ type FileActionCopy struct {
func (x *FileActionCopy) Reset() {
*x = FileActionCopy{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[31]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2549,7 +2652,7 @@ func (x *FileActionCopy) String() string {
func (*FileActionCopy) ProtoMessage() {}
func (x *FileActionCopy) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[31]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[32]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2562,7 +2665,7 @@ func (x *FileActionCopy) ProtoReflect() protoreflect.Message {
// Deprecated: Use FileActionCopy.ProtoReflect.Descriptor instead.
func (*FileActionCopy) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{31}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{32}
}
func (x *FileActionCopy) GetSrc() string {
@@ -2695,7 +2798,7 @@ type FileActionMkFile struct {
func (x *FileActionMkFile) Reset() {
*x = FileActionMkFile{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[32]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2707,7 +2810,7 @@ func (x *FileActionMkFile) String() string {
func (*FileActionMkFile) ProtoMessage() {}
func (x *FileActionMkFile) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[32]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[33]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2720,7 +2823,7 @@ func (x *FileActionMkFile) ProtoReflect() protoreflect.Message {
// Deprecated: Use FileActionMkFile.ProtoReflect.Descriptor instead.
func (*FileActionMkFile) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{32}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{33}
}
func (x *FileActionMkFile) GetPath() string {
@@ -2774,7 +2877,7 @@ type FileActionSymlink struct {
func (x *FileActionSymlink) Reset() {
*x = FileActionSymlink{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[33]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2786,7 +2889,7 @@ func (x *FileActionSymlink) String() string {
func (*FileActionSymlink) ProtoMessage() {}
func (x *FileActionSymlink) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[33]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[34]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2799,7 +2902,7 @@ func (x *FileActionSymlink) ProtoReflect() protoreflect.Message {
// Deprecated: Use FileActionSymlink.ProtoReflect.Descriptor instead.
func (*FileActionSymlink) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{33}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{34}
}
func (x *FileActionSymlink) GetOldpath() string {
@@ -2848,7 +2951,7 @@ type FileActionMkDir struct {
func (x *FileActionMkDir) Reset() {
*x = FileActionMkDir{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[34]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2860,7 +2963,7 @@ func (x *FileActionMkDir) String() string {
func (*FileActionMkDir) ProtoMessage() {}
func (x *FileActionMkDir) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[34]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2873,7 +2976,7 @@ func (x *FileActionMkDir) ProtoReflect() protoreflect.Message {
// Deprecated: Use FileActionMkDir.ProtoReflect.Descriptor instead.
func (*FileActionMkDir) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{34}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{35}
}
func (x *FileActionMkDir) GetPath() string {
@@ -2925,7 +3028,7 @@ type FileActionRm struct {
func (x *FileActionRm) Reset() {
*x = FileActionRm{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[35]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2937,7 +3040,7 @@ func (x *FileActionRm) String() string {
func (*FileActionRm) ProtoMessage() {}
func (x *FileActionRm) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[35]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2950,7 +3053,7 @@ func (x *FileActionRm) ProtoReflect() protoreflect.Message {
// Deprecated: Use FileActionRm.ProtoReflect.Descriptor instead.
func (*FileActionRm) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{35}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{36}
}
func (x *FileActionRm) GetPath() string {
@@ -2984,7 +3087,7 @@ type ChownOpt struct {
func (x *ChownOpt) Reset() {
*x = ChownOpt{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[36]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2996,7 +3099,7 @@ func (x *ChownOpt) String() string {
func (*ChownOpt) ProtoMessage() {}
func (x *ChownOpt) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[36]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[37]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3009,7 +3112,7 @@ func (x *ChownOpt) ProtoReflect() protoreflect.Message {
// Deprecated: Use ChownOpt.ProtoReflect.Descriptor instead.
func (*ChownOpt) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{36}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{37}
}
func (x *ChownOpt) GetUser() *UserOpt {
@@ -3041,7 +3144,7 @@ type UserOpt struct {
func (x *UserOpt) Reset() {
*x = UserOpt{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[37]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3053,7 +3156,7 @@ func (x *UserOpt) String() string {
func (*UserOpt) ProtoMessage() {}
func (x *UserOpt) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[37]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3066,7 +3169,7 @@ func (x *UserOpt) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserOpt.ProtoReflect.Descriptor instead.
func (*UserOpt) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{37}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{38}
}
func (x *UserOpt) GetUser() isUserOpt_User {
@@ -3120,7 +3223,7 @@ type NamedUserOpt struct {
func (x *NamedUserOpt) Reset() {
*x = NamedUserOpt{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[38]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3132,7 +3235,7 @@ func (x *NamedUserOpt) String() string {
func (*NamedUserOpt) ProtoMessage() {}
func (x *NamedUserOpt) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[38]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3145,7 +3248,7 @@ func (x *NamedUserOpt) ProtoReflect() protoreflect.Message {
// Deprecated: Use NamedUserOpt.ProtoReflect.Descriptor instead.
func (*NamedUserOpt) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{38}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{39}
}
func (x *NamedUserOpt) GetName() string {
@@ -3171,7 +3274,7 @@ type MergeInput struct {
func (x *MergeInput) Reset() {
*x = MergeInput{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[39]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3183,7 +3286,7 @@ func (x *MergeInput) String() string {
func (*MergeInput) ProtoMessage() {}
func (x *MergeInput) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[39]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3196,7 +3299,7 @@ func (x *MergeInput) ProtoReflect() protoreflect.Message {
// Deprecated: Use MergeInput.ProtoReflect.Descriptor instead.
func (*MergeInput) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{39}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{40}
}
func (x *MergeInput) GetInput() int64 {
@@ -3215,7 +3318,7 @@ type MergeOp struct {
func (x *MergeOp) Reset() {
*x = MergeOp{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[40]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3227,7 +3330,7 @@ func (x *MergeOp) String() string {
func (*MergeOp) ProtoMessage() {}
func (x *MergeOp) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[40]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3240,7 +3343,7 @@ func (x *MergeOp) ProtoReflect() protoreflect.Message {
// Deprecated: Use MergeOp.ProtoReflect.Descriptor instead.
func (*MergeOp) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{40}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{41}
}
func (x *MergeOp) GetInputs() []*MergeInput {
@@ -3259,7 +3362,7 @@ type LowerDiffInput struct {
func (x *LowerDiffInput) Reset() {
*x = LowerDiffInput{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[41]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3271,7 +3374,7 @@ func (x *LowerDiffInput) String() string {
func (*LowerDiffInput) ProtoMessage() {}
func (x *LowerDiffInput) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[41]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3284,7 +3387,7 @@ func (x *LowerDiffInput) ProtoReflect() protoreflect.Message {
// Deprecated: Use LowerDiffInput.ProtoReflect.Descriptor instead.
func (*LowerDiffInput) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{41}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{42}
}
func (x *LowerDiffInput) GetInput() int64 {
@@ -3303,7 +3406,7 @@ type UpperDiffInput struct {
func (x *UpperDiffInput) Reset() {
*x = UpperDiffInput{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[42]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3315,7 +3418,7 @@ func (x *UpperDiffInput) String() string {
func (*UpperDiffInput) ProtoMessage() {}
func (x *UpperDiffInput) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[42]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3328,7 +3431,7 @@ func (x *UpperDiffInput) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpperDiffInput.ProtoReflect.Descriptor instead.
func (*UpperDiffInput) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{42}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{43}
}
func (x *UpperDiffInput) GetInput() int64 {
@@ -3348,7 +3451,7 @@ type DiffOp struct {
func (x *DiffOp) Reset() {
*x = DiffOp{}
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[43]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3360,7 +3463,7 @@ func (x *DiffOp) String() string {
func (*DiffOp) ProtoMessage() {}
func (x *DiffOp) ProtoReflect() protoreflect.Message {
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[43]
mi := &file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[44]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3373,7 +3476,7 @@ func (x *DiffOp) ProtoReflect() protoreflect.Message {
// Deprecated: Use DiffOp.ProtoReflect.Descriptor instead.
func (*DiffOp) Descriptor() ([]byte, []int) {
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{43}
return file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP(), []int{44}
}
func (x *DiffOp) GetLower() *LowerDiffInput {
@@ -3515,14 +3618,15 @@ const file_github_com_moby_buildkit_solver_pb_ops_proto_rawDesc = "" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\"\n" +
"\n" +
"BuildInput\x12\x14\n" +
"\x05input\x18\x01 \x01(\x03R\x05input\"\x87\x03\n" +
"\x05input\x18\x01 \x01(\x03R\x05input\"\xc4\x03\n" +
"\n" +
"OpMetadata\x12!\n" +
"\fignore_cache\x18\x01 \x01(\bR\vignoreCache\x12A\n" +
"\vdescription\x18\x02 \x03(\v2\x1f.pb.OpMetadata.DescriptionEntryR\vdescription\x122\n" +
"\fexport_cache\x18\x04 \x01(\v2\x0f.pb.ExportCacheR\vexportCache\x12,\n" +
"\x04caps\x18\x05 \x03(\v2\x18.pb.OpMetadata.CapsEntryR\x04caps\x128\n" +
"\x0eprogress_group\x18\x06 \x01(\v2\x11.pb.ProgressGroupR\rprogressGroup\x1a>\n" +
"\x0eprogress_group\x18\x06 \x01(\v2\x11.pb.ProgressGroupR\rprogressGroup\x12;\n" +
"\x0flinux_resources\x18\a \x01(\v2\x12.pb.LinuxResourcesR\x0elinuxResources\x1a>\n" +
"\x10DescriptionEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a7\n" +
@@ -3559,7 +3663,21 @@ const file_github_com_moby_buildkit_solver_pb_ops_proto_rawDesc = "" +
"\rProgressGroup\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
"\x04weak\x18\x03 \x01(\bR\x04weak\"\x9f\x01\n" +
"\x04weak\x18\x03 \x01(\bR\x04weak\"\xe0\x01\n" +
"\x0eLinuxResources\x12\x16\n" +
"\x06memory\x18\x01 \x01(\x03R\x06memory\x12\x1e\n" +
"\n" +
"memorySwap\x18\x02 \x01(\x03R\n" +
"memorySwap\x12\x1c\n" +
"\tcpuShares\x18\x03 \x01(\x04R\tcpuShares\x12\x1c\n" +
"\tcpuPeriod\x18\x04 \x01(\x04R\tcpuPeriod\x12\x1a\n" +
"\bcpuQuota\x18\x05 \x01(\x03R\bcpuQuota\x12\x1e\n" +
"\n" +
"cpusetCpus\x18\x06 \x01(\tR\n" +
"cpusetCpus\x12\x1e\n" +
"\n" +
"cpusetMems\x18\a \x01(\tR\n" +
"cpusetMems\"\x9f\x01\n" +
"\bProxyEnv\x12\x1d\n" +
"\n" +
"http_proxy\x18\x01 \x01(\tR\thttpProxy\x12\x1f\n" +
@@ -3691,7 +3809,7 @@ func file_github_com_moby_buildkit_solver_pb_ops_proto_rawDescGZIP() []byte {
}
var file_github_com_moby_buildkit_solver_pb_ops_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
var file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes = make([]protoimpl.MessageInfo, 51)
var file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes = make([]protoimpl.MessageInfo, 52)
var file_github_com_moby_buildkit_solver_pb_ops_proto_goTypes = []any{
(NetMode)(0), // 0: pb.NetMode
(SecurityMode)(0), // 1: pb.SecurityMode
@@ -3724,49 +3842,50 @@ var file_github_com_moby_buildkit_solver_pb_ops_proto_goTypes = []any{
(*Position)(nil), // 28: pb.Position
(*ExportCache)(nil), // 29: pb.ExportCache
(*ProgressGroup)(nil), // 30: pb.ProgressGroup
(*ProxyEnv)(nil), // 31: pb.ProxyEnv
(*WorkerConstraints)(nil), // 32: pb.WorkerConstraints
(*Definition)(nil), // 33: pb.Definition
(*FileOp)(nil), // 34: pb.FileOp
(*FileAction)(nil), // 35: pb.FileAction
(*FileActionCopy)(nil), // 36: pb.FileActionCopy
(*FileActionMkFile)(nil), // 37: pb.FileActionMkFile
(*FileActionSymlink)(nil), // 38: pb.FileActionSymlink
(*FileActionMkDir)(nil), // 39: pb.FileActionMkDir
(*FileActionRm)(nil), // 40: pb.FileActionRm
(*ChownOpt)(nil), // 41: pb.ChownOpt
(*UserOpt)(nil), // 42: pb.UserOpt
(*NamedUserOpt)(nil), // 43: pb.NamedUserOpt
(*MergeInput)(nil), // 44: pb.MergeInput
(*MergeOp)(nil), // 45: pb.MergeOp
(*LowerDiffInput)(nil), // 46: pb.LowerDiffInput
(*UpperDiffInput)(nil), // 47: pb.UpperDiffInput
(*DiffOp)(nil), // 48: pb.DiffOp
nil, // 49: pb.SourceOp.AttrsEntry
nil, // 50: pb.BuildOp.InputsEntry
nil, // 51: pb.BuildOp.AttrsEntry
nil, // 52: pb.OpMetadata.DescriptionEntry
nil, // 53: pb.OpMetadata.CapsEntry
nil, // 54: pb.Source.LocationsEntry
nil, // 55: pb.Definition.MetadataEntry
(*LinuxResources)(nil), // 31: pb.LinuxResources
(*ProxyEnv)(nil), // 32: pb.ProxyEnv
(*WorkerConstraints)(nil), // 33: pb.WorkerConstraints
(*Definition)(nil), // 34: pb.Definition
(*FileOp)(nil), // 35: pb.FileOp
(*FileAction)(nil), // 36: pb.FileAction
(*FileActionCopy)(nil), // 37: pb.FileActionCopy
(*FileActionMkFile)(nil), // 38: pb.FileActionMkFile
(*FileActionSymlink)(nil), // 39: pb.FileActionSymlink
(*FileActionMkDir)(nil), // 40: pb.FileActionMkDir
(*FileActionRm)(nil), // 41: pb.FileActionRm
(*ChownOpt)(nil), // 42: pb.ChownOpt
(*UserOpt)(nil), // 43: pb.UserOpt
(*NamedUserOpt)(nil), // 44: pb.NamedUserOpt
(*MergeInput)(nil), // 45: pb.MergeInput
(*MergeOp)(nil), // 46: pb.MergeOp
(*LowerDiffInput)(nil), // 47: pb.LowerDiffInput
(*UpperDiffInput)(nil), // 48: pb.UpperDiffInput
(*DiffOp)(nil), // 49: pb.DiffOp
nil, // 50: pb.SourceOp.AttrsEntry
nil, // 51: pb.BuildOp.InputsEntry
nil, // 52: pb.BuildOp.AttrsEntry
nil, // 53: pb.OpMetadata.DescriptionEntry
nil, // 54: pb.OpMetadata.CapsEntry
nil, // 55: pb.Source.LocationsEntry
nil, // 56: pb.Definition.MetadataEntry
}
var file_github_com_moby_buildkit_solver_pb_ops_proto_depIdxs = []int32{
7, // 0: pb.Op.inputs:type_name -> pb.Input
8, // 1: pb.Op.exec:type_name -> pb.ExecOp
19, // 2: pb.Op.source:type_name -> pb.SourceOp
34, // 3: pb.Op.file:type_name -> pb.FileOp
35, // 3: pb.Op.file:type_name -> pb.FileOp
20, // 4: pb.Op.build:type_name -> pb.BuildOp
45, // 5: pb.Op.merge:type_name -> pb.MergeOp
48, // 6: pb.Op.diff:type_name -> pb.DiffOp
46, // 5: pb.Op.merge:type_name -> pb.MergeOp
49, // 6: pb.Op.diff:type_name -> pb.DiffOp
6, // 7: pb.Op.platform:type_name -> pb.Platform
32, // 8: pb.Op.constraints:type_name -> pb.WorkerConstraints
33, // 8: pb.Op.constraints:type_name -> pb.WorkerConstraints
9, // 9: pb.ExecOp.meta:type_name -> pb.Meta
14, // 10: pb.ExecOp.mounts:type_name -> pb.Mount
0, // 11: pb.ExecOp.network:type_name -> pb.NetMode
1, // 12: pb.ExecOp.security:type_name -> pb.SecurityMode
12, // 13: pb.ExecOp.secretenv:type_name -> pb.SecretEnv
13, // 14: pb.ExecOp.cdiDevices:type_name -> pb.CDIDevice
31, // 15: pb.Meta.proxy_env:type_name -> pb.ProxyEnv
32, // 15: pb.Meta.proxy_env:type_name -> pb.ProxyEnv
10, // 16: pb.Meta.extraHosts:type_name -> pb.HostIP
11, // 17: pb.Meta.ulimit:type_name -> pb.Ulimit
2, // 18: pb.Mount.mountType:type_name -> pb.MountType
@@ -3776,47 +3895,48 @@ var file_github_com_moby_buildkit_solver_pb_ops_proto_depIdxs = []int32{
18, // 22: pb.Mount.SSHOpt:type_name -> pb.SSHOpt
3, // 23: pb.Mount.contentCache:type_name -> pb.MountContentCache
4, // 24: pb.CacheOpt.sharing:type_name -> pb.CacheSharingOpt
49, // 25: pb.SourceOp.attrs:type_name -> pb.SourceOp.AttrsEntry
50, // 26: pb.BuildOp.inputs:type_name -> pb.BuildOp.InputsEntry
33, // 27: pb.BuildOp.def:type_name -> pb.Definition
51, // 28: pb.BuildOp.attrs:type_name -> pb.BuildOp.AttrsEntry
52, // 29: pb.OpMetadata.description:type_name -> pb.OpMetadata.DescriptionEntry
50, // 25: pb.SourceOp.attrs:type_name -> pb.SourceOp.AttrsEntry
51, // 26: pb.BuildOp.inputs:type_name -> pb.BuildOp.InputsEntry
34, // 27: pb.BuildOp.def:type_name -> pb.Definition
52, // 28: pb.BuildOp.attrs:type_name -> pb.BuildOp.AttrsEntry
53, // 29: pb.OpMetadata.description:type_name -> pb.OpMetadata.DescriptionEntry
29, // 30: pb.OpMetadata.export_cache:type_name -> pb.ExportCache
53, // 31: pb.OpMetadata.caps:type_name -> pb.OpMetadata.CapsEntry
54, // 31: pb.OpMetadata.caps:type_name -> pb.OpMetadata.CapsEntry
30, // 32: pb.OpMetadata.progress_group:type_name -> pb.ProgressGroup
54, // 33: pb.Source.locations:type_name -> pb.Source.LocationsEntry
25, // 34: pb.Source.infos:type_name -> pb.SourceInfo
26, // 35: pb.Locations.locations:type_name -> pb.Location
33, // 36: pb.SourceInfo.definition:type_name -> pb.Definition
27, // 37: pb.Location.ranges:type_name -> pb.Range
28, // 38: pb.Range.start:type_name -> pb.Position
28, // 39: pb.Range.end:type_name -> pb.Position
55, // 40: pb.Definition.metadata:type_name -> pb.Definition.MetadataEntry
23, // 41: pb.Definition.Source:type_name -> pb.Source
35, // 42: pb.FileOp.actions:type_name -> pb.FileAction
36, // 43: pb.FileAction.copy:type_name -> pb.FileActionCopy
37, // 44: pb.FileAction.mkfile:type_name -> pb.FileActionMkFile
39, // 45: pb.FileAction.mkdir:type_name -> pb.FileActionMkDir
40, // 46: pb.FileAction.rm:type_name -> pb.FileActionRm
38, // 47: pb.FileAction.symlink:type_name -> pb.FileActionSymlink
41, // 48: pb.FileActionCopy.owner:type_name -> pb.ChownOpt
41, // 49: pb.FileActionMkFile.owner:type_name -> pb.ChownOpt
41, // 50: pb.FileActionSymlink.owner:type_name -> pb.ChownOpt
41, // 51: pb.FileActionMkDir.owner:type_name -> pb.ChownOpt
42, // 52: pb.ChownOpt.user:type_name -> pb.UserOpt
42, // 53: pb.ChownOpt.group:type_name -> pb.UserOpt
43, // 54: pb.UserOpt.byName:type_name -> pb.NamedUserOpt
44, // 55: pb.MergeOp.inputs:type_name -> pb.MergeInput
46, // 56: pb.DiffOp.lower:type_name -> pb.LowerDiffInput
47, // 57: pb.DiffOp.upper:type_name -> pb.UpperDiffInput
21, // 58: pb.BuildOp.InputsEntry.value:type_name -> pb.BuildInput
24, // 59: pb.Source.LocationsEntry.value:type_name -> pb.Locations
22, // 60: pb.Definition.MetadataEntry.value:type_name -> pb.OpMetadata
61, // [61:61] is the sub-list for method output_type
61, // [61:61] is the sub-list for method input_type
61, // [61:61] is the sub-list for extension type_name
61, // [61:61] is the sub-list for extension extendee
0, // [0:61] is the sub-list for field type_name
31, // 33: pb.OpMetadata.linux_resources:type_name -> pb.LinuxResources
55, // 34: pb.Source.locations:type_name -> pb.Source.LocationsEntry
25, // 35: pb.Source.infos:type_name -> pb.SourceInfo
26, // 36: pb.Locations.locations:type_name -> pb.Location
34, // 37: pb.SourceInfo.definition:type_name -> pb.Definition
27, // 38: pb.Location.ranges:type_name -> pb.Range
28, // 39: pb.Range.start:type_name -> pb.Position
28, // 40: pb.Range.end:type_name -> pb.Position
56, // 41: pb.Definition.metadata:type_name -> pb.Definition.MetadataEntry
23, // 42: pb.Definition.Source:type_name -> pb.Source
36, // 43: pb.FileOp.actions:type_name -> pb.FileAction
37, // 44: pb.FileAction.copy:type_name -> pb.FileActionCopy
38, // 45: pb.FileAction.mkfile:type_name -> pb.FileActionMkFile
40, // 46: pb.FileAction.mkdir:type_name -> pb.FileActionMkDir
41, // 47: pb.FileAction.rm:type_name -> pb.FileActionRm
39, // 48: pb.FileAction.symlink:type_name -> pb.FileActionSymlink
42, // 49: pb.FileActionCopy.owner:type_name -> pb.ChownOpt
42, // 50: pb.FileActionMkFile.owner:type_name -> pb.ChownOpt
42, // 51: pb.FileActionSymlink.owner:type_name -> pb.ChownOpt
42, // 52: pb.FileActionMkDir.owner:type_name -> pb.ChownOpt
43, // 53: pb.ChownOpt.user:type_name -> pb.UserOpt
43, // 54: pb.ChownOpt.group:type_name -> pb.UserOpt
44, // 55: pb.UserOpt.byName:type_name -> pb.NamedUserOpt
45, // 56: pb.MergeOp.inputs:type_name -> pb.MergeInput
47, // 57: pb.DiffOp.lower:type_name -> pb.LowerDiffInput
48, // 58: pb.DiffOp.upper:type_name -> pb.UpperDiffInput
21, // 59: pb.BuildOp.InputsEntry.value:type_name -> pb.BuildInput
24, // 60: pb.Source.LocationsEntry.value:type_name -> pb.Locations
22, // 61: pb.Definition.MetadataEntry.value:type_name -> pb.OpMetadata
62, // [62:62] is the sub-list for method output_type
62, // [62:62] is the sub-list for method input_type
62, // [62:62] is the sub-list for extension type_name
62, // [62:62] is the sub-list for extension extendee
0, // [0:62] is the sub-list for field type_name
}
func init() { file_github_com_moby_buildkit_solver_pb_ops_proto_init() }
@@ -3832,14 +3952,14 @@ func file_github_com_moby_buildkit_solver_pb_ops_proto_init() {
(*Op_Merge)(nil),
(*Op_Diff)(nil),
}
file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[30].OneofWrappers = []any{
file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[31].OneofWrappers = []any{
(*FileAction_Copy)(nil),
(*FileAction_Mkfile)(nil),
(*FileAction_Mkdir)(nil),
(*FileAction_Rm)(nil),
(*FileAction_Symlink)(nil),
}
file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[37].OneofWrappers = []any{
file_github_com_moby_buildkit_solver_pb_ops_proto_msgTypes[38].OneofWrappers = []any{
(*UserOpt_ByName)(nil),
(*UserOpt_ByID)(nil),
}
@@ -3849,7 +3969,7 @@ func file_github_com_moby_buildkit_solver_pb_ops_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_github_com_moby_buildkit_solver_pb_ops_proto_rawDesc), len(file_github_com_moby_buildkit_solver_pb_ops_proto_rawDesc)),
NumEnums: 5,
NumMessages: 51,
NumMessages: 52,
NumExtensions: 0,
NumServices: 0,
},
+15
View File
@@ -228,6 +228,8 @@ message OpMetadata {
map<string, bool> caps = 5;
ProgressGroup progress_group = 6;
LinuxResources linux_resources = 7;
}
// Source is a source mapping description for a file
@@ -277,6 +279,19 @@ message ProgressGroup {
bool weak = 3;
}
// LinuxResources specifies cgroup resource limits for containers.
// Used in OpMetadata to set per-step resource constraints without
// affecting the cache key.
message LinuxResources {
int64 memory = 1; // memory limit in bytes
int64 memorySwap = 2; // memory+swap limit in bytes
uint64 cpuShares = 3; // relative CPU weight
uint64 cpuPeriod = 4; // CFS period in microseconds
int64 cpuQuota = 5; // CFS quota in microseconds
string cpusetCpus = 6; // CPU affinity (e.g., "0-3")
string cpusetMems = 7; // memory node affinity (e.g., "0,1")
}
message ProxyEnv {
string http_proxy = 1;
string https_proxy = 2;
+429
View File
@@ -495,6 +495,7 @@ func (m *OpMetadata) CloneVT() *OpMetadata {
r.IgnoreCache = m.IgnoreCache
r.ExportCache = m.ExportCache.CloneVT()
r.ProgressGroup = m.ProgressGroup.CloneVT()
r.LinuxResources = m.LinuxResources.CloneVT()
if rhs := m.Description; rhs != nil {
tmpContainer := make(map[string]string, len(rhs))
for k, v := range rhs {
@@ -693,6 +694,29 @@ func (m *ProgressGroup) CloneMessageVT() proto.Message {
return m.CloneVT()
}
func (m *LinuxResources) CloneVT() *LinuxResources {
if m == nil {
return (*LinuxResources)(nil)
}
r := new(LinuxResources)
r.Memory = m.Memory
r.MemorySwap = m.MemorySwap
r.CpuShares = m.CpuShares
r.CpuPeriod = m.CpuPeriod
r.CpuQuota = m.CpuQuota
r.CpusetCpus = m.CpusetCpus
r.CpusetMems = m.CpusetMems
if len(m.unknownFields) > 0 {
r.unknownFields = make([]byte, len(m.unknownFields))
copy(r.unknownFields, m.unknownFields)
}
return r
}
func (m *LinuxResources) CloneMessageVT() proto.Message {
return m.CloneVT()
}
func (m *ProxyEnv) CloneVT() *ProxyEnv {
if m == nil {
return (*ProxyEnv)(nil)
@@ -1973,6 +1997,9 @@ func (this *OpMetadata) EqualVT(that *OpMetadata) bool {
if !this.ProgressGroup.EqualVT(that.ProgressGroup) {
return false
}
if !this.LinuxResources.EqualVT(that.LinuxResources) {
return false
}
return string(this.unknownFields) == string(that.unknownFields)
}
@@ -2221,6 +2248,43 @@ func (this *ProgressGroup) EqualMessageVT(thatMsg proto.Message) bool {
}
return this.EqualVT(that)
}
func (this *LinuxResources) EqualVT(that *LinuxResources) bool {
if this == that {
return true
} else if this == nil || that == nil {
return false
}
if this.Memory != that.Memory {
return false
}
if this.MemorySwap != that.MemorySwap {
return false
}
if this.CpuShares != that.CpuShares {
return false
}
if this.CpuPeriod != that.CpuPeriod {
return false
}
if this.CpuQuota != that.CpuQuota {
return false
}
if this.CpusetCpus != that.CpusetCpus {
return false
}
if this.CpusetMems != that.CpusetMems {
return false
}
return string(this.unknownFields) == string(that.unknownFields)
}
func (this *LinuxResources) EqualMessageVT(thatMsg proto.Message) bool {
that, ok := thatMsg.(*LinuxResources)
if !ok {
return false
}
return this.EqualVT(that)
}
func (this *ProxyEnv) EqualVT(that *ProxyEnv) bool {
if this == that {
return true
@@ -4270,6 +4334,16 @@ func (m *OpMetadata) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.LinuxResources != nil {
size, err := m.LinuxResources.MarshalToSizedBufferVT(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
i--
dAtA[i] = 0x3a
}
if m.ProgressGroup != nil {
size, err := m.ProgressGroup.MarshalToSizedBufferVT(dAtA[:i])
if err != nil {
@@ -4766,6 +4840,78 @@ func (m *ProgressGroup) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *LinuxResources) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *LinuxResources) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *LinuxResources) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.CpusetMems) > 0 {
i -= len(m.CpusetMems)
copy(dAtA[i:], m.CpusetMems)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CpusetMems)))
i--
dAtA[i] = 0x3a
}
if len(m.CpusetCpus) > 0 {
i -= len(m.CpusetCpus)
copy(dAtA[i:], m.CpusetCpus)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CpusetCpus)))
i--
dAtA[i] = 0x32
}
if m.CpuQuota != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.CpuQuota))
i--
dAtA[i] = 0x28
}
if m.CpuPeriod != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.CpuPeriod))
i--
dAtA[i] = 0x20
}
if m.CpuShares != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.CpuShares))
i--
dAtA[i] = 0x18
}
if m.MemorySwap != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MemorySwap))
i--
dAtA[i] = 0x10
}
if m.Memory != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Memory))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func (m *ProxyEnv) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
@@ -6565,6 +6711,10 @@ func (m *OpMetadata) SizeVT() (n int) {
l = m.ProgressGroup.SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.LinuxResources != nil {
l = m.LinuxResources.SizeVT()
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
@@ -6727,6 +6877,39 @@ func (m *ProgressGroup) SizeVT() (n int) {
return n
}
func (m *LinuxResources) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Memory != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.Memory))
}
if m.MemorySwap != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.MemorySwap))
}
if m.CpuShares != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.CpuShares))
}
if m.CpuPeriod != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.CpuPeriod))
}
if m.CpuQuota != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.CpuQuota))
}
l = len(m.CpusetCpus)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.CpusetMems)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *ProxyEnv) SizeVT() (n int) {
if m == nil {
return 0
@@ -10973,6 +11156,42 @@ func (m *OpMetadata) UnmarshalVT(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field LinuxResources", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.LinuxResources == nil {
m.LinuxResources = &LinuxResources{}
}
if err := m.LinuxResources.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
@@ -12001,6 +12220,216 @@ func (m *ProgressGroup) UnmarshalVT(dAtA []byte) error {
}
return nil
}
func (m *LinuxResources) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: LinuxResources: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: LinuxResources: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType)
}
m.Memory = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Memory |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field MemorySwap", wireType)
}
m.MemorySwap = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.MemorySwap |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CpuShares", wireType)
}
m.CpuShares = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.CpuShares |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CpuPeriod", wireType)
}
m.CpuPeriod = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.CpuPeriod |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CpuQuota", wireType)
}
m.CpuQuota = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.CpuQuota |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field CpusetCpus", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.CpusetCpus = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field CpusetMems", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.CpusetMems = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *ProxyEnv) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
+7
View File
@@ -56,6 +56,13 @@ type VertexOptions struct {
ExportCache *bool
// WorkerConstraint
ProgressGroup *pb.ProgressGroup
Metadata VertexMetadata
}
// VertexMetadata is opaque per-vertex metadata that gets merged when the same
// vertex is shared by multiple jobs.
type VertexMetadata interface {
Merge(other VertexMetadata) VertexMetadata
}
// Result is an abstract return value for a solve
+90
View File
@@ -0,0 +1,90 @@
// Package cpuset parses and formats cpuset strings like "0-3,5,7-9".
package cpuset
import (
"slices"
"strconv"
"strings"
"github.com/pkg/errors"
)
// MaxCPU is the largest CPU/memory-node index Parse accepts. It bounds the set
// Parse allocates so an untrusted value like "0-999999999" cannot exhaust memory.
const MaxCPU = 8192
// Parse parses a cpuset string into a set of integers. An empty input returns
// an empty set with no error. Elements must be in the range [0, MaxCPU].
func Parse(s string) (map[int]struct{}, error) {
out := make(map[int]struct{})
for part := range strings.SplitSeq(s, ",") {
part = strings.TrimSpace(part)
if part == "" {
continue
}
lo, hi, found := strings.Cut(part, "-")
if !found {
n, err := strconv.Atoi(part)
if err != nil || n < 0 {
return nil, errors.Errorf("invalid cpuset element %q", part)
}
if n > MaxCPU {
return nil, errors.Errorf("cpuset element %q exceeds maximum %d", part, MaxCPU)
}
out[n] = struct{}{}
continue
}
loN, err1 := strconv.Atoi(strings.TrimSpace(lo))
hiN, err2 := strconv.Atoi(strings.TrimSpace(hi))
if err1 != nil || err2 != nil || loN < 0 || hiN < loN {
return nil, errors.Errorf("invalid cpuset range %q", part)
}
if hiN > MaxCPU {
return nil, errors.Errorf("cpuset range %q exceeds maximum %d", part, MaxCPU)
}
for i := loN; i <= hiN; i++ {
out[i] = struct{}{}
}
}
return out, nil
}
// Validate returns an error if s is not a syntactically valid cpuset string.
// An empty string is valid (means "unset").
func Validate(s string) error {
_, err := Parse(s)
return err
}
// Format emits a sorted, range-collapsed cpuset string from a set.
func Format(set map[int]struct{}) string {
if len(set) == 0 {
return ""
}
vals := make([]int, 0, len(set))
for v := range set {
vals = append(vals, v)
}
slices.Sort(vals)
var sb strings.Builder
i := 0
for i < len(vals) {
j := i
for j+1 < len(vals) && vals[j+1] == vals[j]+1 {
j++
}
if sb.Len() > 0 {
sb.WriteByte(',')
}
if i == j {
sb.WriteString(strconv.Itoa(vals[i]))
} else {
sb.WriteString(strconv.Itoa(vals[i]))
sb.WriteByte('-')
sb.WriteString(strconv.Itoa(vals[j]))
}
i = j + 1
}
return sb.String()
}
+115
View File
@@ -0,0 +1,115 @@
package cpuset
import (
"strconv"
"testing"
"github.com/stretchr/testify/require"
)
func TestParse(t *testing.T) {
t.Parallel()
t.Run("empty is valid", func(t *testing.T) {
set, err := Parse("")
require.NoError(t, err)
require.Empty(t, set)
})
t.Run("single value", func(t *testing.T) {
set, err := Parse("3")
require.NoError(t, err)
require.Equal(t, map[int]struct{}{3: {}}, set)
})
t.Run("range expands inclusively", func(t *testing.T) {
set, err := Parse("0-3")
require.NoError(t, err)
require.Equal(t, map[int]struct{}{0: {}, 1: {}, 2: {}, 3: {}}, set)
})
t.Run("comma list and range mix", func(t *testing.T) {
set, err := Parse("0,2-3,5")
require.NoError(t, err)
require.Equal(t, map[int]struct{}{0: {}, 2: {}, 3: {}, 5: {}}, set)
})
t.Run("whitespace tolerated", func(t *testing.T) {
set, err := Parse(" 0 , 2 - 3 ")
require.NoError(t, err)
require.Equal(t, map[int]struct{}{0: {}, 2: {}, 3: {}}, set)
})
t.Run("negative rejected", func(t *testing.T) {
_, err := Parse("-1")
require.Error(t, err)
})
t.Run("non-numeric rejected", func(t *testing.T) {
_, err := Parse("abc")
require.Error(t, err)
})
t.Run("reversed range rejected", func(t *testing.T) {
_, err := Parse("5-2")
require.Error(t, err)
})
t.Run("element at max accepted", func(t *testing.T) {
set, err := Parse(strconv.Itoa(MaxCPU))
require.NoError(t, err)
require.Equal(t, map[int]struct{}{MaxCPU: {}}, set)
})
t.Run("element above max rejected", func(t *testing.T) {
_, err := Parse(strconv.Itoa(MaxCPU + 1))
require.Error(t, err)
})
t.Run("range above max rejected without huge allocation", func(t *testing.T) {
_, err := Parse("0-999999999")
require.Error(t, err)
})
}
func TestFormat(t *testing.T) {
t.Parallel()
t.Run("empty", func(t *testing.T) {
require.Equal(t, "", Format(nil))
require.Equal(t, "", Format(map[int]struct{}{}))
})
t.Run("single value", func(t *testing.T) {
require.Equal(t, "3", Format(map[int]struct{}{3: {}}))
})
t.Run("collapses contiguous range", func(t *testing.T) {
set := map[int]struct{}{0: {}, 1: {}, 2: {}, 3: {}}
require.Equal(t, "0-3", Format(set))
})
t.Run("preserves gaps", func(t *testing.T) {
set := map[int]struct{}{0: {}, 2: {}, 3: {}, 5: {}}
require.Equal(t, "0,2-3,5", Format(set))
})
t.Run("round-trip", func(t *testing.T) {
const input = "0,2-3,5-9,11"
set, err := Parse(input)
require.NoError(t, err)
require.Equal(t, input, Format(set))
})
}
func TestValidate(t *testing.T) {
t.Parallel()
require.NoError(t, Validate(""))
require.NoError(t, Validate("0-3"))
require.NoError(t, Validate("0,2-3,5"))
require.Error(t, Validate("abc"))
require.Error(t, Validate("5-2"))
require.Error(t, Validate("-1"))
}
+6 -1
View File
@@ -34,6 +34,7 @@ import (
"github.com/moby/buildkit/snapshot/imagerefchecker"
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/llbsolver/cdidevices"
"github.com/moby/buildkit/solver/llbsolver/linuxresources"
"github.com/moby/buildkit/solver/llbsolver/mounts"
"github.com/moby/buildkit/solver/llbsolver/ops"
"github.com/moby/buildkit/solver/pb"
@@ -360,7 +361,11 @@ func (w *Worker) ResolveOp(v solver.Vertex, s frontend.FrontendLLBBridge, sm *se
case *pb.Op_Source:
return ops.NewSourceOp(v, op, baseOp.Platform, w.SourceManager, w.ParallelismSem, sm, w)
case *pb.Op_Exec:
return ops.NewExecOp(v, op, baseOp.Platform, w.CacheMgr, w.ParallelismSem, sm, w.WorkerOpt.Executor, w)
var linuxResources *pb.LinuxResources
if m, ok := v.Options().Metadata.(*linuxresources.Metadata); ok && m != nil {
linuxResources = m.LinuxResources
}
return ops.NewExecOp(v, op, baseOp.Platform, w.CacheMgr, w.ParallelismSem, sm, w.WorkerOpt.Executor, w, linuxResources)
case *pb.Op_File:
return ops.NewFileOp(v, op, w.CacheMgr, w.ParallelismSem, w)
case *pb.Op_Build: