Skip to content

Commit

Permalink
expose registry untag image feature
Browse files Browse the repository at this point in the history
  • Loading branch information
levimm committed Jun 20, 2018
1 parent fbc9c42 commit ee5a589
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 0 deletions.
66 changes: 66 additions & 0 deletions registry/api/v2/descriptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ var (
Description: `Name of the target repository.`,
}

tagParameterDescriptor = ParameterDescriptor{
Name: "tag",
Type: "string",
Format: reference.TagRegexp.String(),
Required: true,
Description: `Tag of the target manifest.`,
}

referenceParameterDescriptor = ParameterDescriptor{
Name: "reference",
Type: "string",
Expand Down Expand Up @@ -500,6 +508,64 @@ var routeDescriptors = []RouteDescriptor{
},
},
},
{
Name: RouteNameTag,
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/tags/{tag:" + reference.TagRegexp.String() + "}",
Entity: "Tags",
Description: "Delete tag",
Methods: []MethodDescriptor{
{
Method: "DELETE",
Description: "Delete a tag identified by `name` and `tag`.",
Requests: []RequestDescriptor{
{
Name: "Tags",
Description: "Delete a tag identified by `name` and `tag`.",
Headers: []ParameterDescriptor{
hostHeader,
authHeader,
},
PathParameters: []ParameterDescriptor{
nameParameterDescriptor,
tagParameterDescriptor,
},
Successes: []ResponseDescriptor{
{
StatusCode: http.StatusAccepted,
},
},
Failures: []ResponseDescriptor{
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
tooManyRequestsDescriptor,
{
Name: "Unknown Tag",
Description: "The specified `name` or `Tag` are unknown to the registry and the delete was unable to proceed. Clients can assume the tag was already deleted if this response is returned.",
StatusCode: http.StatusNotFound,
ErrorCodes: []errcode.ErrorCode{
ErrorCodeNameUnknown,
ErrorCodeTagUnknown,
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
},
{
Name: "Not allowed",
Description: "Tag delete is not allowed because the registry is configured as a pull-through cache or `delete` has been disabled.",
StatusCode: http.StatusMethodNotAllowed,
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnsupported,
},
},
},
},
},
},
},
},
{
Name: RouteNameManifest,
Path: "/v2/{name:" + reference.NameRegexp.String() + "}/manifests/{reference:" + reference.TagRegexp.String() + "|" + digest.DigestRegexp.String() + "}",
Expand Down
9 changes: 9 additions & 0 deletions registry/api/v2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ var (
HTTPStatusCode: http.StatusNotFound,
})

// ErrorCodeTagUnknown when the tag is not known.
ErrorCodeTagUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{
Value: "TAG_UNKNOWN",
Message: "tag not known to registry",
Description: `This is returned if the tag used during an operation is
unknown to the registry.`,
HTTPStatusCode: http.StatusNotFound,
})

// ErrorCodeManifestUnknown returned when image manifest is unknown.
ErrorCodeManifestUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{
Value: "MANIFEST_UNKNOWN",
Expand Down
2 changes: 2 additions & 0 deletions registry/api/v2/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (
RouteNameBase = "base"
RouteNameManifest = "manifest"
RouteNameTags = "tags"
RouteNameTag = "tag"
RouteNameBlob = "blob"
RouteNameBlobUpload = "blob-upload"
RouteNameBlobUploadChunk = "blob-upload-chunk"
Expand All @@ -18,6 +19,7 @@ var allEndpoints = []string{
RouteNameManifest,
RouteNameCatalog,
RouteNameTags,
RouteNameTag,
RouteNameBlob,
RouteNameBlobUpload,
RouteNameBlobUploadChunk,
Expand Down
16 changes: 16 additions & 0 deletions registry/api/v2/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ func TestRouter(t *testing.T) {
"name": "docker.com/foo/bar/baz",
},
},
{
RouteName: RouteNameTag,
RequestURI: "/v2/docker.com/foo/bar/baz/tags/latest",
Vars: map[string]string{
"name": "docker.com/foo/bar/baz",
"tag": "latest",
},
},
{
RouteName: RouteNameTag,
RequestURI: "/v2/docker.com/foo/bar/baz/tags/v1",
Vars: map[string]string{
"name": "docker.com/foo/bar/baz",
"tag": "v1",
},
},
{
RouteName: RouteNameBlob,
RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234",
Expand Down
1 change: 1 addition & 0 deletions registry/handlers/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
app.register(v2.RouteNameManifest, imageManifestDispatcher)
app.register(v2.RouteNameCatalog, catalogDispatcher)
app.register(v2.RouteNameTags, tagsDispatcher)
app.register(v2.RouteNameTag, tagDispatcher)
app.register(v2.RouteNameBlob, blobDispatcher)
app.register(v2.RouteNameBlobUpload, blobUploadDispatcher)
app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher)
Expand Down
7 changes: 7 additions & 0 deletions registry/handlers/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ func TestAppDispatcher(t *testing.T) {
"name", "foo/bar",
},
},
{
endpoint: v2.RouteNameTag,
vars: []string{
"name", "foo/bar",
"tag", "sometag",
},
},
{
endpoint: v2.RouteNameBlobUpload,
vars: []string{
Expand Down
4 changes: 4 additions & 0 deletions registry/handlers/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ func getReference(ctx context.Context) (reference string) {
return ctxu.GetStringValue(ctx, "vars.reference")
}

func getTag(ctx context.Context) (tag string) {
return ctxu.GetStringValue(ctx, "vars.tag")
}

var errDigestNotAvailable = fmt.Errorf("digest not available in context")

func getDigest(ctx context.Context) (dgst digest.Digest, err error) {
Expand Down
39 changes: 39 additions & 0 deletions registry/handlers/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
"github.com/gorilla/handlers"
storagedriver "github.com/docker/distribution/registry/storage/driver"
)

// tagsDispatcher constructs the tags handler api endpoint.
Expand All @@ -21,11 +22,33 @@ func tagsDispatcher(ctx *Context, r *http.Request) http.Handler {
}
}

// tagDispatcher constructs the tag delete handler
func tagDispatcher(ctx *Context, r *http.Request) http.Handler {
tagHandler := &tagHandler{
Context: ctx,
Tag: getTag(ctx),
}

thandler := handlers.MethodHandler{
}

if !ctx.readOnly {
thandler["DELETE"] = http.HandlerFunc(tagHandler.DeleteTag)
}

return thandler
}

// tagsHandler handles requests for lists of tags under a repository name.
type tagsHandler struct {
*Context
}

type tagHandler struct {
*Context
Tag string
}

type tagsAPIResponse struct {
Name string `json:"name"`
Tags []string `json:"tags"`
Expand Down Expand Up @@ -60,3 +83,19 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
return
}
}

// DeleteTag only do the untag and leave the manifest untouched
func (th *tagHandler) DeleteTag(w http.ResponseWriter, r *http.Request) {
tagService := th.Repository.Tags(th)
if err := tagService.Untag(th.Context, th.Tag); err != nil {
switch err.(type) {
case storagedriver.PathNotFoundError:
th.Errors = append(th.Errors, v2.ErrorCodeTagUnknown)
default:
th.Errors = append(th.Errors, err)
}
return
}

w.WriteHeader(http.StatusAccepted)
}

0 comments on commit ee5a589

Please sign in to comment.