mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adding /distribution/{name}/json endpoint to contact registry
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
This commit is contained in:
parent
e842c653a0
commit
41b27de41b
6 changed files with 231 additions and 0 deletions
14
api/server/router/distribution/backend.go
Normal file
14
api/server/router/distribution/backend.go
Normal 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)
|
||||||
|
}
|
31
api/server/router/distribution/distribution.go
Normal file
31
api/server/router/distribution/distribution.go
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
114
api/server/router/distribution/distribution_routes.go
Normal file
114
api/server/router/distribution/distribution_routes.go
Normal 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)
|
||||||
|
}
|
|
@ -8274,3 +8274,60 @@ paths:
|
||||||
format: "int64"
|
format: "int64"
|
||||||
required: true
|
required: true
|
||||||
tags: ["Secret"]
|
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"]
|
||||||
|
|
|
@ -3,6 +3,9 @@ package registry
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceConfig stores daemon registry services configuration.
|
// 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 is a slice containing the actual results for the search
|
||||||
Results []SearchResult `json:"results"`
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/docker/docker/api/server/router/build"
|
"github.com/docker/docker/api/server/router/build"
|
||||||
checkpointrouter "github.com/docker/docker/api/server/router/checkpoint"
|
checkpointrouter "github.com/docker/docker/api/server/router/checkpoint"
|
||||||
"github.com/docker/docker/api/server/router/container"
|
"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/image"
|
||||||
"github.com/docker/docker/api/server/router/network"
|
"github.com/docker/docker/api/server/router/network"
|
||||||
pluginrouter "github.com/docker/docker/api/server/router/plugin"
|
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),
|
build.NewRouter(buildbackend.NewBackend(d, d), d),
|
||||||
swarmrouter.NewRouter(c),
|
swarmrouter.NewRouter(c),
|
||||||
pluginrouter.NewRouter(d.PluginManager()),
|
pluginrouter.NewRouter(d.PluginManager()),
|
||||||
|
distributionrouter.NewRouter(d),
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.NetworkControllerEnabled() {
|
if d.NetworkControllerEnabled() {
|
||||||
|
|
Loading…
Reference in a new issue