Merge pull request #3876 from jirimoravcik/feat/per-step-resource-limits
build: Add per-step CPU and memory resource limits
This commit is contained in:
+53
-26
@@ -606,6 +606,13 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
||||
override.Append = appendTo
|
||||
override.ArrValue = append(override.ArrValue, parts[1])
|
||||
}
|
||||
case "resources":
|
||||
if len(keys) != 3 {
|
||||
return nil, errors.Errorf("invalid key %s, resources requires name", parts[0])
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
override.Value = parts[1]
|
||||
}
|
||||
case "args":
|
||||
if len(keys) != 3 {
|
||||
return nil, errors.Errorf("invalid key %s, args requires name", parts[0])
|
||||
@@ -745,32 +752,33 @@ type Target struct {
|
||||
// Inherits is the only field that cannot be overridden with --set
|
||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
||||
|
||||
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
|
||||
Attest buildflags.Attests `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
||||
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
|
||||
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
|
||||
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
||||
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
|
||||
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
|
||||
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
||||
Secrets buildflags.Secrets `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
||||
SSH buildflags.SSHKeys `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
|
||||
Outputs buildflags.Exports `json:"output,omitempty" hcl:"output,optional" cty:"output"`
|
||||
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
|
||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
|
||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
||||
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional" cty:"shm-size"`
|
||||
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"`
|
||||
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
|
||||
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
|
||||
ExtraHosts map[string]*string `json:"extra-hosts,omitempty" hcl:"extra-hosts,optional" cty:"extra-hosts"`
|
||||
Policy buildflags.PolicyConfigs `json:"policy,omitempty" hcl:"policy,optional" cty:"policy"`
|
||||
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
|
||||
Attest buildflags.Attests `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
||||
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
|
||||
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
|
||||
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
||||
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
|
||||
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
|
||||
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
||||
Secrets buildflags.Secrets `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
||||
SSH buildflags.SSHKeys `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
|
||||
Outputs buildflags.Exports `json:"output,omitempty" hcl:"output,optional" cty:"output"`
|
||||
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
|
||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
|
||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
||||
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional" cty:"shm-size"`
|
||||
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"`
|
||||
Resources *buildflags.ResourcesConfig `json:"resources,omitempty" hcl:"resources,optional" cty:"resources"`
|
||||
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
|
||||
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
|
||||
ExtraHosts map[string]*string `json:"extra-hosts,omitempty" hcl:"extra-hosts,optional" cty:"extra-hosts"`
|
||||
Policy buildflags.PolicyConfigs `json:"policy,omitempty" hcl:"policy,optional" cty:"policy"`
|
||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
|
||||
|
||||
// linked is a private field to mark a target used as a linked one
|
||||
@@ -944,6 +952,9 @@ func (t *Target) Merge(t2 *Target) {
|
||||
if t2.Ulimits != nil { // merge
|
||||
t.Ulimits = append(t.Ulimits, t2.Ulimits...)
|
||||
}
|
||||
if t2.Resources != nil { // merge
|
||||
t.Resources = t.Resources.Merge(t2.Resources)
|
||||
}
|
||||
if t2.Description != "" {
|
||||
t.Description = t2.Description
|
||||
}
|
||||
@@ -1138,6 +1149,16 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
||||
} else {
|
||||
t.Ulimits = o.ArrValue
|
||||
}
|
||||
case "resources":
|
||||
if len(keys) != 2 {
|
||||
return errors.Errorf("invalid format for resources, expecting resources.<name>=<value>")
|
||||
}
|
||||
if t.Resources == nil {
|
||||
t.Resources = &buildflags.ResourcesConfig{}
|
||||
}
|
||||
if err := t.Resources.SetField(keys[1], value); err != nil {
|
||||
return err
|
||||
}
|
||||
case "network":
|
||||
t.NetworkMode = &value
|
||||
case "pull":
|
||||
@@ -1612,6 +1633,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
}
|
||||
bo.Ulimits = ulimits
|
||||
|
||||
resourceLimits, err := build.ParseResourceLimits(t.Resources.ToEntries())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bo.ResourceLimits = resourceLimits
|
||||
|
||||
bo.Allow = append(bo.Allow, t.Entitlements...)
|
||||
|
||||
return bo, nil
|
||||
|
||||
@@ -27,6 +27,15 @@ target "webDEP" {
|
||||
no-cache = true
|
||||
shm-size = "128m"
|
||||
ulimits = ["nofile=1024:1024"]
|
||||
resources = {
|
||||
memory = "2g"
|
||||
memory-swap = "4g"
|
||||
cpu-shares = 1024
|
||||
cpu-period = 100000
|
||||
cpu-quota = 50000
|
||||
cpuset-cpus = "0-3"
|
||||
cpuset-mems = "0,1"
|
||||
}
|
||||
extra-hosts = {
|
||||
my_hostname = "8.8.8.8"
|
||||
}
|
||||
@@ -67,6 +76,14 @@ target "webapp" {
|
||||
require.Equal(t, true, *m["webapp"].NoCache)
|
||||
require.Equal(t, "128m", *m["webapp"].ShmSize)
|
||||
require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits)
|
||||
require.NotNil(t, m["webapp"].Resources)
|
||||
require.Equal(t, ptrstr("2g"), m["webapp"].Resources.Memory)
|
||||
require.Equal(t, ptrstr("4g"), m["webapp"].Resources.MemorySwap)
|
||||
require.Equal(t, int64(1024), *m["webapp"].Resources.CPUShares)
|
||||
require.Equal(t, int64(100000), *m["webapp"].Resources.CPUPeriod)
|
||||
require.Equal(t, int64(50000), *m["webapp"].Resources.CPUQuota)
|
||||
require.Equal(t, ptrstr("0-3"), m["webapp"].Resources.CPUSetCPUs)
|
||||
require.Equal(t, ptrstr("0,1"), m["webapp"].Resources.CPUSetMems)
|
||||
require.Equal(t, map[string]*string{"my_hostname": ptrstr("8.8.8.8")}, m["webapp"].ExtraHosts)
|
||||
require.Nil(t, m["webapp"].Pull)
|
||||
|
||||
@@ -226,6 +243,21 @@ target "webapp" {
|
||||
require.Equal(t, "256m", *m["webapp"].ShmSize)
|
||||
})
|
||||
|
||||
t.Run("ResourceLimitsOverride", func(t *testing.T) {
|
||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{
|
||||
"webapp.resources.memory=512m",
|
||||
"webapp.resources.cpu-quota=25000",
|
||||
}, nil, nil, &EntitlementConf{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ptrstr("512m"), m["webapp"].Resources.Memory)
|
||||
require.Equal(t, int64(25000), *m["webapp"].Resources.CPUQuota)
|
||||
})
|
||||
|
||||
t.Run("ResourceLimitsInvalidOverride", func(t *testing.T) {
|
||||
_, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.resources.cpu-quota=notanumber"}, nil, nil, &EntitlementConf{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("PullOverride", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}, nil, nil, &EntitlementConf{})
|
||||
@@ -1834,6 +1866,100 @@ func TestExtraHostsDeterministicOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceLimitsToBuildOpt(t *testing.T) {
|
||||
newInt64 := func(v int64) *int64 { return &v }
|
||||
bo, err := toBuildOpt(&Target{
|
||||
DockerfileInline: ptrstr("FROM scratch"),
|
||||
Resources: &buildflags.ResourcesConfig{
|
||||
Memory: ptrstr("2g"),
|
||||
MemorySwap: ptrstr("4g"),
|
||||
CPUShares: newInt64(1024),
|
||||
CPUPeriod: newInt64(100000),
|
||||
CPUQuota: newInt64(50000),
|
||||
CPUSetCPUs: ptrstr("0-3"),
|
||||
CPUSetMems: ptrstr("0,1"),
|
||||
},
|
||||
}, &Input{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(2*1024*1024*1024), bo.ResourceLimits.Memory.Value())
|
||||
require.Equal(t, int64(4*1024*1024*1024), bo.ResourceLimits.MemorySwap.Value())
|
||||
require.Equal(t, int64(1024), bo.ResourceLimits.CPUShares)
|
||||
require.Equal(t, int64(100000), bo.ResourceLimits.CPUPeriod)
|
||||
require.Equal(t, int64(50000), bo.ResourceLimits.CPUQuota)
|
||||
require.Equal(t, "0-3", bo.ResourceLimits.CPUSetCPUs)
|
||||
require.Equal(t, "0,1", bo.ResourceLimits.CPUSetMems)
|
||||
}
|
||||
|
||||
func TestResourceLimitsUnlimitedSwap(t *testing.T) {
|
||||
bo, err := toBuildOpt(&Target{
|
||||
DockerfileInline: ptrstr("FROM scratch"),
|
||||
Resources: &buildflags.ResourcesConfig{MemorySwap: ptrstr("-1")},
|
||||
}, &Input{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(-1), bo.ResourceLimits.MemorySwap.Value())
|
||||
}
|
||||
|
||||
func TestResourceLimitsInvalid(t *testing.T) {
|
||||
_, err := toBuildOpt(&Target{
|
||||
DockerfileInline: ptrstr("FROM scratch"),
|
||||
Resources: &buildflags.ResourcesConfig{Memory: ptrstr("notabyte")},
|
||||
}, &Input{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResourcesObject(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
cpuShares, cpuQuota := int64(1024), int64(50000)
|
||||
want := &buildflags.ResourcesConfig{
|
||||
Memory: ptrstr("2g"),
|
||||
MemorySwap: ptrstr("4g"),
|
||||
CPUShares: &cpuShares,
|
||||
CPUQuota: &cpuQuota,
|
||||
CPUSetCPUs: ptrstr("0-3"),
|
||||
}
|
||||
|
||||
data := `target "app" {
|
||||
resources = {
|
||||
memory = "2g"
|
||||
memory-swap = "4g"
|
||||
cpu-shares = 1024
|
||||
cpu-quota = 50000
|
||||
cpuset-cpus = "0-3"
|
||||
}
|
||||
}`
|
||||
|
||||
m, _, err := ReadTargets(ctx, []File{{Name: "docker-bake.hcl", Data: []byte(data)}}, []string{"app"}, nil, nil, nil, &EntitlementConf{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, want, m["app"].Resources)
|
||||
}
|
||||
|
||||
// TestResourcesInheritIsolation ensures merging resources from a shared base
|
||||
// target doesn't leak a child's override into the base or a sibling.
|
||||
func TestResourcesInheritIsolation(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
data := `target "base" {
|
||||
resources = {
|
||||
memory = "1g"
|
||||
}
|
||||
}
|
||||
target "child1" {
|
||||
inherits = ["base"]
|
||||
resources = {
|
||||
memory = "2g"
|
||||
}
|
||||
}
|
||||
target "child2" {
|
||||
inherits = ["base"]
|
||||
}`
|
||||
|
||||
m, _, err := ReadTargets(ctx, []File{{Name: "docker-bake.hcl", Data: []byte(data)}}, []string{"child1", "child2"}, nil, nil, nil, &EntitlementConf{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ptrstr("2g"), m["child1"].Resources.Memory)
|
||||
require.Equal(t, ptrstr("1g"), m["child2"].Resources.Memory)
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
|
||||
@@ -87,6 +87,7 @@ type Options struct {
|
||||
Tags []string
|
||||
Target string
|
||||
Ulimits *opts.UlimitOpt
|
||||
ResourceLimits ResourceLimits
|
||||
|
||||
Session []session.Attachable
|
||||
Linked bool // Linked marks this target as exclusively linked (not requested by the user).
|
||||
@@ -98,6 +99,18 @@ type Options struct {
|
||||
Policy []buildflags.PolicyConfig
|
||||
}
|
||||
|
||||
// ResourceLimits holds the cgroup resource constraints applied to individual
|
||||
// build steps (RUN instructions). They don't affect the build cache key.
|
||||
type ResourceLimits struct {
|
||||
Memory opts.MemBytes
|
||||
MemorySwap opts.MemSwapBytes
|
||||
CPUShares int64
|
||||
CPUPeriod int64
|
||||
CPUQuota int64
|
||||
CPUSetCPUs string
|
||||
CPUSetMems string
|
||||
}
|
||||
|
||||
type CallFunc struct {
|
||||
Name string
|
||||
Format string
|
||||
|
||||
@@ -610,6 +610,9 @@ func toSolveOpt(ctx context.Context, np *noderesolver.ResolvedNode, multiDriver
|
||||
so.FrontendAttrs["ulimit"] = ulimits
|
||||
}
|
||||
|
||||
// setup per-step resource limits
|
||||
addResourceLimits(opt.ResourceLimits, so.FrontendAttrs)
|
||||
|
||||
// mark call request as internal
|
||||
if opt.CallFunc != nil {
|
||||
so.Internal = true
|
||||
|
||||
@@ -99,6 +99,97 @@ func toBuildkitUlimits(inp *opts.UlimitOpt) (string, error) {
|
||||
return strings.Join(ulimits, ","), nil
|
||||
}
|
||||
|
||||
// User-facing resource keys accepted in `--resource key=value` entries, mirroring docker run flag names.
|
||||
const (
|
||||
resourceKeyMemory = "memory"
|
||||
resourceKeyMemorySwap = "memory-swap"
|
||||
resourceKeyCPUShares = "cpu-shares"
|
||||
resourceKeyCPUPeriod = "cpu-period"
|
||||
resourceKeyCPUQuota = "cpu-quota"
|
||||
resourceKeyCPUSetCPUs = "cpuset-cpus"
|
||||
resourceKeyCPUSetMems = "cpuset-mems"
|
||||
)
|
||||
|
||||
// Frontend attribute keys, must match those parsed by BuildKit's dockerui frontend.
|
||||
const (
|
||||
attrMemory = "memory"
|
||||
attrMemorySwap = "memswap"
|
||||
attrCPUShares = "cpushares"
|
||||
attrCPUPeriod = "cpuperiod"
|
||||
attrCPUQuota = "cpuquota"
|
||||
attrCPUSetCPUs = "cpusetcpus"
|
||||
attrCPUSetMems = "cpusetmems"
|
||||
)
|
||||
|
||||
// ParseResourceLimits parses `key=value` entries from the `--resource` flag into ResourceLimits.
|
||||
func ParseResourceLimits(entries []string) (ResourceLimits, error) {
|
||||
var rl ResourceLimits
|
||||
for _, entry := range entries {
|
||||
k, v, ok := strings.Cut(entry, "=")
|
||||
if !ok {
|
||||
return rl, errors.Errorf("invalid resource %q, expected key=value", entry)
|
||||
}
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.TrimSpace(v)
|
||||
switch k {
|
||||
case resourceKeyMemory:
|
||||
if err := rl.Memory.Set(v); err != nil {
|
||||
return rl, errors.Wrapf(err, "invalid value %q for resource %s", v, k)
|
||||
}
|
||||
case resourceKeyMemorySwap:
|
||||
if err := rl.MemorySwap.Set(v); err != nil {
|
||||
return rl, errors.Wrapf(err, "invalid value %q for resource %s", v, k)
|
||||
}
|
||||
case resourceKeyCPUShares, resourceKeyCPUPeriod, resourceKeyCPUQuota:
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return rl, errors.Wrapf(err, "invalid value %q for resource %s", v, k)
|
||||
}
|
||||
switch k {
|
||||
case resourceKeyCPUShares:
|
||||
rl.CPUShares = n
|
||||
case resourceKeyCPUPeriod:
|
||||
rl.CPUPeriod = n
|
||||
case resourceKeyCPUQuota:
|
||||
rl.CPUQuota = n
|
||||
}
|
||||
case resourceKeyCPUSetCPUs:
|
||||
rl.CPUSetCPUs = v
|
||||
case resourceKeyCPUSetMems:
|
||||
rl.CPUSetMems = v
|
||||
default:
|
||||
return rl, errors.Errorf("unknown resource %q", k)
|
||||
}
|
||||
}
|
||||
return rl, nil
|
||||
}
|
||||
|
||||
// addResourceLimits sets the frontend attributes for the resource limits.
|
||||
// Only non-zero values are sent, so builds against a daemon without the feature keep working.
|
||||
func addResourceLimits(rl ResourceLimits, attrs map[string]string) {
|
||||
if v := rl.Memory.Value(); v > 0 {
|
||||
attrs[attrMemory] = strconv.FormatInt(v, 10)
|
||||
}
|
||||
if v := rl.MemorySwap.Value(); v != 0 {
|
||||
attrs[attrMemorySwap] = strconv.FormatInt(v, 10)
|
||||
}
|
||||
if rl.CPUShares > 0 {
|
||||
attrs[attrCPUShares] = strconv.FormatInt(rl.CPUShares, 10)
|
||||
}
|
||||
if rl.CPUPeriod > 0 {
|
||||
attrs[attrCPUPeriod] = strconv.FormatInt(rl.CPUPeriod, 10)
|
||||
}
|
||||
if rl.CPUQuota > 0 {
|
||||
attrs[attrCPUQuota] = strconv.FormatInt(rl.CPUQuota, 10)
|
||||
}
|
||||
if rl.CPUSetCPUs != "" {
|
||||
attrs[attrCPUSetCPUs] = rl.CPUSetCPUs
|
||||
}
|
||||
if rl.CPUSetMems != "" {
|
||||
attrs[attrCPUSetMems] = rl.CPUSetMems
|
||||
}
|
||||
}
|
||||
|
||||
func notSupported(f driver.Feature, d *driver.DriverHandle, docs string) error {
|
||||
return errors.Errorf(`%s is not supported for the %s driver.
|
||||
Switch to a different driver, or turn on the containerd image store, and try again.
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
dockeropts "github.com/docker/cli/opts"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -150,3 +151,109 @@ func TestToBuildkitExtraHosts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddResourceLimits(t *testing.T) {
|
||||
mustMemSwap := func(v string) dockeropts.MemSwapBytes {
|
||||
var m dockeropts.MemSwapBytes
|
||||
require.NoError(t, m.Set(v))
|
||||
return m
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
limits ResourceLimits
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
limits: ResourceLimits{},
|
||||
expected: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "all",
|
||||
limits: ResourceLimits{
|
||||
Memory: dockeropts.MemBytes(2 * 1024 * 1024 * 1024),
|
||||
MemorySwap: mustMemSwap("4g"),
|
||||
CPUShares: 1024,
|
||||
CPUPeriod: 100000,
|
||||
CPUQuota: 50000,
|
||||
CPUSetCPUs: "0-3",
|
||||
CPUSetMems: "0,1",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"memory": "2147483648",
|
||||
"memswap": "4294967296",
|
||||
"cpushares": "1024",
|
||||
"cpuperiod": "100000",
|
||||
"cpuquota": "50000",
|
||||
"cpusetcpus": "0-3",
|
||||
"cpusetmems": "0,1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unlimited swap",
|
||||
limits: ResourceLimits{MemorySwap: mustMemSwap("-1")},
|
||||
expected: map[string]string{
|
||||
"memswap": "-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "partial",
|
||||
limits: ResourceLimits{Memory: dockeropts.MemBytes(512 * 1024 * 1024)},
|
||||
expected: map[string]string{
|
||||
"memory": "536870912",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
attrs := map[string]string{}
|
||||
addResourceLimits(tc.limits, attrs)
|
||||
require.Equal(t, tc.expected, attrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseResourceLimits(t *testing.T) {
|
||||
t.Run("all", func(t *testing.T) {
|
||||
rl, err := ParseResourceLimits([]string{
|
||||
"memory=2g",
|
||||
"memory-swap=4g",
|
||||
"cpu-shares=1024",
|
||||
"cpu-period=100000",
|
||||
"cpu-quota=50000",
|
||||
"cpuset-cpus=0-3",
|
||||
"cpuset-mems=0,1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(2*1024*1024*1024), rl.Memory.Value())
|
||||
require.Equal(t, int64(4*1024*1024*1024), rl.MemorySwap.Value())
|
||||
require.Equal(t, int64(1024), rl.CPUShares)
|
||||
require.Equal(t, int64(100000), rl.CPUPeriod)
|
||||
require.Equal(t, int64(50000), rl.CPUQuota)
|
||||
require.Equal(t, "0-3", rl.CPUSetCPUs)
|
||||
require.Equal(t, "0,1", rl.CPUSetMems)
|
||||
})
|
||||
|
||||
t.Run("unlimited swap", func(t *testing.T) {
|
||||
rl, err := ParseResourceLimits([]string{"memory-swap=-1"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(-1), rl.MemorySwap.Value())
|
||||
})
|
||||
|
||||
t.Run("missing value", func(t *testing.T) {
|
||||
_, err := ParseResourceLimits([]string{"memory"})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("unknown key", func(t *testing.T) {
|
||||
_, err := ParseResourceLimits([]string{"bogus=1"})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid int", func(t *testing.T) {
|
||||
_, err := ParseResourceLimits([]string{"cpu-shares=notanumber"})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
+51
-22
@@ -89,6 +89,11 @@ type buildOptions struct {
|
||||
target string
|
||||
ulimits *dockeropts.UlimitOpt
|
||||
|
||||
resources []string
|
||||
// legacyResources is kept separate from resources because the pflag
|
||||
// StringArray for --resource resets its backing slice on first use.
|
||||
legacyResources []string
|
||||
|
||||
attests []string
|
||||
sbom string
|
||||
provenance string
|
||||
@@ -133,6 +138,7 @@ func (o *buildOptions) toOptions() (*BuildOptions, error) {
|
||||
Tags: o.tags,
|
||||
Target: o.target,
|
||||
Ulimits: o.ulimits,
|
||||
Resources: append(o.legacyResources, o.resources...),
|
||||
Builder: o.builder,
|
||||
NoCache: o.noCache,
|
||||
Pull: o.pull,
|
||||
@@ -234,6 +240,20 @@ func (o *buildOptions) toDisplayMode() (progressui.DisplayMode, error) {
|
||||
return progress, nil
|
||||
}
|
||||
|
||||
// legacyResourceValue is a pflag.Value that appends a "<key>=value" entry to
|
||||
// the --resource list, wiring the hidden legacy flags into the same code path.
|
||||
type legacyResourceValue struct {
|
||||
key string
|
||||
resources *[]string
|
||||
}
|
||||
|
||||
func (v *legacyResourceValue) String() string { return "" }
|
||||
func (v *legacyResourceValue) Type() string { return "string" }
|
||||
func (v *legacyResourceValue) Set(s string) error {
|
||||
*v.resources = append(*v.resources, v.key+"="+s)
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
commandNameAttribute = attribute.Key("command.name")
|
||||
commandOptionsHash = attribute.Key("command.options.hash")
|
||||
@@ -583,6 +603,8 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugger debuggerOpt
|
||||
options.ulimits = dockeropts.NewUlimitOpt(nil)
|
||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
||||
|
||||
flags.StringArrayVar(&options.resources, "resource", []string{}, `Resource limits for build containers (format: "memory=2g", "cpu-quota=50000")`)
|
||||
|
||||
flags.StringArrayVar(&options.attests, "attest", []string{}, `Attestation parameters (format: "type=sbom,generator=image")`)
|
||||
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
|
||||
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`)
|
||||
@@ -595,12 +617,33 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugger debuggerOpt
|
||||
var ignore string
|
||||
var ignoreSlice []string
|
||||
var ignoreBool bool
|
||||
var ignoreInt int64
|
||||
|
||||
flags.StringVar(&options.callFunc, "print", "", "Print result of information request (e.g., outline, targets)")
|
||||
cobrautil.MarkFlagsExperimental(flags, "print")
|
||||
flags.MarkHidden("print")
|
||||
|
||||
// Legacy per-resource flags, hidden and superseded by --resource.
|
||||
flags.VarP(&legacyResourceValue{key: "memory", resources: &options.legacyResources}, "memory", "m", "Memory limit")
|
||||
flags.MarkHidden("memory")
|
||||
|
||||
flags.VarP(&legacyResourceValue{key: "memory-swap", resources: &options.legacyResources}, "memory-swap", "", `Swap limit equal to memory plus swap: "-1" to enable unlimited swap`)
|
||||
flags.MarkHidden("memory-swap")
|
||||
|
||||
flags.VarP(&legacyResourceValue{key: "cpu-shares", resources: &options.legacyResources}, "cpu-shares", "c", "CPU shares (relative weight)")
|
||||
flags.MarkHidden("cpu-shares")
|
||||
|
||||
flags.VarP(&legacyResourceValue{key: "cpu-period", resources: &options.legacyResources}, "cpu-period", "", "Limit the CPU CFS (Completely Fair Scheduler) period")
|
||||
flags.MarkHidden("cpu-period")
|
||||
|
||||
flags.VarP(&legacyResourceValue{key: "cpu-quota", resources: &options.legacyResources}, "cpu-quota", "", "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||
flags.MarkHidden("cpu-quota")
|
||||
|
||||
flags.VarP(&legacyResourceValue{key: "cpuset-cpus", resources: &options.legacyResources}, "cpuset-cpus", "", `CPUs in which to allow execution ("0-3", "0,1")`)
|
||||
flags.MarkHidden("cpuset-cpus")
|
||||
|
||||
flags.VarP(&legacyResourceValue{key: "cpuset-mems", resources: &options.legacyResources}, "cpuset-mems", "", `MEMs in which to allow execution ("0-3", "0,1")`)
|
||||
flags.MarkHidden("cpuset-mems")
|
||||
|
||||
flags.BoolVar(&ignoreBool, "compress", false, "Compress the build context using gzip")
|
||||
flags.MarkHidden("compress")
|
||||
|
||||
@@ -617,27 +660,6 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugger debuggerOpt
|
||||
flags.SetAnnotation("squash", "flag-warn", []string{"experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency."})
|
||||
cobrautil.MarkFlagsExperimental(flags, "squash")
|
||||
|
||||
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
|
||||
flags.MarkHidden("memory")
|
||||
|
||||
flags.StringVar(&ignore, "memory-swap", "", `Swap limit equal to memory plus swap: "-1" to enable unlimited swap`)
|
||||
flags.MarkHidden("memory-swap")
|
||||
|
||||
flags.Int64VarP(&ignoreInt, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
||||
flags.MarkHidden("cpu-shares")
|
||||
|
||||
flags.Int64Var(&ignoreInt, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
||||
flags.MarkHidden("cpu-period")
|
||||
|
||||
flags.Int64Var(&ignoreInt, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||
flags.MarkHidden("cpu-quota")
|
||||
|
||||
flags.StringVar(&ignore, "cpuset-cpus", "", `CPUs in which to allow execution ("0-3", "0,1")`)
|
||||
flags.MarkHidden("cpuset-cpus")
|
||||
|
||||
flags.StringVar(&ignore, "cpuset-mems", "", `MEMs in which to allow execution ("0-3", "0,1")`)
|
||||
flags.MarkHidden("cpuset-mems")
|
||||
|
||||
flags.BoolVar(&ignoreBool, "rm", true, "Remove intermediate containers after a successful build")
|
||||
flags.MarkHidden("rm")
|
||||
|
||||
@@ -990,6 +1012,7 @@ type BuildOptions struct {
|
||||
Tags []string
|
||||
Target string
|
||||
Ulimits *dockeropts.UlimitOpt
|
||||
Resources []string
|
||||
Builder string
|
||||
NoCache bool
|
||||
Pull bool
|
||||
@@ -1014,6 +1037,11 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *BuildOptions, inSt
|
||||
contexts[name] = build.NamedContext{Path: path}
|
||||
}
|
||||
|
||||
resourceLimits, err := build.ParseResourceLimits(in.Resources)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
opts := build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: in.ContextPath,
|
||||
@@ -1034,6 +1062,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *BuildOptions, inSt
|
||||
Tags: in.Tags,
|
||||
Target: in.Target,
|
||||
Ulimits: in.Ulimits,
|
||||
ResourceLimits: resourceLimits,
|
||||
GroupRef: in.GroupRef,
|
||||
ProvenanceResponseMode: confutil.ParseMetadataProvenance(in.ProvenanceResponseMode),
|
||||
}
|
||||
|
||||
@@ -110,6 +110,13 @@ type configOutput struct {
|
||||
|
||||
ShmSize string `json:",omitempty"`
|
||||
Ulimit string `json:",omitempty"`
|
||||
Memory string `json:",omitempty"`
|
||||
MemorySwap string `json:",omitempty"`
|
||||
CPUShares string `json:",omitempty"`
|
||||
CPUPeriod string `json:",omitempty"`
|
||||
CPUQuota string `json:",omitempty"`
|
||||
CPUSetCPUs string `json:",omitempty"`
|
||||
CPUSetMems string `json:",omitempty"`
|
||||
CacheMountNS string `json:",omitempty"`
|
||||
DockerfileCheckConfig string `json:",omitempty"`
|
||||
SourceDateEpoch string `json:",omitempty"`
|
||||
@@ -391,6 +398,13 @@ workers0:
|
||||
|
||||
readAttr(attrs, "shm-size", &out.Config.ShmSize, nil)
|
||||
readAttr(attrs, "ulimit", &out.Config.Ulimit, nil)
|
||||
readAttr(attrs, "memory", &out.Config.Memory, nil)
|
||||
readAttr(attrs, "memswap", &out.Config.MemorySwap, nil)
|
||||
readAttr(attrs, "cpushares", &out.Config.CPUShares, nil)
|
||||
readAttr(attrs, "cpuperiod", &out.Config.CPUPeriod, nil)
|
||||
readAttr(attrs, "cpuquota", &out.Config.CPUQuota, nil)
|
||||
readAttr(attrs, "cpusetcpus", &out.Config.CPUSetCPUs, nil)
|
||||
readAttr(attrs, "cpusetmems", &out.Config.CPUSetMems, nil)
|
||||
readAttr(attrs, "build-arg:BUILDKIT_CACHE_MOUNT_NS", &out.Config.CacheMountNS, nil)
|
||||
readAttr(attrs, "build-arg:BUILDKIT_DOCKERFILE_CHECK", &out.Config.DockerfileCheckConfig, nil)
|
||||
readAttr(attrs, "build-arg:"+epoch.SourceDateEpochEnv, &out.Config.SourceDateEpoch, nil)
|
||||
@@ -570,6 +584,27 @@ workers0:
|
||||
if out.Config.Ulimit != "" {
|
||||
fmt.Fprintf(tw, "Resource Limits:\t%s\n", out.Config.Ulimit)
|
||||
}
|
||||
if out.Config.Memory != "" {
|
||||
fmt.Fprintf(tw, "Memory:\t%s\n", out.Config.Memory)
|
||||
}
|
||||
if out.Config.MemorySwap != "" {
|
||||
fmt.Fprintf(tw, "Memory Swap:\t%s\n", out.Config.MemorySwap)
|
||||
}
|
||||
if out.Config.CPUShares != "" {
|
||||
fmt.Fprintf(tw, "CPU Shares:\t%s\n", out.Config.CPUShares)
|
||||
}
|
||||
if out.Config.CPUPeriod != "" {
|
||||
fmt.Fprintf(tw, "CPU Period:\t%s\n", out.Config.CPUPeriod)
|
||||
}
|
||||
if out.Config.CPUQuota != "" {
|
||||
fmt.Fprintf(tw, "CPU Quota:\t%s\n", out.Config.CPUQuota)
|
||||
}
|
||||
if out.Config.CPUSetCPUs != "" {
|
||||
fmt.Fprintf(tw, "CPUSet CPUs:\t%s\n", out.Config.CPUSetCPUs)
|
||||
}
|
||||
if out.Config.CPUSetMems != "" {
|
||||
fmt.Fprintf(tw, "CPUSet Mems:\t%s\n", out.Config.CPUSetMems)
|
||||
}
|
||||
if out.Config.CacheMountNS != "" {
|
||||
fmt.Fprintf(tw, "Cache Mount Namespace:\t%s\n", out.Config.CacheMountNS)
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ The following table shows the complete list of attributes that you can assign to
|
||||
| [`policy`](#targetpolicy) | List | Policies to validate build sources and metadata |
|
||||
| [`platforms`](#targetplatforms) | List | Target platforms |
|
||||
| [`pull`](#targetpull) | Boolean | Always pull images |
|
||||
| [`resources`](#targetresources) | Map | Resource limits for build containers |
|
||||
| [`secret`](#targetsecret) | List | Secrets to expose to the build |
|
||||
| [`shm-size`](#targetshm-size) | List | Size of `/dev/shm` |
|
||||
| [`ssh`](#targetssh) | List | SSH agent sockets or keys to expose to the build |
|
||||
@@ -939,6 +940,29 @@ target "default" {
|
||||
}
|
||||
```
|
||||
|
||||
### `target.resources`
|
||||
|
||||
Sets cgroup resource limits for the containers that run `RUN` instructions
|
||||
during the build. The supported keys are `memory`, `memory-swap`, `cpu-shares`,
|
||||
`cpu-period`, `cpu-quota`, `cpuset-cpus`, and `cpuset-mems`. These map to the
|
||||
equivalent `docker build` flags and to the
|
||||
[`--resource`](https://docs.docker.com/reference/cli/docker/buildx/build/#resource)
|
||||
flag for `docker buildx build`.
|
||||
|
||||
```hcl
|
||||
target "default" {
|
||||
resources = {
|
||||
memory = "2g"
|
||||
memory-swap = "4g"
|
||||
cpu-quota = 50000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> These limits require a BuildKit daemon that supports per-step resource limits
|
||||
> and only take effect on Linux. They don't affect the build cache key.
|
||||
|
||||
### `target.secret`
|
||||
|
||||
Defines secrets to expose to the build target.
|
||||
|
||||
@@ -44,6 +44,7 @@ Start a build
|
||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||
| [`--push`](#push) | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||
| [`--resource`](#resource) | `stringArray` | | Resource limits for build containers (format: `memory=2g`, `cpu-quota=50000`) |
|
||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Shared memory size for build containers |
|
||||
@@ -1158,3 +1159,40 @@ $ docker buildx build --ulimit nofile=1024:1024 .
|
||||
> In most cases, it is recommended to let the builder automatically determine
|
||||
> the appropriate configurations. Manual adjustments should only be considered
|
||||
> when specific performance tuning is required for complex build scenarios.
|
||||
|
||||
### <a name="resource"></a> Set CPU and memory limits for build containers (--resource)
|
||||
|
||||
The `--resource` flag constrains the resources available to the containers that
|
||||
run your `RUN` instructions during the build. It's repeatable and takes
|
||||
`key=value` pairs, where `key` is one of:
|
||||
|
||||
| Key | Description |
|
||||
|:--------------|:-----------------------------------------------------------------------------|
|
||||
| `memory` | Memory limit (format: `<number><unit>`, e.g. `512m`, `2g`). |
|
||||
| `memory-swap` | Total memory plus swap limit. Set to `-1` to allow unlimited swap. |
|
||||
| `cpu-shares` | CPU shares (relative weight). |
|
||||
| `cpu-period` | Length of a CPU CFS (Completely Fair Scheduler) period, in microseconds. |
|
||||
| `cpu-quota` | CPU CFS quota, in microseconds, within each `cpu-period`. |
|
||||
| `cpuset-cpus` | CPUs in which to allow execution (`0-3`, `0,1`). |
|
||||
| `cpuset-mems` | Memory nodes (MEMs) in which to allow execution (`0-3`, `0,1`). |
|
||||
|
||||
```console
|
||||
$ docker buildx build --resource memory=2g --resource cpu-quota=50000 --resource cpu-period=100000 .
|
||||
```
|
||||
|
||||
These map to the cgroup resource limits of the legacy `docker build` API and
|
||||
only apply to individual build steps. They don't affect the build cache key.
|
||||
|
||||
> [!NOTE]
|
||||
> These limits require a BuildKit daemon that supports per-step resource limits
|
||||
> (the `exec.meta.linux.resources` capability) and only take effect on Linux.
|
||||
|
||||
> [!NOTE]
|
||||
> Because BuildKit can run build steps in parallel, these limits apply to each
|
||||
> step in isolation rather than to the build as a whole. When the same step is
|
||||
> requested with different limits, the most relaxed limits are used.
|
||||
|
||||
> [!NOTE]
|
||||
> In most cases, it is recommended to let the builder automatically determine
|
||||
> the appropriate configurations. Manual adjustments should only be considered
|
||||
> when specific performance tuning is required for complex build scenarios.
|
||||
|
||||
@@ -36,6 +36,7 @@ Start a build
|
||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||
| `--push` | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||
| `--resource` | `stringArray` | | Resource limits for build containers (format: `memory=2g`, `cpu-quota=50000`) |
|
||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
||||
|
||||
@@ -40,6 +40,7 @@ Start a build
|
||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||
| `--push` | `bool` | | Shorthand for `--output=type=registry,unpack=false` |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||
| `--resource` | `stringArray` | | Resource limits for build containers (format: `memory=2g`, `cpu-quota=50000`) |
|
||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package buildflags
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ResourcesConfig holds the cgroup resource limits applied to individual build
|
||||
// steps (RUN instructions). It maps to the `--resource` build flags.
|
||||
type ResourcesConfig struct {
|
||||
Memory *string `json:"memory,omitempty"`
|
||||
MemorySwap *string `json:"memory-swap,omitempty"`
|
||||
CPUShares *int64 `json:"cpu-shares,omitempty"`
|
||||
CPUPeriod *int64 `json:"cpu-period,omitempty"`
|
||||
CPUQuota *int64 `json:"cpu-quota,omitempty"`
|
||||
CPUSetCPUs *string `json:"cpuset-cpus,omitempty"`
|
||||
CPUSetMems *string `json:"cpuset-mems,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ResourcesConfig) Merge(other *ResourcesConfig) *ResourcesConfig {
|
||||
if r == nil {
|
||||
r = &ResourcesConfig{}
|
||||
}
|
||||
merged := *r
|
||||
if other != nil {
|
||||
if other.Memory != nil {
|
||||
merged.Memory = other.Memory
|
||||
}
|
||||
if other.MemorySwap != nil {
|
||||
merged.MemorySwap = other.MemorySwap
|
||||
}
|
||||
if other.CPUShares != nil {
|
||||
merged.CPUShares = other.CPUShares
|
||||
}
|
||||
if other.CPUPeriod != nil {
|
||||
merged.CPUPeriod = other.CPUPeriod
|
||||
}
|
||||
if other.CPUQuota != nil {
|
||||
merged.CPUQuota = other.CPUQuota
|
||||
}
|
||||
if other.CPUSetCPUs != nil {
|
||||
merged.CPUSetCPUs = other.CPUSetCPUs
|
||||
}
|
||||
if other.CPUSetMems != nil {
|
||||
merged.CPUSetMems = other.CPUSetMems
|
||||
}
|
||||
}
|
||||
return &merged
|
||||
}
|
||||
|
||||
// ToEntries renders the config as `key=value` entries for build.ParseResourceLimits.
|
||||
func (r *ResourcesConfig) ToEntries() []string {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
var entries []string
|
||||
if r.Memory != nil {
|
||||
entries = append(entries, "memory="+*r.Memory)
|
||||
}
|
||||
if r.MemorySwap != nil {
|
||||
entries = append(entries, "memory-swap="+*r.MemorySwap)
|
||||
}
|
||||
if r.CPUShares != nil {
|
||||
entries = append(entries, "cpu-shares="+strconv.FormatInt(*r.CPUShares, 10))
|
||||
}
|
||||
if r.CPUPeriod != nil {
|
||||
entries = append(entries, "cpu-period="+strconv.FormatInt(*r.CPUPeriod, 10))
|
||||
}
|
||||
if r.CPUQuota != nil {
|
||||
entries = append(entries, "cpu-quota="+strconv.FormatInt(*r.CPUQuota, 10))
|
||||
}
|
||||
if r.CPUSetCPUs != nil {
|
||||
entries = append(entries, "cpuset-cpus="+*r.CPUSetCPUs)
|
||||
}
|
||||
if r.CPUSetMems != nil {
|
||||
entries = append(entries, "cpuset-mems="+*r.CPUSetMems)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
// SetField sets a single resource field by name, used by bake `--set` overrides.
|
||||
func (r *ResourcesConfig) SetField(name, value string) error {
|
||||
switch name {
|
||||
case "memory":
|
||||
r.Memory = &value
|
||||
case "memory-swap":
|
||||
r.MemorySwap = &value
|
||||
case "cpu-shares", "cpu-period", "cpu-quota":
|
||||
n, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid value %s for int64 key resources.%s", value, name)
|
||||
}
|
||||
switch name {
|
||||
case "cpu-shares":
|
||||
r.CPUShares = &n
|
||||
case "cpu-period":
|
||||
r.CPUPeriod = &n
|
||||
case "cpu-quota":
|
||||
r.CPUQuota = &n
|
||||
}
|
||||
case "cpuset-cpus":
|
||||
r.CPUSetCPUs = &value
|
||||
case "cpuset-mems":
|
||||
r.CPUSetMems = &value
|
||||
default:
|
||||
return errors.Errorf("unknown resources key %s", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package buildflags
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
var resourcesType = sync.OnceValue(func() cty.Type {
|
||||
return cty.ObjectWithOptionalAttrs(
|
||||
map[string]cty.Type{
|
||||
"memory": cty.String,
|
||||
"memory-swap": cty.String,
|
||||
"cpu-shares": cty.Number,
|
||||
"cpu-period": cty.Number,
|
||||
"cpu-quota": cty.Number,
|
||||
"cpuset-cpus": cty.String,
|
||||
"cpuset-mems": cty.String,
|
||||
},
|
||||
[]string{"memory", "memory-swap", "cpu-shares", "cpu-period", "cpu-quota", "cpuset-cpus", "cpuset-mems"},
|
||||
)
|
||||
})
|
||||
|
||||
func (r *ResourcesConfig) FromCtyValue(in cty.Value, p cty.Path) error {
|
||||
conv, err := convert.Convert(in, resourcesType())
|
||||
if err != nil {
|
||||
return p.NewError(err)
|
||||
}
|
||||
|
||||
if v := conv.GetAttr("memory"); !v.IsNull() && v.IsKnown() {
|
||||
s := v.AsString()
|
||||
r.Memory = &s
|
||||
}
|
||||
if v := conv.GetAttr("memory-swap"); !v.IsNull() && v.IsKnown() {
|
||||
s := v.AsString()
|
||||
r.MemorySwap = &s
|
||||
}
|
||||
if v := conv.GetAttr("cpu-shares"); !v.IsNull() && v.IsKnown() {
|
||||
var n int64
|
||||
if err := gocty.FromCtyValue(v, &n); err != nil {
|
||||
return p.NewError(err)
|
||||
}
|
||||
r.CPUShares = &n
|
||||
}
|
||||
if v := conv.GetAttr("cpu-period"); !v.IsNull() && v.IsKnown() {
|
||||
var n int64
|
||||
if err := gocty.FromCtyValue(v, &n); err != nil {
|
||||
return p.NewError(err)
|
||||
}
|
||||
r.CPUPeriod = &n
|
||||
}
|
||||
if v := conv.GetAttr("cpu-quota"); !v.IsNull() && v.IsKnown() {
|
||||
var n int64
|
||||
if err := gocty.FromCtyValue(v, &n); err != nil {
|
||||
return p.NewError(err)
|
||||
}
|
||||
r.CPUQuota = &n
|
||||
}
|
||||
if v := conv.GetAttr("cpuset-cpus"); !v.IsNull() && v.IsKnown() {
|
||||
s := v.AsString()
|
||||
r.CPUSetCPUs = &s
|
||||
}
|
||||
if v := conv.GetAttr("cpuset-mems"); !v.IsNull() && v.IsKnown() {
|
||||
s := v.AsString()
|
||||
r.CPUSetMems = &s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ResourcesConfig) ToCtyValue() cty.Value {
|
||||
if r == nil {
|
||||
return cty.NullVal(resourcesType())
|
||||
}
|
||||
|
||||
vals := map[string]cty.Value{
|
||||
"memory": cty.NullVal(cty.String),
|
||||
"memory-swap": cty.NullVal(cty.String),
|
||||
"cpu-shares": cty.NullVal(cty.Number),
|
||||
"cpu-period": cty.NullVal(cty.Number),
|
||||
"cpu-quota": cty.NullVal(cty.Number),
|
||||
"cpuset-cpus": cty.NullVal(cty.String),
|
||||
"cpuset-mems": cty.NullVal(cty.String),
|
||||
}
|
||||
if r.Memory != nil {
|
||||
vals["memory"] = cty.StringVal(*r.Memory)
|
||||
}
|
||||
if r.MemorySwap != nil {
|
||||
vals["memory-swap"] = cty.StringVal(*r.MemorySwap)
|
||||
}
|
||||
if r.CPUShares != nil {
|
||||
vals["cpu-shares"] = cty.NumberIntVal(*r.CPUShares)
|
||||
}
|
||||
if r.CPUPeriod != nil {
|
||||
vals["cpu-period"] = cty.NumberIntVal(*r.CPUPeriod)
|
||||
}
|
||||
if r.CPUQuota != nil {
|
||||
vals["cpu-quota"] = cty.NumberIntVal(*r.CPUQuota)
|
||||
}
|
||||
if r.CPUSetCPUs != nil {
|
||||
vals["cpuset-cpus"] = cty.StringVal(*r.CPUSetCPUs)
|
||||
}
|
||||
if r.CPUSetMems != nil {
|
||||
vals["cpuset-mems"] = cty.StringVal(*r.CPUSetMems)
|
||||
}
|
||||
return cty.ObjectVal(vals)
|
||||
}
|
||||
Reference in New Issue
Block a user