Skip to content

Commit b6b322b

Browse files
Implement executor on Windows
This change splits the containerdexecutor.Run() function into smaller pieces and enables it to run on Windows. Signed-off-by: Gabriel Adrian Samfira <[email protected]>
1 parent ec2d958 commit b6b322b

File tree

8 files changed

+530
-112
lines changed

8 files changed

+530
-112
lines changed

executor/containerdexecutor/executor.go

Lines changed: 47 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"path/filepath"
88
"runtime"
9+
"strings"
910
"sync"
1011
"syscall"
1112
"time"
@@ -17,19 +18,13 @@ import (
1718
"github.com/containerd/containerd"
1819
"github.com/containerd/containerd/cio"
1920
"github.com/containerd/containerd/mount"
20-
containerdoci "github.com/containerd/containerd/oci"
21-
"github.com/containerd/continuity/fs"
22-
"github.com/docker/docker/pkg/idtools"
2321
"github.com/moby/buildkit/executor"
2422
"github.com/moby/buildkit/executor/oci"
2523
resourcestypes "github.com/moby/buildkit/executor/resources/types"
2624
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
2725
"github.com/moby/buildkit/identity"
28-
"github.com/moby/buildkit/snapshot"
2926
"github.com/moby/buildkit/solver/pb"
3027
"github.com/moby/buildkit/util/network"
31-
rootlessspecconv "github.com/moby/buildkit/util/rootless/specconv"
32-
"github.com/opencontainers/runtime-spec/specs-go"
3328
"github.com/pkg/errors"
3429
)
3530

@@ -39,7 +34,7 @@ type containerdExecutor struct {
3934
networkProviders map[pb.NetMode]network.Provider
4035
cgroupParent string
4136
dnsConfig *oci.DNSConfig
42-
running map[string]chan error
37+
running map[string]*jobDetails
4338
mu sync.Mutex
4439
apparmorProfile string
4540
selinux bool
@@ -72,23 +67,36 @@ func New(client *containerd.Client, root, cgroup string, networkProviders map[pb
7267
networkProviders: networkProviders,
7368
cgroupParent: cgroup,
7469
dnsConfig: dnsConfig,
75-
running: make(map[string]chan error),
70+
running: make(map[string]*jobDetails),
7671
apparmorProfile: apparmorProfile,
7772
selinux: selinux,
7873
traceSocket: traceSocket,
7974
rootless: rootless,
8075
}
8176
}
8277

78+
type jobDetails struct {
79+
done chan error
80+
// On linux the rootfsPath is used to ensure the CWD exists, to fetch user information
81+
// and as a bind mount for the root FS of the container.
82+
rootfsPath string
83+
// On Windows we need to use the root mounts to achieve the same thing that Linux does
84+
// with rootfsPath. So we save both in details.
85+
rootMounts []mount.Mount
86+
}
87+
8388
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) {
8489
if id == "" {
8590
id = identity.NewID()
8691
}
8792

8893
startedOnce := sync.Once{}
8994
done := make(chan error, 1)
95+
details := &jobDetails{
96+
done: done,
97+
}
9098
w.mu.Lock()
91-
w.running[id] = done
99+
w.running[id] = details
92100
w.mu.Unlock()
93101
defer func() {
94102
w.mu.Lock()
@@ -104,60 +112,16 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
104112
}()
105113

106114
meta := process.Meta
107-
108-
resolvConf, err := oci.GetResolvConf(ctx, w.root, nil, w.dnsConfig)
109-
if err != nil {
110-
return nil, err
111-
}
112-
113-
hostsFile, clean, err := oci.GetHostsFile(ctx, w.root, meta.ExtraHosts, nil, meta.Hostname)
114-
if err != nil {
115-
return nil, err
116-
}
117-
if clean != nil {
118-
defer clean()
119-
}
120-
121-
mountable, err := root.Src.Mount(ctx, false)
115+
releasers, resolvConf, hostsFile, err := w.prepareExecutionEnv(ctx, root, mounts, meta, details)
122116
if err != nil {
117+
releasers()
123118
return nil, err
124119
}
120+
defer releasers()
125121

126-
rootMounts, release, err := mountable.Mount()
127-
if err != nil {
122+
if err := w.ensureCWD(ctx, details, meta); err != nil {
128123
return nil, err
129124
}
130-
if release != nil {
131-
defer release()
132-
}
133-
134-
lm := snapshot.LocalMounterWithMounts(rootMounts)
135-
rootfsPath, err := lm.Mount()
136-
if err != nil {
137-
return nil, err
138-
}
139-
defer lm.Unmount()
140-
defer executor.MountStubsCleaner(ctx, rootfsPath, mounts, meta.RemoveMountStubsRecursive)()
141-
142-
uid, gid, sgids, err := oci.GetUser(rootfsPath, meta.User)
143-
if err != nil {
144-
return nil, err
145-
}
146-
147-
identity := idtools.Identity{
148-
UID: int(uid),
149-
GID: int(gid),
150-
}
151-
152-
newp, err := fs.RootPath(rootfsPath, meta.Cwd)
153-
if err != nil {
154-
return nil, errors.Wrapf(err, "working dir %s points to invalid target", newp)
155-
}
156-
if _, err := os.Stat(newp); err != nil {
157-
if err := idtools.MkdirAllAndChown(newp, 0755, identity); err != nil {
158-
return nil, errors.Wrapf(err, "failed to create working directory %s", newp)
159-
}
160-
}
161125

162126
provider, ok := w.networkProviders[meta.NetMode]
163127
if !ok {
@@ -173,23 +137,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
173137
bklog.G(ctx).Info("enabling HostNetworking")
174138
}
175139

176-
opts := []containerdoci.SpecOpts{oci.WithUIDGID(uid, gid, sgids)}
177-
if meta.ReadonlyRootFS {
178-
opts = append(opts, containerdoci.WithRootFSReadonly())
179-
}
180-
181-
processMode := oci.ProcessSandbox // FIXME(AkihiroSuda)
182-
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, processMode, nil, w.apparmorProfile, w.selinux, w.traceSocket, opts...)
140+
spec, specReleasers, err := w.getOCISpec(ctx, id, resolvConf, hostsFile, namespace, mounts, meta, details)
183141
if err != nil {
142+
specReleasers()
184143
return nil, err
185144
}
186-
defer cleanup()
187-
spec.Process.Terminal = meta.Tty
188-
if w.rootless {
189-
if err := rootlessspecconv.ToRootless(spec); err != nil {
190-
return nil, err
191-
}
192-
}
145+
defer specReleasers()
193146

194147
container, err := w.client.NewContainer(ctx, id,
195148
containerd.WithSpec(spec),
@@ -210,20 +163,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
210163
cioOpts = append(cioOpts, cio.WithTerminal)
211164
}
212165

213-
rootfs := containerd.WithRootFS([]mount.Mount{{
214-
Source: rootfsPath,
215-
Type: "bind",
216-
Options: []string{"rbind"},
217-
}})
218-
if runtime.GOOS == "freebsd" {
219-
rootfs = containerd.WithRootFS([]mount.Mount{{
220-
Source: rootfsPath,
221-
Type: "nullfs",
222-
Options: []string{},
223-
}})
166+
taskOpts, err := w.getTaskOpts(ctx, details)
167+
if err != nil {
168+
return nil, err
224169
}
225170

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

207+
w.mu.Lock()
208+
details, ok := w.running[id]
209+
w.mu.Unlock()
210+
211+
if !ok {
212+
return errors.Errorf("container %s not found", id)
213+
}
262214
var container containerd.Container
263215
var task containerd.Task
264216
for {
265-
w.mu.Lock()
266-
done, ok := w.running[id]
267-
w.mu.Unlock()
268-
269-
if !ok {
270-
return errors.Errorf("container %s not found", id)
271-
}
272-
273217
if container == nil {
274218
container, _ = w.client.LoadContainer(ctx, id)
275219
}
@@ -285,7 +229,7 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
285229
select {
286230
case <-ctx.Done():
287231
return ctx.Err()
288-
case err, ok := <-done:
232+
case err, ok := <-details.done:
289233
if !ok || err == nil {
290234
return errors.Errorf("container %s has stopped", id)
291235
}
@@ -301,23 +245,24 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
301245
}
302246

303247
proc := spec.Process
304-
305-
// TODO how do we get rootfsPath for oci.GetUser in case user passed in username rather than uid:gid?
306-
// For now only support uid:gid
307248
if meta.User != "" {
308-
uid, gid, err := oci.ParseUIDGID(meta.User)
249+
userSpec, err := getUserSpec(meta.User, details.rootfsPath)
309250
if err != nil {
310251
return errors.WithStack(err)
311252
}
312-
proc.User = specs.User{
313-
UID: uid,
314-
GID: gid,
315-
AdditionalGids: []uint32{},
316-
}
253+
proc.User = userSpec
317254
}
318255

319256
proc.Terminal = meta.Tty
320-
proc.Args = meta.Args
257+
258+
if runtime.GOOS == "windows" {
259+
// On Windows passing in Args will lead to double escaping by hcsshim, which leads to errors.
260+
// The recommendation is to use CommandLine.
261+
proc.CommandLine = strings.Join(meta.Args, " ")
262+
} else {
263+
proc.Args = meta.Args
264+
}
265+
321266
if meta.Cwd != "" {
322267
spec.Process.Cwd = meta.Cwd
323268
}

0 commit comments

Comments
 (0)