Skip to content

Commit 2cce40e

Browse files
committed
[image-builder] Copy image rather than use buildkit
To work around: containerd/containerd#5978
1 parent 1337d47 commit 2cce40e

File tree

1 file changed

+199
-45
lines changed
  • components/image-builder-bob/pkg/builder

1 file changed

+199
-45
lines changed

components/image-builder-bob/pkg/builder/builder.go

Lines changed: 199 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ package builder
66

77
import (
88
"context"
9+
"encoding/json"
910
"errors"
11+
"fmt"
12+
"io"
1013
"io/ioutil"
1114
"os"
1215
"os/exec"
@@ -17,14 +20,13 @@ import (
1720
"github.com/gitpod-io/gitpod/common-go/log"
1821

1922
"github.com/containerd/console"
23+
"github.com/containerd/containerd/errdefs"
2024
"github.com/containerd/containerd/remotes"
2125
"github.com/containerd/containerd/remotes/docker"
2226
"github.com/moby/buildkit/client"
23-
"github.com/moby/buildkit/client/llb"
2427
"github.com/moby/buildkit/session"
25-
"github.com/moby/buildkit/util/contentutil"
26-
"github.com/moby/buildkit/util/imageutil"
2728
"github.com/moby/buildkit/util/progress/progressui"
29+
"github.com/opencontainers/go-digest"
2830
specs "github.com/opencontainers/image-spec/specs-go/v1"
2931
"golang.org/x/sync/errgroup"
3032
"golang.org/x/xerrors"
@@ -193,20 +195,15 @@ func (b *Builder) buildBaseLayer(ctx context.Context, cl *client.Client) error {
193195
}
194196

195197
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
204202
if gplayerAuth := b.Config.WorkspaceLayerAuth; gplayerAuth != "" {
205-
auth, err := newAuthProviderFromEnvvar(gplayerAuth)
203+
_, err := newAuthProviderFromEnvvar(gplayerAuth)
206204
if err != nil {
207205
return err
208206
}
209-
sess = append(sess, auth)
210207

211208
authorizer, err := newDockerAuthorizerFromEnvvar(gplayerAuth)
212209
if err != nil {
@@ -218,55 +215,212 @@ func (b *Builder) buildWorkspaceImage(ctx context.Context, cl *client.Client) (e
218215
} else {
219216
resolver = docker.NewResolver(docker.ResolverOptions{})
220217
}
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+
}
221291

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)
224294
if err != nil {
225295
return err
226296
}
227-
state, err := llb.Image(b.Config.BaseRef).WithImageConfig(cfg)
297+
fetcher, err := resolver.Fetcher(ctx, fromRef)
228298
if err != nil {
229299
return err
230300
}
231301

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)
233337
if err != nil {
234338
return err
235339
}
236340

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+
}
240345

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
254356
}
255357

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+
}
260390
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")
262394
}
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
270424
}
271425

272426
func waitForBuildContext(ctx context.Context) error {

0 commit comments

Comments
 (0)