1
1
package docker
2
2
3
3
import (
4
- "archive/tar"
5
4
"bytes"
6
5
"context"
7
6
"io"
8
7
"io/ioutil"
9
8
"net"
10
9
"os"
10
+ "path"
11
+ "path/filepath"
11
12
"time"
12
13
13
14
"github.com/docker/buildx/driver"
14
15
"github.com/docker/buildx/driver/bkimage"
15
16
"github.com/docker/buildx/util/imagetools"
16
17
"github.com/docker/buildx/util/progress"
18
+ "github.com/docker/cli/cli/command"
17
19
"github.com/docker/docker/api/types"
18
20
dockertypes "github.com/docker/docker/api/types"
19
21
"github.com/docker/docker/api/types/container"
20
22
"github.com/docker/docker/api/types/mount"
21
23
"github.com/docker/docker/api/types/network"
22
24
dockerclient "github.com/docker/docker/client"
25
+ dockerarchive "github.com/docker/docker/pkg/archive"
23
26
"github.com/docker/docker/pkg/stdcopy"
27
+ "github.com/docker/docker/pkg/system"
24
28
"github.com/moby/buildkit/client"
25
29
"github.com/moby/buildkit/util/tracing/detect"
30
+ "github.com/pelletier/go-toml"
26
31
"github.com/pkg/errors"
27
32
)
28
33
@@ -33,7 +38,8 @@ const (
33
38
// stores its state. The container driver creates a Linux container, so
34
39
// this should match the location for Linux, as defined in:
35
40
// https://github.com/moby/buildkit/blob/v0.9.0/util/appdefaults/appdefaults_unix.go#L11-L15
36
- containerBuildKitRootDir = "/var/lib/buildkit"
41
+ containerBuildKitRootDir = "/var/lib/buildkit"
42
+ containerBuildKitConfigDir = "/etc/buildkit"
37
43
)
38
44
39
45
type Driver struct {
@@ -139,12 +145,14 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
139
145
return err
140
146
}
141
147
if f := d .InitConfig .ConfigFile ; f != "" {
142
- buf , err := readFileToTar (f )
143
- if err != nil {
144
- return err
145
- }
146
- if err := d .DockerAPI .CopyToContainer (ctx , d .Name , "/" , buf , dockertypes.CopyToContainerOptions {}); err != nil {
147
- return err
148
+ if _ , err := os .Stat (d .InitConfig .ConfigFile ); err == nil {
149
+ if err := d .copyBuildKitConfToContainer (ctx , f ); err != nil {
150
+ return err
151
+ }
152
+ } else if errors .Is (err , os .ErrNotExist ) {
153
+ return errors .Wrapf (err , "buildkit configuration file not found: %s" , d .InitConfig .ConfigFile )
154
+ } else {
155
+ return errors .Wrapf (err , "invalid buildkit configuration file: %s" , d .InitConfig .ConfigFile )
148
156
}
149
157
}
150
158
if err := d .start (ctx , l ); err != nil {
@@ -205,6 +213,205 @@ func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
205
213
return rc .Close ()
206
214
}
207
215
216
+ // copyToContainer is based on the implementation from docker/cli
217
+ // https://github.com/docker/cli/blob/master/cli/command/container/cp.go
218
+ func (d * Driver ) copyToContainer (ctx context.Context , srcPath string , dstPath string ) error {
219
+ var err error
220
+
221
+ // Get an absolute source path.
222
+ srcPath , err = resolveLocalPath (srcPath )
223
+ if err != nil {
224
+ return err
225
+ }
226
+
227
+ // Prepare the destination.
228
+ dstInfo := dockerarchive.CopyInfo {Path : dstPath }
229
+ dstStat , err := d .DockerAPI .ContainerStatPath (ctx , d .Name , dstPath )
230
+
231
+ // If the destination is a symbolic link, we should evaluate it.
232
+ if err == nil && dstStat .Mode & os .ModeSymlink != 0 {
233
+ linkTarget := dstStat .LinkTarget
234
+ if ! system .IsAbs (linkTarget ) {
235
+ dstParent , _ := dockerarchive .SplitPathDirEntry (dstPath )
236
+ linkTarget = filepath .Join (dstParent , linkTarget )
237
+ }
238
+ dstInfo .Path = linkTarget
239
+ if dstStat , err = d .DockerAPI .ContainerStatPath (ctx , d .Name , linkTarget ); err != nil {
240
+ return err
241
+ }
242
+ }
243
+
244
+ // Validate the destination path.
245
+ if err := command .ValidateOutputPathFileMode (dstStat .Mode ); err != nil {
246
+ return errors .Wrapf (err , `destination "%s:%s" must be a directory or a regular file` , d .Name , dstPath )
247
+ }
248
+
249
+ // Ignore any error and assume that the parent directory of the destination
250
+ // path exists, in which case the copy may still succeed. If there is any
251
+ // type of conflict (e.g., non-directory overwriting an existing directory
252
+ // or vice versa) the extraction will fail. If the destination simply did
253
+ // not exist, but the parent directory does, the extraction will still
254
+ // succeed.
255
+ dstInfo .Exists , dstInfo .IsDir = true , dstStat .Mode .IsDir ()
256
+
257
+ // Prepare source copy info.
258
+ srcInfo , err := dockerarchive .CopyInfoSourcePath (srcPath , true )
259
+ if err != nil {
260
+ return err
261
+ }
262
+
263
+ srcArchive , err := dockerarchive .TarResource (srcInfo )
264
+ if err != nil {
265
+ return err
266
+ }
267
+ defer srcArchive .Close ()
268
+
269
+ // With the stat info about the local source as well as the
270
+ // destination, we have enough information to know whether we need to
271
+ // alter the archive that we upload so that when the server extracts
272
+ // it to the specified directory in the container we get the desired
273
+ // copy behavior.
274
+
275
+ // See comments in the implementation of `dockerarchive.PrepareArchiveCopy`
276
+ // for exactly what goes into deciding how and whether the source
277
+ // archive needs to be altered for the correct copy behavior when it is
278
+ // extracted. This function also infers from the source and destination
279
+ // info which directory to extract to, which may be the parent of the
280
+ // destination that the user specified.
281
+ dstDir , preparedArchive , err := dockerarchive .PrepareArchiveCopy (srcArchive , srcInfo , dstInfo )
282
+ if err != nil {
283
+ return err
284
+ }
285
+ defer preparedArchive .Close ()
286
+
287
+ return d .DockerAPI .CopyToContainer (ctx , d .Name , dstDir , preparedArchive , dockertypes.CopyToContainerOptions {
288
+ AllowOverwriteDirWithFile : false ,
289
+ })
290
+ }
291
+
292
+ func resolveLocalPath (localPath string ) (absPath string , err error ) {
293
+ if absPath , err = filepath .Abs (localPath ); err != nil {
294
+ return
295
+ }
296
+ return dockerarchive .PreserveTrailingDotOrSeparator (absPath , localPath , filepath .Separator ), nil
297
+ }
298
+
299
+ // copyBuildKitConfToContainer copy BuildKit config and registry
300
+ // certificates to the containers
301
+ func (d * Driver ) copyBuildKitConfToContainer (ctx context.Context , bkconfig string ) error {
302
+ // Load BuildKit config tree
303
+ btoml , err := loadBuildKitConfigTree (bkconfig )
304
+ if err != nil {
305
+ return err
306
+ }
307
+
308
+ // Temp dir that will be copied to the container
309
+ tmpDir , err := os .MkdirTemp ("" , "buildkitd-config" )
310
+ if err != nil {
311
+ return err
312
+ }
313
+ defer os .RemoveAll (tmpDir )
314
+
315
+ // Create BuildKit config folders
316
+ tmpBuildKitConfigDir := path .Join (tmpDir , containerBuildKitConfigDir )
317
+ tmpBuildKitCertsDir := path .Join (tmpBuildKitConfigDir , "certs" )
318
+ if err := os .MkdirAll (tmpBuildKitCertsDir , 0755 ); err != nil {
319
+ return err
320
+ }
321
+
322
+ // Iterate through registry config to copy certs and update
323
+ // BuildKit config with the underlying certs' path in the container.
324
+ //
325
+ // The following BuildKit config:
326
+ //
327
+ // [registry."myregistry.io"]
328
+ // ca=["/etc/config/myca.pem"]
329
+ // [[registry."myregistry.io".keypair]]
330
+ // key="/etc/config/key.pem"
331
+ // cert="/etc/config/cert.pem"
332
+ //
333
+ // will be translated in the container as:
334
+ //
335
+ // [registry."myregistry.io"]
336
+ // ca=["/etc/buildkit/certs/myregistry.io/myca.pem"]
337
+ // [[registry."myregistry.io".keypair]]
338
+ // key="/etc/buildkit/certs/myregistry.io/key.pem"
339
+ // cert="/etc/buildkit/certs/myregistry.io/cert.pem"
340
+ for regName := range btoml .GetArray ("registry" ).(* toml.Tree ).Values () {
341
+ regConf := btoml .GetPath ([]string {"registry" , regName }).(* toml.Tree )
342
+ if regConf == nil {
343
+ continue
344
+ }
345
+
346
+ // Create registry certs folder
347
+ regCertsDir := path .Join (tmpBuildKitCertsDir , regName )
348
+ if err := os .Mkdir (regCertsDir , 0755 ); err != nil {
349
+ return err
350
+ }
351
+
352
+ regCAs := regConf .GetArray ("ca" ).([]string )
353
+ if len (regCAs ) > 0 {
354
+ var cas []string
355
+ for _ , ca := range regCAs {
356
+ cas = append (cas , path .Join (containerBuildKitConfigDir , "certs" , regName , path .Base (ca )))
357
+ if err := copyfile (ca , path .Join (regCertsDir , path .Base (ca ))); err != nil {
358
+ return err
359
+ }
360
+ }
361
+ regConf .Set ("ca" , cas )
362
+ }
363
+
364
+ regKeyPairs := regConf .GetArray ("keypair" ).([]* toml.Tree )
365
+ if len (regKeyPairs ) > 0 {
366
+ for _ , kp := range regKeyPairs {
367
+ key := kp .Get ("key" ).(string )
368
+ if len (key ) > 0 {
369
+ kp .Set ("key" , path .Join (containerBuildKitConfigDir , "certs" , regName , path .Base (key )))
370
+ if err := copyfile (key , path .Join (regCertsDir , path .Base (key ))); err != nil {
371
+ return err
372
+ }
373
+ }
374
+ cert := kp .Get ("cert" ).(string )
375
+ if len (cert ) > 0 {
376
+ kp .Set ("cert" , path .Join (containerBuildKitConfigDir , "certs" , regName , path .Base (cert )))
377
+ if err := copyfile (cert , path .Join (regCertsDir , path .Base (cert ))); err != nil {
378
+ return err
379
+ }
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ // Write BuildKit config
386
+ bkfile , err := os .OpenFile (path .Join (tmpBuildKitConfigDir , "buildkitd.toml" ), os .O_CREATE | os .O_WRONLY , 0600 )
387
+ if err != nil {
388
+ return err
389
+ }
390
+ _ , err = btoml .WriteTo (bkfile )
391
+ if err != nil {
392
+ return err
393
+ }
394
+
395
+ return d .copyToContainer (ctx , tmpDir + "/." , "/" )
396
+ }
397
+
398
+ // loadBuildKitConfigTree loads toml BuildKit config tree
399
+ func loadBuildKitConfigTree (fp string ) (* toml.Tree , error ) {
400
+ f , err := os .Open (fp )
401
+ if err != nil {
402
+ if errors .Is (err , os .ErrNotExist ) {
403
+ return nil , nil
404
+ }
405
+ return nil , errors .Wrapf (err , "failed to load config from %s" , fp )
406
+ }
407
+ defer f .Close ()
408
+ t , err := toml .LoadReader (f )
409
+ if err != nil {
410
+ return t , errors .Wrap (err , "failed to parse config" )
411
+ }
412
+ return t , nil
413
+ }
414
+
208
415
func (d * Driver ) exec (ctx context.Context , cmd []string ) (string , net.Conn , error ) {
209
416
execConfig := types.ExecConfig {
210
417
Cmd : cmd ,
@@ -366,29 +573,6 @@ func (d *demux) Read(dt []byte) (int, error) {
366
573
return d .Reader .Read (dt )
367
574
}
368
575
369
- func readFileToTar (fn string ) (* bytes.Buffer , error ) {
370
- buf := bytes .NewBuffer (nil )
371
- tw := tar .NewWriter (buf )
372
- dt , err := ioutil .ReadFile (fn )
373
- if err != nil {
374
- return nil , err
375
- }
376
- if err := tw .WriteHeader (& tar.Header {
377
- Name : "/etc/buildkit/buildkitd.toml" ,
378
- Size : int64 (len (dt )),
379
- Mode : 0644 ,
380
- }); err != nil {
381
- return nil , err
382
- }
383
- if _ , err := tw .Write (dt ); err != nil {
384
- return nil , err
385
- }
386
- if err := tw .Close (); err != nil {
387
- return nil , err
388
- }
389
- return buf , nil
390
- }
391
-
392
576
type logWriter struct {
393
577
logger progress.SubLogger
394
578
stream int
@@ -398,3 +582,35 @@ func (l *logWriter) Write(dt []byte) (int, error) {
398
582
l .logger .Log (l .stream , dt )
399
583
return len (dt ), nil
400
584
}
585
+
586
+ func copyfile (src string , dst string ) error {
587
+ si , err := os .Stat (src )
588
+ if err != nil {
589
+ return err
590
+ }
591
+
592
+ if si .Mode ()& os .ModeSymlink != 0 {
593
+ if src , err = os .Readlink (src ); err != nil {
594
+ return err
595
+ }
596
+ si , err = os .Stat (src )
597
+ if err != nil {
598
+ return err
599
+ }
600
+ }
601
+
602
+ sf , err := os .Open (src )
603
+ if err != nil {
604
+ return err
605
+ }
606
+ defer sf .Close ()
607
+
608
+ df , err := os .OpenFile (dst , os .O_CREATE | os .O_WRONLY , si .Mode ())
609
+ if err != nil {
610
+ return err
611
+ }
612
+ defer df .Close ()
613
+
614
+ _ , err = io .Copy (df , sf )
615
+ return err
616
+ }
0 commit comments