Skip to content

Commit

Permalink
implement listTags tests (#918)
Browse files Browse the repository at this point in the history
Signed-off-by: Carlos Panato <ctadeu@gmail.com>
  • Loading branch information
cpanato committed Feb 11, 2021
1 parent 04be424 commit 6a1151b
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 1 deletion.
26 changes: 25 additions & 1 deletion pkg/crane/crane_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"github.com/google/go-containerregistry/pkg/v1/remote"
)

// TODO(jonjohnsonjr): Test crane.ListTags behavior.
// TODO(jonjohnsonjr): Test crane.Catalog behavior.
// TODO(jonjohnsonjr): Test crane.Copy failures.
func TestCraneRegistry(t *testing.T) {
Expand Down Expand Up @@ -148,6 +147,31 @@ func TestCraneRegistry(t *testing.T) {
t.Fatal(err)
}

// List Tags
// dst variable have: latest and crane-tag
tags, err := crane.ListTags(dst)
if err != nil {
t.Fatal(err)
}
if len(tags) != 2 {
t.Fatalf("wanted 2 tags, got %d", len(tags))
}

// create 4 tags for dst
for i := 1; i < 5; i++ {
if err := crane.Tag(dst, fmt.Sprintf("honk-tag-%d", i)); err != nil {
t.Fatal(err)
}
}

tags, err = crane.ListTags(dst)
if err != nil {
t.Fatal(err)
}
if len(tags) != 6 {
t.Fatalf("wanted 6 tags, got %d", len(tags))
}

// Delete the non existing image
if err := crane.Delete("honk-image"); err == nil {
t.Fatal("wanted err, got nil")
Expand Down
75 changes: 75 additions & 0 deletions pkg/registry/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"sort"
"strconv"
"strings"
"sync"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
)

type listTags struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}

type manifest struct {
contentType string
blob []byte
Expand All @@ -48,6 +56,15 @@ func isManifest(req *http.Request) bool {
return elems[len(elems)-2] == "manifests"
}

func isTags(req *http.Request) bool {
elems := strings.Split(req.URL.Path, "/")
elems = elems[1:]
if len(elems) < 4 {
return false
}
return elems[len(elems)-2] == "tags"
}

// https://github.com/opencontainers/distribution-spec/blob/master/spec.md#pulling-an-image-manifest
// https://github.com/opencontainers/distribution-spec/blob/master/spec.md#pushing-an-image
func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regError {
Expand All @@ -59,6 +76,7 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
if req.Method == "GET" {
m.lock.Lock()
defer m.lock.Unlock()

c, ok := m.manifests[repo]
if !ok {
return &regError{
Expand Down Expand Up @@ -198,3 +216,60 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
Message: "We don't understand your method + url",
}
}

func (m *manifests) handleTags(resp http.ResponseWriter, req *http.Request) *regError {
elem := strings.Split(req.URL.Path, "/")
elem = elem[1:]
repo := strings.Join(elem[1:len(elem)-2], "/")
query := req.URL.Query()
nStr := query.Get("n")
n := 1000
if nStr != "" {
n, _ = strconv.Atoi(nStr)
}

if req.Method == "GET" {
m.lock.Lock()
defer m.lock.Unlock()

c, ok := m.manifests[repo]
if !ok {
return &regError{
Status: http.StatusNotFound,
Code: "NAME_UNKNOWN",
Message: "Unknown name",
}
}

var tags []string
countTags := 0
// TODO: implement pagination https://github.com/opencontainers/distribution-spec/blob/b505e9cc53ec499edbd9c1be32298388921bb705/detail.md#tags-paginated
for tag := range c {
if countTags >= n {
break
}
countTags++
if !strings.Contains(tag, "sha256:") {
tags = append(tags, tag)
}
}
sort.Strings(tags)

tagsToList := listTags{
Name: repo,
Tags: tags,
}

msg, _ := json.Marshal(tagsToList)
resp.Header().Set("Content-Length", fmt.Sprint(len(msg)))
resp.WriteHeader(http.StatusOK)
io.Copy(resp, bytes.NewReader([]byte(msg)))
return nil
}

return &regError{
Status: http.StatusBadRequest,
Code: "METHOD_UNKNOWN",
Message: "We don't understand your method + url",
}
}
3 changes: 3 additions & 0 deletions pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ func (r *registry) v2(resp http.ResponseWriter, req *http.Request) *regError {
if isManifest(req) {
return r.manifests.handle(resp, req)
}
if isTags(req) {
return r.manifests.handleTags(resp, req)
}
resp.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
if req.URL.Path != "/v2/" && req.URL.Path != "/v2" {
return &regError{
Expand Down
13 changes: 13 additions & 0 deletions pkg/registry/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,19 @@ func TestCalls(t *testing.T) {
URL: "/v2/foo/manifests/sha256:" + sha256String("foo"),
Code: http.StatusAccepted,
},
{
Description: "list tags",
Manifests: map[string]string{"foo/manifests/latest": "foo", "foo/manifests/tag1": "foo"},
Method: "GET",
URL: "/v2/foo/tags/list?n=1000",
Code: http.StatusOK,
},
{
Description: "list non existing tags",
Method: "GET",
URL: "/v2/foo/tags/list?n=1000",
Code: http.StatusNotFound,
},
}

for _, tc := range tcs {
Expand Down

0 comments on commit 6a1151b

Please sign in to comment.