You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buildx/vendor/github.com/moby/buildkit/util/testutil/integration/dockerd.go

249 lines
5.6 KiB
Go

package integration
import (
"context"
"encoding/json"
"io"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/docker/docker/client"
"github.com/moby/buildkit/cmd/buildkitd/config"
"github.com/moby/buildkit/util/testutil/dockerd"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
// InitDockerdWorker registers a dockerd worker with the global registry.
func InitDockerdWorker() {
Register(&Moby{
ID: "dockerd",
IsRootless: false,
Unsupported: []string{
FeatureCacheExport,
FeatureCacheImport,
FeatureCacheBackendAzblob,
FeatureCacheBackendGha,
FeatureCacheBackendLocal,
FeatureCacheBackendRegistry,
FeatureCacheBackendS3,
FeatureDirectPush,
FeatureImageExporter,
FeatureMultiCacheExport,
FeatureMultiPlatform,
FeatureOCIExporter,
FeatureOCILayout,
FeatureProvenance,
FeatureSBOM,
FeatureSecurityMode,
FeatureCNINetwork,
},
})
Register(&Moby{
ID: "dockerd-containerd",
IsRootless: false,
ContainerdSnapshotter: true,
Unsupported: []string{
FeatureSecurityMode,
FeatureCNINetwork,
},
})
}
type Moby struct {
ID string
IsRootless bool
ContainerdSnapshotter bool
Unsupported []string
}
func (c Moby) Name() string {
return c.ID
}
func (c Moby) Rootless() bool {
return c.IsRootless
}
func (c Moby) New(ctx context.Context, cfg *BackendConfig) (b Backend, cl func() error, err error) {
if err := requireRoot(); err != nil {
return nil, nil, err
}
bkcfg, err := config.LoadFile(cfg.ConfigFile)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to load buildkit config file %s", cfg.ConfigFile)
}
dcfg := dockerd.Config{
Features: map[string]bool{
"containerd-snapshotter": c.ContainerdSnapshotter,
},
}
if reg, ok := bkcfg.Registries["docker.io"]; ok && len(reg.Mirrors) > 0 {
for _, m := range reg.Mirrors {
dcfg.Mirrors = append(dcfg.Mirrors, "http://"+m)
}
}
if bkcfg.Entitlements != nil {
for _, e := range bkcfg.Entitlements {
switch e {
case "network.host":
dcfg.Builder.Entitlements.NetworkHost = true
case "security.insecure":
dcfg.Builder.Entitlements.SecurityInsecure = true
}
}
}
dcfgdt, err := json.Marshal(dcfg)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to marshal dockerd config")
}
deferF := &multiCloser{}
cl = deferF.F()
defer func() {
if err != nil {
deferF.F()()
cl = nil
}
}()
var proxyGroup errgroup.Group
deferF.append(proxyGroup.Wait)
workDir, err := os.MkdirTemp("", "integration")
if err != nil {
return nil, nil, err
}
d, err := dockerd.NewDaemon(workDir)
if err != nil {
return nil, nil, errors.Errorf("new daemon error: %q, %s", err, formatLogs(cfg.Logs))
}
dockerdConfigFile := filepath.Join(workDir, "daemon.json")
if err := os.WriteFile(dockerdConfigFile, dcfgdt, 0644); err != nil {
return nil, nil, err
}
dockerdFlags := []string{
"--config-file", dockerdConfigFile,
"--userland-proxy=false",
"--debug",
}
if s := os.Getenv("BUILDKIT_INTEGRATION_DOCKERD_FLAGS"); s != "" {
dockerdFlags = append(dockerdFlags, strings.Split(strings.TrimSpace(s), "\n")...)
}
err = d.StartWithError(cfg.Logs, dockerdFlags...)
if err != nil {
return nil, nil, err
}
deferF.append(d.StopWithError)
if err := waitUnix(d.Sock(), 5*time.Second, nil); err != nil {
return nil, nil, errors.Errorf("dockerd did not start up: %q, %s", err, formatLogs(cfg.Logs))
}
dockerAPI, err := client.NewClientWithOpts(client.WithHost(d.Sock()))
if err != nil {
return nil, nil, err
}
deferF.append(dockerAPI.Close)
err = waitForAPI(ctx, dockerAPI, 5*time.Second)
if err != nil {
return nil, nil, errors.Wrapf(err, "dockerd client api timed out: %s", formatLogs(cfg.Logs))
}
// Create a file descriptor to be used as a Unix domain socket.
// Remove it immediately (the name will still be valid for the socket) so that
// we don't leave files all over the users tmp tree.
f, err := os.CreateTemp("", "buildkit-integration")
if err != nil {
return
}
localPath := f.Name()
f.Close()
os.Remove(localPath)
listener, err := net.Listen("unix", localPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "dockerd listener error: %s", formatLogs(cfg.Logs))
}
deferF.append(listener.Close)
proxyGroup.Go(func() error {
for {
tmpConn, err := listener.Accept()
if err != nil {
// Ignore the error from accept which is always a system error.
return nil
}
conn, err := dockerAPI.DialHijack(ctx, "/grpc", "h2c", nil)
if err != nil {
return err
}
proxyGroup.Go(func() error {
_, err := io.Copy(conn, tmpConn)
if err != nil {
return err
}
return tmpConn.Close()
})
proxyGroup.Go(func() error {
_, err := io.Copy(tmpConn, conn)
if err != nil {
return err
}
return conn.Close()
})
}
})
return backend{
address: "unix://" + listener.Addr().String(),
dockerAddress: d.Sock(),
rootless: c.IsRootless,
isDockerd: true,
unsupportedFeatures: c.Unsupported,
}, cl, nil
}
func waitForAPI(ctx context.Context, apiClient *client.Client, d time.Duration) error {
step := 50 * time.Millisecond
i := 0
for {
if _, err := apiClient.Ping(ctx); err == nil {
break
}
i++
if time.Duration(i)*step > d {
return errors.New("failed to connect to /_ping endpoint")
}
time.Sleep(step)
}
return nil
}
func IsTestDockerd() bool {
return os.Getenv("TEST_DOCKERD") == "1"
}
func IsTestDockerdMoby(sb Sandbox) bool {
b, err := getBackend(sb)
if err != nil {
return false
}
return b.isDockerd && sb.Name() == "dockerd"
}