mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #32061 from nishanttotla/engine-api-manifest
Adding Docker API endpoint to inspect image manifest
This commit is contained in:
commit
28d428f4ec
8 changed files with 276 additions and 2 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),
|
||||
}
|
||||
}
|
126
api/server/router/distribution/distribution_routes.go
Normal file
126
api/server/router/distribution/distribution_routes.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
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
|
||||
}
|
||||
blobsrvc := distrepo.Blobs(ctx)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
distributionInspect.Descriptor, err = distrepo.Tags(ctx).Get(ctx, taggedRef.Tag())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// TODO(nishanttotla): Once manifests can be looked up as a blob, the
|
||||
// descriptor should be set using blobsrvc.Stat(ctx, canonicalRef.Digest())
|
||||
// instead of having to manually fill in the fields
|
||||
distributionInspect.Descriptor.Digest = canonicalRef.Digest()
|
||||
}
|
||||
|
||||
// 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.Descriptor.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mediaType, payload, err := mnfst.Payload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// update MediaType because registry might return something incorrect
|
||||
distributionInspect.Descriptor.MediaType = mediaType
|
||||
if distributionInspect.Descriptor.Size == 0 {
|
||||
distributionInspect.Descriptor.Size = int64(len(payload))
|
||||
}
|
||||
|
||||
// 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:
|
||||
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,90 @@ 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: "descriptor and platform information"
|
||||
schema:
|
||||
type: "object"
|
||||
x-go-name: DistributionInspect
|
||||
required: [Descriptor, Platforms]
|
||||
properties:
|
||||
Descriptor:
|
||||
type: "object"
|
||||
description: "A descriptor struct containing digest, media type, and size"
|
||||
properties:
|
||||
MediaType:
|
||||
type: "string"
|
||||
Size:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
Digest:
|
||||
type: "string"
|
||||
URLs:
|
||||
type: "array"
|
||||
items:
|
||||
type: "string"
|
||||
Platforms:
|
||||
type: "array"
|
||||
description: "An array containing all platforms supported by the image"
|
||||
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"
|
||||
examples:
|
||||
application/json:
|
||||
Descriptor:
|
||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json"
|
||||
Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96"
|
||||
Size: 3987495
|
||||
URLs:
|
||||
- ""
|
||||
Platforms:
|
||||
- Architecture: "amd64"
|
||||
OS: "linux"
|
||||
OSVersion: ""
|
||||
OSFeatures:
|
||||
- ""
|
||||
Variant: ""
|
||||
Features:
|
||||
- ""
|
||||
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"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
)
|
||||
|
||||
// ServiceConfig stores daemon registry services configuration.
|
||||
|
@ -102,3 +105,14 @@ 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 {
|
||||
// Descriptor contains information about the manifest, including
|
||||
// the content addressable digest
|
||||
Descriptor distribution.Descriptor
|
||||
// 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() {
|
||||
|
|
|
@ -55,7 +55,7 @@ type Backend interface {
|
|||
UnsubscribeFromEvents(listener chan interface{})
|
||||
UpdateAttachment(string, string, string, *network.NetworkingConfig) error
|
||||
WaitForDetachment(context.Context, string, string, string, string) error
|
||||
GetRepository(context.Context, reference.NamedTagged, *types.AuthConfig) (distribution.Repository, bool, error)
|
||||
GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, bool, error)
|
||||
LookupImage(name string) (*types.ImageInspect, error)
|
||||
PluginManager() *plugin.Manager
|
||||
PluginGetter() *plugin.Store
|
||||
|
|
|
@ -111,7 +111,7 @@ func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.
|
|||
}
|
||||
|
||||
// GetRepository returns a repository from the registry.
|
||||
func (daemon *Daemon) GetRepository(ctx context.Context, ref reference.NamedTagged, authConfig *types.AuthConfig) (dist.Repository, bool, error) {
|
||||
func (daemon *Daemon) GetRepository(ctx context.Context, ref reference.Named, authConfig *types.AuthConfig) (dist.Repository, bool, error) {
|
||||
// get repository info
|
||||
repoInfo, err := daemon.RegistryService.ResolveRepository(ref)
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Reference in a new issue