From 989ebe612dc875e3d31f44449c12d889e9f97ffd Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Fri, 18 Sep 2020 15:50:29 -0400 Subject: [PATCH] correctly mirror manifest lists for multi-arch images --- README.md | 4 +- cmd/app/kodata/index.html | 2 +- cmd/mirror/main.go | 10 ++-- pkg/serve/serve.go | 100 ++++++++++++++++++++++++-------------- 4 files changed, 73 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index d04953f..8b0882f 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ time they are requested. `docker pull random.kontain.me/random:latest` serves an image containing random data. By default the image contains one layer containing 10 MB of random bytes. You can request a specific size and shape of random image. For example, -`kontain.me/random:4x100` generates a random image of 4 layers of 100 random -bytes each. +`random.kontain.me/random:4x100` generates a random image of 4 layers of 100 +random bytes each. # `ko.kontain.me` diff --git a/cmd/app/kodata/index.html b/cmd/app/kodata/index.html index 79f4898..4a872d2 100644 --- a/cmd/app/kodata/index.html +++ b/cmd/app/kodata/index.html @@ -17,7 +17,7 @@ time they are requested.

docker pull random.kontain.me/random:latest serves an image containing random data. By default the image contains one layer containing 10 MB of random bytes. You can request a specific size and shape of random image. For example, -kontain.me/random:4x100 generates a random image of 4 layers of +random.kontain.me/random:4x100 generates a random image of 4 layers of 100 random bytes each.

ko.kontain.me

diff --git a/cmd/mirror/main.go b/cmd/mirror/main.go index 4a21225..650773d 100644 --- a/cmd/mirror/main.go +++ b/cmd/mirror/main.go @@ -93,13 +93,17 @@ func (s *server) serveMirrorManifest(w http.ResponseWriter, r *http.Request) { s.info.Printf("INFO (serve.BlobExists(%q)): %v", d, err) // Blob doesn't exist yet. Try to get the image manifest+layers // and cache them. - img, err := remote.Image(ref) + idx, err := remote.Index(ref) if err != nil { - s.error.Printf("ERROR (remote.Image): %v", err) + s.error.Printf("ERROR (remote.Index): %v", err) + serve.Error(w, err) + return + } + if err := serve.Index(w, r, idx); err != nil { + s.error.Printf("ERROR (serve.Index): %v", err) serve.Error(w, err) return } - serve.Manifest(w, r, img) } else { serve.Blob(w, r, d.Digest.String()) } diff --git a/pkg/serve/serve.go b/pkg/serve/serve.go index cd8740c..91487d6 100644 --- a/pkg/serve/serve.go +++ b/pkg/serve/serve.go @@ -66,82 +66,108 @@ func writeBlob(h v1.Hash, rc io.ReadCloser, contentType string) error { return nil } -// Manifest writes config and layer blobs for the image, then serves the -// manifest contents pointing to those blobs. -func Manifest(w http.ResponseWriter, r *http.Request, img v1.Image) { +// Index writes manifest, config and layer blobs for each image in the index, +// then writes and redirects to the index manifest contents pointing to those +// blobs. +func Index(w http.ResponseWriter, r *http.Request, idx v1.ImageIndex) error { + im, err := idx.IndexManifest() + if err != nil { + return err + } + for _, m := range im.Manifests { + img, err := idx.Image(m.Digest) + if err != nil { + return err + } + if _, err := writeManifest(img); err != nil { + return err + } + } + + // Write the manifest as a blob. + b, err := idx.RawManifest() + if err != nil { + return err + } + mt, err := idx.MediaType() + if err != nil { + return err + } + digest, err := idx.Digest() + if err != nil { + return err + } + if err := writeBlob(digest, ioutil.NopCloser(bytes.NewReader(b)), string(mt)); err != nil { + return err + } + // Redirect to manifest blob. + Blob(w, r, digest.String()) + return nil +} + +func writeManifest(img v1.Image) (*v1.Hash, error) { // Write config blob for later serving. ch, err := img.ConfigName() if err != nil { - log.Printf("ERROR (serveManifest ConfigName): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } cb, err := img.RawConfigFile() if err != nil { - log.Printf("ERROR (serveManifest RawConfigFile): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } log.Printf("writing config blob %q", ch) if err := writeBlob(ch, ioutil.NopCloser(bytes.NewReader(cb)), ""); err != nil { - log.Printf("ERROR (serveManifest writeBlob): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } // Write layer blobs for later serving. layers, err := img.Layers() if err != nil { - log.Printf("ERROR (serveManifest Layers): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } for _, l := range layers { rc, err := l.Compressed() if err != nil { - log.Printf("ERROR (serveManifest l.Compressed): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } lh, err := l.Digest() if err != nil { - log.Printf("ERROR (serveManifest l.Digest): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } log.Printf("writing layer blob %q", lh) if err := writeBlob(lh, rc, ""); err != nil { - log.Printf("ERROR (serveManifest writeBlob): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } } // Write the manifest as a blob. b, err := img.RawManifest() if err != nil { - log.Printf("ERROR (serveManifest RawManifest): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } mt, err := img.MediaType() if err != nil { - log.Printf("ERROR (serveManifest MediaType): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } digest, err := img.Digest() if err != nil { - log.Printf("ERROR (serveManifest Digest): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } if err := writeBlob(digest, ioutil.NopCloser(bytes.NewReader(b)), string(mt)); err != nil { - log.Printf("ERROR (serveManifest writeBlob): %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, err } + return &digest, nil +} + +// Manifest writes config and layer blobs for the image, then writes and +// redirects to the image manifest contents pointing to those blobs. +func Manifest(w http.ResponseWriter, r *http.Request, img v1.Image) error { + digest, err := writeManifest(img) + if err != nil { + return err + } + // Redirect to manifest blob. Blob(w, r, digest.String()) - fmt.Printf("Served manifest: %s", string(b)) + return nil }