Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Windows] Implement executor on Windows #3517

Merged
merged 2 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 42 additions & 100 deletions executor/containerdexecutor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"sync"
"syscall"
"time"
Expand All @@ -17,19 +16,13 @@ import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/mount"
containerdoci "github.com/containerd/containerd/oci"
"github.com/containerd/continuity/fs"
"github.com/docker/docker/pkg/idtools"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
resourcestypes "github.com/moby/buildkit/executor/resources/types"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/network"
rootlessspecconv "github.com/moby/buildkit/util/rootless/specconv"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)

Expand All @@ -39,7 +32,7 @@ type containerdExecutor struct {
networkProviders map[pb.NetMode]network.Provider
cgroupParent string
dnsConfig *oci.DNSConfig
running map[string]chan error
running map[string]*containerState
mu sync.Mutex
apparmorProfile string
selinux bool
Expand Down Expand Up @@ -72,23 +65,36 @@ func New(client *containerd.Client, root, cgroup string, networkProviders map[pb
networkProviders: networkProviders,
cgroupParent: cgroup,
dnsConfig: dnsConfig,
running: make(map[string]chan error),
running: make(map[string]*containerState),
apparmorProfile: apparmorProfile,
selinux: selinux,
traceSocket: traceSocket,
rootless: rootless,
}
}

type containerState struct {
done chan error
// On linux the rootfsPath is used to ensure the CWD exists, to fetch user information
// and as a bind mount for the root FS of the container.
rootfsPath string
// On Windows we need to use the root mounts to achieve the same thing that Linux does
// with rootfsPath. So we save both in details.
rootMounts []mount.Mount
}

func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.Mount, mounts []executor.Mount, process executor.ProcessInfo, started chan<- struct{}) (rec resourcestypes.Recorder, err error) {
if id == "" {
id = identity.NewID()
}

startedOnce := sync.Once{}
done := make(chan error, 1)
details := &containerState{
done: done,
}
w.mu.Lock()
w.running[id] = done
w.running[id] = details
w.mu.Unlock()
defer func() {
w.mu.Lock()
Expand All @@ -104,61 +110,19 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
}()

meta := process.Meta

resolvConf, err := oci.GetResolvConf(ctx, w.root, nil, w.dnsConfig)
if err != nil {
return nil, err
}

hostsFile, clean, err := oci.GetHostsFile(ctx, w.root, meta.ExtraHosts, nil, meta.Hostname)
if err != nil {
return nil, err
}
if clean != nil {
defer clean()
}

mountable, err := root.Src.Mount(ctx, false)
if err != nil {
return nil, err
}

rootMounts, release, err := mountable.Mount()
resolvConf, hostsFile, releasers, err := w.prepareExecutionEnv(ctx, root, mounts, meta, details)
if err != nil {
return nil, err
}
if release != nil {
defer release()
}

lm := snapshot.LocalMounterWithMounts(rootMounts)
rootfsPath, err := lm.Mount()
if err != nil {
return nil, err
if releasers != nil {
defer releasers()
}
defer lm.Unmount()
defer executor.MountStubsCleaner(ctx, rootfsPath, mounts, meta.RemoveMountStubsRecursive)()

uid, gid, sgids, err := oci.GetUser(rootfsPath, meta.User)
if err != nil {
if err := w.ensureCWD(ctx, details, meta); err != nil {
return nil, err
}

identity := idtools.Identity{
UID: int(uid),
GID: int(gid),
}

newp, err := fs.RootPath(rootfsPath, meta.Cwd)
if err != nil {
return nil, errors.Wrapf(err, "working dir %s points to invalid target", newp)
}
if _, err := os.Stat(newp); err != nil {
if err := idtools.MkdirAllAndChown(newp, 0755, identity); err != nil {
return nil, errors.Wrapf(err, "failed to create working directory %s", newp)
}
}

provider, ok := w.networkProviders[meta.NetMode]
if !ok {
return nil, errors.Errorf("unknown network mode %s", meta.NetMode)
Expand All @@ -173,22 +137,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
bklog.G(ctx).Info("enabling HostNetworking")
}

opts := []containerdoci.SpecOpts{oci.WithUIDGID(uid, gid, sgids)}
if meta.ReadonlyRootFS {
opts = append(opts, containerdoci.WithRootFSReadonly())
}

processMode := oci.ProcessSandbox // FIXME(AkihiroSuda)
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, processMode, nil, w.apparmorProfile, w.selinux, w.traceSocket, opts...)
spec, releaseSpec, err := w.createOCISpec(ctx, id, resolvConf, hostsFile, namespace, mounts, meta, details)
if err != nil {
return nil, err
}
defer cleanup()
spec.Process.Terminal = meta.Tty
if w.rootless {
if err := rootlessspecconv.ToRootless(spec); err != nil {
return nil, err
}
if releaseSpec != nil {
defer releaseSpec()
}

container, err := w.client.NewContainer(ctx, id,
Expand All @@ -210,20 +164,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
cioOpts = append(cioOpts, cio.WithTerminal)
}

rootfs := containerd.WithRootFS([]mount.Mount{{
Source: rootfsPath,
Type: "bind",
Options: []string{"rbind"},
}})
if runtime.GOOS == "freebsd" {
rootfs = containerd.WithRootFS([]mount.Mount{{
Source: rootfsPath,
Type: "nullfs",
Options: []string{},
}})
taskOpts, err := details.getTaskOpts()
if err != nil {
return nil, err
}

task, err := container.NewTask(ctx, cio.NewCreator(cioOpts...), rootfs)
task, err := container.NewTask(ctx, cio.NewCreator(cioOpts...), taskOpts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -259,17 +205,16 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
// is in the process of being created and check again every 100ms or until
// context is canceled.

w.mu.Lock()
details, ok := w.running[id]
w.mu.Unlock()

if !ok {
return errors.Errorf("container %s not found", id)
}
var container containerd.Container
var task containerd.Task
for {
w.mu.Lock()
done, ok := w.running[id]
w.mu.Unlock()

if !ok {
return errors.Errorf("container %s not found", id)
}

if container == nil {
container, _ = w.client.LoadContainer(ctx, id)
}
Expand All @@ -285,7 +230,7 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
select {
case <-ctx.Done():
return ctx.Err()
case err, ok := <-done:
case err, ok := <-details.done:
if !ok || err == nil {
return errors.Errorf("container %s has stopped", id)
}
Expand All @@ -301,23 +246,20 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
}

proc := spec.Process

// TODO how do we get rootfsPath for oci.GetUser in case user passed in username rather than uid:gid?
// For now only support uid:gid
if meta.User != "" {
uid, gid, err := oci.ParseUIDGID(meta.User)
userSpec, err := getUserSpec(meta.User, details.rootfsPath)
if err != nil {
return errors.WithStack(err)
}
proc.User = specs.User{
UID: uid,
GID: gid,
AdditionalGids: []uint32{},
}
proc.User = userSpec
}

proc.Terminal = meta.Tty
proc.Args = meta.Args
// setArgs will set the proper command line arguments for this process.
// On Windows, this will set the CommandLine field. On Linux it will set the
// Args field.
setArgs(proc, meta.Args)

if meta.Cwd != "" {
spec.Process.Cwd = meta.Cwd
}
Expand Down
Loading