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

Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
This commit is contained in:
Nishant Totla 2017-05-01 16:17:35 -07:00
parent e842c653a0
commit 41b27de41b
No known key found for this signature in database
GPG key ID: 7EA5781C9B3D0C19
6 changed files with 231 additions and 0 deletions

View file

@ -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)
}

View file

@ -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),
}
}

View file

@ -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)
}

View file

@ -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"]

View file

@ -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
}

View file

@ -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() {