Port of moby/buildkit#6569 to buildx. Adds --memory, --memory-swap, --cpu-shares, --cpu-period, --cpu-quota, --cpuset-cpus, and --cpuset-mems flags to build, plus the equivalent bake target attributes and compose x-bake fields. Signed-off-by: Jiří Moravčík <jiri.moravcik@gmail.com>
210 lines
5.9 KiB
Go
210 lines
5.9 KiB
Go
package build
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/docker/buildx/driver"
|
|
"github.com/docker/cli/opts"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
// archiveHeaderSize is the number of bytes in an archive header
|
|
archiveHeaderSize = 512
|
|
// mobyHostGatewayName defines a special string which users can append to
|
|
// --add-host to add an extra entry in /etc/hosts that maps
|
|
// host.docker.internal to the host IP
|
|
mobyHostGatewayName = "host-gateway"
|
|
)
|
|
|
|
func isArchive(header []byte) bool {
|
|
for _, m := range [][]byte{
|
|
{0x42, 0x5A, 0x68}, // bzip2
|
|
{0x1F, 0x8B, 0x08}, // gzip
|
|
{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz
|
|
} {
|
|
if len(header) < len(m) {
|
|
continue
|
|
}
|
|
if bytes.Equal(m, header[:len(m)]) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
r := tar.NewReader(bytes.NewBuffer(header))
|
|
_, err := r.Next()
|
|
return err == nil
|
|
}
|
|
|
|
// toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
|
|
func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver.DriverHandle) (string, error) {
|
|
if len(inp) == 0 {
|
|
return "", nil
|
|
}
|
|
hosts := make([]string, 0, len(inp))
|
|
for _, h := range inp {
|
|
host, ip, ok := strings.Cut(h, "=")
|
|
if !ok {
|
|
host, ip, ok = strings.Cut(h, ":")
|
|
}
|
|
if !ok || host == "" || ip == "" {
|
|
return "", errors.Errorf("invalid host %s", h)
|
|
}
|
|
// If the IP Address is a "host-gateway", replace this value with the
|
|
// IP address provided by the worker's label.
|
|
var ips []string
|
|
if ip == mobyHostGatewayName {
|
|
hgip, err := nodeDriver.HostGatewayIP(ctx)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "unable to derive the IP value for host-gateway")
|
|
}
|
|
ips = append(ips, hgip.String())
|
|
} else {
|
|
for v := range strings.SplitSeq(ip, ",") {
|
|
// If the address is enclosed in square brackets, extract it
|
|
// (for IPv6, but permit it for IPv4 as well; we don't know the
|
|
// address family here, but it's unambiguous).
|
|
if len(v) > 2 && v[0] == '[' && v[len(v)-1] == ']' {
|
|
v = v[1 : len(v)-1]
|
|
}
|
|
if net.ParseIP(v) == nil {
|
|
return "", errors.Errorf("invalid host %s", h)
|
|
}
|
|
ips = append(ips, v)
|
|
}
|
|
}
|
|
for _, v := range ips {
|
|
hosts = append(hosts, host+"="+v)
|
|
}
|
|
}
|
|
return strings.Join(hosts, ","), nil
|
|
}
|
|
|
|
// toBuildkitUlimits converts ulimits from docker type=soft:hard format to buildkit's csv format
|
|
func toBuildkitUlimits(inp *opts.UlimitOpt) (string, error) {
|
|
if inp == nil || len(inp.GetList()) == 0 {
|
|
return "", nil
|
|
}
|
|
ulimits := make([]string, 0, len(inp.GetList()))
|
|
for _, ulimit := range inp.GetList() {
|
|
ulimits = append(ulimits, ulimit.String())
|
|
}
|
|
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.
|
|
Learn more at %s`, f, d.Factory().Name(), docs)
|
|
}
|
|
|
|
func noDefaultLoad() bool {
|
|
v, ok := os.LookupEnv("BUILDX_NO_DEFAULT_LOAD")
|
|
if !ok {
|
|
return false
|
|
}
|
|
b, err := strconv.ParseBool(v)
|
|
if err != nil {
|
|
logrus.Warnf("invalid non-bool value for BUILDX_NO_DEFAULT_LOAD: %s", v)
|
|
}
|
|
return b
|
|
}
|