Browse Source

Try to get a schema2 manifest first

Version 2 of the Docker Registry API has two different schemas for
image manifests: schema1 and schema2.

Different registries deal with this differently. Google Container
Registry (gcr.io) will serve only schema2, at least for some
images. In this case we get an HTTP 404 response -- the
docker-registry-client library sets an `Accept` header withthe schema1
media type, which the registry then refuses to supply.

Thus, in this commit we ask for a schema2 image first, which is then
processed slightly differently (the data we want is in a layer, which
we have to download subsequently). If we get a 404 from that, fall
back to asking for a schema1 manifest.

It's possible to get an empty schema2 result from some registries: the
registry ignores the Accept header and sends a schema1 manifest
anyway (possibly). In this case, fall back to asking for schema1.
Michael Bridgen 2 years ago
parent
commit
4f7575f591
1 changed files with 47 additions and 1 deletions
  1. 47
    1
      registry/client.go

+ 47
- 1
registry/client.go View File

@@ -3,9 +3,12 @@ package registry
3 3
 import (
4 4
 	"context"
5 5
 	"encoding/json"
6
+	"fmt"
7
+	"net/http"
6 8
 	"time"
7 9
 
8 10
 	"github.com/go-kit/kit/log"
11
+	dockerregistry "github.com/heroku/docker-registry-client/registry"
9 12
 	"github.com/pkg/errors"
10 13
 
11 14
 	"github.com/weaveworks/flux"
@@ -25,7 +28,7 @@ type Client interface {
25 28
 // An implementation of Client that represents a Remote registry.
26 29
 // E.g. docker hub.
27 30
 type Remote struct {
28
-	Registry   HerokuRegistryLibrary
31
+	Registry   *herokuManifestAdaptor
29 32
 	CancelFunc context.CancelFunc
30 33
 }
31 34
 
@@ -37,6 +40,49 @@ func (a *Remote) Tags(id flux.ImageID) ([]string, error) {
37 40
 // We need to do some adapting here to convert from the return values
38 41
 // from dockerregistry to our domain types.
39 42
 func (a *Remote) Manifest(id flux.ImageID) (flux.Image, error) {
43
+	manifestV2, err := a.Registry.ManifestV2(id.NamespaceImage(), id.Tag)
44
+	if err != nil {
45
+		if err, ok := err.(*dockerregistry.HttpStatusError); ok {
46
+			if err.Response.StatusCode == http.StatusNotFound {
47
+				return a.ManifestFromV1(id)
48
+			}
49
+		}
50
+		return flux.Image{}, err
51
+	}
52
+	// The above request will happily return a bogus, empty manifest
53
+	// if handed something other than a schema2 manifest.
54
+	if manifestV2.Config.Digest == "" {
55
+		return a.ManifestFromV1(id)
56
+	}
57
+
58
+	// schema2 manifests have a reference to a blog that contains the
59
+	// image config. We have to fetch that in order to get the created
60
+	// datetime.
61
+	conf := manifestV2.Config
62
+	reader, err := a.Registry.DownloadLayer(id.NamespaceImage(), conf.Digest)
63
+	if err != nil {
64
+		return flux.Image{}, err
65
+	}
66
+	if reader == nil {
67
+		return flux.Image{}, fmt.Errorf("nil reader from DownloadLayer")
68
+	}
69
+
70
+	type config struct {
71
+		Created time.Time `json:created`
72
+	}
73
+	var imageConf config
74
+
75
+	err = json.NewDecoder(reader).Decode(&imageConf)
76
+	if err != nil {
77
+		return flux.Image{}, err
78
+	}
79
+	return flux.Image{
80
+		ID:        id,
81
+		CreatedAt: imageConf.Created,
82
+	}, nil
83
+}
84
+
85
+func (a *Remote) ManifestFromV1(id flux.ImageID) (flux.Image, error) {
40 86
 	history, err := a.Registry.Manifest(id.NamespaceImage(), id.Tag)
41 87
 	if err != nil || history == nil {
42 88
 		return flux.Image{}, errors.Wrap(err, "getting remote manifest")

Loading…
Cancel
Save