Skip to content

Commit

Permalink
Merge pull request #3517 from gabriel-samfira/enable-windows-executor
Browse files Browse the repository at this point in the history
[Windows] Implement executor on Windows
  • Loading branch information
AkihiroSuda authored Aug 30, 2023
2 parents 6ae419d + cc5657c commit 05eb728
Show file tree
Hide file tree
Showing 8 changed files with 542 additions and 108 deletions.
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

0 comments on commit 05eb728

Please sign in to comment.