Browse code

Adding /distribution/{name}/json endpoint to contact registry

Signed-off-by: Nishant Totla <nishanttotla@gmail.com>

Nishant Totla authored on 2017/05/02 08:17:35
Showing 6 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package distribution
1
+
2
+import (
3
+	"github.com/docker/distribution"
4
+	"github.com/docker/distribution/reference"
5
+	"github.com/docker/docker/api/types"
6
+	"golang.org/x/net/context"
7
+)
8
+
9
+// Backend is all the methods that need to be implemented
10
+// to provide image specific functionality.
11
+type Backend interface {
12
+	GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, bool, error)
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package distribution
1
+
2
+import "github.com/docker/docker/api/server/router"
3
+
4
+// distributionRouter is a router to talk with the registry
5
+type distributionRouter struct {
6
+	backend Backend
7
+	routes  []router.Route
8
+}
9
+
10
+// NewRouter initializes a new distribution router
11
+func NewRouter(backend Backend) router.Router {
12
+	r := &distributionRouter{
13
+		backend: backend,
14
+	}
15
+	r.initRoutes()
16
+	return r
17
+}
18
+
19
+// Routes returns the available routes
20
+func (r *distributionRouter) Routes() []router.Route {
21
+	return r.routes
22
+}
23
+
24
+// initRoutes initializes the routes in the distribution router
25
+func (r *distributionRouter) initRoutes() {
26
+	r.routes = []router.Route{
27
+		// GET
28
+		router.NewGetRoute("/distribution/{name:.*}/json", r.getDistributionInfo),
29
+	}
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,114 @@
0
+package distribution
1
+
2
+import (
3
+	"encoding/base64"
4
+	"encoding/json"
5
+	"net/http"
6
+	"strings"
7
+
8
+	"github.com/docker/distribution/manifest/manifestlist"
9
+	"github.com/docker/distribution/manifest/schema1"
10
+	"github.com/docker/distribution/manifest/schema2"
11
+	"github.com/docker/distribution/reference"
12
+	"github.com/docker/docker/api/server/httputils"
13
+	"github.com/docker/docker/api/types"
14
+	registrytypes "github.com/docker/docker/api/types/registry"
15
+	"github.com/pkg/errors"
16
+	"golang.org/x/net/context"
17
+)
18
+
19
+func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
20
+	if err := httputils.ParseForm(r); err != nil {
21
+		return err
22
+	}
23
+
24
+	w.Header().Set("Content-Type", "application/json")
25
+
26
+	var (
27
+		config              = &types.AuthConfig{}
28
+		authEncoded         = r.Header.Get("X-Registry-Auth")
29
+		distributionInspect registrytypes.DistributionInspect
30
+	)
31
+
32
+	if authEncoded != "" {
33
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
34
+		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
35
+			// for a search it is not an error if no auth was given
36
+			// to increase compatibility with the existing api it is defaulting to be empty
37
+			config = &types.AuthConfig{}
38
+		}
39
+	}
40
+
41
+	image := vars["name"]
42
+
43
+	ref, err := reference.ParseAnyReference(image)
44
+	if err != nil {
45
+		return err
46
+	}
47
+	namedRef, ok := ref.(reference.Named)
48
+	if !ok {
49
+		if _, ok := ref.(reference.Digested); ok {
50
+			// full image ID
51
+			return errors.Errorf("no manifest found for full image ID")
52
+		}
53
+		return errors.Errorf("unknown image reference format: %s", image)
54
+	}
55
+
56
+	distrepo, _, err := s.backend.GetRepository(ctx, namedRef, config)
57
+	if err != nil {
58
+		return err
59
+	}
60
+
61
+	if canonicalRef, ok := namedRef.(reference.Canonical); !ok {
62
+		namedRef = reference.TagNameOnly(namedRef)
63
+
64
+		taggedRef, ok := namedRef.(reference.NamedTagged)
65
+		if !ok {
66
+			return errors.Errorf("image reference not tagged: %s", image)
67
+		}
68
+
69
+		dscrptr, err := distrepo.Tags(ctx).Get(ctx, taggedRef.Tag())
70
+		if err != nil {
71
+			return err
72
+		}
73
+		distributionInspect.Digest = dscrptr.Digest
74
+	} else {
75
+		distributionInspect.Digest = canonicalRef.Digest()
76
+	}
77
+	// at this point, we have a digest, so we can retrieve the manifest
78
+
79
+	mnfstsrvc, err := distrepo.Manifests(ctx)
80
+	if err != nil {
81
+		return err
82
+	}
83
+	mnfst, err := mnfstsrvc.Get(ctx, distributionInspect.Digest)
84
+	if err != nil {
85
+		return err
86
+	}
87
+
88
+	// retrieve platform information depending on the type of manifest
89
+	switch mnfstObj := mnfst.(type) {
90
+	case *manifestlist.DeserializedManifestList:
91
+		for _, m := range mnfstObj.Manifests {
92
+			distributionInspect.Platforms = append(distributionInspect.Platforms, m.Platform)
93
+		}
94
+	case *schema2.DeserializedManifest:
95
+		blobsrvc := distrepo.Blobs(ctx)
96
+		configJSON, err := blobsrvc.Get(ctx, mnfstObj.Config.Digest)
97
+		var platform manifestlist.PlatformSpec
98
+		if err == nil {
99
+			err := json.Unmarshal(configJSON, &platform)
100
+			if err == nil {
101
+				distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
102
+			}
103
+		}
104
+	case *schema1.SignedManifest:
105
+		platform := manifestlist.PlatformSpec{
106
+			Architecture: mnfstObj.Architecture,
107
+			OS:           "linux",
108
+		}
109
+		distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
110
+	}
111
+
112
+	return httputils.WriteJSON(w, http.StatusOK, distributionInspect)
113
+}
... ...
@@ -8274,3 +8274,60 @@ paths:
8274 8274
           format: "int64"
8275 8275
           required: true
8276 8276
       tags: ["Secret"]
8277
+  /distribution/{name}/json:
8278
+    get:
8279
+      summary: "Get image information from the registry"
8280
+      description: "Return image digest and platform information by contacting the registry."
8281
+      operationId: "DistributionInspect"
8282
+      produces:
8283
+        - "application/json"
8284
+      responses:
8285
+        200:
8286
+          description: "digest and platform information"
8287
+          schema:
8288
+            type: "object"
8289
+            x-go-name: DistributionInspect
8290
+            required: [Digest, ID, Platforms]
8291
+            properties:
8292
+              Digest:
8293
+                type: "string"
8294
+                x-nullable: false
8295
+              Platforms:
8296
+                type: "array"
8297
+                items:
8298
+                  type: "object"
8299
+                  properties:
8300
+                    Architecture:
8301
+                      type: "string"
8302
+                    OS:
8303
+                      type: "string"
8304
+                    OSVersion:
8305
+                      type: "string"
8306
+                    OSFeatures:
8307
+                      type: "array"
8308
+                      items:
8309
+                        type: "string"
8310
+                    Variant:
8311
+                      type: "string"
8312
+                    Features:
8313
+                      type: "array"
8314
+                      items:
8315
+                        type: "string"
8316
+        401:
8317
+          description: "Failed authentication or no image found"
8318
+          schema:
8319
+            $ref: "#/definitions/ErrorResponse"
8320
+          examples:
8321
+            application/json:
8322
+              message: "No such image: someimage (tag: latest)"
8323
+        500:
8324
+          description: "Server error"
8325
+          schema:
8326
+            $ref: "#/definitions/ErrorResponse"
8327
+      parameters:
8328
+        - name: "name"
8329
+          in: "path"
8330
+          description: "Image name or id"
8331
+          type: "string"
8332
+          required: true
8333
+      tags: ["Distribution"]
... ...
@@ -3,6 +3,9 @@ package registry
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"net"
6
+
7
+	"github.com/docker/distribution/manifest/manifestlist"
8
+	digest "github.com/opencontainers/go-digest"
6 9
 )
7 10
 
8 11
 // ServiceConfig stores daemon registry services configuration.
... ...
@@ -102,3 +105,13 @@ type SearchResults struct {
102 102
 	// Results is a slice containing the actual results for the search
103 103
 	Results []SearchResult `json:"results"`
104 104
 }
105
+
106
+// DistributionInspect describes the result obtained from contacting the
107
+// registry to retrieve image metadata
108
+type DistributionInspect struct {
109
+	// Digest is the content addressable digest for the image on the registry
110
+	Digest digest.Digest
111
+	// Platforms contains the list of platforms supported by the image,
112
+	// obtained by parsing the manifest
113
+	Platforms []manifestlist.PlatformSpec
114
+}
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"github.com/docker/docker/api/server/router/build"
21 21
 	checkpointrouter "github.com/docker/docker/api/server/router/checkpoint"
22 22
 	"github.com/docker/docker/api/server/router/container"
23
+	distributionrouter "github.com/docker/docker/api/server/router/distribution"
23 24
 	"github.com/docker/docker/api/server/router/image"
24 25
 	"github.com/docker/docker/api/server/router/network"
25 26
 	pluginrouter "github.com/docker/docker/api/server/router/plugin"
... ...
@@ -487,6 +488,7 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
487 487
 		build.NewRouter(buildbackend.NewBackend(d, d), d),
488 488
 		swarmrouter.NewRouter(c),
489 489
 		pluginrouter.NewRouter(d.PluginManager()),
490
+		distributionrouter.NewRouter(d),
490 491
 	}
491 492
 
492 493
 	if d.NetworkControllerEnabled() {