Files
buildkit/executor/containerdexecutor/executor.go
Michael Crosby b97bc71adb Refactor networking with ns paths
This fixes the issues where buildkit and callers do not have to be a
subpreaper in order to use networking.  I can add CNI provider later,
with a hidden sub command to create a new network namespace and bind
mount it to buildkit's state dir.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2018-08-21 13:37:47 -04:00

180 lines
4.1 KiB
Go

package containerdexecutor
import (
"context"
"io"
"syscall"
"time"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/contrib/seccomp"
containerdoci "github.com/containerd/containerd/oci"
"github.com/moby/buildkit/cache"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/network"
"github.com/moby/buildkit/util/system"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type containerdExecutor struct {
client *containerd.Client
root string
networkProviders map[pb.NetMode]network.Provider
}
func New(client *containerd.Client, root string, networkProviders map[pb.NetMode]network.Provider) executor.Executor {
return containerdExecutor{
client: client,
root: root,
networkProviders: networkProviders,
}
}
func (w containerdExecutor) Exec(ctx context.Context, meta executor.Meta, root cache.Mountable, mounts []executor.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) (err error) {
id := identity.NewID()
resolvConf, err := oci.GetResolvConf(ctx, w.root)
if err != nil {
return err
}
hostsFile, clean, err := oci.GetHostsFile(ctx, w.root, meta.ExtraHosts)
if err != nil {
return err
}
if clean != nil {
defer clean()
}
mountable, err := root.Mount(ctx, false)
if err != nil {
return err
}
rootMounts, err := mountable.Mount()
if err != nil {
return err
}
defer mountable.Release()
var sgids []uint32
uid, gid, err := oci.ParseUIDGID(meta.User)
if err != nil {
lm := snapshot.LocalMounterWithMounts(rootMounts)
rootfsPath, err := lm.Mount()
if err != nil {
return err
}
uid, gid, sgids, err = oci.GetUser(ctx, rootfsPath, meta.User)
if err != nil {
lm.Unmount()
return err
}
lm.Unmount()
}
// FIXME: still uses host if no provider configured
if meta.NetMode == pb.NetMode_UNSET {
meta.NetMode = pb.NetMode_HOST
}
provider, ok := w.networkProviders[meta.NetMode]
if !ok {
return errors.Errorf("unknown network mode %s", meta.NetMode)
}
namespace, err := provider.New()
if err != nil {
return err
}
defer namespace.Close()
if meta.NetMode == pb.NetMode_HOST {
logrus.Info("enabling HostNetworking")
}
opts := []containerdoci.SpecOpts{oci.WithUIDGID(uid, gid, sgids)}
if meta.ReadonlyRootFS {
opts = append(opts, containerdoci.WithRootFSReadonly())
}
if system.SeccompSupported() {
opts = append(opts, seccomp.WithDefaultProfile())
}
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, opts...)
if err != nil {
return err
}
defer cleanup()
container, err := w.client.NewContainer(ctx, id,
containerd.WithSpec(spec),
)
if err != nil {
return err
}
defer func() {
if err1 := container.Delete(context.TODO()); err == nil && err1 != nil {
err = errors.Wrapf(err1, "failed to delete container %s", id)
}
}()
if stdin == nil {
stdin = &emptyReadCloser{}
}
task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStreams(stdin, stdout, stderr)), containerd.WithRootFS(rootMounts))
if err != nil {
return err
}
defer func() {
if _, err1 := task.Delete(context.TODO()); err == nil && err1 != nil {
err = errors.Wrapf(err1, "failed to delete task %s", id)
}
}()
if err := task.Start(ctx); err != nil {
return err
}
statusCh, err := task.Wait(context.Background())
if err != nil {
return err
}
var cancel func()
ctxDone := ctx.Done()
for {
select {
case <-ctxDone:
ctxDone = nil
var killCtx context.Context
killCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
task.Kill(killCtx, syscall.SIGKILL)
case status := <-statusCh:
if cancel != nil {
cancel()
}
if status.ExitCode() != 0 {
return errors.Errorf("process returned non-zero exit code: %d", status.ExitCode())
}
return nil
}
}
}
type emptyReadCloser struct{}
func (*emptyReadCloser) Read([]byte) (int, error) {
return 0, io.EOF
}
func (*emptyReadCloser) Close() error {
return nil
}