Skip to content

Commit

Permalink
Implement executor on Windows
Browse files Browse the repository at this point in the history
This change enables the containerd executor to run on Windows.

Signed-off-by: Gabriel Adrian Samfira <[email protected]>
  • Loading branch information
gabriel-samfira committed Jan 18, 2023
1 parent c3eddbf commit aaed23e
Show file tree
Hide file tree
Showing 6 changed files with 476 additions and 92 deletions.
104 changes: 23 additions & 81 deletions executor/containerdexecutor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,12 @@ 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"
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 Down Expand Up @@ -102,92 +95,45 @@ 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 err
}

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

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

rootMounts, release, err := mountable.Mount()
if err != nil {
return err
}
if release != nil {
defer release()
}

lm := snapshot.LocalMounterWithMounts(rootMounts)
rootfsPath, err := lm.Mount()
if err != nil {
return err
// On Windows, the Cwd should have the drive letter removed by the time it gets here,
// so we should be able to just compare with /
if filepath.ToSlash(meta.Cwd) != "/" {
if err := w.ensureCWD(ctx, mountable, meta); err != nil {
return err
}
}
defer lm.Unmount()
defer executor.MountStubsCleaner(rootfsPath, mounts, meta.RemoveMountStubsRecursive)()

uid, gid, sgids, err := oci.GetUser(rootfsPath, meta.User)
ociSpecOpts, err := w.getOCISpecOpts(ctx, meta, mountable)
if err != nil {
return err
}

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

newp, err := fs.RootPath(rootfsPath, meta.Cwd)
if err != nil {
return 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 errors.Wrapf(err, "failed to create working directory %s", newp)
}
}

provider, ok := w.networkProviders[meta.NetMode]
if !ok {
return errors.Errorf("unknown network mode %s", meta.NetMode)
}

namespace, err := provider.New(ctx, meta.Hostname)
if err != nil {
return err
}
defer namespace.Close()

if meta.NetMode == pb.NetMode_HOST {
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, cleanup, err := w.getOCISpec(ctx, id, meta, mounts, namespace, ociSpecOpts...)
defer cleanup()
if err != nil {
return err
}
defer cleanup()
spec.Process.Terminal = meta.Tty
if w.rootless {
if err := rootlessspecconv.ToRootless(spec); err != nil {
return err
}

if meta.NetMode == pb.NetMode_HOST {
bklog.G(ctx).Info("enabling HostNetworking")
}
spec.Process.Terminal = meta.Tty

container, err := w.client.NewContainer(ctx, id,
containerd.WithSpec(spec),
Expand All @@ -208,11 +154,13 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
cioOpts = append(cioOpts, cio.WithTerminal)
}

task, err := container.NewTask(ctx, cio.NewCreator(cioOpts...), containerd.WithRootFS([]mount.Mount{{
Source: rootfsPath,
Type: "bind",
Options: []string{"rbind"},
}}))
taskOpts, taskOptsRelease, err := w.getTaskOpts(ctx, mountable, mounts)
defer taskOptsRelease()
if err != nil {
return err
}

task, err := container.NewTask(ctx, cio.NewCreator(cioOpts...), taskOpts)
if err != nil {
return err
}
Expand Down Expand Up @@ -291,18 +239,12 @@ 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)
if err != nil {
return errors.WithStack(err)
}
proc.User = specs.User{
UID: uid,
GID: gid,
AdditionalGids: []uint32{},
}
proc.User = userSpec
}

proc.Terminal = meta.Tty
Expand Down
163 changes: 163 additions & 0 deletions executor/containerdexecutor/executor_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//go:build !windows
// +build !windows

package containerdexecutor

import (
"context"
"os"

"github.com/containerd/containerd"
"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"
"github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/util/network"
rootlessspecconv "github.com/moby/buildkit/util/rootless/specconv"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)

func (w *containerdExecutor) getTaskOpts(ctx context.Context, rootMount snapshot.Mountable, mounts []executor.Mount) (containerd.NewTaskOpts, func(), error) {
var releasers []func() error
releaseAll := func() {
for _, release := range releasers {
release()
}
}
rootMounts, release, err := rootMount.Mount()
if err != nil {
return nil, nil, err
}
releasers = append(releasers, release)
lm := snapshot.LocalMounterWithMounts(rootMounts)
rootfsPath, err := lm.Mount()
if err != nil {
return nil, releaseAll, err
}

cleanStubs := func() error {
executor.MountStubsCleaner(rootfsPath, mounts)
return nil
}
releasers = append(releasers, lm.Unmount)
releasers = append(releasers, cleanStubs)
return containerd.WithRootFS([]mount.Mount{{
Source: rootfsPath,
Type: "bind",
Options: []string{"rbind"},
}}), releaseAll, nil
}

func (w *containerdExecutor) getOCISpecOpts(ctx context.Context, meta executor.Meta, rootMount snapshot.Mountable) ([]containerdoci.SpecOpts, error) {
rootMounts, release, err := rootMount.Mount()
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
}
defer lm.Unmount()
uid, gid, sgids, err := oci.GetUser(rootfsPath, meta.User)
if err != nil {
return nil, err
}
opts := []containerdoci.SpecOpts{oci.WithUIDGID(uid, gid, sgids)}
if meta.ReadonlyRootFS {
opts = append(opts, containerdoci.WithRootFSReadonly())
}
return opts, nil
}

func (w *containerdExecutor) getOCISpec(ctx context.Context, id string, meta executor.Meta, mounts []executor.Mount, namespace network.Namespace, opts ...containerdoci.SpecOpts) (*specs.Spec, func(), error) {
var releasers []func()
releaseAll := func() {
for _, release := range releasers {
release()
}
}
resolvConf, err := oci.GetResolvConf(ctx, w.root, nil, w.dnsConfig)
if err != nil {
return nil, releaseAll, errors.Wrap(err, "getting resolvconf")
}

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

releasers = append(releasers, clean)

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...)
if err != nil {
return nil, releaseAll, err
}
releasers = append(releasers, cleanup)
if w.rootless {
if err := rootlessspecconv.ToRootless(spec); err != nil {
return nil, releaseAll, err
}
}
return spec, releaseAll, nil
}

func (w *containerdExecutor) ensureCWD(ctx context.Context, rootMount snapshot.Mountable, meta executor.Meta) error {
rootMounts, release, err := rootMount.Mount()
if err != nil {
return err
}
defer release()

lm := snapshot.LocalMounterWithMounts(rootMounts)
rootfsPath, err := lm.Mount()
if err != nil {
return err
}
defer lm.Unmount()

newp, err := fs.RootPath(rootfsPath, meta.Cwd)
if err != nil {
return errors.Wrapf(err, "working dir %s points to invalid target", newp)
}

uid, gid, _, err := oci.GetUser(rootfsPath, meta.User)
if err != nil {
return err
}

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

if _, err := os.Stat(newp); err != nil {
if err := idtools.MkdirAllAndChown(newp, 0755, cwdOwner); err != nil {
return errors.Wrapf(err, "failed to create working directory %s", newp)
}
}
return nil
}

func getUserSpec(user string) (specs.User, error) {
// 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
uid, gid, err := oci.ParseUIDGID(user)
if err != nil {
return specs.User{}, errors.WithStack(err)
}
return specs.User{
UID: uid,
GID: gid,
AdditionalGids: []uint32{},
}, nil
}
Loading

0 comments on commit aaed23e

Please sign in to comment.