Parcourir la source

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

Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
Nishant Totla il y a 8 ans
Parent
commit
41b27de41b

+ 14 - 0
api/server/router/distribution/backend.go

@@ -0,0 +1,14 @@
+package distribution
+
+import (
+	"github.com/docker/distribution"
+	"github.com/docker/distribution/reference"
+	"github.com/docker/docker/api/types"
+	"golang.org/x/net/context"
+)
+
+// Backend is all the methods that need to be implemented
+// to provide image specific functionality.
+type Backend interface {
+	GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, bool, error)
+}

+ 31 - 0
api/server/router/distribution/distribution.go

@@ -0,0 +1,31 @@
+package distribution
+
+import "github.com/docker/docker/api/server/router"
+
+// distributionRouter is a router to talk with the registry
+type distributionRouter struct {
+	backend Backend
+	routes  []router.Route
+}
+
+// NewRouter initializes a new distribution router
+func NewRouter(backend Backend) router.Router {
+	r := &distributionRouter{
+		backend: backend,
+	}
+	r.initRoutes()
+	return r
+}
+
+// Routes returns the available routes
+func (r *distributionRouter) Routes() []router.Route {
+	return r.routes
+}
+
+// initRoutes initializes the routes in the distribution router
+func (r *distributionRouter) initRoutes() {
+	r.routes = []router.Route{
+		// GET
+		router.NewGetRoute("/distribution/{name:.*}/json", r.getDistributionInfo),
+	}
+}

+ 114 - 0
api/server/router/distribution/distribution_routes.go

@@ -0,0 +1,114 @@
+package distribution
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"net/http"
+	"strings"
+
+	"github.com/docker/distribution/manifest/manifestlist"
+	"github.com/docker/distribution/manifest/schema1"
+	"github.com/docker/distribution/manifest/schema2"
+	"github.com/docker/distribution/reference"
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/api/types"
+	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/pkg/errors"
+	"golang.org/x/net/context"
+)
+
+func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+
+	var (
+		config              = &types.AuthConfig{}
+		authEncoded         = r.Header.Get("X-Registry-Auth")
+		distributionInspect registrytypes.DistributionInspect
+	)
+
+	if authEncoded != "" {
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
+		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
+			// for a search it is not an error if no auth was given
+			// to increase compatibility with the existing api it is defaulting to be empty
+			config = &types.AuthConfig{}
+		}
+	}
+
+	image := vars["name"]
+
+	ref, err := reference.ParseAnyReference(image)
+	if err != nil {
+		return err
+	}
+	namedRef, ok := ref.(reference.Named)
+	if !ok {
+		if _, ok := ref.(reference.Digested); ok {
+			// full image ID
+			return errors.Errorf("no manifest found for full image ID")
+		}
+		return errors.Errorf("unknown image reference format: %s", image)
+	}
+
+	distrepo, _, err := s.backend.GetRepository(ctx, namedRef, config)
+	if err != nil {
+		return err
+	}
+
+	if canonicalRef, ok := namedRef.(reference.Canonical); !ok {
+		namedRef = reference.TagNameOnly(namedRef)
+
+		taggedRef, ok := namedRef.(reference.NamedTagged)
+		if !ok {
+			return errors.Errorf("image reference not tagged: %s", image)
+		}
+
+		dscrptr, err := distrepo.Tags(ctx).Get(ctx, taggedRef.Tag())
+		if err != nil {
+			return err
+		}
+		distributionInspect.Digest = dscrptr.Digest
+	} else {
+		distributionInspect.Digest = canonicalRef.Digest()
+	}
+	// at this point, we have a digest, so we can retrieve the manifest
+
+	mnfstsrvc, err := distrepo.Manifests(ctx)
+	if err != nil {
+		return err
+	}
+	mnfst, err := mnfstsrvc.Get(ctx, distributionInspect.Digest)
+	if err != nil {
+		return err
+	}
+
+	// retrieve platform information depending on the type of manifest
+	switch mnfstObj := mnfst.(type) {
+	case *manifestlist.DeserializedManifestList:
+		for _, m := range mnfstObj.Manifests {
+			distributionInspect.Platforms = append(distributionInspect.Platforms, m.Platform)
+		}
+	case *schema2.DeserializedManifest:
+		blobsrvc := distrepo.Blobs(ctx)
+		configJSON, err := blobsrvc.Get(ctx, mnfstObj.Config.Digest)
+		var platform manifestlist.PlatformSpec
+		if err == nil {
+			err := json.Unmarshal(configJSON, &platform)
+			if err == nil {
+				distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
+			}
+		}
+	case *schema1.SignedManifest:
+		platform := manifestlist.PlatformSpec{
+			Architecture: mnfstObj.Architecture,
+			OS:           "linux",
+		}
+		distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, distributionInspect)
+}

+ 57 - 0
api/swagger.yaml

@@ -8274,3 +8274,60 @@ paths:
           format: "int64"
           required: true
       tags: ["Secret"]
+  /distribution/{name}/json:
+    get:
+      summary: "Get image information from the registry"
+      description: "Return image digest and platform information by contacting the registry."
+      operationId: "DistributionInspect"
+      produces:
+        - "application/json"
+      responses:
+        200:
+          description: "digest and platform information"
+          schema:
+            type: "object"
+            x-go-name: DistributionInspect
+            required: [Digest, ID, Platforms]
+            properties:
+              Digest:
+                type: "string"
+                x-nullable: false
+              Platforms:
+                type: "array"
+                items:
+                  type: "object"
+                  properties:
+                    Architecture:
+                      type: "string"
+                    OS:
+                      type: "string"
+                    OSVersion:
+                      type: "string"
+                    OSFeatures:
+                      type: "array"
+                      items:
+                        type: "string"
+                    Variant:
+                      type: "string"
+                    Features:
+                      type: "array"
+                      items:
+                        type: "string"
+        401:
+          description: "Failed authentication or no image found"
+          schema:
+            $ref: "#/definitions/ErrorResponse"
+          examples:
+            application/json:
+              message: "No such image: someimage (tag: latest)"
+        500:
+          description: "Server error"
+          schema:
+            $ref: "#/definitions/ErrorResponse"
+      parameters:
+        - name: "name"
+          in: "path"
+          description: "Image name or id"
+          type: "string"
+          required: true
+      tags: ["Distribution"]

+ 13 - 0
api/types/registry/registry.go

@@ -3,6 +3,9 @@ package registry
 import (
 	"encoding/json"
 	"net"
+
+	"github.com/docker/distribution/manifest/manifestlist"
+	digest "github.com/opencontainers/go-digest"
 )
 
 // ServiceConfig stores daemon registry services configuration.
@@ -102,3 +105,13 @@ type SearchResults struct {
 	// Results is a slice containing the actual results for the search
 	Results []SearchResult `json:"results"`
 }
+
+// DistributionInspect describes the result obtained from contacting the
+// registry to retrieve image metadata
+type DistributionInspect struct {
+	// Digest is the content addressable digest for the image on the registry
+	Digest digest.Digest
+	// Platforms contains the list of platforms supported by the image,
+	// obtained by parsing the manifest
+	Platforms []manifestlist.PlatformSpec
+}

+ 2 - 0
cmd/dockerd/daemon.go

@@ -20,6 +20,7 @@ import (
 	"github.com/docker/docker/api/server/router/build"
 	checkpointrouter "github.com/docker/docker/api/server/router/checkpoint"
 	"github.com/docker/docker/api/server/router/container"
+	distributionrouter "github.com/docker/docker/api/server/router/distribution"
 	"github.com/docker/docker/api/server/router/image"
 	"github.com/docker/docker/api/server/router/network"
 	pluginrouter "github.com/docker/docker/api/server/router/plugin"
@@ -487,6 +488,7 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
 		build.NewRouter(buildbackend.NewBackend(d, d), d),
 		swarmrouter.NewRouter(c),
 		pluginrouter.NewRouter(d.PluginManager()),
+		distributionrouter.NewRouter(d),
 	}
 
 	if d.NetworkControllerEnabled() {