Skip to content
This repository was archived by the owner on Apr 29, 2024. It is now read-only.

Commit

Permalink
Add reference types support to distribution.
Browse files Browse the repository at this point in the history
Co-authored-by: Tejaswini Duggaraju <[email protected]>
Co-authored-by: Akash Singhal <[email protected]>
Co-authored-by: Aviral Takkar <[email protected]>
  • Loading branch information
4 people committed May 7, 2022
1 parent 985711c commit 142317d
Show file tree
Hide file tree
Showing 29 changed files with 1,970 additions and 8 deletions.
35 changes: 35 additions & 0 deletions cmd/registry/config-example-with-extensions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /opt/data/registry-root-dir
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
# Configuration for extensions. It follows the below schema
# extensions
# namespace:
# configuration for the extension and its components in any schema specific to that namespace
extensions:
oci:
ext:
- discover # enable the discovery extension
artifacts:
- referrers
# "distribution" is the namespace
distribution:
# "registry" is the extension under the namespace
registry:
# "taghistory" & "manifests" are the components under the extension
- taghistory
- manifests
2 changes: 2 additions & 0 deletions cmd/registry/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
_ "github.com/distribution/distribution/v3/registry/auth/silly"
_ "github.com/distribution/distribution/v3/registry/auth/token"
_ "github.com/distribution/distribution/v3/registry/extension/distribution"
_ "github.com/distribution/distribution/v3/registry/extension/oci"
_ "github.com/distribution/distribution/v3/registry/proxy"
_ "github.com/distribution/distribution/v3/registry/storage/driver/azure"
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
Expand Down
6 changes: 6 additions & 0 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,14 @@ type Configuration struct {
Classes []string `yaml:"classes"`
} `yaml:"repository,omitempty"`
} `yaml:"policy,omitempty"`

// Extensions configures options for the distribution extensions
Extensions map[string]ExtensionConfig `yaml:"extensions,omitempty"`
}

// ExtensionConfig is the configuration of an extension namespace. It can comprise of extension and components.
type ExtensionConfig interface{}

// LogHook is composed of hook Level and Type.
// After hooks configuration, it can execute the next handling automatically,
// when defined levels of log message emitted.
Expand Down
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ var ErrUnsupported = errors.New("operation unsupported")
// manifest but the registry is configured to reject it
var ErrSchemaV1Unsupported = errors.New("manifest schema v1 unsupported")

// ErrManifestFormatUnsupported is returned when a manifest handler doesn't support a
// specific manifest format
var ErrManifestFormatUnsupported = errors.New("manifest format not supported by this handler")

// ErrTagUnknown is returned if the given tag is not known by the tag service
type ErrTagUnknown struct {
Tag string
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ require (
github.com/kr/text v0.1.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f // indirect
github.com/oci-playground/artifact-spec v0.0.0-20220506233500-8fed0a29d06f // indirect
github.com/prometheus/client_golang v1.1.0 // indirect
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.6.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/ncw/swift v1.0.47 h1:4DQRPj35Y41WogBxyhOXlrI37nzGlyEcsforeudyYPQ=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/oci-playground/artifact-spec v0.0.0-20220506233500-8fed0a29d06f h1:Dak7aFfhhP+sXD7+1OcRU31haFdKmKasKBSuJJGJEUo=
github.com/oci-playground/artifact-spec v0.0.0-20220506233500-8fed0a29d06f/go.mod h1:NY7llZsIsyNV25K6rnp/j1/Fid+pVPxhhtO/HKCBPhk=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
Expand Down
109 changes: 109 additions & 0 deletions registry/api/v2/descriptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,115 @@ var routeDescriptors = []RouteDescriptor{
},
},
},
{
Name: RouteNameExtensionsRegistry,
Path: "/v2/_ext/discover",
Entity: "Extensions",
Description: "Retrieve information about extensions.",
Methods: []MethodDescriptor{
{
Method: "GET",
Description: "Retrieve a sorted, json list of extensions available at the registry level.",
Requests: []RequestDescriptor{
{
Name: "Extensions",
Description: "Return all extensions for the registry",
Headers: []ParameterDescriptor{
hostHeader,
authHeader,
},
PathParameters: []ParameterDescriptor{
nameParameterDescriptor,
},
Successes: []ResponseDescriptor{
{
StatusCode: http.StatusOK,
Description: "A list of extensions for the registry.",
Headers: []ParameterDescriptor{
{
Name: "Content-Length",
Type: "integer",
Description: "Length of the JSON response body.",
Format: "<length>",
},
},
Body: BodyDescriptor{
ContentType: "application/json",
Format: `{
"extensions": [
{"name": "_<ns>/<ext>/<component>"},
...
]
}`,
},
},
},
Failures: []ResponseDescriptor{
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
tooManyRequestsDescriptor,
},
},
},
},
},
},
{
Name: RouteNameExtensionsRepository,
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/_ext/discover",
Entity: "Extensions",
Description: "Retrieve information about extensions.",
Methods: []MethodDescriptor{
{
Method: "GET",
Description: "Fetch the extensions under the repository identified by `name`.",
Requests: []RequestDescriptor{
{
Name: "Extensions",
Description: "Return all extensions for the repository",
Headers: []ParameterDescriptor{
hostHeader,
authHeader,
},
PathParameters: []ParameterDescriptor{
nameParameterDescriptor,
},
Successes: []ResponseDescriptor{
{
StatusCode: http.StatusOK,
Description: "A list of extensions for the named repository.",
Headers: []ParameterDescriptor{
{
Name: "Content-Length",
Type: "integer",
Description: "Length of the JSON response body.",
Format: "<length>",
},
},
Body: BodyDescriptor{
ContentType: "application/json",
Format: `{
"name": <name>,
"extensions": [
{"name": "_<ns>/<ext>/<component>"},
...
]
}`,
},
},
},
Failures: []ResponseDescriptor{
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
tooManyRequestsDescriptor,
},
},
},
},
},
},
}

var routeDescriptorsMap map[string]RouteDescriptor
Expand Down
39 changes: 39 additions & 0 deletions registry/api/v2/extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package v2

import (
"fmt"

"github.com/distribution/distribution/v3/reference"
)

// ExtendRoute extends the routes using the template.
// The Name and Path in the template will be re-generated according to
// - Namespace (ns)
// - Extension Name (ext)
// - Component name (component)
// Returns the full route descriptor with Name and Path populated.
// Returns true if the route is successfully extended, or false if route exists.
func ExtendRoute(ns, ext, component string, template RouteDescriptor, nameRequired bool) (RouteDescriptor, bool) {
name := RouteNameExtensionsRegistry
path := routeDescriptorsMap[RouteNameBase].Path
if nameRequired {
name = RouteNameExtensionsRepository
path += "{name:" + reference.NameRegexp.String() + "}"
}
name = fmt.Sprintf("%s-%s-%s-%s", name, ns, ext, component)
path = fmt.Sprintf("%s/_%s/%s/%s", path, ns, ext, component)

desc := template
desc.Name = name
desc.Path = path

if _, exists := routeDescriptorsMap[desc.Name]; exists {
return desc, false
}

routeDescriptors = append(routeDescriptors, desc)
routeDescriptorsMap[desc.Name] = desc
APIDescriptor.RouteDescriptors = routeDescriptors

return desc, true
}
16 changes: 9 additions & 7 deletions registry/api/v2/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import "github.com/gorilla/mux"
// The following are definitions of the name under which all V2 routes are
// registered. These symbols can be used to look up a route based on the name.
const (
RouteNameBase = "base"
RouteNameManifest = "manifest"
RouteNameTags = "tags"
RouteNameBlob = "blob"
RouteNameBlobUpload = "blob-upload"
RouteNameBlobUploadChunk = "blob-upload-chunk"
RouteNameCatalog = "catalog"
RouteNameBase = "base"
RouteNameManifest = "manifest"
RouteNameTags = "tags"
RouteNameBlob = "blob"
RouteNameBlobUpload = "blob-upload"
RouteNameBlobUploadChunk = "blob-upload-chunk"
RouteNameCatalog = "catalog"
RouteNameExtensionsRegistry = "extensions-registry"
RouteNameExtensionsRepository = "extensions-repository"
)

// Router builds a gorilla router with named routes for the various API
Expand Down
24 changes: 24 additions & 0 deletions registry/api/v2/urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,30 @@ func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string,
return appendValuesURL(uploadURL, values...).String(), nil
}

// BuildRegistryExtensionsURL constructs a url to list the extensions in the named repository.
func (ub *URLBuilder) BuildRegistryExtensionsURL(values ...url.Values) (string, error) {
route := ub.cloneRoute(RouteNameExtensionsRegistry)

extensionsURL, err := route.URL()
if err != nil {
return "", err
}

return appendValuesURL(extensionsURL, values...).String(), nil
}

// BuildRepositoryExtensionsURL constructs a url to list the extensions in the named repository.
func (ub *URLBuilder) BuildRepositoryExtensionsURL(name reference.Named, values ...url.Values) (string, error) {
route := ub.cloneRoute(RouteNameExtensionsRepository)

extensionsURL, err := route.URL("name", name.Name())
if err != nil {
return "", err
}

return appendValuesURL(extensionsURL, values...).String(), nil
}

// clondedRoute returns a clone of the named route from the router. Routes
// must be cloned to avoid modifying them during url generation.
func (ub *URLBuilder) cloneRoute(name string) clonedRoute {
Expand Down
69 changes: 69 additions & 0 deletions registry/extension/distribution/manifests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package distribution

import (
"encoding/json"
"net/http"

"github.com/distribution/distribution/v3/registry/api/errcode"
v2 "github.com/distribution/distribution/v3/registry/api/v2"
"github.com/distribution/distribution/v3/registry/extension"
"github.com/distribution/distribution/v3/registry/storage"
"github.com/distribution/distribution/v3/registry/storage/driver"
"github.com/opencontainers/go-digest"
)

type manifestsGetAPIResponse struct {
Name string `json:"name"`
Digests []digest.Digest `json:"digests"`
}

// manifestHandler handles requests for manifests under a manifest name.
type manifestHandler struct {
*extension.Context
storageDriver driver.StorageDriver
}

func (th *manifestHandler) getManifests(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

digests, err := th.manifests()
if err != nil {
switch err := err.(type) {
case driver.PathNotFoundError:
th.Errors = append(th.Errors, v2.ErrorCodeNameUnknown.WithDetail(map[string]string{"name": th.Repository.Named().Name()}))
default:
th.Errors = append(th.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
}
return
}

w.Header().Set("Content-Type", "application/json")

enc := json.NewEncoder(w)
if err := enc.Encode(manifestsGetAPIResponse{
Name: th.Repository.Named().Name(),
Digests: digests,
}); err != nil {
th.Errors = append(th.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
return
}
}

func (th *manifestHandler) manifests() ([]digest.Digest, error) {
manifestLinkStore := storage.GetManifestLinkReadOnlyBlobStore(
th.Context,
th.Repository,
th.storageDriver,
nil,
)

var dgsts []digest.Digest
err := manifestLinkStore.Enumerate(th.Context, func(dgst digest.Digest) error {
dgsts = append(dgsts, dgst)
return nil
})
if err != nil {
return nil, err
}
return dgsts, nil
}
Loading

0 comments on commit 142317d

Please sign in to comment.