@@ -6,7 +6,10 @@ package builder
6
6
7
7
import (
8
8
"context"
9
+ "encoding/json"
9
10
"errors"
11
+ "fmt"
12
+ "io"
10
13
"io/ioutil"
11
14
"os"
12
15
"os/exec"
@@ -17,14 +20,13 @@ import (
17
20
"github.com/gitpod-io/gitpod/common-go/log"
18
21
19
22
"github.com/containerd/console"
23
+ "github.com/containerd/containerd/errdefs"
20
24
"github.com/containerd/containerd/remotes"
21
25
"github.com/containerd/containerd/remotes/docker"
22
26
"github.com/moby/buildkit/client"
23
- "github.com/moby/buildkit/client/llb"
24
27
"github.com/moby/buildkit/session"
25
- "github.com/moby/buildkit/util/contentutil"
26
- "github.com/moby/buildkit/util/imageutil"
27
28
"github.com/moby/buildkit/util/progress/progressui"
29
+ "github.com/opencontainers/go-digest"
28
30
specs "github.com/opencontainers/image-spec/specs-go/v1"
29
31
"golang.org/x/sync/errgroup"
30
32
"golang.org/x/xerrors"
@@ -193,20 +195,15 @@ func (b *Builder) buildBaseLayer(ctx context.Context, cl *client.Client) error {
193
195
}
194
196
195
197
func (b * Builder ) buildWorkspaceImage (ctx context.Context , cl * client.Client ) (err error ) {
196
- // Note: buildkit does not handle/export image config by default. That's why we need
197
- // to download it ourselves and explicitely export it.
198
- // See https://github.com/moby/buildkit/issues/2362 for details.
199
-
200
- var (
201
- sess []session.Attachable
202
- resolver remotes.Resolver
203
- )
198
+ // Workaround: buildkit/containerd currently does not support pushing multi-image builds
199
+ // with some registries, e.g. gcr.io. Until https://github.com/containerd/containerd/issues/5978
200
+ // is resolved, we'll manually copy the image.
201
+ var resolver remotes.Resolver
204
202
if gplayerAuth := b .Config .WorkspaceLayerAuth ; gplayerAuth != "" {
205
- auth , err := newAuthProviderFromEnvvar (gplayerAuth )
203
+ _ , err := newAuthProviderFromEnvvar (gplayerAuth )
206
204
if err != nil {
207
205
return err
208
206
}
209
- sess = append (sess , auth )
210
207
211
208
authorizer , err := newDockerAuthorizerFromEnvvar (gplayerAuth )
212
209
if err != nil {
@@ -218,55 +215,212 @@ func (b *Builder) buildWorkspaceImage(ctx context.Context, cl *client.Client) (e
218
215
} else {
219
216
resolver = docker .NewResolver (docker.ResolverOptions {})
220
217
}
218
+ return copyImage (ctx , resolver , b .Config .BaseRef , b .Config .TargetRef )
219
+
220
+ // // Note: buildkit does not handle/export image config by default. That's why we need
221
+ // // to download it ourselves and explicitely export it.
222
+ // // See https://github.com/moby/buildkit/issues/2362 for details.
223
+ // var sess []session.Attachable
224
+ // if gplayerAuth := b.Config.WorkspaceLayerAuth; gplayerAuth != "" {
225
+ // auth, err := newAuthProviderFromEnvvar(gplayerAuth)
226
+ // if err != nil {
227
+ // return err
228
+ // }
229
+ // sess = append(sess, auth)
230
+
231
+ // authorizer, err := newDockerAuthorizerFromEnvvar(gplayerAuth)
232
+ // if err != nil {
233
+ // return err
234
+ // }
235
+ // resolver = docker.NewResolver(docker.ResolverOptions{
236
+ // Authorizer: authorizer,
237
+ // })
238
+ // } else {
239
+ // resolver = docker.NewResolver(docker.ResolverOptions{})
240
+ // }
241
+
242
+ // platform := specs.Platform{OS: "linux", Architecture: "amd64"}
243
+ // _, cfg, err := imageutil.Config(ctx, b.Config.BaseRef, resolver, contentutil.NewBuffer(), nil, &platform)
244
+ // if err != nil {
245
+ // return err
246
+ // }
247
+ // state, err := llb.Image(b.Config.BaseRef).WithImageConfig(cfg)
248
+ // if err != nil {
249
+ // return err
250
+ // }
251
+
252
+ // def, err := state.Marshal(ctx, llb.Platform(platform))
253
+ // if err != nil {
254
+ // return err
255
+ // }
256
+
257
+ // // TODO(cw):
258
+ // // buildkit does not support setting raw annotations yet (https://github.com/moby/buildkit/issues/1220).
259
+ // // Once it does, we should set org.opencontainers.image.base.name as defined in https://github.com/opencontainers/image-spec/blob/main/annotations.md
260
+
261
+ // solveOpt := client.SolveOpt{
262
+ // Exports: []client.ExportEntry{
263
+ // {
264
+ // Type: "image",
265
+ // Attrs: map[string]string{
266
+ // "name": b.Config.TargetRef,
267
+ // "push": "true",
268
+ // "containerimage.config": string(cfg),
269
+ // },
270
+ // },
271
+ // },
272
+ // Session: sess,
273
+ // CacheImports: b.Config.LocalCacheImport(),
274
+ // }
275
+
276
+ // eg, ctx := errgroup.WithContext(ctx)
277
+ // ch := make(chan *client.SolveStatus)
278
+ // eg.Go(func() error {
279
+ // _, err := cl.Solve(ctx, def, solveOpt, ch)
280
+ // if err != nil {
281
+ // return xerrors.Errorf("cannot build Gitpod layer: %w", err)
282
+ // }
283
+ // return nil
284
+ // })
285
+ // eg.Go(func() error {
286
+ // var c console.Console
287
+ // return progressui.DisplaySolveStatus(ctx, "", c, os.Stdout, ch)
288
+ // })
289
+ // return eg.Wait()
290
+ }
221
291
222
- platform := specs. Platform { OS : "linux" , Architecture : "amd64" }
223
- _ , cfg , err := imageutil . Config (ctx , b . Config . BaseRef , resolver , contentutil . NewBuffer (), nil , & platform )
292
+ func copyImage ( ctx context. Context , resolver remotes. Resolver , from , to string ) error {
293
+ fromRef , fromDesc , err := resolver . Resolve (ctx , from )
224
294
if err != nil {
225
295
return err
226
296
}
227
- state , err := llb . Image ( b . Config . BaseRef ). WithImageConfig ( cfg )
297
+ fetcher , err := resolver . Fetcher ( ctx , fromRef )
228
298
if err != nil {
229
299
return err
230
300
}
231
301
232
- def , err := state .Marshal (ctx , llb .Platform (platform ))
302
+ fetch := func (desc specs.Descriptor , out interface {}) error {
303
+ rc , err := fetcher .Fetch (ctx , fromDesc )
304
+ if err != nil {
305
+ return err
306
+ }
307
+ defer rc .Close ()
308
+
309
+ return json .NewDecoder (rc ).Decode (out )
310
+ }
311
+ if fromDesc .MediaType == specs .MediaTypeImageIndex {
312
+ var idx specs.Index
313
+ err := fetch (fromDesc , & idx )
314
+ if err != nil {
315
+ return err
316
+ }
317
+
318
+ var res * specs.Descriptor
319
+ for _ , m := range idx .Manifests {
320
+ if m .Platform != nil && m .Platform .Architecture == "amd64" && m .Platform .OS == "linux" {
321
+ res = & m
322
+ break
323
+ }
324
+ }
325
+ if res == nil {
326
+ return fmt .Errorf ("no manifest for amd64/linux found" )
327
+ }
328
+ fromDesc = * res
329
+ }
330
+
331
+ var manifest specs.Manifest
332
+ err = fetch (fromDesc , & manifest )
333
+ if err != nil {
334
+ return err
335
+ }
336
+ manifestB , err := json .Marshal (manifest )
233
337
if err != nil {
234
338
return err
235
339
}
236
340
237
- // TODO(cw):
238
- // buildkit does not support setting raw annotations yet (https://github.com/moby/buildkit/issues/1220).
239
- // Once it does, we should set org.opencontainers.image.base.name as defined in https://github.com/opencontainers/image-spec/blob/main/annotations.md
341
+ pusher , err := resolver .Pusher (ctx , to )
342
+ if err != nil {
343
+ return err
344
+ }
240
345
241
- solveOpt := client.SolveOpt {
242
- Exports : []client.ExportEntry {
243
- {
244
- Type : "image" ,
245
- Attrs : map [string ]string {
246
- "name" : b .Config .TargetRef ,
247
- "push" : "true" ,
248
- "containerimage.config" : string (cfg ),
249
- },
250
- },
251
- },
252
- Session : sess ,
253
- CacheImports : b .Config .LocalCacheImport (),
346
+ // TODO(cw): optimize layer copy - copy only when pushing to a different registry
347
+ eg , egctx := errgroup .WithContext (ctx )
348
+ for _ , layer := range manifest .Layers {
349
+ layer := layer
350
+ eg .Go (func () error { return copyLayer (egctx , fetcher , pusher , layer ) })
351
+ }
352
+ eg .Go (func () error { return copyLayer (egctx , fetcher , pusher , manifest .Config ) })
353
+ err = eg .Wait ()
354
+ if err != nil {
355
+ return err
254
356
}
255
357
256
- eg , ctx := errgroup .WithContext (ctx )
257
- ch := make (chan * client.SolveStatus )
258
- eg .Go (func () error {
259
- _ , err := cl .Solve (ctx , def , solveOpt , ch )
358
+ mfspec := specs.Descriptor {
359
+ MediaType : specs .MediaTypeImageManifest ,
360
+ Digest : digest .FromBytes (manifestB ),
361
+ Size : int64 (len (manifestB )),
362
+ Platform : & specs.Platform {OS : "linux" , Architecture : "amd64" },
363
+ }
364
+ mfw , err := pusher .Push (ctx , mfspec )
365
+ if err != nil {
366
+ return err
367
+ }
368
+ defer mfw .Close ()
369
+ mfwN , err := mfw .Write (manifestB )
370
+ if err != nil {
371
+ return err
372
+ }
373
+ if mfwN != len (manifestB ) {
374
+ return fmt .Errorf ("cannot write manifest: %w" , io .ErrShortWrite )
375
+ }
376
+ err = mfw .Commit (ctx , mfspec .Size , mfspec .Digest )
377
+ if err != nil {
378
+ return err
379
+ }
380
+
381
+ return nil
382
+ }
383
+
384
+ func copyLayer (ctx context.Context , fetcher remotes.Fetcher , pusher remotes.Pusher , layer specs.Descriptor ) (err error ) {
385
+ log := log .WithField ("blob" , layer )
386
+ defer func () {
387
+ if errdefs .IsAlreadyExists (err ) {
388
+ err = nil
389
+ }
260
390
if err != nil {
261
- return xerrors .Errorf ("cannot build Gitpod layer: %w" , err )
391
+ log .WithError (err ).Error ("failed to copy blob" )
392
+ } else {
393
+ log .Info ("push complete" )
262
394
}
263
- return nil
264
- })
265
- eg .Go (func () error {
266
- var c console.Console
267
- return progressui .DisplaySolveStatus (ctx , "" , c , os .Stdout , ch )
268
- })
269
- return eg .Wait ()
395
+ }()
396
+ log .Info ("pushing blob" )
397
+
398
+ in , err := fetcher .Fetch (ctx , layer )
399
+ if err != nil {
400
+ return err
401
+ }
402
+ defer in .Close ()
403
+
404
+ out , err := pusher .Push (ctx , layer )
405
+ if err != nil {
406
+ return err
407
+ }
408
+ defer out .Close ()
409
+
410
+ n , err := io .Copy (out , in )
411
+ if err != nil {
412
+ return err
413
+ }
414
+ if n != layer .Size {
415
+ return fmt .Errorf ("copied less than %d bytes from layer %s (expected %d bytes)" , n , layer .Digest .String (), layer .Size )
416
+ }
417
+
418
+ err = out .Commit (ctx , n , layer .Digest )
419
+ if err != nil {
420
+ return err
421
+ }
422
+
423
+ return nil
270
424
}
271
425
272
426
func waitForBuildContext (ctx context.Context ) error {
0 commit comments