diff --git a/registry/storage/tagstore.go b/registry/storage/tagstore.go index f80b96282..e4b1ed15a 100644 --- a/registry/storage/tagstore.go +++ b/registry/storage/tagstore.go @@ -196,3 +196,37 @@ func (ts *tagStore) Lookup(ctx context.Context, desc distribution.Descriptor) ([ return tags, nil } + +func (ts *tagStore) Indexes(ctx context.Context, tag string) ([]digest.Digest, error) { + var tagLinkPath = func(name string, dgst digest.Digest) (string, error) { + return pathFor(manifestTagIndexEntryLinkPathSpec{ + name: name, + tag: tag, + revision: dgst, + }) + } + lbs := &linkedBlobStore{ + blobStore: ts.blobStore, + blobAccessController: &linkedBlobStatter{ + blobStore: ts.blobStore, + repository: ts.repository, + linkPathFns: []linkPathFunc{manifestRevisionLinkPath}, + }, + repository: ts.repository, + ctx: ctx, + linkPathFns: []linkPathFunc{tagLinkPath}, + linkDirectoryPathSpec: manifestTagIndexPathSpec{ + name: ts.repository.Named().Name(), + tag: tag, + }, + } + var dgsts []digest.Digest + err := lbs.Enumerate(ctx, func(dgst digest.Digest) error { + dgsts = append(dgsts, dgst) + return nil + }) + if err != nil { + return nil, err + } + return dgsts, nil +} diff --git a/registry/storage/tagstore_test.go b/registry/storage/tagstore_test.go index 314fa433c..938aa69d8 100644 --- a/registry/storage/tagstore_test.go +++ b/registry/storage/tagstore_test.go @@ -2,15 +2,22 @@ package storage import ( "context" + "reflect" "testing" "github.com/docker/distribution" + "github.com/docker/distribution/manifest" + "github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/reference" "github.com/docker/distribution/registry/storage/driver/inmemory" + digest "github.com/opencontainers/go-digest" ) type tagsTestEnv struct { ts distribution.TagService + bs distribution.BlobStore + ms distribution.ManifestService + gbs distribution.BlobStatter ctx context.Context } @@ -27,10 +34,17 @@ func testTagStore(t *testing.T) *tagsTestEnv { if err != nil { t.Fatal(err) } + ms, err := repo.Manifests(ctx) + if err != nil { + t.Fatal(err) + } return &tagsTestEnv{ ctx: ctx, ts: repo.Tags(ctx), + bs: repo.Blobs(ctx), + gbs: reg.BlobStatter(), + ms: ms, } } @@ -205,5 +219,75 @@ func TestTagLookup(t *testing.T) { if len(tags) != 2 { t.Errorf("Lookup of descB returned %d tags, expected 2", len(tags)) } - +} + +func TestTagIndexes(t *testing.T) { + env := testTagStore(t) + tagStore := env.ts + ctx := env.ctx + + indexes, ok := tagStore.(distribution.TagIndexes) + if !ok { + t.Fatal("tagStore does not implement TagIndexes interface") + } + + conf, err := env.bs.Put(ctx, "application/octet-stream", []byte{0}) + if err != nil { + t.Fatal(err) + } + + dgstsSet := make(map[digest.Digest]bool) + for i := 0; i < 3; i++ { + layer, err := env.bs.Put(ctx, "application/octet-stream", []byte{byte(i + 1)}) + if err != nil { + t.Fatal(err) + } + m := schema2.Manifest{ + Versioned: manifest.Versioned{ + SchemaVersion: 2, + MediaType: schema2.MediaTypeManifest, + }, + Config: distribution.Descriptor{ + Digest: conf.Digest, + Size: 1, + MediaType: schema2.MediaTypeImageConfig, + }, + Layers: []distribution.Descriptor{ + { + Digest: layer.Digest, + Size: 1, + MediaType: schema2.MediaTypeLayer, + }, + }, + } + dm, err := schema2.FromStruct(m) + if err != nil { + t.Fatal(err) + } + dgst, err := env.ms.Put(ctx, dm) + if err != nil { + t.Fatal(err) + } + desc, err := env.gbs.Stat(ctx, dgst) + if err != nil { + t.Fatal(err) + } + err = tagStore.Tag(ctx, "t", desc) + if err != nil { + t.Fatal(err) + } + dgstsSet[dgst] = true + } + + gotDgsts, err := indexes.Indexes(ctx, "t") + if err != nil { + t.Fatal(err) + } + gotDgstsSet := make(map[digest.Digest]bool) + for _, dgst := range gotDgsts { + gotDgstsSet[dgst] = true + } + if !reflect.DeepEqual(dgstsSet, gotDgstsSet) { + t.Fatalf("Expected digests: %v but got digests: %v", dgstsSet, gotDgstsSet) + } } diff --git a/tags.go b/tags.go index f22df2b85..60b96b27f 100644 --- a/tags.go +++ b/tags.go @@ -2,6 +2,8 @@ package distribution import ( "context" + + digest "github.com/opencontainers/go-digest" ) // TagService provides access to information about tagged objects. @@ -25,3 +27,10 @@ type TagService interface { // Lookup returns the set of tags referencing the given digest. Lookup(ctx context.Context, digest Descriptor) ([]string, error) } + +// TagIndexes proves method to retreive all the digests that a tag historically pointed to +type TagIndexes interface { + // Indexes returns set of digests that this tag historically pointed to. This also includes + // currently linked digest. There is no ordering guaranteed + Indexes(ctx context.Context, tag string) ([]digest.Digest, error) +}