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"
|
||||
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"]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue