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
}