6
6
"os"
7
7
"path/filepath"
8
8
"runtime"
9
+ "strings"
9
10
"sync"
10
11
"syscall"
11
12
"time"
@@ -17,19 +18,13 @@ import (
17
18
"github.com/containerd/containerd"
18
19
"github.com/containerd/containerd/cio"
19
20
"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"
23
21
"github.com/moby/buildkit/executor"
24
22
"github.com/moby/buildkit/executor/oci"
25
23
resourcestypes "github.com/moby/buildkit/executor/resources/types"
26
24
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
27
25
"github.com/moby/buildkit/identity"
28
- "github.com/moby/buildkit/snapshot"
29
26
"github.com/moby/buildkit/solver/pb"
30
27
"github.com/moby/buildkit/util/network"
31
- rootlessspecconv "github.com/moby/buildkit/util/rootless/specconv"
32
- "github.com/opencontainers/runtime-spec/specs-go"
33
28
"github.com/pkg/errors"
34
29
)
35
30
@@ -39,7 +34,7 @@ type containerdExecutor struct {
39
34
networkProviders map [pb.NetMode ]network.Provider
40
35
cgroupParent string
41
36
dnsConfig * oci.DNSConfig
42
- running map [string ]chan error
37
+ running map [string ]* jobDetails
43
38
mu sync.Mutex
44
39
apparmorProfile string
45
40
selinux bool
@@ -72,23 +67,36 @@ func New(client *containerd.Client, root, cgroup string, networkProviders map[pb
72
67
networkProviders : networkProviders ,
73
68
cgroupParent : cgroup ,
74
69
dnsConfig : dnsConfig ,
75
- running : make (map [string ]chan error ),
70
+ running : make (map [string ]* jobDetails ),
76
71
apparmorProfile : apparmorProfile ,
77
72
selinux : selinux ,
78
73
traceSocket : traceSocket ,
79
74
rootless : rootless ,
80
75
}
81
76
}
82
77
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
+
83
88
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 ) {
84
89
if id == "" {
85
90
id = identity .NewID ()
86
91
}
87
92
88
93
startedOnce := sync.Once {}
89
94
done := make (chan error , 1 )
95
+ details := & jobDetails {
96
+ done : done ,
97
+ }
90
98
w .mu .Lock ()
91
- w .running [id ] = done
99
+ w .running [id ] = details
92
100
w .mu .Unlock ()
93
101
defer func () {
94
102
w .mu .Lock ()
@@ -104,60 +112,16 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
104
112
}()
105
113
106
114
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 )
122
116
if err != nil {
117
+ releasers ()
123
118
return nil , err
124
119
}
120
+ defer releasers ()
125
121
126
- rootMounts , release , err := mountable .Mount ()
127
- if err != nil {
122
+ if err := w .ensureCWD (ctx , details , meta ); err != nil {
128
123
return nil , err
129
124
}
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
- }
161
125
162
126
provider , ok := w .networkProviders [meta .NetMode ]
163
127
if ! ok {
@@ -173,23 +137,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
173
137
bklog .G (ctx ).Info ("enabling HostNetworking" )
174
138
}
175
139
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 )
183
141
if err != nil {
142
+ specReleasers ()
184
143
return nil , err
185
144
}
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 ()
193
146
194
147
container , err := w .client .NewContainer (ctx , id ,
195
148
containerd .WithSpec (spec ),
@@ -210,20 +163,12 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
210
163
cioOpts = append (cioOpts , cio .WithTerminal )
211
164
}
212
165
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
224
169
}
225
170
226
- task , err := container .NewTask (ctx , cio .NewCreator (cioOpts ... ), rootfs )
171
+ task , err := container .NewTask (ctx , cio .NewCreator (cioOpts ... ), taskOpts )
227
172
if err != nil {
228
173
return nil , err
229
174
}
@@ -259,17 +204,16 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
259
204
// is in the process of being created and check again every 100ms or until
260
205
// context is canceled.
261
206
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
+ }
262
214
var container containerd.Container
263
215
var task containerd.Task
264
216
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
-
273
217
if container == nil {
274
218
container , _ = w .client .LoadContainer (ctx , id )
275
219
}
@@ -285,7 +229,7 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
285
229
select {
286
230
case <- ctx .Done ():
287
231
return ctx .Err ()
288
- case err , ok := <- done :
232
+ case err , ok := <- details . done :
289
233
if ! ok || err == nil {
290
234
return errors .Errorf ("container %s has stopped" , id )
291
235
}
@@ -301,23 +245,24 @@ func (w *containerdExecutor) Exec(ctx context.Context, id string, process execut
301
245
}
302
246
303
247
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
307
248
if meta .User != "" {
308
- uid , gid , err := oci . ParseUIDGID (meta .User )
249
+ userSpec , err := getUserSpec (meta .User , details . rootfsPath )
309
250
if err != nil {
310
251
return errors .WithStack (err )
311
252
}
312
- proc .User = specs.User {
313
- UID : uid ,
314
- GID : gid ,
315
- AdditionalGids : []uint32 {},
316
- }
253
+ proc .User = userSpec
317
254
}
318
255
319
256
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
+
321
266
if meta .Cwd != "" {
322
267
spec .Process .Cwd = meta .Cwd
323
268
}
0 commit comments